Welcome to convclasses’s documentation!

Contents:

convclasses

https://img.shields.io/github/workflow/status/zeburek/convclasses/Test%20package/master https://badge.fury.io/py/convclasses.svg https://img.shields.io/pypi/dm/convclasses.svg https://img.shields.io/pypi/l/convclasses.svg https://img.shields.io/pypi/pyversions/convclasses.svg https://readthedocs.org/projects/convclasses/badge/?version=stable

convclasses is an open source Python library for structuring and unstructuring data. convclasses works best with dataclasses classes 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, 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 are values are valid inputs to your programs. In Python, structured data is better represented with classes and enumerations. dataclasses 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…), convclasses helps to convert this data into structured data. When you have to convert your structured data into data types other libraries can handle, convclasses 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 convclasses
>>> from typing import Tuple
>>>
>>> convclasses.structure([1.0, 2, "3"], Tuple[int, int, int])
(1, 2, 3)

convclasses works well with dataclasses classes out of the box.

>>> import convclasses
>>> from dataclasses import dataclass
>>> from typing import Any
>>> @dataclass(frozen=True)  # It works with normal classes too.
... class C:
...     a: Any
...     b: Any
...
>>> instance = C(1, 'a')
>>> convclasses.unstructure(instance)
{'a': 1, 'b': 'a'}
>>> convclasses.structure({'a': 1, 'b': 'a'}, C)
C(a=1, b='a')

Here’s a much more complex example, involving dataclasses classes with type metadata.

>>> from enum import unique, Enum
>>> from typing import Any, List, Optional, Sequence, Union
>>> from convclasses import structure, unstructure
>>> from dataclasses import dataclass
>>>
>>> @unique
... class CatBreed(Enum):
...     SIAMESE = "siamese"
...     MAINE_COON = "maine_coon"
...     SACRED_BIRMAN = "birman"
...
>>> @dataclass
... class Cat:
...     breed: CatBreed
...     names: Sequence[str]
...
>>> @dataclass
... class DogMicrochip:
...     chip_id: Any
...     time_chipped: float
...
>>> @dataclass
... class Dog:
...     cuteness: int
...     chip: Optional[DogMicrochip]
...
>>> 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 dataclasses type metadata to add type metadata to attributes, so convclasses will know how to structure and destructure them.

Features

  • Converts structured data into unstructured data, recursively:

    • dataclasses classes are converted into dictionaries in a way similar to dataclasses.asdict, or into tuples in a way similar to dataclasses.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-dataclasses 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].

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

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

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

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

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

    • dataclasses 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 dataclasses classes with the usual __init__, if their complex attributes have type metadata.

    • typing.Union s of supported dataclasses classes, given that all of the classes have a unique field.

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

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

Credits

Major credits and best wishes for the original creator of this concept - Tinche, he developed cattrs which this project is fork of.

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

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

convclasses is benchmarked using perf, by Victor Stinner.

Installation

Stable release

To install convclasses, run this command in your terminal:

$ pip install convclasses

This is the preferred method to install convclasses, as it will always install the most recent stable release.

If you don’t have pip installed, this Python installation guide can guide you through the process.

From sources

The sources for convclasses can be downloaded from the Github repo.

You can either clone the public repository:

$ git clone git://github.com/zeburek/convclasses

Or download the tarball:

$ curl  -OL https://github.com/zeburek/convclasses/tarball/master

Once you have a copy of the source, you can install it with:

$ pip install -e .

Common Usage Examples

This section covers common use examples of convclasses features.

Using Pendulum for Dates and Time

To use the excellent Arrow library for datetimes, we need to register structuring and unstructuring hooks for it.

First, we need to decide on the unstructured representation of a datetime instance. Since all our datetimes will use the UTC time zone, we decide to use the UNIX epoch timestamp as our unstructured representation.

Define a class using Arrow’s DateTime:

import arrow
from arrow import Arrow

@dataclass
class MyRecord:
    a_string: str
    a_datetime: Arrow

Next, we register hooks for the Arrow class on a new Converter instance.

converter = Converter()

converter.register_unstructure_hook(Arrow, lambda ar: ar.float_timestamp)

converter.register_structure_hook(Arrow, lambda ts, _: arrow.get(ts))

