Gut docs for now

This commit is contained in:
Hynek Schlawack 2015-01-27 22:41:24 +01:00
parent 9560908555
commit c7381fd0e2
10 changed files with 167 additions and 191 deletions

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014 Hynek Schlawack
Copyright (c) 2015 Hynek Schlawack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -14,7 +14,7 @@ attrs: Python attributes without boilerplate.
:target: https://coveralls.io/r/hynek/attrs?branch=master
:alt: Current coverage
.. begin
.. teaser-begin
``attrs`` is an `MIT <http://choosealicense.com/licenses/mit/>`_-licensed Python package with class decorators that ease the chores of implementing the most common attribute-related object protocols:
@ -49,5 +49,7 @@ This gives you the power to use actual classes with actual types in your code in
So put down that type-less data structures and welcome some class into your life!
.. teaser-end
``attrs``\ s documentation lives at `Read the Docs <https://attrs.readthedocs.org/>`_, the code on `GitHub <https://github.com/hynek/attrs>`_.
Its rigorously tested on Python 2.6, 2.7, 3.3+, and PyPy.

View File

@ -85,17 +85,17 @@ qthelp:
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/characteristic.qhcp"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/attrs.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/characteristic.qhc"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/attrs.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/characteristic"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/characteristic"
@echo "# mkdir -p $$HOME/.local/share/devhelp/attrs"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/attrs"
@echo "# devhelp"
epub:

View File

@ -2,100 +2,3 @@
API
===
``characteristic`` consists of class decorators that add attribute-related features to your classes.
.. currentmodule:: characteristic
There are two approaches on how to define those attributes:
#. By defining those attributes as class variables using instances of the :class:`Attribute` class.
This approach has been added as of version 14.0 to make ``characteristic`` future-proof by adding more flexibility.
#. Using a list of names which I henceforth refer to as the 'legacy way'.
As per our backward compatibility policy, support for this approach will *not* be removed before 15.0 (if ever), however no new features will be added so I strongly urge you to *not* use it.
Both approaches usually entail the usage of the :func:`@attributes <attributes>` decorator which will automatically detect the desired approach and prevent mixing of them.
.. autofunction:: attributes
.. autoclass:: Attribute
Legacy
------
There are three that start with ``@with_`` that add *one* feature to your class based on a list of attributes.
Then there's the helper :func:`@attributes <attributes>` that combines them all into one decorator so you don't have to repeat the attribute list multiple times.
.. autofunction:: with_repr
.. doctest::
>>> from characteristic import with_repr
>>> @with_repr(["a", "b"])
... class RClass(object):
... def __init__(self, a, b):
... self.a = a
... self.b = b
>>> c = RClass(42, "abc")
>>> print c
<RClass(a=42, b='abc')>
.. autofunction:: with_cmp
.. doctest::
>>> from characteristic import with_cmp
>>> @with_cmp(["a", "b"])
... class CClass(object):
... def __init__(self, a, b):
... self.a = a
... self.b = b
>>> o1 = CClass(1, "abc")
>>> o2 = CClass(1, "abc")
>>> o1 == o2 # o1.a == o2.a and o1.b == o2.b
True
>>> o1.c = 23
>>> o2.c = 42
>>> o1 == o2 # attributes that are not passed to with_cmp are ignored
True
>>> o3 = CClass(2, "abc")
>>> o1 < o3 # because 1 < 2
True
>>> o4 = CClass(1, "bca")
>>> o1 < o4 # o1.a == o4.a, but o1.b < o4.b
True
.. autofunction:: with_init
.. doctest::
>>> from characteristic import with_init
>>> @with_init(["a", "b"], defaults={"b": 2})
... class IClass(object):
... def __init__(self):
... if self.b != 2:
... raise ValueError("'b' must be 2!")
>>> o1 = IClass(a=1, b=2)
>>> o2 = IClass(a=1)
>>> o1.a == o2.a
True
>>> o1.b == o2.b
True
>>> IClass()
Traceback (most recent call last):
...
ValueError: Missing keyword value for 'a'.
>>> IClass(a=1, b=3) # the custom __init__ is called after the attributes are initialized
Traceback (most recent call last):
...
ValueError: 'b' must be 2!
.. note::
The generated initializer explicitly does *not* support positional
arguments. Those are *always* passed to the existing ``__init__``
unaltered.

