diff --git a/LICENSE b/LICENSE
index 27acfefc..7ae3df93 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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
diff --git a/README.rst b/README.rst
index 9ea02b5c..b9184c5b 100644
--- a/README.rst
+++ b/README.rst
@@ -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 `_-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 `_, the code on `GitHub `_.
It’s rigorously tested on Python 2.6, 2.7, 3.3+, and PyPy.
diff --git a/docs/Makefile b/docs/Makefile
index 7232ea5a..3143891d 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -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:
diff --git a/docs/api.rst b/docs/api.rst
index ab28c340..d1908443 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -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 ` 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 ` 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
-
-
-
-.. 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.
diff --git a/docs/conf.py b/docs/conf.py
index c1dc3fe9..ef046966 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -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}
diff --git a/docs/examples.rst b/docs/examples.rst
index 9790c189..d975cbf3 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -2,39 +2,3 @@
Examples
========
-
-
-:func:`@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
-
- >>> 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
diff --git a/docs/index.rst b/docs/index.rst
index 8d2864ac..7a00c9f0 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -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? `).
.. 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
-
- >>> obj1 == obj2
- False
- >>> obj2 == obj3
- True
+ :start-after: teaser-begin
+ :end-before: teaser-end
User's Guide
diff --git a/docs/license.rst b/docs/license.rst
index 7532d60b..d04b53f5 100644
--- a/docs/license.rst
+++ b/docs/license.rst
@@ -1,8 +1,8 @@
License and Hall of Fame
========================
-``characteristic`` is licensed under the permissive `MIT `_ license.
-The full license text can be also found in the `source code repository `_.
+``attrs`` is licensed under the permissive `MIT `_ license.
+The full license text can be also found in the `source code repository `_.
.. _authors:
diff --git a/docs/why.rst b/docs/why.rst
index 6e6a7b21..d08c833a 100644
--- a/docs/why.rst
+++ b/docs/why.rst
@@ -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::
+
+
+
+or::
+
+ (1, 2)
+
+?
+
+Let's add even more ambiguity::
+
+
+
+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 namedtuple’s 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)
+
+
+is
+
+.. doctest::
+
+ >>> class ArtisinalClass(object):
+ ... def __init__(self, a, b):
+ ... self.a = a
+ ... self.b = b
+ ...
+ ... def __repr__(self):
+ ... return "".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)
+
+
+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. :)
diff --git a/tox.ini b/tox.ini
index caa3828f..7c6b01b2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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 =