And we can proceed with unstructuring and structuring instances of MyRecord.

>>> my_record = MyRecord('test', arrow.Arrow(2018, 7, 28, 18, 24))
>>> my_record
MyRecord(a_string='test', a_datetime=<Arrow [2018-07-28T18:24:00+00:00]>)

>>> converter.unstructure(my_record)
{'a_string': 'test', 'a_datetime': 1532802240.0}

>>> converter.structure({'a_string': 'test', 'a_datetime': 1532802240.0}, MyRecord)
MyRecord(a_string='test', a_datetime=<Arrow [2018-07-28T18:24:00+00:00]>)

After a while, we realize we will need our datetimes to have timezone information. We decide to switch to using the ISO 8601 format for our unstructured datetime instances.

>>> converter = convclasses.Converter()
>>> converter.register_unstructure_hook(Arrow, lambda dt: dt.isoformat())
>>> converter.register_structure_hook(Arrow, lambda isostring, _: arrow.get(isostring))

>>> my_record = MyRecord('test', arrow.Arrow(2018, 7, 28, 18, 24, tzinfo='Europe/Paris'))
>>> my_record
MyRecord(a_string='test', a_datetime=<Arrow [2018-07-28T18:24:00+02:00]>)

>>> converter.unstructure(my_record)
{'a_string': 'test', 'a_datetime': '2018-07-28T18:24:00+02:00'}

>>> converter.structure({'a_string': 'test', 'a_datetime': '2018-07-28T18:24:00+02:00'}, MyRecord)
MyRecord(a_string='test', a_datetime=<Arrow [2018-07-28T18:24:00+02:00]>)

Converters

All convclasses functionality is exposed through a convclasses.Converter object. Global convclasses functions, such as convclasses.unstructure(), use a single global converter. Changes done to this global converter, such as registering new structure and unstructure hooks, affect all code using the global functions.

Global converter

A global converter is provided for convenience as convclasses.global_converter. The following functions implicitly use this global converter:

  • convclasses.structure

  • convclasses.unstructure

  • convclasses.structure_dataclass_fromtuple

  • convclasses.structure_dataclass_fromdict

Changes made to the global converter will affect the behavior of these functions.

Larger applications are strongly encouraged to create and customize a different, private instance of Converter.

Converter objects

To create a private converter, simply instantiate a convclasses.Converter. Currently, a converter contains the following state:

  • a registry of unstructure hooks, backed by a singledispatch and a function_dispatch.

  • a registry of structure hooks, backed by a different singledispatch and function_dispatch.

  • a LRU cache of union disambiguation functions.

  • a reference to an unstructuring strategy (either AS_DICT or AS_TUPLE).

  • a dict_factory callable, used for creating dicts when dumping dataclasses classes using AS_DICT.

What you can structure and how

The philosophy of convclasses structuring is simple: give it an instance of Python built-in types and collections, and a type describing the data you want out. convclasses will convert the input data into the type you want, or throw an exception.

All loading conversions are composable, where applicable. This is demonstrated further in the examples.

Primitive values

typing.Any

Use typing.Any to avoid applying any conversions to the object you’re loading; it will simply be passed through.

>>> convclasses.structure(1, Any)
1
>>> d = {1: 1}
>>> convclasses.structure(d, Any) is d
True

int, float, str, bytes

Use any of these primitive types to convert the object to the type.

>>> convclasses.structure(1, str)
'1'
>>> convclasses.structure("1", float)
1.0

In case the conversion isn’t possible, the expected exceptions will be propagated out. The particular exceptions are the same as if you’d tried to do the conversion yourself, directly.

>>> convclasses.structure("not-an-int", int)
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'not-an-int'

Enums

Enums will be structured by their values. This works even for complex values, like tuples.

>>> @unique
... class CatBreed(Enum):
...    SIAMESE = "siamese"
...    MAINE_COON = "maine_coon"
...    SACRED_BIRMAN = "birman"
...
>>> convclasses.structure("siamese", CatBreed)
<CatBreed.SIAMESE: 'siamese'>

Again, in case of errors, the expected exceptions will fly out.

