.. _examples:

``attrs`` by Example
====================


Basics
------

The simplest possible usage is:

.. doctest::

   >>> import attr
   >>> @attr.s
   ... class Empty(object):
   ...     pass
   >>> Empty()
   Empty()
   >>> Empty() == Empty()
   True
   >>> Empty() is Empty()
   False

So in other words: ``attrs`` is useful even without actual attributes!

But you'll usually want some data on your classes, so let's add some:

.. doctest::

   >>> @attr.s
   ... class Coordinates(object):
   ...     x = attr.ib()
   ...     y = attr.ib()

By default, all features are added, so you immediately have a fully functional data class with a nice ``repr`` string and comparison methods.

.. doctest::

   >>> c1 = Coordinates(1, 2)
   >>> c1
   Coordinates(x=1, y=2)
   >>> c2 = Coordinates(x=2, y=1)
   >>> c2
   Coordinates(x=2, y=1)
   >>> c1 == c2
   False

As shown, the generated ``__init__`` method allows for both positional and keyword arguments.

If playful naming turns you off, ``attrs`` comes with serious business aliases:

.. doctest::

   >>> from attr import attrs, attrib
   >>> @attrs
   ... class SeriousCoordinates(object):
   ...     x = attrib()
   ...     y = attrib()
   >>> SeriousCoordinates(1, 2)
   SeriousCoordinates(x=1, y=2)
   >>> attr.fields(Coordinates) == attr.fields(SeriousCoordinates)
   True

For private attributes, ``attrs`` will strip the leading underscores for keyword arguments:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     _x = attr.ib()
   >>> C(x=1)
   C(_x=1)

If you want to initialize your private attributes yourself, you can do that too:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     _x = attr.ib(init=False, default=42)
   >>> C()
   C(_x=42)
   >>> C(23)
   Traceback (most recent call last):
      ...
   TypeError: __init__() takes exactly 1 argument (2 given)

An additional way of defining attributes is supported too.
This is useful in times when you want to enhance classes that are not yours (nice ``__repr__`` for Django models anyone?):

.. doctest::

   >>> class SomethingFromSomeoneElse(object):
   ...     def __init__(self, x):
   ...         self.x = x
   >>> SomethingFromSomeoneElse = attr.s(
   ...     these={
   ...         "x": attr.ib()
   ...     }, init=False)(SomethingFromSomeoneElse)
   >>> SomethingFromSomeoneElse(1)
   SomethingFromSomeoneElse(x=1)


`Subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc>`_, but ``attrs`` will still do what you'd hope for:

.. doctest::

   >>> @attr.s
   ... class A(object):
   ...     a = attr.ib()
   ...     def get_a(self):
   ...         return self.a
   >>> @attr.s
   ... class B(object):
   ...     b = attr.ib()
   >>> @attr.s
   ... class C(A, B):
   ...     c = attr.ib()
   >>> i = C(1, 2, 3)
   >>> i
   C(a=1, b=2, c=3)
   >>> i == C(1, 2, 3)
   True
   >>> i.get_a()
   1

The order of the attributes is defined by the `MRO <https://www.python.org/download/releases/2.3/mro/>`_.

In Python 3, classes defined within other classes are `detected <https://www.python.org/dev/peps/pep-3155/>`_ and reflected in the ``__repr__``.
In Python 2 though, it's impossible.
Therefore ``@attr.s`` comes with the ``repr_ns`` option to set it manually:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     @attr.s(repr_ns="C")
   ...     class D(object):
   ...         pass
   >>> C.D()
   C.D()

``repr_ns`` works on both Python 2 and 3.
On Python 3 it overrides the implicit detection.


.. _asdict:

Converting to Collections Types
-------------------------------

When you have a class with data, it often is very convenient to transform that class into a :class:`dict` (for example if you want to serialize it to JSON):

.. doctest::

   >>> attr.asdict(Coordinates(x=1, y=2))
   {'x': 1, 'y': 2}

Some fields cannot or should not be transformed.
For that, :func:`attr.asdict` offers a callback that decides whether an attribute should be included:

.. doctest::

   >>> @attr.s
   ... class UserList(object):
   ...     users = attr.ib()
   >>> @attr.s
   ... class User(object):
   ...     email = attr.ib()
   ...     password = attr.ib()
   >>> attr.asdict(UserList([User("jane@doe.invalid", "s33kred"),
   ...                       User("joe@doe.invalid", "p4ssw0rd")]),
   ...             filter=lambda attr, value: attr.name != "password")
   {'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}

For the common case where you want to :func:`include <attr.filters.include>` or :func:`exclude <attr.filters.exclude>` certain types or attributes, ``attrs`` ships with a few helpers:

.. doctest::

   >>> @attr.s
   ... class User(object):
   ...     login = attr.ib()
   ...     password = attr.ib()
   ...     id = attr.ib()
   >>> attr.asdict(
   ...     User("jane", "s33kred", 42),
   ...     filter=attr.filters.exclude(attr.fields(User).password, int))
   {'login': 'jane'}
   >>> @attr.s
   ... class C(object):
   ...     x = attr.ib()
   ...     y = attr.ib()
   ...     z = attr.ib()
   >>> attr.asdict(C("foo", "2", 3),
   ...             filter=attr.filters.include(int, attr.fields(C).x))
   {'x': 'foo', 'z': 3}

Other times, all you want is a tuple and ``attrs`` won't let you down:

.. doctest::

   >>> import sqlite3
   >>> import attr
   >>> @attr.s
   ... class Foo:
   ...    a = attr.ib()
   ...    b = attr.ib()
   >>> foo = Foo(2, 3)
   >>> with sqlite3.connect(":memory:") as conn:
   ...    c = conn.cursor()
   ...    c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS
   ...    c.execute("INSERT INTO foo VALUES (?, ?)", attr.astuple(foo)) #doctest: +ELLIPSIS
   ...    foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone())
   <sqlite3.Cursor object at ...>
   <sqlite3.Cursor object at ...>
   >>> foo == foo2
   True


