Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,102 @@ Validators
attr.exceptions.NotCallableError: 'x' must be callable (got 'not a callable' that is a <class 'str'>).


.. autofunction:: attr.validators.lt

For example:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.lt(3))
...
>>> C(2)
C(x=2)
>>> C(3)
Traceback (most recent call last):
...
ValueError: 'x' must be < 3: 3


.. autofunction:: attr.validators.le

For example:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.le(3))
...
>>> C(3)
C(x=3)
>>> C(4)
Traceback (most recent call last):
...
ValueError: 'x' must be <= 3: 4


.. autofunction:: attr.validators.ge

For example:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.ge(3))
...
>>> C(3)
C(x=3)
>>> C(2)
Traceback (most recent call last):
...
ValueError: 'x' must be >= 3: 2


.. autofunction:: attr.validators.gt

For example:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.gt(3))
...
>>> C(4)
C(x=4)
>>> C(3)
Traceback (most recent call last):
...
ValueError: 'x' must be > 3: 3


.. autofunction:: attr.validators.maxlen

For example:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.maxlen(4))
...
>>> C('spam')
C(x='spam')
>>> C(list(range(4)))
C(x=[0, 1, 2, 3])
>>> C('bacon')
Traceback (most recent call last):
...
ValueError: Lenght of 'x' must be <= 4: 5
>>> C(list(range(5)))
Traceback (most recent call last):
...
ValueError: Lenght of 'x' must be <= 4: 5


.. autofunction:: attr.validators.matches_re

For example:
Expand Down Expand Up @@ -549,6 +645,29 @@ Converters
C(x='')


.. autofunction:: attr.converters.to_attrs

.. autofunction:: attr.converters.to_dt

.. autofunction:: attr.converters.to_iterable

.. autofunction:: attr.converters.to_tuple

.. autofunction:: attr.converters.to_mapping

.. autofunction:: attr.converters.to_union

Hooks
-----

Hooks for automatic data conversion based on type annotations.

.. autofunction:: attr.hooks.make_auto_converter

.. autofunction:: attr.hooks.auto_convert

.. autofunction:: attr.hooks.auto_serialize

.. _api_setters:

Setters
Expand Down
57 changes: 50 additions & 7 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -505,16 +505,16 @@ If you don't mind annotating *all* attributes, you can even drop the `attr.ib` a

.. doctest::

>>> import typing
>>> from typing import Any, ClassVar, List
>>> @attr.s(auto_attribs=True)
... class AutoC:
... cls_var: typing.ClassVar[int] = 5 # this one is ignored
... l: typing.List[int] = attr.Factory(list)
... cls_var: ClassVar[int] = 5 # this one is ignored
... l: 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
... bar: Any = None
>>> attr.fields(AutoC).l.type
typing.List[int]
>>> attr.fields(AutoC).x.type
Expand Down Expand Up @@ -557,10 +557,53 @@ This will replace the *type* attribute in the respective fields.
>>> attr.fields(A).b.type
<class 'B'>

.. 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.
Automatic type conversion
-------------------------

Based on the type annoations shown in the examples above, attrs can automatically create converters for a class' attributes.
This can be very useful when loading data from JSON or dumping to it.

.. doctest::

>>> from datetime import datetime
>>> from enum import Enum
>>> from typing import List, Set
>>> import json
>>>
>>> import attr
>>>
>>>
>>> class LeEnum(Enum):
... spam = "Le spam"
... eggs = "Le eggs"
...
>>> @attr.frozen(field_transformer=attr.auto_convert)
... class Child:
... x: int = attr.ib()
... y: int = attr.ib(converter=float)
...
>>> @attr.frozen(kw_only=True, field_transformer=attr.auto_convert)
... class Parent:
... child: Child
... d: datetime
... e: LeEnum
... f: List[float]
...
>>> DATA = {
... "d": "2020-05-04T13:37:00",
... "e": "Le spam",
... "f": [2.3, "1"], # There is a string in it :-O
... "child": {"x": "23", "y": "42"},
... }
>>> p = Parent(**DATA)
>>> p
Parent(child=Child(x=23, y=42.0), d=datetime.datetime(2020, 5, 4, 13, 37), e=<LeEnum.spam: 'Le spam'>, f=[2.3, 1.0])
>>> d = attr.asdict(p, value_serializer=attr.auto_serialize)
>>> d
{'child': {'x': 23, 'y': 42.0}, 'd': '2020-05-04T13:37:00', 'e': 'Le spam', 'f': [2.3, 1.0]}
>>> # Do a JSON round-trip:
>>> assert Parent(**json.loads(json.dumps(d))) == p


Slots
Expand Down
15 changes: 15 additions & 0 deletions docs/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,21 @@ A more realistic example would be to automatically convert data that you, e.g.,
Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37))


Automatically convert data
++++++++++++++++++++++++++

todo


Extend the auto converter
+++++++++++++++++++++++++

todo





Customize Value Serialization in ``asdict()``
---------------------------------------------

Expand Down
6 changes: 5 additions & 1 deletion src/attr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from functools import partial

from . import converters, exceptions, filters, setters, validators
from . import converters, exceptions, filters, hooks, setters, validators
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
from ._make import (
Expand All @@ -19,6 +19,7 @@
validate,
)
from ._version_info import VersionInfo
from .hooks import auto_convert, auto_serialize


__version__ = "20.3.0.dev0"
Expand Down Expand Up @@ -52,6 +53,8 @@
"attrib",
"attributes",
"attrs",
"auto_convert",
"auto_serialize",
"converters",
"evolve",
"exceptions",
Expand All @@ -60,6 +63,7 @@
"filters",
"get_run_validators",
"has",
"hooks",
"ib",
"make_class",
"resolve_types",
Expand Down
11 changes: 11 additions & 0 deletions src/attr/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
ordered_dict = OrderedDict


if sys.version_info[:2] >= (3, 6):
from typing import get_args, get_origin
else:

def get_args(t):
return getattr(t, "__args__", None)

def get_origin(t):
return getattr(t, "__origin__", None)


if PY2:
from collections import Mapping, Sequence

Expand Down
Loading