From c7381fd0e21431ec737b29a3e767afb8efb219d8 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Tue, 27 Jan 2015 22:41:24 +0100 Subject: [PATCH] Gut docs for now --- LICENSE | 2 +- README.rst | 4 +- docs/Makefile | 8 +-- docs/api.rst | 97 ----------------------------- docs/conf.py | 2 +- docs/examples.rst | 36 ----------- docs/index.rst | 32 ++-------- docs/license.rst | 4 +- docs/why.rst | 155 ++++++++++++++++++++++++++++++++++++++++++---- tox.ini | 18 +++--- 10 files changed, 167 insertions(+), 191 deletions(-) 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 =