Skip to content

Commit

Permalink
Add narrative chapter about initialization (#370)
Browse files Browse the repository at this point in the history
* Add narrative chapter on initialization

* Be more explicit about defining validators both ways

* Stress that the name of default/validator != attr name

* Mention nested schemas

* Explain handling of private attributes

* Add another consequence of _ stripping

* Stress that nothing should overwrite attributes on class body

* Better wording

* typo

* Dedup examples

* Address review feedback

* Add newsfragments
  • Loading branch information
hynek authored and Tinche committed Apr 24, 2018
1 parent 7cb8c82 commit 879f43d
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 124 deletions.
6 changes: 6 additions & 0 deletions changelog.d/369.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
We have restructured the documentation a bit to account for ``attrs``' growth in scope.
Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.

So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.

Expect more to come!
6 changes: 6 additions & 0 deletions changelog.d/370.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
We have restructured the documentation a bit to account for ``attrs``' growth in scope.
Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.

So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.

Expect more to come!
49 changes: 0 additions & 49 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,55 +143,6 @@ Core
y = attr.ib()


Influencing Initialization
++++++++++++++++++++++++++

Generally speaking, it's best to keep logic out of your ``__init__``.
The moment you need a finer control over how your class is instantiated, it's usually best to use a classmethod factory or to apply the `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern>`_.

However, sometimes you need to do that one quick thing after your class is initialized.
And for that ``attrs`` offers the ``__attrs_post_init__`` hook that is automatically detected and run after ``attrs`` is done initializing your instance:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib(init=False)
... def __attrs_post_init__(self):
... self.y = self.x + 1
>>> C(1)
C(x=1, y=2)

Please note that you can't directly set attributes on frozen classes:

.. doctest::

>>> @attr.s(frozen=True)
... class FrozenBroken(object):
... x = attr.ib()
... y = attr.ib(init=False)
... def __attrs_post_init__(self):
... self.y = self.x + 1
>>> FrozenBroken(1)
Traceback (most recent call last):
...
attr.exceptions.FrozenInstanceError: can't set attribute

If you need to set attributes on a frozen class, you'll have to resort to the :ref:`same trick <how-frozen>` as ``attrs`` and use :meth:`object.__setattr__`:

.. doctest::

>>> @attr.s(frozen=True)
... class Frozen(object):
... x = attr.ib()
... y = attr.ib(init=False)
... def __attrs_post_init__(self):
... object.__setattr__(self, "y", self.x + 1)
>>> Frozen(1)
Frozen(x=1, y=2)


.. _helpers:

Helpers
Expand Down
92 changes: 17 additions & 75 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,6 @@ Other times, all you want is a tuple and ``attrs`` won't let you down:
True




Defaults
--------

Expand Down Expand Up @@ -301,18 +299,7 @@ Although your initializers should do as little as possible (ideally: just initia

``attrs`` offers two ways to define validators for each attribute and it's up to you to choose which one suites better your style and project.


Decorator
~~~~~~~~~

The more straightforward way is by using the attribute's ``validator`` method as a decorator.
The method has to accept three arguments:

#. the *instance* that's being validated (aka ``self``),
#. the *attribute* that it's validating, and finally
#. the *value* that is passed for it.

If the value does not pass the validator's standards, it just raises an appropriate exception.
You can use a decorator:

.. doctest::

Expand All @@ -330,15 +317,7 @@ If the value does not pass the validator's standards, it just raises an appropri
...
ValueError: x must be smaller or equal to 42


Callables
~~~~~~~~~

If you want to re-use your validators, you should have a look at the ``validator`` argument to :func:`attr.ib()`.

It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.

Since the validators runs *after* the instance is initialized, you can refer to other attributes while validating:
...or a callable...

.. doctest::

Expand All @@ -357,34 +336,7 @@ Since the validators runs *after* the instance is initialized, you can refer to
...
ValueError: 'x' has to be smaller than 'y'!

This example also shows of some syntactic sugar for using the :func:`attr.validators.and_` validator: if you pass a list, all validators have to pass.

``attrs`` won't intercept your changes to those attributes but you can always call :func:`attr.validate` on any instance to verify that it's still valid:

.. doctest::

>>> i = C(4, 5)
>>> i.x = 5 # works, no magic here
>>> attr.validate(i)
Traceback (most recent call last):
...
ValueError: 'x' has to be smaller than 'y'!

``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.instance_of(int))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')

Of course you can mix and match the two approaches at your convenience:
...or both at once:

.. doctest::

Expand All @@ -406,16 +358,22 @@ Of course you can mix and match the two approaches at your convenience:
...
ValueError: value out of bounds

And finally you can disable validators globally:

>>> attr.set_run_validators(False)
>>> C("128")
C(x='128')
>>> attr.set_run_validators(True)
>>> C("128")
``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.instance_of(int))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), <class 'int'>, '128')
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')

Check out :ref:`validators` for more details.


Conversion
Expand All @@ -433,23 +391,7 @@ This can be useful for doing type-conversions on values that you don't want to f
>>> o.x
1

Converters are run *before* validators, so you can use validators to check the final form of the value.

.. doctest::

>>> def validate_x(instance, attribute, value):
... if value < 0:
... raise ValueError("x must be be at least 0.")
>>> @attr.s
... class C(object):
... x = attr.ib(converter=int, validator=validate_x)
>>> o = C("0")
>>> o.x
0
>>> C("-1")
Traceback (most recent call last):
...
ValueError: x must be be at least 0.
Check out :ref:`converters` for more details.


.. _metadata:
Expand Down
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Day-to-Day Usage
================

- Once you're comfortable with the concepts, our :doc:`api` contains all information you need to use ``attrs`` to its fullest.
- Instance initialization is one of ``attrs`` key feature areas.
Our goal is to relieve you from writing as much code as possible.
:doc:`init` gives you an overview what ``attrs`` has to offer and explains some related philosophies we believe in.
- If you want to put objects into sets or use them as keys in dictionaries, they have to be hashable.
The simplest way to do that is to use frozen classes, but the topic is more complex than it seems and :doc:`hashing` will give you a primer on what to look out for.
- ``attrs`` is built for extension from the ground up.
Expand Down Expand Up @@ -70,6 +73,7 @@ Full Table of Contents
overview
why
examples
init
hashing
api
extending
Expand Down
Loading

0 comments on commit 879f43d

Please sign in to comment.