View File

@ -303,4 +303,4 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
intersphinx_mapping = {'https://docs.python.org/2': None}

View File

@ -2,39 +2,3 @@
Examples
========
:func:`@attributes <characteristic.attributes>` together with the definition of the attributes using class attributes enhances your class by:
- a nice ``__repr__``,
- comparison methods that compare instances as if they were tuples of their attributes,
- and optionally but by default an initializer that uses the keyword arguments to initialize the specified attributes before running the class own initializer (you just write the validator!).
.. doctest::
>>> from characteristic import Attribute, attributes
>>> @attributes
... class C(object):
... a = Attribute()
... b = Attribute()
>>> obj1 = C(a=1, b="abc")
>>> obj1
<C(a=1, b='abc')>
>>> obj2 = C(a=2, b="abc")
>>> obj1 == obj2
False
>>> obj1 < obj2
True
>>> obj3 = C(a=1, b="bca")
>>> obj3 > obj1
True
>>> @attributes
... class CWithDefaults(object):
... a = Attribute()
... b = Attribute()
... c = Attribute(default=3)
>>> obj4 = CWithDefaults(a=1, b=2)
>>> obj5 = CWithDefaults(a=1, b=2, c=3)
>>> obj4 == obj5
True

View File

@ -1,36 +1,12 @@
characteristic: Say 'yes' to types but 'no' to typing!
======================================================
attrs: Say 'yes' to types but 'no' to typing!
=============================================
Release v\ |release| (:doc:`What's new? <changelog>`).
.. include:: ../README.rst
:start-after: begin
Teaser
------
.. doctest::
>>> from characteristic import Attribute, attributes
>>> @attributes
... class AClass(object):
... a = Attribute()
... b = Attribute()
>>> @attributes
... class AnotherClass(object):
... a = Attribute()
... b = Attribute(default="abc")
>>> obj1 = AClass(a=1, b="abc")
>>> obj2 = AnotherClass(a=1, b="abc")
>>> obj3 = AnotherClass(a=1)
>>> print obj1, obj2, obj3
<AClass(a=1, b='abc')> <AnotherClass(a=1, b='abc')> <AnotherClass(a=1, b='abc')>
>>> obj1 == obj2
False
>>> obj2 == obj3
True
:start-after: teaser-begin
:end-before: teaser-end
User's Guide

View File

@ -1,8 +1,8 @@
License and Hall of Fame
========================
``characteristic`` is licensed under the permissive `MIT <http://choosealicense.com/licenses/mit/>`_ license.
The full license text can be also found in the `source code repository <https://github.com/hynek/characteristic/blob/master/LICENSE>`_.
``attrs`` is licensed under the permissive `MIT <http://choosealicense.com/licenses/mit/>`_ license.
The full license text can be also found in the `source code repository <https://github.com/hynek/attrs/blob/master/LICENSE>`_.
.. _authors:

View File

