Gut docs for now
This commit is contained in:
parent
9560908555
commit
c7381fd0e2
2
LICENSE
2
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
|
||||
|
|
|
@ -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>`_.
|
||||
It’s rigorously tested on Python 2.6, 2.7, 3.3+, and PyPy.
|
||||
|
|
|
@ -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:
|
||||
|
|
97
docs/api.rst
97
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 <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.
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
155
docs/why.rst
155
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::
|
||||
|
||||
<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 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)
|
||||
<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
18
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 =
|
||||
|
|
Loading…
Reference in New Issue