>>> convclasses.structure("alsatian", CatBreed)
Traceback (most recent call last):
...
ValueError: 'alsatian' is not a valid CatBreed

Collections and other generics

Optionals

Optional primitives and collections are supported out of the box.

>>> convclasses.structure(None, int)
Traceback (most recent call last):
...
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
>>> convclasses.structure(None, Optional[int])
>>> # None was returned.

Bare Optional s (non-parameterized, just Optional, as opposed to Optional[str]) aren’t supported, use Optional[Any] instead.

This generic type is composable with all other converters.

>>> convclasses.structure(1, Optional[float])
1.0

Lists

Lists can be produced from any iterable object. Types converting to lists are:

  • Sequence[T]

  • MutableSequence[T]

  • List[T]

In all cases, a new list will be returned, so this operation can be used to copy an iterable into a list. A bare type, for example Sequence instead of Sequence[int], is equivalent to Sequence[Any].

>>> convclasses.structure((1, 2, 3), MutableSequence[int])
[1, 2, 3]

These generic types are composable with all other converters.

>>> convclasses.structure((1, None, 3), List[Optional[str]])
['1', None, '3']

Sets and frozensets

Sets and frozensets can be produced from any iterable object. Types converting to sets are:

  • Set[T]

  • MutableSet[T]

Types converting to frozensets are:

  • FrozenSet[T]

In all cases, a new set or frozenset will be returned, so this operation can be used to copy an iterable into a set. A bare type, for example MutableSet instead of MutableSet[int], is equivalent to MutableSet[Any].

>>> convclasses.structure([1, 2, 3, 4], Set)
{1, 2, 3, 4}

These generic types are composable with all other converters.

>>> convclasses.structure([[1, 2], [3, 4]], Set[FrozenSet[str]])
{frozenset({'1', '2'}), frozenset({'4', '3'})}

Dictionaries

Dicts can be produced from other mapping objects. To be more precise, the object being converted must expose an items() method producing an iterable key-value tuples, and be able to be passed to the dict constructor as an argument. Types converting to dictionaries are:

  • Dict[K, V]

  • MutableMapping[K, V]

  • Mapping[K, V]

In all cases, a new dict will be returned, so this operation can be used to copy a mapping into a dict. Any type parameters set to typing.Any will be passed through unconverted. If both type parameters are absent, they will be treated as Any too.

>>> from collections import OrderedDict
>>> convclasses.structure(OrderedDict([(1, 2), (3, 4)]), Dict)
{1: 2, 3: 4}

These generic types are composable with all other converters. Note both keys and values can be converted.

>>> convclasses.structure({1: None, 2: 2.0}, Dict[str, Optional[int]])
{'1': None, '2': 2}

Homogeneous and heterogeneous tuples

Homogeneous and heterogeneous tuples can be produced from iterable objects. Heterogeneous tuples require an iterable with the number of elements matching the number of type parameters exactly. Use:

  • Tuple[A, B, C, D]

Homogeneous tuples use:

  • Tuple[T, ...]

In all cases a tuple will be returned. Any type parameters set to typing.Any will be passed through unconverted.

>>> convclasses.structure([1, 2, 3], Tuple[int, str, float])
(1, '2', 3.0)

The tuple conversion is composable with all other converters.

>>> convclasses.structure([{1: 1}, {2: 2}], Tuple[Dict[str, float], ...])
({'1': 1.0}, {'2': 2.0})

Unions

Unions of NoneType and a single other type are supported (also known as Optional s). All other unions a require a disambiguation function.

Automatic Disambiguation

In the case of a union consisting exclusively of dataclasses classes, convclasses will attempt to generate a disambiguation function automatically; this will succeed only if each class has a unique field. Given the following classes:

>>> @dataclass
... class A:
...     a: Any
...     x: Any
...
>>> @dataclass
... class B:
...     a: Any
...     y: Any
...
>>> @dataclass
... class C:
...     a: Any
...     z: Any
...

convclasses can deduce only instances of A will contain x, only instances of B will contain y, etc. A disambiguation function using this information will then be generated and cached. This will happen automatically, the first time an appropriate union is structured.

Manual Disambiguation

To support arbitrary unions, register a custom structuring hook for the union (see Registering custom structuring hooks).

dataclasses classes

