GithubHelp home page GithubHelp logo

python-attrs / cattrs Goto Github PK

View Code? Open in Web Editor NEW
755.0 21.0 108.0 2.17 MB

Composable custom class converters for attrs.

Home Page: https://catt.rs

License: MIT License

Makefile 0.44% Python 99.56%
attrs deserialization serialization

cattrs's Introduction

cattrs

Great software needs great data structures.

Documentation Status Supported Python versions


cattrs is an open source Python library for structuring and unstructuring data. cattrs works best with attrs classes, dataclasses and the usual Python collections, but other kinds of classes are supported by manually registering converters.

Python has a rich set of powerful, easy to use, built-in data types like dictionaries, lists and tuples. These data types are also the lingua franca of most data serialization libraries, for formats like json, msgpack, cbor, yaml or toml.

Data types like this, and mappings like dict s in particular, represent unstructured data. Your data is, in all likelihood, structured: not all combinations of field names or values are valid inputs to your programs. In Python, structured data is better represented with classes and enumerations. attrs is an excellent library for declaratively describing the structure of your data, and validating it.

When you're handed unstructured data (by your network, file system, database...), cattrs helps to convert this data into structured data. When you have to convert your structured data into data types other libraries can handle, cattrs turns your classes and enumerations into dictionaries, integers and strings.

Here's a simple taste. The list containing a float, an int and a string gets converted into a tuple of three ints.

>>> import cattrs

>>> cattrs.structure([1.0, 2, "3"], tuple[int, int, int])
(1, 2, 3)

cattrs works well with attrs classes out of the box.

>>> from attrs import frozen
>>> import cattrs

>>> @frozen  # It works with non-frozen classes too.
... class C:
...     a: int
...     b: str

>>> instance = C(1, 'a')
>>> cattrs.unstructure(instance)
{'a': 1, 'b': 'a'}
>>> cattrs.structure({'a': 1, 'b': 'a'}, C)
C(a=1, b='a')

Here's a much more complex example, involving attrs classes with type metadata.

>>> from enum import unique, Enum
>>> from typing import Optional, Sequence, Union
>>> from cattrs import structure, unstructure
>>> from attrs import define, field

>>> @unique
... class CatBreed(Enum):
...     SIAMESE = "siamese"
...     MAINE_COON = "maine_coon"
...     SACRED_BIRMAN = "birman"

>>> @define
... class Cat:
...     breed: CatBreed
...     names: Sequence[str]

>>> @define
... class DogMicrochip:
...     chip_id = field()  # Type annotations are optional, but recommended
...     time_chipped: float = field()

>>> @define
... class Dog:
...     cuteness: int
...     chip: Optional[DogMicrochip] = None

>>> p = unstructure([Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)),
...                  Cat(breed=CatBreed.MAINE_COON, names=('Fluffly', 'Fluffer'))])

>>> print(p)
[{'cuteness': 1, 'chip': {'chip_id': 1, 'time_chipped': 10.0}}, {'breed': 'maine_coon', 'names': ('Fluffly', 'Fluffer')}]
>>> print(structure(p, list[Union[Dog, Cat]]))
[Dog(cuteness=1, chip=DogMicrochip(chip_id=1, time_chipped=10.0)), Cat(breed=<CatBreed.MAINE_COON: 'maine_coon'>, names=['Fluffly', 'Fluffer'])]

Consider unstructured data a low-level representation that needs to be converted to structured data to be handled, and use structure. When you're done, unstructure the data to its unstructured form and pass it along to another library or module. Use attrs type metadata to add type metadata to attributes, so cattrs will know how to structure and destructure them.

  • Free software: MIT license
  • Documentation: https://catt.rs
  • Python versions supported: 3.8 and up. (Older Python versions are supported by older versions; see the changelog.)

Features

  • Converts structured data into unstructured data, recursively:

    • attrs classes and dataclasses are converted into dictionaries in a way similar to attrs.asdict, or into tuples in a way similar to attrs.astuple.
    • Enumeration instances are converted to their values.
    • Other types are let through without conversion. This includes types such as integers, dictionaries, lists and instances of non-attrs classes.
    • Custom converters for any type can be registered using register_unstructure_hook.
  • Converts unstructured data into structured data, recursively, according to your specification given as a type. The following types are supported:

    • typing.Optional[T] and its 3.10+ form, T | None.

    • list[T], typing.List[T], typing.MutableSequence[T], typing.Sequence[T] (converts to a list).

    • tuple and typing.Tuple (both variants, tuple[T, ...] and tuple[X, Y, Z]).

    • set[T], typing.MutableSet[T], typing.Set[T] (converts to a set).

    • frozenset[T], typing.FrozenSet[T] (converts to a frozenset).

    • dict[K, V], typing.Dict[K, V], typing.MutableMapping[K, V], typing.Mapping[K, V] (converts to a dict).

    • typing.TypedDict, ordinary and generic.

    • typing.NewType

    • PEP 695 type aliases on 3.12+

    • attrs classes with simple attributes and the usual __init__.

      • Simple attributes are attributes that can be assigned unstructured data, like numbers, strings, and collections of unstructured data.
    • All attrs classes and dataclasses with the usual __init__, if their complex attributes have type metadata.

    • Unions of supported attrs classes, given that all of the classes have a unique field.

    • Unions s of anything, given that you provide a disambiguation function for it.

    • Custom converters for any type can be registered using register_structure_hook.

cattrs comes with preconfigured converters for a number of serialization libraries, including json, msgpack, cbor2, bson, yaml and toml. For details, see the cattrs.preconf package.

Design Decisions

cattrs is based on a few fundamental design decisions.

  • Un/structuring rules are separate from the models. This allows models to have a one-to-many relationship with un/structuring rules, and to create un/structuring rules for models which you do not own and you cannot change. (cattrs can be configured to use un/structuring rules from models using the use_class_methods strategy.)
  • Invent as little as possible; reuse existing ordinary Python instead. For example, cattrs did not have a custom exception type to group exceptions until the sanctioned Python exceptiongroups. A side-effect of this design decision is that, in a lot of cases, when you're solving cattrs problems you're actually learning Python instead of learning cattrs.
  • Refuse the temptation to guess. If there are two ways of solving a problem, cattrs should refuse to guess and let the user configure it themselves.

A foolish consistency is the hobgoblin of little minds so these decisions can and are sometimes broken, but they have proven to be a good foundation.

Additional documentation and talks

Credits

Major credits to Hynek Schlawack for creating attrs and its predecessor, characteristic.

cattrs is tested with Hypothesis, by David R. MacIver.

cattrs is benchmarked using perf and pytest-benchmark.

This package was created with Cookiecutter and the audreyr/cookiecutter-pypackage project template.

cattrs's People

Contributors

aarnphm avatar adetokunbo avatar alexwaygood avatar asford avatar bibajz avatar bluetech avatar brakhane avatar dependabot[bot] avatar dswistowski avatar ericbn avatar henryiii avatar jap avatar jarnorfb avatar joeldodge79 avatar kaxil avatar layday avatar madig avatar mgorny avatar micaeljarniac avatar natemcmaster avatar peter554 avatar petergaultney avatar pig208 avatar pombredanne avatar raabf avatar sahu-sunil avatar sobolevn avatar tinche avatar tjni avatar toumorokoshi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cattrs's Issues

Possible to support attr.ib(init=False) ?

  • cattrs version: 0.9.0
  • Python version: 3.7.0
  • Operating System: Windows

Description

Right now cattr can only convert dictionaries back to the respective attr object if the field can be initialized in __init__ (because it calls cl(**conv_obj)). Is it possible for cattr to work with fields with init set to False?

What I Did

Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import attr
>>> import cattr
>>> @attr.s
... class A:
...     a: int = attr.ib(init=False)
...
>>> a = A()
>>> a.a = 10
>>> s = cattr.unstructure(a)
>>> cattr.structure(s, A)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\.virtualenvs\test\lib\site-packages\cattr\converters.py", line 178, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
  File "D:\.virtualenvs\test\lib\site-packages\cattr\converters.py", line 298, in structure_attrs_fromdict
    return cl(**conv_obj)
TypeError: __init__() got an unexpected keyword argument 'a'
>>>

set of enums is not automatically unstructured

  • cattrs version: 0.9.0
  • Python version: 3.7.1
  • Operating System: Linux

Description

I tried to call cattr.unstructure on a set containing enum.IntEnum values.
I expected that it would return a set of enum values (the wrapped MyEnum(0).value), whereas I got a set of enum objects, so effectively no unstructuring occurred.

Note that if I use a list of enums instead of a set, then the IntEnum values are unpacked and I get a list of integers.

What I Did

In [1]: import cattr

In [2]: from enum import IntEnum

In [3]: class MyEnum(IntEnum):
   ...:     A = 1
   ...:     B = 2
   ...:     C = 3
   ...:     

In [4]: lst = [MyEnum(1), MyEnum(2)]

In [5]: cattr.unstructure(lst)
Out[5]: [1, 2]

In [6]: s = {MyEnum(2), MyEnum(3)}

In [7]: cattr.unstructure(s)
Out[7]: {<MyEnum.B: 2>, <MyEnum.C: 3>}