@ -1,35 +1,87 @@
.. _why:
Why?
====
Why not…
========
The difference between namedtuple_\ s and classes decorated by ``characteristic`` is that the latter are type-sensitive and less typing aside regular classes:
…tuples?
--------
Readability
^^^^^^^^^^^
What makes more sense while debugging::
<Point(x=1, x=2)>
or::
(1, 2)
?
Let's add even more ambiguity::
<Customer(id=42, reseller=23, first_name="Jane", last_name="John")>
or::
(42, 23, "Jane", "John")
?
Why would you want to write ``customer[2]`` instead of ``customer.first_name``?
Don't get me started when you add nesting.
If you've never ran into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter then I am.
Using proper classes with names and types makes program code much more readable and comprehensible_.
Especially when trying to grok a new piece of software or returning to old code after several months.
.. _comprehensible: http://arxiv.org/pdf/1304.5257.pdf
Extendability
^^^^^^^^^^^^^
Imagine you have a function that takes or returns a tuple.
Especially if you use tuple unpacking (eg. ``x, y = get_point()``), adding additional data means that you have to change the invocation of that function *everywhere*.
Adding an attribute to a class concerns only those who actually care about that attribute.
…namedtuples?
-------------
The difference between :func:`collections.namedtuple`\ s and classes decorated by ``attrs`` is that the latter are type-sensitive and less typing aside regular classes:
.. doctest::
>>> from characteristic import Attribute, attributes
>>> @attributes
>>> @attributes([Attribute("a", instance_of=int)])
... class C1(object):
... a = Attribute()
... def __init__(self):
... if not isinstance(self.a, int):
... raise ValueError("'a' must be an integer.")
... if self.a >= 5:
... raise ValueError("'a' must be smaller 5!")
... def print_a(self):
... print self.a
>>> @attributes
>>> @attributes([Attribute("a", instance_of=int)])
... class C2(object):
... a = Attribute()
... pass
>>> c1 = C1(a=1)
>>> c2 = C2(a=1)
>>> c1.a == c2.a
True
>>> c1 == c2
False
>>> c1.print_a()
1
>>> C1(a="hello")
>>> C1(a=5)
Traceback (most recent call last):
...
ValueError: 'a' must be an integer.
ValueError: 'a' must be smaller 5!
…while namedtuples purpose is *explicitly* to behave like tuples:
@ -48,5 +100,84 @@ The difference between namedtuple_\ s and classes decorated by ``characteristic`
This can easily lead to surprising and unintended behaviors.
.. _namedtuple: https://docs.python.org/2/library/collections.html#collections.namedtuple
Other than that, ``attrs`` also adds nifty features like validators or default values.
.. _tuple: https://docs.python.org/2/tutorial/datastructures.html#tuples-and-sequences
…hand-written classes?
----------------------
While I'm a fan of all things artisanal, writing the same nine methods all over again doesn't qualify for me.
I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested.
To bring it into perspective, the equivalent of
.. doctest::
>>> @attributes(["a", "b"])
... class SmartClass(object):
... pass
>>> SmartClass(a=1, b=2)
<SmartClass(a=1, b=2)>
is
.. doctest::
>>> class ArtisinalClass(object):
... def __init__(self, a, b):
... self.a = a
... self.b = b
...
... def __repr__(self):
... return "<ArtisinalClass(a={}, b={})>".format(self.a, self.b)
...
... def __eq__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) == (other.a, other.b)
... else:
... return NotImplemented
...
... def __ne__(self, other):
... result = self.__eq__(other)
... if result is NotImplemented:
... return NotImplemented
... else:
... return not result
...
... def __lt__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) < (other.a, other.b)
... else:
... return NotImplemented
...
... def __le__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) <= (other.a, other.b)
... else:
... return NotImplemented
...
... def __gt__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) > (other.a, other.b)
... else:
... return NotImplemented
...
... def __ge__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) >= (other.a, other.b)
... else:
... return NotImplemented
...
... def __hash__(self):
... return hash((self.a, self.b))
>>> ArtisinalClass(a=1, b=2)
<ArtisinalClass(a=1, b=2)>
which is quite a mouthful and it doesn't even use any of ``attrs``'s more advanced features like validators or defaults values.
Also: no tests whatsoever.
And who will guarantee you, that you don't accidentally flip the ``<`` in your tenth implementation of ``__gt__``?
If you don't care and like typing, I'm not gonna stop you.
But if you ever get sick of the repetitiveness, ``attrs`` will be waiting for you. :)

18
tox.ini
View File

@ -14,15 +14,15 @@ deps =
flake8
commands = flake8 attr tests
; [testenv:docs]
; basepython = python2.7
; setenv =
; PYTHONHASHSEED = 0
; deps =
; sphinx
; commands =
; sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
; sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
[testenv:docs]
basepython = python2.7
setenv =
PYTHONHASHSEED = 0
deps =
sphinx
commands =
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
[testenv:manifest]
deps =