Simple dataclasses classes

dataclasses classes using primitives, collections of primitives and their own converters work out of the box. Given a mapping d and class A, convclasses will simply instantiate A with d unpacked.

 >>> @dataclass
 ... class A:
 ...     a: Any
 ...     b: int
 ...
 >>> convclasses.structure({'a': 1, 'b': '2'}, A)
 A(a=1, b=2)

dataclasses classes deconstructed into tuples can be structured using convclasses.structure_attrs_fromtuple (fromtuple as in the opposite of dataclasses.astuple and converter.unstructure_attrs_astuple).

>>> # Loading from tuples can be made the default by creating a new
... @dataclass
... class A:
...     a: Any
...     b: int
...
>>> convclasses.structure_dataclass_fromtuple(['string', '2'], A)
A(a='string', b=2)

Loading from tuples can be made the default by creating a new Converter with unstruct_strat=convclasses.UnstructureStrategy.AS_TUPLE.

>>> converter = convclasses.Converter(unstruct_strat=convclasses.UnstructureStrategy.AS_TUPLE)
>>> @dataclass
... class A:
...     a: Any
...     b: int
...
>>> converter.structure(['string', '2'], A)
A(a='string', b=2)

Structuring from tuples can also be made the default for specific classes only; see registering custom structure hooks below.

Complex dataclasses classes

Complex dataclasses classes are classes with type information available for some or all attributes. These classes support almost arbitrary nesting.

Type information can be set using type annotations when using Python 3.6+.

>>> @dataclass
... class A:
...     a: int
...
>>> fields(A) 
(Field(name='a',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x...>,default_factory=<dataclasses._MISSING_TYPE object at 0x...>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD),)

Type information, when provided, can be used for all attribute types, not only attributes holding dataclasses classes.

>>> @dataclass
... class A:
...     a: int = field(default=0)
...
>>> @dataclass
... class B:
...     b: A
...
>>> convclasses.structure({'b': {'a': '1'}}, B)
B(b=A(a=1))

Registering custom structuring hooks

convclasses doesn’t know how to structure non-dataclasses classes by default, so it has to be taught. This can be done by registering structuring hooks on a converter instance (including the global converter).

Here’s an example involving a simple, classic (i.e. non-dataclasses) Python class.

>>> class C(object):
...     def __init__(self, a):
...         self.a = a
...     def __repr__(self):
...         return f'C(a={self.a})'
>>> convclasses.structure({'a': 1}, C)
Traceback (most recent call last):
...
ValueError: Unsupported type: <class '__main__.C'>. Register a structure hook for it.
>>> convclasses.register_structure_hook(C, lambda d, t: C(**d))
>>> convclasses.structure({'a': 1}, C)
C(a=1)

The structuring hooks are callables that take two arguments: the object to convert to the desired class and the type to convert to. The type may seem redundant but is useful when dealing with generic types.

When using convclasses.register_structure_hook, the hook will be registered on the global converter. If you want to avoid changing the global converter, create an instance of convclasses.Converter and register the hook on that.

In some situations, it is not possible to decide on the converter using typing mechanisms alone (such as with dataclasses classes). In these situations, convclasses provides a register_structure_func_hook instead, which accepts a function to determine whether that type can be handled instead.

The function-based hooks are evaluated after the class-based hooks. In the case where both a class-based hook and a function-based hook are present, the class-based hook will be used.

>>> class D(object):
...     custom = True
...     def __init__(self, a):
...         self.a = a
...     def __repr__(self):
...         return f'D(a={self.a})'
...     @classmethod
...     def deserialize(cls, data):
...         return cls(data["a"])
>>> convclasses.register_structure_hook_func(lambda cls: getattr(cls, "custom", False), lambda d, t: t.deserialize(d))
>>> convclasses.structure({'a': 2}, D)
D(a=2)

What you can unstructure and how

Unstructuring is intended to convert high-level, structured Python data (like instances of complex classes) into simple, unstructured data (like dictionaries).

Unstructuring is simpler than structuring in that no target types are required. Simply provide an argument to unstructure and convclasses will produce a result based on the registered unstructuring hooks. A number of default unstructuring hooks are documented here.

Unstructuring is primarily done using Converter.unstructure.