Defaults
--------

Sometimes you want to have default values for your initializer.
And sometimes you even want mutable objects as default values (ever used accidentally ``def f(arg=[])``?).
``attrs`` has you covered in both cases:

.. doctest::

   >>> import collections
   >>> @attr.s
   ... class Connection(object):
   ...     socket = attr.ib()
   ...     @classmethod
   ...     def connect(cls, db_string):
   ...        # ... connect somehow to db_string ...
   ...        return cls(socket=42)
   >>> @attr.s
   ... class ConnectionPool(object):
   ...     db_string = attr.ib()
   ...     pool = attr.ib(default=attr.Factory(collections.deque))
   ...     debug = attr.ib(default=False)
   ...     def get_connection(self):
   ...         try:
   ...             return self.pool.pop()
   ...         except IndexError:
   ...             if self.debug:
   ...                 print("New connection!")
   ...             return Connection.connect(self.db_string)
   ...     def free_connection(self, conn):
   ...         if self.debug:
   ...             print("Connection returned!")
   ...         self.pool.appendleft(conn)
   ...
   >>> cp = ConnectionPool("postgres://localhost")
   >>> cp
   ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False)
   >>> conn = cp.get_connection()
   >>> conn
   Connection(socket=42)
   >>> cp.free_connection(conn)
   >>> cp
   ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False)

More information on why class methods for constructing objects are awesome can be found in this insightful `blog post <http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html>`_.

Default factories can also be set using a decorator.
The method receives the partially initialized instance which enables you to base a default value on other attributes:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     x = attr.ib(default=1)
   ...     y = attr.ib()
   ...     @y.default
   ...     def name_does_not_matter(self):
   ...         return self.x + 1
   >>> C()
   C(x=1, y=2)


And since the case of ``attr.ib(default=attr.Factory(f))`` is so common, ``attrs`` also comes with syntactic sugar for it:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     x = attr.ib(factory=list)
   >>> C()
   C(x=[])

.. _examples_validators:

Validators
----------

Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments.

``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.

You can use a decorator:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     x = attr.ib()
   ...     @x.validator
   ...     def check(self, attribute, value):
   ...         if value > 42:
   ...             raise ValueError("x must be smaller or equal to 42")
   >>> C(42)
   C(x=42)
   >>> C(43)
   Traceback (most recent call last):
      ...
   ValueError: x must be smaller or equal to 42

 ...or a callable...

.. doctest::

   >>> def x_smaller_than_y(instance, attribute, value):
   ...     if value >= instance.y:
   ...         raise ValueError("'x' has to be smaller than 'y'!")
   >>> @attr.s
   ... class C(object):
   ...     x = attr.ib(validator=[attr.validators.instance_of(int),
   ...                            x_smaller_than_y])
   ...     y = attr.ib()
   >>> C(x=3, y=4)
   C(x=3, y=4)
   >>> C(x=4, y=3)
   Traceback (most recent call last):
      ...
   ValueError: 'x' has to be smaller than 'y'!

...or both at once:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     x = attr.ib(validator=attr.validators.instance_of(int))
   ...     @x.validator
   ...     def fits_byte(self, attribute, value):
   ...         if not 0 <= value < 256:
   ...             raise ValueError("value out of bounds")
   >>> C(128)
   C(x=128)
   >>> C("128")
   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=one), <class 'int'>, '128')
   >>> C(256)
   Traceback (most recent call last):
      ...
   ValueError: value out of bounds


``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')

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


Conversion
----------

Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
This can be useful for doing type-conversions on values that you don't want to force your callers to do.

.. doctest::

    >>> @attr.s
    ... class C(object):
    ...     x = attr.ib(converter=int)
    >>> o = C("1")
    >>> o.x
    1

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


.. _metadata:

Metadata
--------

All ``attrs`` attributes may include arbitrary metadata in the form of a read-only dictionary.

.. doctest::

    >>> @attr.s
    ... class C(object):
    ...    x = attr.ib(metadata={'my_metadata': 1})
    >>> attr.fields(C).x.metadata
    mappingproxy({'my_metadata': 1})
    >>> attr.fields(C).x.metadata['my_metadata']
    1