In the last line, I would expect to get {2, 3} as the result.

Explicitly specify the field which does not require structuring

  • cattrs version: 0.9.0
  • Python version: 3.6.6
  • Operating System: macOS

Description

I use cattrs + attrs (w/ type annotations and auto_attribs=True) + marshmallow. Marshmallow does data normalization and then the data is passed through cattr converter to be deserealized into Python objects. When the data is normalized, I already get proper "simple" types, like datetime, so I don't need another conversion.
I would like to indicate to cattrs that it should not do conversion, ideally on per field basis, not on per type basis.
Currently, I specify lambda x, _: x converter per type.

I have similar problem when I specify Union of different types. Those can be float, str, datetime and I don't need to convert them.
May be it is already possible, but I haven't found this in the docs.

typing.Any is not desirable, because then I lose my types

add py.typed for MyPy

  • cattrs version: 1.0.0.rc0

Description

If cattrs is going to start supporting MyPy type checking (meaning I don't need my own stubs anymore), I think the standard practice, following PEP 561, is to add an empty py.typed file to the top level of the package alongside __init__.py. This lets MyPy know that this package is safe to type-check.

Maybe 1.0.0 isn't fully there, in which case it probably isn't time for this. But just in case it had been overlooked...

Pypi package contains unknown files

  • cattrs version: 0.8.0
  • Python version: 3.5
  • Operating System: Linux

Description

I looked into the files that are installed after pip install cattrs. It includes some files which are not included in the source repository: metadata.py, experimental/, vendor/.

I assume this is not intended.

Tuple[A, B] can be structured from a list with less than two values

  • cattrs version: 0.9.0
  • Python version: 3.7.2
  • Operating System: Win 10 x64

Description

When structuring a list using the type Tuple[float, float], cattrs will happily construct a tuple from a list with less than two values. I was expecting an error instead. Is this intentional?

What I Did

In [17]: from typing import Tuple

In [18]: cattr.structure([1,2], Tuple[float, float])
Out[18]: (1.0, 2.0)

In [19]: cattr.structure([1], Tuple[float, float])
Out[19]: (1.0,)

In [20]: cattr.structure([], Tuple[float, float])
Out[20]: ()

cattrs has trouble loading attr classes with a list

  • cattrs version: 0.2.0
  • Python version: 3.5.2 (pyenv)
  • Operating System: macOS 10.12

Description

Tried to load an attrs with a list, I'll just dump the code & file here:

File:
[{"id": 1, "name": "task", "counts": []}]

Code:

import attr
import cattr
import json

from typing import List
from attr.validators import instance_of


from twisted.python.filepath import FilePath


@attr.s
class WordCount(object):
    at = attr.ib(validator=instance_of(int))
    count = attr.ib(validator=instance_of(int))

@attr.s
class Work(object):
    id = attr.ib(validator=instance_of(int))
    name = attr.ib(validator=instance_of(str))
    counts = attr.ib(validator=instance_of(list))
    completed = attr.ib(validator=instance_of(bool), default=False)


def dump_works(works):
    return json.dumps(cattr.dumps(works), separators=(',',':')).encode('utf8')


def save_works(works):
    dest = FilePath("works.json")
    dest.setContent(dump_works(works))


def load_works():

    dest = FilePath("works.json")

    if not dest.exists():
        dest.setContent(b"[]")

    loaded = cattr.loads(
        json.loads(dest.getContent().decode('utf8')),
        List[Work])

    loaded.sort(key=lambda x: x.id)
    return loaded

What I Did

Got an exception :(

	  File "/Users/red/code/taptapstats/src/taptap/web.py", line 17, in works_root
	    works = load_works()
	  File "/Users/red/code/taptapstats/src/taptap/work.py", line 45, in load_works
	    List[Work])
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 83, in loads
	    return self._loads.dispatch(cl)(cl, obj)
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 187, in _loads_list
	    return [conv(elem_type, e) for e in obj]
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 187, in <listcomp>
	    return [conv(elem_type, e) for e in obj]
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 136, in _loads_default
	    return self._loads_attrs(cl, obj)
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 157, in _loads_attrs
	    converted = self._handle_attr_attribute(name, validator, obj)
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 176, in _handle_attr_attribute
	    return self._loads.dispatch(type_)(type_, mapping.get(name))
	  File "/Users/red/code/taptapstats/venv/lib/python3.5/site-packages/cattr/converters.py", line 182, in _loads_list
	    if not cl.__args__ or cl.__args__[0] is Any:
	builtins.AttributeError: type object 'list' has no attribute '__args__'

Allow passing tuple_factory to attr.astuple?

A simple suggestion: attr.astuple supports passing tuple_factory argument, so that you could, for instance, serialize attrs as lists:

attr.astuple(Foo, tuple_factory=list)

This makes it nicer to work with some serializers (e.g. yaml) where you actually want a list and not a tuple since they may get serialized differently.

// There's also a few other useful arguments worth considering, e.g. retain_collection_types.

Maybe something like this would work?

cattr.register_unstructure_hook(Foo, attr.astuple, tuple_factory=list)

Switch to registering functions vs singledispatch

  • cattrs version: 0.4.0

Description

I've run into a couple issues with using cattrs, and I think switching from singledispatch to a combination of can_handle_type functions and a lrucache will fix them.

Here's the problems I've found:

  1. importing typing and cattrs in the same python interpreter leads to an infinite recursion error for Python2.

I understand the motivation for vendor typing, but it makes it impossible to use a project with the standard typing module and cattrs. This example in Python2 leads to an recursive error, recursing in the vendored typing forever.

import attr
import cattr
from typing import List


@attr.s
class Point(object):
    x = cattr.typed(int)
    y = cattr.typed(float)


point = cattr.structure({"x": 1, "y": 1.0}, Point)

def foo():
    return [1, 2, 3]
  1. The vendored typing is old

The vendored typing module doesn't contain some fixes, such as:

https://bugs.python.org/issue28339

Leading to an error message like:

https://travis-ci.org/toumorokoshi/transmute-core/jobs/269864064

  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/_pytest/config.py", line 379, in _importconftest
    mod = conftestpath.pyimport()
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/py/_path/local.py", line 662, in pyimport
    __import__(modname)
  File "/home/travis/build/toumorokoshi/transmute-core/transmute_core/__init__.py", line 12, in <module>
    from .swagger import generate_swagger_html, get_swagger_static_root, SwaggerSpec
  File "/home/travis/build/toumorokoshi/transmute-core/transmute_core/swagger/__init__.py", line 1, in <module>
    import jinja2
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/jinja2/__init__.py", line 33, in <module>
    from jinja2.environment import Environment, Template
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/jinja2/environment.py", line 15, in <module>
    from jinja2 import nodes
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/jinja2/nodes.py", line 19, in <module>
    from jinja2.utils import Markup
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/jinja2/utils.py", line 486, in <module>
    MutableMapping.register(LRUCache)
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/abc.py", line 158, in register
    if issubclass(subclass, cls):
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/abc.py", line 226, in __subclasscheck__
    if issubclass(subclass, scls):
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/abc.py", line 207, in __subclasscheck__
    ok = cls.__subclasshook__(subclass)
  File "/opt/python/3.5.3/lib/python3.5/typing.py", line 860, in __extrahook__
    if issubclass(subclass, scls):
  File "/home/travis/build/toumorokoshi/transmute-core/lib/python3.5/site-packages/cattr/vendor/python3/typing.py", line 1139, in __subclasscheck__
    raise TypeError("Parameterized generics cannot be used with class "
TypeError: Parameterized generics cannot be used with class or instance checks
ERROR: could not load /home/travis/build/toumorokoshi/transmute-core/transmute_core/tests/conftest.py
  1. Unable to add typing or model systems which aren't depending on types.

For example, Schematics uses instances of classes sometimes, rather than a type per se:

http://schematics.readthedocs.io/en/latest/

The fields specifically are instances of XType() classes, rather than a class in and of itself.

From my understanding, the rationale to vendor typing is to allow usage of singledispatch. Since it looks like there's already issues with this strategy today (python/typing#405), I think it'd be best to move to a more flexible system. E.g. a simple system that registers function and a structure / unstructure hook would be enough to support all of these situations:

# maybe find a non-private way to do this, such as typing-inspeect
from typing import _gorg, GenericMeta, List
self.structure.register(lambda x: isinstance(x, GenericMeta) and _gorg(x) is List, _structure_seq)

To optimize, one could use the id of the type as a key in a lrucache.

I'd send a PR, but wanted to jot down thoughts first.

cannot import name '_Union'

  • cattrs version: 0.9.0
  • Python version: 3.5.2
  • Operating System: ubuntu xenial64

Description

On a clean ubuntu VM, I pip3 install cattrs, and import cattr doesn't seem to work.

Is something funny with the typing module in python 3.5.2 or something? It looks like travis tested Python 3.5, so I don't understand how this happened.

What I Did

> vagrant init ubuntu/xenial64
...
> vagrant up
...
> vagrant ssh
...
vagrant@ubuntu-xenial:~$ wget https://bootstrap.pypa.io/get-pip.py
...
vagrant@ubuntu-xenial:~$ sudo python3 get-pip.py
...
vagrant@ubuntu-xenial:~$ pip3 install cattr
...
Successfully installed attrs-18.2.0 cattrs-0.9.0
vagrant@ubuntu-xenial:~$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cattr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.5/dist-packages/cattr/__init__.py", line 2, in <module>
    from .converters import Converter, UnstructureStrategy
  File "/usr/local/lib/python3.5/dist-packages/cattr/converters.py", line 3, in
<module>
    from ._compat import (
  File "/usr/local/lib/python3.5/dist-packages/cattr/_compat.py", line 86, in <module>
    from typing import _Union
ImportError: cannot import name '_Union'
>>> import typing
>>> dir(typing)
['AbstractSet', 'Any', 'AnyMeta', 'AnyStr', 'AsyncIterable', 'AsyncIterator', 'Awaitable', 'BinaryIO', 'ByteString', 'CT', 'Callable', 'CallableMeta', 'Container', 'DefaultDict', 'Dict', 'Final', 'FrozenSet', 'Generator', 'Generic', 'GenericMeta', 'Hashable', 'IO', 'ItemsView', 'Iterable', 'Iterator', 'KT', 'KeysView', 'List', 'Mapping', 'MappingView', 'Match', 'MutableMapping', 'MutableSequence', 'MutableSet', 'NamedTuple', 'NewType', 'Optional', 'OptionalMeta', 'Pattern', 'Reversible', 'Sequence', 'Set', 'Sized', 'SupportsAbs', 'SupportsBytes', 'SupportsComplex', 'SupportsFloat', 'SupportsInt', 'SupportsRound', 'T', 'TYPE_CHECKING', 'T_co', 'T_contra', 'Text', 'TextIO', 'Tuple', 'TupleMeta', 'Type', 'TypeVar', 'TypingMeta', 'Union', 'UnionMeta', 'VT', 'VT_co', 'V_co', 'ValuesView', '_ForwardRef', '_FrozenSetMeta', '_G_base', '_Protocol', '_ProtocolMeta', '_TypeAlias', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_eval_type', '_geqv', '_get_defaults',
'_get_type_vars', '_gorg', '_next_in_mro', '_overload_dummy', '_qualname', '_type_check', '_type_repr', '_type_vars', 'abc', 'abstractmethod', 'abstractproperty', 'cast', 'collections', 'collections_abc', 'contextlib', 'functools', 'get_type_hints', 'io', 'no_type_check', 'no_type_check_decorator', 'overload', 're', 'stdlib_re', 'sys', 'types']

there is no '_Union' in there
on my local system, I have python 3.6.4

Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing
>>> dir(typing)
['AbstractSet', 'Any', 'AnyStr', 'AsyncContextManager', 'AsyncGenerator', 'AsyncIterable', 'AsyncIterator', 'Awaitable', 'BinaryIO', 'ByteString', 'CT_co', 'Callable', 'CallableMeta', 'ChainMap', 'ClassVar', 'Collection', 'Container', 'ContextManager', 'Coroutine', 'Counter', 'DefaultDict', 'Deque', 'Dict', 'FrozenSet', 'Generator', 'Generic', 'GenericMeta', 'Hashable', 'IO', 'ItemsView', 'Iterable', 'Iterator', 'KT', 'KeysView', 'List', 'Mapping', 'MappingView', 'Match', 'MethodDescriptorType', 'MethodWrapperType', 'MutableMapping', 'MutableSequence', 'MutableSet', 'NamedTuple', 'NamedTupleMeta', 'NewType', 'NoReturn', 'Optional',
'Pattern', 'Reversible', 'Sequence', 'Set', 'Sized', 'SupportsAbs', 'SupportsBytes', 'SupportsComplex', 'SupportsFloat', 'SupportsInt', 'SupportsRound', 'T', 'TYPE_CHECKING', 'T_co', 'T_contra', 'Text', 'TextIO', 'Tuple', 'TupleMeta', 'Type', 'TypeVar', 'TypingMeta', 'Union', 'VT', 'VT_co', 'V_co', 'ValuesView', 'WrapperDescriptorType', '_Any', '_ClassVar', '_FinalTypingBase', '_ForwardRef', '_G_base', '_NoReturn', '_Optional', '_PY36', '_Protocol', '_ProtocolMeta', '_TypeAlias', '_TypingBase', '_TypingEllipsis', '_TypingEmpty', '_Union', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_allowed_types', '_check_generic', '_cleanups', '_collections_abc', '_eval_type', '_generic_new', '_get_defaults', '_get_type_vars', '_make_nmtuple', '_make_subclasshook', '_next_in_mro', '_no_slots_copy', '_overload_dummy', '_prohibited', '_qualname', '_remove_dups_flatten', '_replace_arg', '_special', '_subs_tree', '_tp_cache', '_trim_name', '_type_check', '_type_repr', '_type_vars', 'abc', 'abstractmethod', 'abstractproperty', 'cast', 'collections',
'collections_abc', 'contextlib', 'functools', 'get_type_hints', 'io', 'no_type_check', 'no_type_check_decorator', 'overload', 're', 'stdlib_re', 'sys', 'types']

That one has a '_Union'

Structuring Generics

I realized I forgot to file an issue before submitting a PR, but this is to capture #51 .

The problem I have experienced is complex object with generic can be tedious to make work. Take the below code as an example, to use cattrs I need to register a hook for type ~T which in this case would be assign it to int, or I could register a hook on M.

T = TypeVar("T")

@attrs(auto_attrib=True)
class M(Generic[T]):
    prop: List[T]

data = {
  "prop": [1,2,3]
}

result = structure(data, M)

If a TypeVar with the name T is used anywhere else in my code it must be of type int or structuring is going to have issues.; This gets even worse when you have nested generics that share the same type var name. I typically just use T more my generic variable name, so I run into this problem often.

To address this, what I would like to see cattrs support is passing a _GenericAlias into the structure function and automatically handle the structuring if possible. The code above would stay the same except the last line which would become

result = structure(data, M[str])

Given this support, I no longer need to manually register ~T or M and the existing structuring mechanisms could be utilized to handle the structuring. The mentioned PR is my first pass at implementing this feature, looking to see if this i something the library is willing support before I continue the work in polishing the code, documentation and tests.

Composition/nested classes

  • cattrs version:
    0.6.0
  • Python version:
    3.6.4
  • Operating System:
    Mac os x/ubuntu 16.04/Python Alpine

Description

I have @attrs classes using composition - i.e. class members are other @attrs classes. When I unstructure the top level object, there is no class information in the nested objects.
I can understand if this doesn't handle that, but there are many people who would like to do this, and the examples pointedly show only the simplest use cases and don't mention composition or nested classes at all, or inheritance. I know there are hooks, but whether they are a good solution or this library is suitable for serializing and deserializing objects with inheritance or nesting is not clear.

What I Did

Too much code, but I'd be happy to pm it.

Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.

cattr.structure does not respect default value from attr.ib

  • cattrs version: 0.40
  • Python version: 3.6.2
  • Operating System: Ubuntu 16.04 x86_64

Description

The attrs package allows default values for any given field, and allows initialisation of an attrs class without keys for which a default was provided.

With the following definition,

@attr.s(slots=True, frozen=True)
class Cfg(object):
    a = typed(int)
    b = typed(Optional[int])
    c = attr.ib(convert=int, default=7)
    d = typed(Optional[int], default=5)

We can instantiate a Cfg object as:

In [243]: Cfg(**dt)
Out[243]: Cfg(a=5, b=None, c=7, d=5)

When used with cattr.structure, however, the default values seem to be entirely ignored, moreover there is a KeyError exception thrown. While wrapping the attr.ib type in an Optional avoids the error, we wind up with None instead of the provided default value.

cattr.structure_attrs_fromdict({'a':5,'b':None}, Cfg)

In [208]: cattr.structure_attrs_fromdict(dt, Cfg)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-208-5adc18822e6a> in <module>()
----> 1 cattr.structure_attrs_fromdict(dt, Cfg)

~/dev/survey_stats/venv/lib/python3.6/site-packages/cattr/converters.py in structure_attrs_fromdict(self, obj, cl)
    255             name = a.name
    256             # We detect the type by metadata.
--> 257             converted = self._structure_attr_from_dict(a, name, obj)
    258             conv_obj[name] = converted
    259

~/dev/survey_stats/venv/lib/python3.6/site-packages/cattr/converters.py in _structure_attr_from_dict(self, a, name, mapping)
    265         if type_ is None:
    266             # No type.
--> 267             return mapping[name]
    268         if isinstance(type_, _Union):
    269             # This is a union.

KeyError: 'c'

In [249]: cattr.structure_attrs_fromdict({'a':5,'b':None, 'c':3}, Cfg)
Out[249]: Cfg(a=5, b=None, c=3, d=None)

Default value is being ignored while structuring the attr class

  • cattrs version: 0.5.0
  • Python version: 3.6.2
  • Operating System: Debian GNU/Linux 8.9 (jessie)

Description

When I am trying use structure on an attr class having default values set for few optional attributes, those default values are being ignored and not returning to returned object

What I Did

Here is the sample code:

import attr
from cattr import structure, typed
from typing import Optional


@attr.s
class A(object):
    a = typed(Optional[int], default=0)

@attr.s
class B(object):
    b = attr.ib(validator=optional(instance_of(int)), default=0)

When I tried structure({}, A) I expected A1(a=0) but I get A1(a=None)
Similarly for structure({}, B) I get following error

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/xxx/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/xxx/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/xxx/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/xxx/cattr/converters.py", line 286, in _structure_attr_from_dict
    return mapping[name]
KeyError: 'b'

A fix something like will fix the issue: shwetas1205@7d6d6fa

cattr.structure converting None

  • cattrs version: 0.9.0
  • Python version: 3.6
  • Operating System: Ubuntu 18.04

Description

structure is converting None to 'None' when it is set to a str field.

What I Did

dataclass = partial(attrs, auto_attribs=True, frozen=True)

@dataclass
class BaseData:
    def to_dict(self):
        return asdict(self)

@dataclass
class A(BaseData):
    a: str
    b: str

print(structure({'a': 'asd', 'b': None}, A))
>>> A(a='asd', b='None')

Breaks when PEP 563 is active

  • cattrs version: All versions, as far as I can tell
  • Python version: Python 3.7
  • Operating System: Arch Linux

Description

cattr.structure breaks when PEP 563 is activated. This can be done in Python 3.7 with the new from __future__ import annotations.

In short, this defers the evaluation of type annotations. So consider this class:

@attr.s(auto_attribs=True)
class Foo:
    bar: str

Traditionally, __annotations__ looks like this:

>>> Foo.__annotations__
... {'bar': str}

but under PEP 563 (and eventually the default behaviour), it'll look like this:

>>> Foo.__annotations__
... {'bar': 'str'}

This seems to confuse cattr and causes problems. Round-tripping the above class, we now see exceptions like this:

In [16]: cattr.structure(cattr.unstructure(Foo('Hello World')), Foo)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-16-1bbcc538d3df> in <module>()
----> 1 cattr.structure(cattr.unstructure(Foo('Hello World')), Foo)

~/.local/share/virtualenvs/aastrology-5jK3bc4O/lib/python3.7/site-packages/cattr/converters.py in structure(self, obj, cl)
    176         """Convert unstructured Python data structures to structured data."""
    177         # type: (Any, Type) -> Any
--> 178         return self._structure_func.dispatch(cl)(obj, cl)
    179 
    180     # Classes to Python primitives.

~/.local/share/virtualenvs/aastrology-5jK3bc4O/lib/python3.7/site-packages/cattr/converters.py in structure_attrs_fromdict(self, obj, cl)
    294             except KeyError:
    295                 continue
--> 296             conv_obj[name] = dispatch(type_)(val, type_)
    297 
    298         return cl(**conv_obj)

~/.local/share/virtualenvs/aastrology-5jK3bc4O/lib/python3.7/site-packages/cattr/converters.py in _structure_default(self, obj, cl)
    238             "it.".format(cl)
    239         )
--> 240         raise ValueError(msg)
    241 
    242     def _structure_call(self, obj, cl):

ValueError: Unsupported type: str. Register a structure hook for it.

This fix is probably to apply this logic: https://www.python.org/dev/peps/pep-0563/#resolving-type-hints-at-runtime

Here:

https://github.com/Tinche/cattrs/blob/6e90fd28af8e23c44798b5cbd98e8d5a06eb87ba/src/cattr/converters.py#L287-L296

Which I'm currently digging into.

Test failing with Python 2.7

  • cattrs version: 0.9.0
  • Python version: 2.7.15, 3.6.5
  • Operating System: Fedora 28

Description

I packaged cattrs for Fedora a while back, using 0.6.0, and building for Python 2.7 and 3.6. Now I'm trying to update the package to 0.9.0 to support Python 3.7 for Fedora 29. However, even when building on Fedora 28 with Python 2.7.15 and 3.6.5, when running the tests py.test is giving me an error about an unexpected keyword suppress_health_checks.

What I Did

command to start the build:

rpmbuild -ba python-cattrs.spec

excerpt of output:

+ py.test-2.7 -v
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/_pytest/config.py", line 366, in _importconftest
    mod = conftestpath.pyimport()
  File "/usr/lib/python2.7/site-packages/py/_path/local.py", line 668, in pyimport
    __import__(modname)
  File "/usr/lib/python2.7/site-packages/_pytest/assertion/rewrite.py", line 213, in load_module
    py.builtin.exec_(co, mod.__dict__)
  File "/usr/lib/python2.7/site-packages/py/_builtin.py", line 221, in exec_
    exec2(obj, globals, locals)
  File "<string>", line 7, in exec2
  File "/home/eric/rpmbuild/BUILD/cattrs-0.9.0/tests/conftest.py", line 14, in <module>
    "tests", suppress_health_check=(HealthCheck.too_slow,)
TypeError: register_profile() got an unexpected keyword argument 'suppress_health_check'
ERROR: could not load /home/eric/rpmbuild/BUILD/cattrs-0.9.0/tests/conftest.py

structure: how to report path to invalid data element

  • cattrs version: 0.9.0
  • Python version: 3.6.ř
  • Operating System: Debian 9

Description

I want to cattrs to load complex nested data and in case some validation/conversion fails, I want to provide reasonable context information about what part of data did not work properly.

What I Did

Having attrs based classes: Config with attributes source, fetch and publish, each holding value of specific (attrs based) class Source, Fetch and Publish.

If some data element is wrong (e.g. expecting integer and providing string "5a"), the structure process fails raising ValueError("could not convert string to float: '5a'",)

However, the error does not include any contextual information about where in my nested input the problem was read from.

It would be nice to get some sort of path in the exception, which I could use. marshmallow and trafaret are examples of similar solutions providing contextual information.

Pip install fails for cattrs==0.7.0 with Python 3

  • cattrs version: 0.7.0
  • Python version: 3.6
  • Operating System: macOS 10.13.4

Description

Installing latest cattrs via pip fails with python 3.

What I Did

pip install cattrs

Collecting cattrs
  Using cached cattrs-0.7.0-py2.py3-none-any.whl
Collecting typing>=3.5.3 (from cattrs)
  Downloading typing-3.6.4-py3-none-any.whl
Collecting attrs>=17.3 (from cattrs)
  Using cached attrs-17.4.0-py2.py3-none-any.whl
Collecting functools32>=3.2.3 (from cattrs)
  Using cached functools32-3.2.3-2.tar.gz
    Complete output from command python setup.py egg_info:
    This backport is for Python 2.7 only.

Self-referential type specification issues

  • cattrs version: 02d9da0
  • Python version: 3.6.2
  • Operating System: Kubuntu 16.04

Description

Proper definition of self-referential types seems unclear. This would relate to the forward declarations problem discussed in PEP484. I started digging on this because of the typing.List[typing.Union['WithUnion', WithChild]] case but provide a few attempts building up to that for reference.

My big picture is to easily [de]serialize attrs classes which I am using for a PyQt5 tree model. They can allow multiple types of children within lists (a group node would be able to hold both other groups and parameters, for example).

In some of the cases self-reference seems achievable by registering typing._ForwardRef('MyClass') but it doesn't seem clean to have to register a private member of typing so I thought it would be worth considering a cleaner approach. I haven't worked with typing before so I'm not sure exactly how it goes about dealing with class name strings and disambiguation of matching class names in different scopes or modules.

As a side note, I first rolled my own [de]serialization scheme around attrs. Then started doing an attrs/marshmallow integration in graham. Now I am trying out cattrs for the same roll.

What I Did

import traceback
import typing

import attr
import cattr


@attr.s
class WithChild:
    child = cattr.typed(
        default=None,
        type='WithChild',
    )


@attr.s
class WithChildren:
    children = cattr.typed(
        default=attr.Factory(list),
        type=typing.List['WithChildren'],
    )


@attr.s
class WithUnion:
    children = cattr.typed(
        default=attr.Factory(list),
        type=typing.List[typing.Union['WithUnion']]
    )


@attr.s
class WithUnionTwo:
    children = cattr.typed(
        default=attr.Factory(list),
        type=typing.List[typing.Union['WithUnion', WithChild]]
    )


def tryit():
    try:
        cattr.structure({}, WithChild)
    except:
        print(' - - - - - - WithChild failed')
        print(traceback.format_exc())
    else:
        print(' - - - - - - WithChild succeeded')

    try:
        cattr.structure({'children': [{}]}, WithChildren)
    except:
        print(' - - - - - - WithChildren failed')
        print(traceback.format_exc())
    else:
        print(' - - - - - - WithChildren succeeded')

    try:
        cattr.structure({'children': [{}]}, WithUnion)
    except:
        print(' - - - - - - WithUnion failed')
        print(traceback.format_exc())
    else:
        print(' - - - - - - WithUnion succeeded')

    try:
        cattr.structure({'children': [{}]}, WithUnionTwo)
    except:
        print(' - - - - - - WithUnionTwo failed')
        print(traceback.format_exc())
    else:
        print(' - - - - - - WithUnionTwo succeeded')


print(' == == == == == == == == == == == == No registration\n')
tryit()

cattr.register_structure_hook('WithChild', lambda d, t: WithChild(**d))
cattr.register_structure_hook('WithChildren', lambda d, t: WithChildren(**d))
cattr.register_structure_hook('WithUnion', lambda d, t: WithUnion(**d))
cattr.register_structure_hook('WithUnionTwo', lambda d, t: WithUnionTwo(**d))

print(' == == == == == == == == == == == == Registered strings\n')
tryit()

cattr.register_structure_hook(
    typing._ForwardRef('WithChild'),
    lambda d, t: WithChild(**d),
)
cattr.register_structure_hook(
    typing._ForwardRef('WithChildren'),
    lambda d, t: WithChildren(**d),
)
cattr.register_structure_hook(
    typing._ForwardRef('WithUnion'),
    lambda d, t: WithUnion(**d),
)
cattr.register_structure_hook(
    typing._ForwardRef('WithUnionTwo'),
    lambda d, t: WithUnionTwo(**d),
)

print(' == == == == == == == == == == == == Registered typing._ForwardRef()\n')
tryit()
 == == == == == == == == == == == == No registration

 - - - - - - WithChild failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 42, in tryit
    cattr.structure({}, WithChild)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: WithChild. Register a structure hook for it.

 - - - - - - WithChildren failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 50, in tryit
    cattr.structure({'children': [{}]}, WithChildren)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: _ForwardRef('WithChildren'). Register a structure hook for it.

 - - - - - - WithUnion failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 58, in tryit
    cattr.structure({'children': [{}]}, WithUnion)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: _ForwardRef('WithUnion'). Register a structure hook for it.

 - - - - - - WithUnionTwo failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 66, in tryit
    cattr.structure({'children': [{}]}, WithUnionTwo)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 372, in _structure_union
    cl = self._dis_func_cache(union)(obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 398, in _get_dis_func
    raise ValueError('Only unions of attr classes supported '
ValueError: Only unions of attr classes supported currently. Register a loads hook manually.

 == == == == == == == == == == == == Registered strings

 - - - - - - WithChild failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 42, in tryit
    cattr.structure({}, WithChild)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: WithChild. Register a structure hook for it.

 - - - - - - WithChildren failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 50, in tryit
    cattr.structure({'children': [{}]}, WithChildren)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: _ForwardRef('WithChildren'). Register a structure hook for it.

 - - - - - - WithUnion failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 58, in tryit
    cattr.structure({'children': [{}]}, WithUnion)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: _ForwardRef('WithUnion'). Register a structure hook for it.

 - - - - - - WithUnionTwo failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 66, in tryit
    cattr.structure({'children': [{}]}, WithUnionTwo)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 372, in _structure_union
    cl = self._dis_func_cache(union)(obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 398, in _get_dis_func
    raise ValueError('Only unions of attr classes supported '
ValueError: Only unions of attr classes supported currently. Register a loads hook manually.

 == == == == == == == == == == == == Registered typing._ForwardRef()

 - - - - - - WithChild failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 42, in tryit
    cattr.structure({}, WithChild)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 229, in _structure_default
    raise ValueError(msg)
ValueError: Unsupported type: WithChild. Register a structure hook for it.

 - - - - - - WithChildren succeeded
 - - - - - - WithUnion succeeded
 - - - - - - WithUnionTwo failed
Traceback (most recent call last):
  File "/epc/t/472/p/venv/src/graham/src/graham/tests/test_cattrs_a.py", line 66, in tryit
    cattr.structure({'children': [{}]}, WithUnionTwo)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 171, in structure
    return self.structure_func.dispatch(cl)(obj, cl)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 86, in <lambda>
    lambda *args, **kwargs: self.structure_attrs(*args, **kwargs))
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 276, in structure_attrs_fromdict
    converted = self._structure_attr_from_dict(a, name, obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 293, in _structure_attr_from_dict
    return self._structure.dispatch(type_)(mapping.get(a.name), type_)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in _structure_list
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 303, in <listcomp>
    for e in obj]
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 372, in _structure_union
    cl = self._dis_func_cache(union)(obj)
  File "/epc/t/472/p/venv/src/cattrs/cattr/converters.py", line 398, in _get_dis_func
    raise ValueError('Only unions of attr classes supported '
ValueError: Only unions of attr classes supported currently. Register a loads hook manually.

'install_requires' must be a string or list of strings

  • cattrs version: 0.6.0
  • Python version: 3.4.2
  • Operating System: linux

Description

The library can not be installed due to a wrong format of the install_requires field. So the library is unable to install and use.

What I Did

python3 setup.py install

...

Installed /usr/local/lib/python3.4/dist-packages/My_Smart_Home-0.1.0-py3.4.egg
Processing dependencies for My-Smart-Home==0.1.0
Searching for cattrs==0.6.0
Reading https://pypi.python.org/simple/cattrs/
Best match: cattrs 0.6.0
Downloading https://pypi.python.org/packages/ab/6c/e490455f33c9c36ca14bfc7385e72b1b504192eea36aa39ff693604f0790/cattrs-0.6.0.tar.gz#md5=d61409430787857cd970b0cba7a64965
Processing cattrs-0.6.0.tar.gz
Writing /tmp/easy_install-u0fnf4u5/cattrs-0.6.0/setup.cfg
Running cattrs-0.6.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-u0fnf4u5/cattrs-0.6.0/egg-dist-tmp-jw_e8t2c
error: Setup script exited with error in cattrs setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers

Skip serialization if value equals default

Consider a class like this:

@attr.s(slots=True)
class Point(object):
    x: int = attr.ib()
    y: int = attr.ib()
    type: Optional[str] = attr.ib(default=None)

It would be nice if the type attribute doesn't get serialized when its value is the default value. Is that possible with the current cattrs?

fix python 3.7

  • cattrs version: 0.8.1
  • Python version: 3.7.0
  • Operating System: Mac OS High sierra

Description

cattrs does not work with python 3.7.

What I Did

Traceback (most recent call last):
  File "/Users/macbook/virtualenvs/venom_venv/lib/python3.7/site-packages/natrixclient/client.py", line 8, in <module>
    import cattr
  File "/Users/macbook/virtualenvs/venom_venv/lib/python3.7/site-packages/cattr/__init__.py", line 2, in <module>
    from .converters import Converter, UnstructureStrategy
  File "/Users/macbook/virtualenvs/venom_venv/lib/python3.7/site-packages/cattr/converters.py", line 2, in <module>
    from typing import (
ImportError: cannot import name '_Union' from 'typing' (/Users/macbook/.pyenv/versions/3.7.0/lib/python3.7/typing.py)

Process finished with exit code 1

cattrs should use `dump` and `load` vs `dumps` and `loads`

dumps and loads in json (and other modules) stands for 'dump (to) string' or 'load (from) string', so the similar API doesn't copy across exactly. json in particular offers dump and load to read from a file, maybe cattrs should use these names because it is operating on types, not strings.

Structure and unstructure generic classes

It is a feature request.

it would be nice if cattrs would support Generic classes definition, i.e.

T = TypeVar("T")

@attr.dataclass
class A(Generic[T]):
    a: T = attr.ib()

@attr.dataclass
class B:
    a_inst: A[int] = attr.ib()

typing.Union manual disambiguation function troubles

  • cattrs version: 0.9.0
  • Python version: 3.7
  • Operating System: Debian

Description

Have cattrs structure the following:

import typing
import attr
from foobar import (TypeOne, TypeTwo)

@attr.s(auto_attribs=True)
class Container:
    types: typing.List[typing.Union[TypeOne, TypeTwo]]

@attr.s(auto_attribs=True)
class TypeOne:
    pass

@attr.s(auto_attribs=True)
class TypeTwo:
    pass

And then have:

cattr.structure(cattr.unstructure(Container()))

I tried to simplify / adapt the example for this ticket ...

What I Did

Tried to write a disambiguation function but did not have much luck.

ValueError: <class 'Container'> has no usable unique attributes.

Goal

If anyone can help me along here, I'd glady submit a documentation PR.

Thanks for this great library 🚀

Release a version on PyPI?

@Tinche Hi - I was wondering if it was possible to release the latest version on PyPI (since there were quite a few changes from 0.6.0)?

Thanks!

Testing, "Data generation is extremely slow"

  • cattrs version: 0.8.1
  • Python version: 2.7.15
  • Operating System: Fedora rawhide

Description

I can build an RPM of cattrs fine on my own machine with Python 2.7.15 and 3.6.5, but it the test case generation is so slow that building the RPM fails on Fedora's koji build infrastructure. Because the testing with Python 2.7.15 failed, I don't know whether it would have succeeded with 3.6.5.

Possibly code should be added to some test cases to suppress the "health check" for slow data generation?

What I Did

The source RPM invokes the tests with both versions of Python using the commands:

PYTHONPATH=%{buildroot}/%{python2_sitelib} py.test-2.7 -v
PYTHONPATH=%{buildroot}/%{python3_sitelib} py.test-3 -v

The complete RPM build log is at https://kojipkgs.fedoraproject.org//work/tasks/6618/27926618/build.log. Only the output for the failures is quoted below.

=================================== FAILURES ===================================
________________________________ test_fallback _________________________________
    @given(simple_classes(defaults=False))
>   def test_fallback(cl_and_vals):
E   FailedHealthCheck: Data generation is extremely slow: Only produced 1 valid examples in 3.76 seconds (0 invalid ones and 0 exceeded maximum size). Try decreasing size of the data you're generating (with e.g.max_size or max_leaves parameters).
E   See https://hypothesis.readthedocs.io/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.too_slow to the suppress_health_check settings for this test.
tests/test_disambigutors.py:45: FailedHealthCheck
---------------------------------- Hypothesis ----------------------------------
You can add @seed(191380151927182284124397332077793207442) to this test or run pytest with --hypothesis-seed=191380151927182284124397332077793207442 to reproduce this failure.
________________________ test_attrs_asdict_unstructure _________________________
converter = <cattr.converters.Converter object at 0xffffae9d16d0>
    @given(nested_classes)
>   def test_attrs_asdict_unstructure(converter, nested_class):
E   FailedHealthCheck: Data generation is extremely slow: Only produced 7 valid examples in 1.57 seconds (0 invalid ones and 0 exceeded maximum size). Try decreasing size of the data you're generating (with e.g.max_size or max_leaves parameters).
E   See https://hypothesis.readthedocs.io/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.too_slow to the suppress_health_check settings for this test.
tests/test_unstructure.py:61: FailedHealthCheck
---------------------------------- Hypothesis ----------------------------------
You can add @seed(302934307671667531413257853548643485645) to this test or run pytest with --hypothesis-seed=302934307671667531413257853548643485645 to reproduce this failure.
________________________ test_attrs_astuple_unstructure ________________________
    @given(nested_classes)
>   def test_attrs_astuple_unstructure(nested_class):
E   FailedHealthCheck: Data generation is extremely slow: Only produced 7 valid examples in 1.35 seconds (0 invalid ones and 0 exceeded maximum size). Try decreasing size of the data you're generating (with e.g.max_size or max_leaves parameters).
E   See https://hypothesis.readthedocs.io/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.too_slow to the suppress_health_check settings for this test.
tests/test_unstructure.py:69: FailedHealthCheck
---------------------------------- Hypothesis ----------------------------------
You can add @seed(302934307671667531413257853548643485645) to this test or run pytest with --hypothesis-seed=302934307671667531413257853548643485645 to reproduce this failure.
____________________________ test_nested_roundtrip _____________________________
    @given(nested_typed_classes, unstructure_strats)
>   def test_nested_roundtrip(cls_and_vals, strat):
        """
        Nested classes with metadata can be unstructured and restructured.
E       FailedHealthCheck: Data generation is extremely slow: Only produced 6 valid examples in 1.15 seconds (0 invalid ones and 0 exceeded maximum size). Try decreasing size of the data you're generating (with e.g.max_size or max_leaves parameters).
E       See https://hypothesis.readthedocs.io/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.too_slow to the suppress_health_check settings for this test.
tests/metadata/test_roundtrips.py:44: FailedHealthCheck
---------------------------------- Hypothesis ----------------------------------
You can add @seed(302934307671667531413257853548643485645) to this test or run pytest with --hypothesis-seed=302934307671667531413257853548643485645 to reproduce this failure.
__________________________ test_union_field_roundtrip __________________________
    @given(
>       simple_typed_classes(defaults=False),
        simple_typed_classes(defaults=False),
        unstructure_strats,
    )
    def test_union_field_roundtrip(cl_and_vals_a, cl_and_vals_b, strat):
        """
        Classes with union fields can be unstructured and structured.
E       FailedHealthCheck: Data generation is extremely slow: Only produced 3 valid examples in 1.14 seconds (3 invalid ones and 0 exceeded maximum size). Try decreasing size of the data you're generating (with e.g.max_size or max_leaves parameters).
E       See https://hypothesis.readthedocs.io/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.too_slow to the suppress_health_check settings for this test.
tests/metadata/test_roundtrips.py:56: FailedHealthCheck
---------------------------------- Hypothesis ----------------------------------
You can add @seed(302934307671667531413257853548643485645) to this test or run pytest with --hypothesis-seed=302934307671667531413257853548643485645 to reproduce this failure.

How can I structure to an OrderedDict?

cattr.structure(OrderedDict([(1, 2), (3, 4)]), Dict) returns an unordered mapping. Is there a way to structure to an ordered mapping instead? (I am on Python 2.7 btw)

Type validation on structuring?

  • cattrs version: 0.6.0, master
  • Python version: 2.7.14, 3.6.4
  • Operating System: Linux

Description

cattrs.unstructure implicitly converts dictionary members to the type of the target class' Attribute. This means I can't use cattrs to enforce basic type constraints on deserialized data. Is it a design decision?

What I Did

The following program allows a list to be deserialized as a string and a string as an integer:

import attr
import cattr


@attr.s
class Wat(object):
    a_string = attr.ib(type=str)
    an_integer = attr.ib(type=int)


print(cattr.structure({"a_string": [1, 2, 3], "an_integer": "10"}, Wat))

This prints Wat(a_string='[1, 2, 3]', an_integer=10).

cattrs silently converts the list provided as a_string to a str because Converter._structure_unicode calls str on an object if it isn't bytes/str or str/unicode.

The string provided as an_integer becomes an integer because of a more general problem: primitive types other than strings are structured with Converter._structure_call, which in turn directly applies the specified type's constructor to the received object.

Raising an exception when a value doesn't match the expected type is a valuable tool in writing robust deserialization code. Right now, I have to use something like schema with cattrs to achieve this, which unfortunately requires that I write my class' type specifications twice.

It's not possible to work around this with a attrs validator (e.g., with a_string = attr.ib(type=str, validator=attr.validators.instance_of(str))) because by the time Wat's initializer the value has been already been converted to the target type. It is possible to use a custom type wrapper, but this requires registering structuring hooks and wreaks havoc with mypy.

Can cattrs be made to raise an exception when values for primitive types are not of the right class?

A few syntax sugar ideas

Hi! First, a very neat idea and a neat implementation :)

As I munged with it for a bit, I was wondering if you'd consider a few potential syntactic shortcuts?

  1. Registering both hooks at once:

For example, if I want pandas Timedeltas to be serialized/deserialized as strings, I have to do something like

cattr.register_structure_hook(pd.Timedelta, pd.Timedelta)
cattr.register_unstructure_hook(pd.Timedelta, str)

This seems like a quite common case (registering both at once), may something like this could work?

cattr.register_hooks(pd.Timedelta, pd.Timedelta, str)
  1. Same as above, but for attr classes. Also, maybe support passing attr.asdict and attr.astuple instead of cattr.{structure,unstructure}_attrs_from{dict,tuple}? E.g., instead of
cattr.register_structure_hook(Foo, cattr.structure_attrs_fromtuple)
cattr.register_unstructure_hook(Foo, cattr.unstructure_attrs_fromtuple)

maybe you could also just do

cattr.register_hooks(Foo, attr.astuple)
  1. Registering hooks for attr types. Maybe allow embedding conversion information into the types themselves? E.g. via some predefined dunder magics, or decorators? Hypothetically, kind of like:
@attr.s(slots=True)
class Foo:
    a: int = attr.ib()
    
    __cattr__ = attr.astuple  # or separate __structure__ / __unstructure__

or maybe even

@cattr.s(slots=True, cattr=attr.astuple)  # or structure= / unstructure=
class Foo:
    a: int = attr.ib()

This one's may be a bit too much, but I thought I'd share all my somewhat random thoughts anyhow :)

Thanks!

Use attr.ib type information

As of the just released version 17.3.0 of the attrs library, attributes can use an additional keyword argument type or even use Python 3.6 type information (see http://www.attrs.org/en/stable/examples.html#types).

cattrs currently only looks at the explicitly given metadata attribute.

Would adding support for the new type information of attr make sense? Would you accept a PR for this?

Attrs and underscore attributes

So attrs has this feature where underscore-prefixed attributes are expected without the underscore in the ctor. But that means cattr.structure(cattr.unstructure(myinstance), myclass) will traceback with unexpected keyword argument.

Since cattrs is specifically made to work with attrs, shouldn't it roundtrip underscore-prefixed attributes by default?

suggestion: retain extra keys in attrs object that subclasses dict

I use cattrs to back an API, which is to say I use it "in between" a user-facing API and a NoSQL database API. It reduces my code surface by an amazing amount, because my backend types can be a superset of my API types (I am in GraphQL, but it would work almost as well in a pure REST scenario) and I never have to manually transform data - cattrs 'just works'.

I've built a few small extensions to cattrs that make it even better for my use cases, and one of them is the ability to 'handle' attrs classes that inherit from dict. Normally I avoid inheritance (and, in fact, classes themselves - I use attrs as a way of defining plain data objects and not much more) as much as possible, but in this case it actually makes a lot of sense for my plain data objects. If data from the database contains more keys than the attrs type defines, it is very handy to be able to have those extra key-value pairs slotted into the base dict automatically at structure time, and have them unstructured as well. My types can therefore be 'partial' types without dropping data, meaning that I can safely perform data copies or moves, or do a write-in-place operation without having to skip typing.

It turns out to be reasonably easy to build this on top of cattrs - I have defined a pair of methods that look like this:

WC = TypeVar('WC', bound=ty.MutableMapping)

def structure_wildcat(d: ty.Mapping, Type: ty.Type[WC]) -> WC:
    """A Wildcat is a Cat (an attrs class) that additionally allows
    arbitrary key-value access as though it were a dict.

    This is intended to provide a smooth transition for types that have
    some dynamic elements, but as though dynamic elements become 'settled',
    they can be directly typed and statically typed-checked.
    """
    wildcat_attrs_names = get_attrs_names(Type)
    # use global converter directly to avoid infinite recursion
    wildcat = cattr.global_converter.structure_attrs_fromdict(d, Type)  # type: ignore
    # we have a partial wildcat. now add in all the things that weren't structured
    wildcat.update({key: value for key, value in d.items() if key not in wildcat_attrs_names})
    return wildcat


def unstructure_wildcat(wildcat: WC) -> dict:
    """Unstructures a Wildcat by extracting the untyped key/value pairs,

    then updating that dict with the result of the typed attrs object unstructure.

    Note that this always chooses the typed value in any key collisions if the Wildcat
    implementation happens to allow those.
    """
    # use global converter directly to avoid infinite recursion
    wildcat_attrs_names = get_attrs_names(type(wildcat))
    wildcat_dict = cattr.global_converter.unstructure_attrs_asdict(wildcat)  # type: ignore
    wildcat_nonattrs_dict = {key: value for key, value in wildcat.items() if key not in wildcat_attrs_names}
    # note that typed entries take absolute precedence over untyped in case of collisions
    return {**wildcat_nonattrs_dict, **wildcat_dict}

The complication here is that this structurer must be registered for every attrs class that subclasses dict. And though this isn't particularly difficult, it actually seems to me that the principle of least surprise for a class that inherits from dict would be for this to happen automatically, without the need for configuration or registered hooks. Note also that these functions presuppose the use of the global converter, though clearly that's an implementation detail that could be changed by allowing the converter to be passed in.

What I'm leading up to here is that I'd be interested in making a PR to cattrs to support this behavior by default, if you'd be willing to accept one. There are a few valid ways of discovering whether an attrs type has a built-in dictionary, but probably the simplest would be to take the Pythonic approach and attempt it and catch the exceptions if they fail.

Most elegant way of doing error-checking and post-processing on `structure`d data?

  • cattrs version: 0.9.0
  • Python version: 3.7.2
  • Operating System: Windows 10 x64

Description

I want to turn a JSON file containing some mandatory and some optional fields into a set of frozen data classes, that are fully sanity-checked and have all optional fields filled in with inferred data, so that I can subsequently leave out any further checks before I use it in a different context. Example file:

{
  "axes": [
    {
      "name": "Inline Skeleton"
    },
    {
      "name": "Worm Skeleton"
    },
    {
      "name": "Stripes"
    }
  ]
}

Data classes:

from typing import List, Optional

import attr

@attr.s(auto_attribs=True, frozen=True, slots=True)
class Axis:
    name: str
    ordering: Optional[int] = None

@attr.s(auto_attribs=True, frozen=True, slots=True)
class Axes:
    axes: List[Axis]
In [51]: cattr.structure(example_json, Axes)
Out[51]: Axes(axes=[Axis(name='Inline Skeleton', ordering=None), Axis(name='Worm Skeleton', ordering=None), Axis(name='Stripes', ordering=None)])

In this case,

  1. if the user specified ordering for at least one Axis, she needs to specify it for all of them and the orderings have to make sense (i.e. no out of bounds, ...)
  2. if the user did not specify any, fill in the implicit order, so the first axis gets a zero, the second a one, etc.

I can do 1. in __attrs_post_init__, but how to best do 2. on the frozen classes? I can use the object.__setattr__(self, "attribute_name", value) escape hatch described in the attrs API reference to get around the freeze, but is there a more elegant way? I thought about writing a custom structure hook for Axes, but the problem is that to fill stuff in, I first need to structure it entirely, so this would recurse? Also, I'd still need to use the escape hatch because everything is frozen?

mypy cannot resolve cattr's path

  • cattrs version: 1.0.0rc0
  • Python version: 3.7.4
  • Operating System: Mac OS X 10.14.6

Description

My system cannot find cattr even when it's definitely installed. mypy cannot resolve the path to it, but it works fine with other packages installed in the same site-packages directory.

What I Did

Here's a peek into my virtual environment:

❯ which mypy
/Users/myuser/.local/share/virtualenvs/python-tSv9DKEa/bin/mypy

❯ pipenv run pip install cattrs
Requirement already satisfied: cattrs in /Users/myuser/.local/share/virtualenvs/python-tSv9DKEa/lib/python3.7/site-packages (1.0.0rc0)
Requirement already satisfied: attrs>=17.3 in /Users/myuser/.local/share/virtualenvs/python-tSv9DKEa/lib/python3.7/site-packages (from cattrs) (19.1.0)

❯ which cattrs
cattrs not found

❯ which cattr
cattr not found

❯ which pytest
/Users/myuser/.local/share/virtualenvs/python-tSv9DKEa/bin/pytest

❯ ls -l /Users/myuser/.local/share/virtualenvs/python-tSv9DKEa/lib/python3.7/site-packages
drwxr-xr-x   13 myuser  staff       416  8 Aug 17:28 cattr
drwxr-xr-x    8 myuser  staff       256  8 Aug 17:28 cattrs-0.9.0.dist-info

Is there interest/value in making cattrs work on python 2.7?

  • cattrs version: 0.2.0
  • Python version: 2.7.13
  • Operating System: MacOS Sierra 10.12.2

Description

I wanted to try to use cattrs on python 2.7 for a personal project
I know that this is supposed to be a python3-specific project, but ...

What I Did

... I made a few changes, and was able to create a branch that successful runs the current tests on python 2.7. I.e

tox -e py27

works.

There's still work to do:

  • the changes have broken python3.5, though that looks to be due to differences between what's available in the typing backport on pypi and what's available in python3.5, rather than changes needed to make cattrs work on py2.7

I can work on fixing the issues and submit a PR, but I wanted to get early feedback before spending time on it. E.g, it'd be worth knowing if cattrs should support python2.7 or not.

Confusing error when structuring

  • cattrs version: 0.9.
  • Python version: 3.6.6
  • Operating System: Ubuntu 18.04.1

Description

When structuring an object that has an attribute with type dict, cattrs fails with a confusing error message.

It wasn't until after I created a minimal reproduction case and came here to file an issue, thinking it was a bug, that I discovered from #3 that cattrs doesn't support dict (or list).

I think either these attempting to use these types should fail with a clear error ("dict is not a supported type hint for cattrs") or alternatively if possible they should work identically to typing.Dict

What I Did

>>> from attr import attrs, attrib
>>> import cattr
>>> @attrs
... class Foo:
...     bar: dict = attrib()
... 
>>> cattr.structure({'bar': {}}, Foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/cattr/converters.py", line 178, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
  File "/usr/local/lib/python3.6/dist-packages/cattr/converters.py", line 296, in structure_attrs_fromdict
    conv_obj[name] = dispatch(type_)(val, type_)
  File "/usr/local/lib/python3.6/dist-packages/cattr/converters.py", line 337, in _structure_dict
    if is_bare(cl) or cl.__args__ == (Any, Any):
  File "/usr/local/lib/python3.6/dist-packages/cattr/_compat.py", line 108, in is_bare
    return not type.__args__
AttributeError: type object 'dict' has no attribute '__args__'

Thanks for cattrs, overall it's been extremely easy to use and cleaned up a large chunk of braindead-but-easy-to-screw-up serialization code.

Faster and more flexible structuring/unstructuring

Since people have started using cattrs, I've been getting more requests and contributions concerning faster and more flexible structuring/unstructuring.

We should start generating structuring/unstructuring functions for attrs classes, like recent releases of attrs generate hash and equality methods. This functionality should be then exposed directly to users.

I'm still unsure about the API.

For example, for a sample class:

@attr.s
class C:
    a: int = attr.ib()
    b: float = attr.ib()

a converter should be able to generate the following Python code:

def unstructure_dict_C(c):
    return {
        "a": c.a,
        "b": c.b,
    }

def structure_dict_C(dict_c, converter):
    return C(
        a=int(dict_c['a']),
        b=float(dict_c['b']),
    )

(this could get complex with generics, union and nested classes, so I've kept the example simple).

We could generate these on first contact with a class, and/or expose this directly to users so users can customize behavior:

cattr.register_unstructure_hook(C, cattr.gen.make_dict_unstructure(C, skip_defaults=True))

Bonus points if the generators could produce pieces of Python code along with functions, so we could generate complex structure/unstructure functions without calling back into the converter (since Python function calls are kinda expensive). Then cattrs could generate faster code than is generally written by hand.

What help is needed to release the project?

Hi there,

cattrs is really nice! Do you need help getting it released?

From my perspective, being able to do nested deserialization and serialization of attrs objects to primitives is really, really cool.

I've been looking for a really fast library to pair up with work an api generation library I've been working on:

http://aiohttp-transmute.readthedocs.io/

and attrs w/ validators is by far the fastest. If it's a small push to get it released, I'd be glad to help.

determine a way to work in a world where Unions don't exist at runtime any more

This might be related to #37 . (Fortuitous ticket number there.)

Recent versions of MyPy emit errors if you try to use a Union at all at runtime, which appears to be a strong signal that cattrs's approach to serialization may be at odds with future plans for the typing module.

I've filed this upstream for discussion at python/mypy#5354 but it might be worthwhile to start figuring out if there's a way to do this that is compatible with the future that is implied by this change.

Structuring a subset of input keys.

Feature proposal/request.
@Tinche Would you be open to a pull with this feature added an optional flag on Converter?

One scenario I've frequently encountered is the need to extract a subset of data from an unstructured input blob. This occurs, for example, when processing a potentially-heterogeneous record from a backend or an external API call when only some of the result is required. It would be very useful in those scenarios to support a "tolerant" structuring mode for attrs-based classes, in which extra keys within the unstructured data are ignored rather than passed into the attrs constructor.

As a minimal motivating example, consider the canonical github ping webhook. All I may want to know from the hooked could be summarized in:

import attr

@attr.s(auto_attribs=True)
class Repo:
    full_name: str

@attr.s(auto_attribs=True)
class Ping:
    zen: str
    hook_id: int
    repository: Repo

However, the payload of the ping includes:

{
  "zen": "Practicality beats purity.",
  "hook_id": 1663 
  "hook": {
    "type": "Repository",
    "id": 1663
    "name": "web",
    ... full hook details ...
  },
  "repository": {
    "id": 314159,
    "name": "name",
    "full_name": "full/name",
    "owner": {
      "login": "asford",
      ... owner details ...
    },
    "private": false,
    ... dozens of urls...
    "created_at": "2018-06-09T17:06:29Z",
    "updated_at": "2018-06-14T09:42:29Z",
    "pushed_at": "2018-06-14T09:42:27Z",
    ... repo details ...
  },
  "sender": {
    "login": "asford",
    ... sender details ...
  }
}

I would be great if I could initialize a custom Converter with a tolerant parsing mode to extract this subset, performing type checking and structuring over the attributes I've defined but silently dropping the "extra" information coming from the api body:

ping_body = cattr.Converter(ignore_extra_attribs=True).structure(body, Ping)

Support transparent empty sequence structuring when None is present

  • cattrs version: 0.9.0
  • Python version: Python 3.6.5
  • Operating System: Mac OS Mojave

Description

Structuring a dictionary to a custom @attr.s type, with defaults provided for all optional arguments.

Cattrs fails to structure the object when there are non-null keys with null/None values where a List/Tuple/Set is expected, because None is not valid (it's not an Optional[List[Foo]], it's just a foolist: List[Foo] = attr.Factory(list).

Suggestion

This is clearly not a bug in cattrs. This is more of a feature request/possible PR: Would you be interested in supporting the use case where a Value of None for a non-Optional List/Tuple/Set in an attrs object would result in filling the attribute with its default value, instead of throwing an error?

Basically, I would like for my attrs types to remain fairly pure - no 'Optional' typing annotation added all over the place - instead, I'd be able to rely on cattrs substituting the default empty list/set/tuple/whatever in the case where there was no underlying object to iterate over because it was null.

Looking through the code, I think this could be supported only for attrs classes by checking in structure_attrs_fromdict to see if val is None, and if so, checking if a.default is not None, and if so, using the default and/or the Factory to generate the 'expected' default.

Again, I realize this is not a bug. Just wanted to raise the possibility. I could probably contribute a PR if it was likely to be accepted. There are already workarounds, like registering structure hooks for every List[Type] for which I want to enable this behavior, but it starts to get a little messy as more types get defined.

Minor note re: test suite

As I'm building the conda package for cattrs (conda-forge/staged-recipes#5651), I've noticed one thing about the tests: they use relative imports. This is highly discouraged, especially when using tools aimed at environment isolation (like tox or conda-build). The reason being that you're not testing the installed package (which is what the end-user will have in their environment), but rather the raw source tree.

The fix is quite simple: remove __init__.py from tests/ and use absolute imports; tox will work just fine, and this will make it possible to test it via conda-build. If you want to be able to quickly run local dev tests outside of tox, you can always do PYTHONPATH=. py.test tests/ so it should be fine as well.

Cheers :)

Python 3.6 Support

  • cattrs version: 0.2.0
  • Python version: 3.6.0
  • Operating System: Linux x64

Description

cattrs fails to import on Python 3.6. :(

2017-01-04T09:10:24.063036+00:00 app[web.1]: Traceback (most recent call last):
2017-01-04T09:10:24.063066+00:00 app[web.1]:   File "/app/.heroku/python/bin/twist", line 11, in <module>
2017-01-04T09:10:24.063243+00:00 app[web.1]:     load_entry_point('Twisted==16.6.0', 'console_scripts', 'twist')()
2017-01-04T09:10:24.063246+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/application/twist/_twist.py", line 132, in main
2017-01-04T09:10:24.063387+00:00 app[web.1]:     options = cls.options(argv)
2017-01-04T09:10:24.063398+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/application/twist/_twist.py", line 38, in options
2017-01-04T09:10:24.063570+00:00 app[web.1]:     options.parseOptions(argv[1:])
2017-01-04T09:10:24.063685+00:00 app[web.1]:     Options.parseOptions(self, options=options)
2017-01-04T09:10:24.063571+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/application/twist/_options.py", line 160, in parseOptions
2017-01-04T09:10:24.063687+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/python/usage.py", line 265, in parseOptions
2017-01-04T09:10:24.063866+00:00 app[web.1]:     self.subOptions = parser()
2017-01-04T09:10:24.063867+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/application/twist/_options.py", line 185, in <lambda>
2017-01-04T09:10:24.064000+00:00 app[web.1]:     lambda plugin=plugin: plugin.options(),
2017-01-04T09:10:24.064002+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/application/service.py", line 76, in get
2017-01-04T09:10:24.064110+00:00 app[web.1]:     return namedAny(self.module).Options
2017-01-04T09:10:24.064111+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/python/reflect.py", line 300, in namedAny
2017-01-04T09:10:24.064278+00:00 app[web.1]:     topLevelPackage = _importAndCheckStack(trialname)
2017-01-04T09:10:24.064279+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/twisted/python/reflect.py", line 239, in _importAndCheckStack
2017-01-04T09:10:24.064416+00:00 app[web.1]:     return __import__(importName)
2017-01-04T09:10:24.064417+00:00 app[web.1]:   File "/app/src/taptap/service.py", line 6, in <module>
2017-01-04T09:10:24.064506+00:00 app[web.1]:     from taptap.web import CoreResource
2017-01-04T09:10:24.064507+00:00 app[web.1]:   File "/app/src/taptap/web.py", line 2, in <module>
2017-01-04T09:10:24.064617+00:00 app[web.1]:     import cattr
2017-01-04T09:10:24.064619+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/cattr/__init__.py", line 1, in <module>
2017-01-04T09:10:24.064692+00:00 app[web.1]:     from .converters import Converter
2017-01-04T09:10:24.064693+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/cattr/converters.py", line 23, in <module>
2017-01-04T09:10:24.064797+00:00 app[web.1]:     class Converter(object):
2017-01-04T09:10:24.064799+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/site-packages/cattr/converters.py", line 180, in Converter
2017-01-04T09:10:24.064946+00:00 app[web.1]:     def _loads_list(self, cl: Type[GenericMeta], obj: Iterable[T]) -> List[T]:
2017-01-04T09:10:24.064947+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 623, in inner
2017-01-04T09:10:24.065204+00:00 app[web.1]:     return cached(*args, **kwds)
2017-01-04T09:10:24.065205+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 1062, in __getitem__
2017-01-04T09:10:24.065572+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 965, in __new__
2017-01-04T09:10:24.065571+00:00 app[web.1]:     orig_bases=self.__orig_bases__)
2017-01-04T09:10:24.066164+00:00 app[web.1]:     self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,))
2017-01-04T09:10:24.066181+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 1007, in _subs_tree
2017-01-04T09:10:24.066791+00:00 app[web.1]:     tree_args = _subs_tree(self, tvars, args)
2017-01-04T09:10:24.066795+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 548, in _subs_tree
2017-01-04T09:10:24.067162+00:00 app[web.1]:     tree_args.append(_replace_arg(arg, tvars, args))
2017-01-04T09:10:24.067166+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 517, in _replace_arg
2017-01-04T09:10:24.067393+00:00 app[web.1]:     return arg._subs_tree(tvars, args)
2017-01-04T09:10:24.067394+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.6/typing.py", line 1005, in _subs_tree
2017-01-04T09:10:24.067754+00:00 app[web.1]:     if self.__origin__ is None:
2017-01-04T09:10:24.067755+00:00 app[web.1]: AttributeError: 'list' object has no attribute '__origin__'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.