Primitive types and collections

Primitive types (integers, floats, strings…) are simply passed through. Collections are copied. There’s relatively little value in unstructuring these types directly as they are already unstructured and third-party libraries tend to support them directly.

A useful use case for unstructuring collections is to create a deep copy of a complex or recursive collection.

>>> # A dictionary of strings to lists of tuples of floats.
>>> data = {'a': [(1.0, 2.0), (3.0, 4.0)]}
>>>
>>> copy = convclasses.unstructure(data)
>>> data == copy
True
>>> data is copy
False

dataclasses classes

dataclasses classes are supported out of the box. Converter s support two unstructuring strategies:

  • UnstructureStrategy.AS_DICT - similar to dataclasses.asdict, unstructures dataclasses instances into dictionaries. This is the default.

  • UnstructureStrategy.AS_TUPLE - similar to dataclasses.astuple, unstructures dataclasses instances into tuples.

>>> @dataclass
... class C:
...     a: Any
...     b: Any
...
>>> inst = C(1, 'a')
>>> converter = convclasses.Converter(unstruct_strat=convclasses.UnstructureStrategy.AS_TUPLE)
>>> converter.unstructure(inst)
(1, 'a')

Mixing and matching strategies

Converters publicly expose two helper metods, Converter.unstructure_attrs_asdict() and Converter.unstructure_attrs_astuple(). These methods can be used with custom unstructuring hooks to selectively apply one strategy to instances of particular classes.

Assume two nested dataclasses classes, Inner and Outer; instances of Outer contain instances of Inner. Instances of Outer should be unstructured as dictionaries, and instances of Inner as tuples. Here’s how to do this.

>>> @dataclass
... class Inner:
...     a: int
...
>>> @dataclass
... class Outer:
...     i: Inner
...
>>> inst = Outer(i=Inner(a=1))
>>>
>>> converter = convclasses.Converter()
>>> converter.register_unstructure_hook(Inner, converter.unstructure_dataclass_astuple)
>>>
>>> converter.unstructure(inst)
{'i': (1,)}

Of course, these methods can be used directly as well, without changing the converter strategy.

>>> @dataclass
... class C:
...     a: int
...     b: str
...
>>> inst = C(1, 'a')
>>>
>>> converter = convclasses.Converter()
>>>
>>> converter.unstructure_dataclass_astuple(inst)  # Default is AS_DICT.
(1, 'a')

Modifiers

Change structuring/unstructuring name in dict

Sometimes you may face a problem, when you need to structure data, that has fields that could not be created in python:

>>> @dataclass
... class TestModel:
...     user_agent: str
...
>>> convclasses.structure({"User-Agent": "curl/7.71.1"}, TestModel)
Traceback (most recent call last):
 ...
TypeError: __init__() missing 1 required positional argument: 'user_agent'

Now you can avoid this, and also use convclasses with such structures:

>>> from convclasses import mod
>>> @dataclass
... class TestModel:
...     user_agent: str = mod.name("User-Agent")
...     other_field: str = mod.name("other field", field(default=None))
...
>>> obj = convclasses.structure({"User-Agent": "curl/7.71.1"}, TestModel)
>>> obj
TestModel(user_agent='curl/7.71.1', other_field=None)
>>> convclasses.unstructure(obj)
{'User-Agent': 'curl/7.71.1', 'other field': None}

Contributing

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at https://github.com/zeburek/convclasses/issues.

If you are reporting a bug, please include:

  • Your operating system name and version.

  • Any details about your local setup that might be helpful in troubleshooting.

  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the GitHub issues for bugs. Anything tagged with “bug” and “help wanted” is open to whoever wants to implement it.

Implement Features

Look through the GitHub issues for features. Anything tagged with “enhancement” and “help wanted” is open to whoever wants to implement it.

Write Documentation

convclasses could always use more documentation, whether as part of the official convclasses docs, in docstrings, or even on the web in blog posts, articles, and such.

Submit Feedback

The best way to send feedback is to file an issue at https://github.com/zeburek/convclasses/issues.

If you are proposing a feature:

  • Explain in detail how it would work.

  • Keep the scope as narrow as possible, to make it easier to implement.

  • Remember that this is a volunteer-driven project, and that contributions are welcome :)