Metadata is not used by ``attrs``, and is meant to enable rich functionality in third-party libraries.
The metadata dictionary follows the normal dictionary rules: keys need to be hashable, and both keys and values are recommended to be immutable.

If you're the author of a third-party library with ``attrs`` integration, please see :ref:`Extending Metadata <extending_metadata>`.


Types
-----

``attrs`` also allows you to associate a type with an attribute using either the *type* argument to :func:`attr.ib` or -- as of Python 3.6 -- using `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_-annotations:


.. doctest::

   >>> @attr.s
   ... class C:
   ...     x = attr.ib(type=int)
   ...     y: int = attr.ib()
   >>> attr.fields(C).x.type
   <class 'int'>
   >>> attr.fields(C).y.type
   <class 'int'>

If you don't mind annotating *all* attributes, you can even drop the :func:`attr.ib` and assign default values instead:

.. doctest::

   >>> import typing
   >>> @attr.s(auto_attribs=True)
   ... class AutoC:
   ...     cls_var: typing.ClassVar[int] = 5  # this one is ignored
   ...     l: typing.List[int] = attr.Factory(list)
   ...     x: int = 1
   ...     foo: str = attr.ib(
   ...          default="every attrib needs a type if auto_attribs=True"
   ...     )
   ...     bar: typing.Any = None
   >>> attr.fields(AutoC).l.type
   typing.List[int]
   >>> attr.fields(AutoC).x.type
   <class 'int'>
   >>> attr.fields(AutoC).foo.type
   <class 'str'>
   >>> attr.fields(AutoC).bar.type
   typing.Any
   >>> AutoC()
   AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None)
   >>> AutoC.cls_var
   5

The generated ``__init__`` method will have an attribute called ``__annotations__`` that contains this type information.

.. warning::

   ``attrs`` itself doesn't have any features that work on top of type metadata *yet*.
   However it's useful for writing your own validators or serialization frameworks.


.. _slots:

Slots
-----

:term:`Slotted classes` have a bunch of advantages on CPython.
Defining ``__slots__`` by hand is tedious, in ``attrs`` it's just a matter of passing ``slots=True``:

.. doctest::

   >>> @attr.s(slots=True)
   ... class Coordinates(object):
   ...     x = attr.ib()
   ...     y = attr.ib()


Immutability
------------

Sometimes you have instances that shouldn't be changed after instantiation.
Immutability is especially popular in functional programming and is generally a very good thing.
If you'd like to enforce it, ``attrs`` will try to help:

.. doctest::

   >>> @attr.s(frozen=True)
   ... class C(object):
   ...     x = attr.ib()
   >>> i = C(1)
   >>> i.x = 2
   Traceback (most recent call last):
      ...
   attr.exceptions.FrozenInstanceError: can't set attribute
   >>> i.x
   1

Please note that true immutability is impossible in Python but it will :ref:`get <how-frozen>` you 99% there.
By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example.

In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes.
In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/assoc>`_ and ``attrs`` shamelessly imitates it: :func:`attr.evolve`:

.. doctest::

   >>> @attr.s(frozen=True)
   ... class C(object):
   ...     x = attr.ib()
   ...     y = attr.ib()
   >>> i1 = C(1, 2)
   >>> i1
   C(x=1, y=2)
   >>> i2 = attr.evolve(i1, y=3)
   >>> i2
   C(x=1, y=3)
   >>> i1 == i2
   False


Other Goodies
-------------

Sometimes you may want to create a class programmatically.
``attrs`` won't let you down and gives you :func:`attr.make_class` :

.. doctest::

   >>> @attr.s
   ... class C1(object):
   ...     x = attr.ib()
   ...     y = attr.ib()
   >>> C2 = attr.make_class("C2", ["x", "y"])
   >>> attr.fields(C1) == attr.fields(C2)
   True

You can still have power over the attributes if you pass a dictionary of name: ``attr.ib`` mappings and can pass arguments to ``@attr.s``:

.. doctest::

   >>> C = attr.make_class("C", {"x": attr.ib(default=42),
   ...                           "y": attr.ib(default=attr.Factory(list))},
   ...                     repr=False)
   >>> i = C()
   >>> i  # no repr added!
   <__main__.C object at ...>
   >>> i.x
   42
   >>> i.y
   []

If you need to dynamically make a class with :func:`attr.make_class` and it needs to be a subclass of something else than ``object``, use the ``bases`` argument:

.. doctest::

  >>> class D(object):
  ...    def __eq__(self, other):
  ...        return True  # arbitrary example
  >>> C = attr.make_class("C", {}, bases=(D,), cmp=False)
  >>> isinstance(C(), D)
  True

Sometimes, you want to have your class's ``__init__`` method do more than just
the initialization, validation, etc. that gets done for you automatically when
using ``@attr.s``.
To do this, just define a ``__attrs_post_init__`` method in your class.
It will get called at the end of the generated ``__init__`` method.

.. doctest::

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

Finally, you can exclude single attributes from certain methods:

.. doctest::

   >>> @attr.s
   ... class C(object):
   ...     user = attr.ib()
   ...     password = attr.ib(repr=False)
   >>> C("me", "s3kr3t")
   C(user='me')