Get Started!

Ready to contribute? Here’s how to set up convclasses for local development.

  1. Fork the convclasses repo on GitHub.

  2. Clone your fork locally:

    $ git clone git@github.com:your_name_here/convclasses.git
    
  3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:

    $ mkvirtualenv convclasses
    $ cd convclasses/
    $ pip install -e .\[dev\]
    
  4. Create a branch for local development:

    $ git checkout -b name-of-your-bugfix-or-feature
    

    Now you can make your changes locally.

  5. When you’re done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:

    $ flake8 convclasses tests
    $ pytest
    $ tox
    

    To get flake8 and tox, just pip install them into your virtualenv.

  6. Commit your changes and push your branch to GitHub:

    $ git add .
    $ git commit -m "Your detailed description of your changes."
    $ git push origin name-of-your-bugfix-or-feature
    
  7. Submit a pull request through the GitHub website.

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request should include tests.

  2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst.

  3. The pull request should work for all supported Python versions. Make sure that the tests pass for all supported Python versions.

Tips

To run a subset of tests:

$ pytest tests.test_unstructure

History

2.0.0

  • Add support for modifiers
    • Add dataclass field name modifier

  • Add support for generic types (ported from cattrs)

1.1.0

  • Removed Python 3.6 support

  • Added Python 3.9 support

1.0.0

  • Rename cattrs into conclasses

  • Move convclasses from attrs usage onto dataclasses

  • Fix incorrect structuring/unstructuring of private fields

  • Change pendulum in docs onto arrow

cattrs history

0.9.1 (2019-10-26)

  • Python 3.8 support.

0.9.0 (2018-07-22)

  • Python 3.7 support.

0.8.1 (2018-06-19)

  • The disambiguation function generator now supports unions of attrs classes and NoneType.

0.8.0 (2018-04-14)

  • Distribution fix.

0.7.0 (2018-04-12)

  • Removed the undocumented Converter.unstruct_strat property setter.

  • Removed the ability to set the Converter.structure_attrs instance field. As an alternative, create a new Converter:

>>> converter = cattr.Converter(unstruct_strat=cattr.UnstructureStrategy.AS_TUPLE)
  • Some micro-optimizations were applied; a structure(unstructure(obj)) roundtrip is now up to 2 times faster.

0.6.0 (2017-12-25)

  • Packaging fixes. (#17)

0.5.0 (2017-12-11)

  • structure/unstructure now supports using functions as well as classes for deciding the appropriate function.

  • added Converter.register_structure_hook_func, to register a function instead of a class for determining handler func.

  • added Converter.register_unstructure_hook_func, to register a function instead of a class for determining handler func.

  • vendored typing is no longer needed, nor provided.

  • Attributes with default values can now be structured if they are missing in the input. (#15)

  • Optional attributes can no longer be structured if they are missing in the input.

In other words, this no longer works:

@attr.s
class A:
    a: Optional[int] = attr.ib()

>>> cattr.structure({}, A)
  • cattr.typed removed since the functionality is now present in attrs itself. Replace instances of cattr.typed(type) with attr.ib(type=type).

0.4.0 (2017-07-17)

  • Converter.loads is now Converter.structure, and Converter.dumps is now Converter.unstructure.

  • Python 2.7 is supported.

  • Moved cattr.typing to cattr.vendor.typing to support different vendored versions of typing.py for Python 2 and Python 3.

  • Type metadata can be added to attrs classes using cattr.typed.

0.3.0 (2017-03-18)

  • Python 3.4 is no longer supported.

  • Introduced cattr.typing for use with Python versions 3.5.2 and 3.6.0.

  • Minor changes to work with newer versions of typing.

    • Bare Optionals are not supported any more (use Optional[Any]).

  • Attempting to load unrecognized classes will result in a ValueError, and a helpful message to register a loads hook.

  • Loading attrs classes is now documented.

  • The global converter is now documented.

  • cattr.loads_attrs_fromtuple and cattr.loads_attrs_fromdict are now exposed.

0.2.0 (2016-10-02)

  • Tests and documentation.

0.1.0 (2016-08-13)

  • First release on PyPI.

Indices and tables