### Install Local Copy with uv Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Install your local copy of 'cattrs' into a virtual environment using uv for development. ```shell $ cd cattrs/ $ uv sync --all-groups --all-extras ``` -------------------------------- ### Installing cattrs with Optional Dependencies Source: https://github.com/python-attrs/cattrs/blob/main/docs/preconf.md Install cattrs with specific optional dependencies for serialization libraries using package managers like pip, uv, pdm, or poetry. ```bash # Using pip $ pip install cattrs[ujson] # Usinv uv $ uv add cattrs[msgspec] # Using pdm $ pdm add cattrs[orjson] # Using poetry $ poetry add --extras tomlkit cattrs ``` -------------------------------- ### Basic Structuring and Unstructuring with cattrs Source: https://github.com/python-attrs/cattrs/blob/main/docs/index.md Demonstrates the fundamental usage of cattrs for structuring a dictionary into an attrs class instance and unstructuring it back into a dictionary. Ensure cattrs and attrs are installed. ```python >>> from attrs import define >>> from cattrs import structure, unstructure >>> @define ... class C: ... a: int ... b: list[str] >>> instance = structure({'a': 1, 'b': ['x', 'y']}, C) >>> instance C(a=1, b=['x', 'y']) >>> unstructure(instance) {'a': 1, 'b': ['x', 'y']} ``` -------------------------------- ### Basic Structuring and Unstructuring with attrs Classes Source: https://github.com/python-attrs/cattrs/blob/main/docs/why.md Demonstrates how to use cattrs to structure a dictionary into an attrs class instance and unstructure it back. This example highlights that conversion details do not affect the data model. ```python from attrs import define from cattrs import structure, unstructure @define class C: a: int b: list[str] instance = structure({'a': 1, 'b': ['x', 'y']}, C) print(instance) unstructure(instance) ``` -------------------------------- ### Run a Subset of Tests Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Execute a specific subset of tests, for example, tests related to unstructuring. ```shell $ just test tests/test_unstructure.py ``` -------------------------------- ### Structuring a List of Mixed Types into a Tuple of Integers Source: https://github.com/python-attrs/cattrs/blob/main/docs/why.md Shows how cattrs can normalize unstructured data into a specific shape, even when types need conversion. This example converts a list containing a float, an int, and a string into a tuple of integers. ```python import cattrs cattrs.structure([1.0, 2, "3"], tuple[int, int, int]) ``` -------------------------------- ### Complex Structuring and Unstructuring with Enums and Nested Data Source: https://github.com/python-attrs/cattrs/blob/main/docs/why.md Illustrates cattrs' capability to handle complex data structures, including enums, nested classes, and unions, for both structuring and unstructuring. This example involves Cat and Dog data models with various types. ```python from enum import unique, Enum from typing import Sequence 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: DogMicrochip | None = 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) structure(p, list[Dog | Cat]) ``` -------------------------------- ### Customize Collection Structuring with Predicates Source: https://github.com/python-attrs/cattrs/blob/main/docs/customizing.md Apply stricter validation to mutable sequence structuring by wrapping the default list structuring hook factory with custom predicates. This example makes mutable sequence structuring stricter. ```python from cattrs.gen import make_dict_structure_fn, override from cattrs.collections import ( # noqa defaultdict_structure_factory, homogenous_tuple_structure_factory, iterable_unstructure_factory, list_structure_factory, mapping_structure_factory, mapping_unstructure_factory, namedtuple_dict_unstructure_factory, namedtuple_structure_factory, is_mutable_sequence, ) @converter.register_structure_hook_func(list) def structure_list(type, obj): return converter.structure_attrs_fromdict(obj, type) converter.register_structure_hook( list, list_structure_factory(omit_if_default=True, is_enabled=is_mutable_sequence), ) ``` -------------------------------- ### Build and Serve Documentation Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Build the project documentation and serve it locally with autoreload for development. ```shell $ just docs $ just htmllive # Build the docs, serve then and autoreload on changes ``` -------------------------------- ### Class Validation Error Example Source: https://github.com/python-attrs/cattrs/blob/main/docs/validation.md Demonstrates how cattrs aggregates validation errors for a class containing a list and a dictionary when structuring fails. This example shows a `cattrs.ClassValidationError` containing nested `cattrs.IterableValidationError` instances for both the list and the dictionary, detailing the specific index or key and the underlying `ValueError`. ```python from attrs import define @define class Class: a_list: list[int] a_dict: dict[str, int] >>> structure({"a_list": ["a"], "a_dict": {"str": "a"}}, Class) + Exception Group Traceback (most recent call last): | File "", line 1, in | File "/Users/tintvrtkovic/pg/cattrs/src/cattr/converters.py", line 276, in structure | return self._structure_func.dispatch(cl)(obj, cl) | File "", line 14, in structure_Class | if errors: raise __c_cve('While structuring Class', errors, __cl) | cattrs.errors.ClassValidationError: While structuring Class +-+---------------- 1 ---------------- | Exception Group Traceback (most recent call last): | File "", line 5, in structure_Class | res['a_list'] = __c_structure_a_list(o['a_list'], __c_type_a_list) | File "/Users/tintvrtkovic/pg/cattrs/src/cattr/converters.py", line 457, in _structure_list | raise IterableValidationError( | cattrs.errors.IterableValidationError: While structuring list[int] | Structuring class Class @ attribute a_list +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "/Users/tintvrtkovic/pg/cattrs/src/cattr/converters.py", line 450, in _structure_list | res.append(handler(e, elem_type)) | File "/Users/tintvrtkovic/pg/cattrs/src/cattr/converters.py", line 375, in _structure_call | return cl(obj) | ValueError: invalid literal for int() with base 10: 'a' | Structuring list[int] @ index 0 +------------------------------------ +---------------- 2 ---------------- | Exception Group Traceback (most recent call last): | File "", line 10, in structure_Class | res['a_dict'] = __c_structure_a_dict(o['a_dict'], __c_type_a_dict) | File "", line 17, in structure_mapping | cattrs.errors.IterableValidationError: While structuring dict | Structuring class Class @ attribute a_dict +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "", line 5, in structure_mapping | ValueError: invalid literal for int() with base 10: 'a' | Structuring mapping value @ key 'str' +------------------------------------ ``` -------------------------------- ### Chaining Converters with Fallback Hooks Source: https://github.com/python-attrs/cattrs/blob/main/docs/indepth.md Demonstrates how to create a child converter that inherits unstructure and structure hooks from a parent converter using fallback factories. This enables a hierarchical or delegated configuration of converters. ```python >>> parent = Converter() >>> child = Converter( ... unstructure_fallback_factory=parent.get_unstructure_hook, ... structure_fallback_factory=parent.get_structure_hook, ... ) ``` -------------------------------- ### Clone the Cattrs Repository Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Clone your forked 'cattrs' repository locally to begin development. ```shell $ git clone git@github.com:your_name_here/cattrs.git ``` -------------------------------- ### Dynamically Switching Initializers with a Hook Factory Source: https://github.com/python-attrs/cattrs/blob/main/docs/recipes.md Dynamically select an initializer at runtime using a hook factory. This allows users to specify the desired representation in the serialization string. ```python from cattrs import Converter class Point: def __init__(self, x: int, y: int): self.x = x self.y = y @classmethod def from_tuple(cls, t: tuple[int, int]) -> "Point": return cls(*t) def dynamic_initializer_hook_factory(key: str): def hook(d, t): initializer = getattr(t, d[key]) return initializer(d['data']) return hook converter = Converter() converter.register_structure_hook_func(lambda t: isinstance(t, type) and issubclass(t, Point), dynamic_initializer_hook_factory('initializer')) point_from_tuple = converter.structure({'initializer': 'from_tuple', 'data': (1, 2)}, Point) assert point_from_tuple.x == 1 and point_from_tuple.y == 2 point_from_init = converter.structure({'initializer': '__init__', 'data': {'x': 3, 'y': 4}}, Point) assert point_from_init.x == 3 and point_from_init.y == 4 ``` -------------------------------- ### Commit and Push Changes Source: https://github.com/python-attrs/cattrs/blob/main/docs/contributing.md Stage all changes, commit them with a descriptive message, and push the branch to GitHub. ```shell git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature ``` -------------------------------- ### Default Behavior Without Include Subclasses Source: https://github.com/python-attrs/cattrs/blob/main/docs/strategies.md Demonstrates the default behavior of unstructuring and structuring when the include_subclasses strategy is not applied, resulting in Parent instances instead of Child instances. ```python >>> converter_no_subclasses = Converter() >>> converter_no_subclasses.unstructure(Child(a=1, b="foo"), unstructure_as=Parent) {'a': 1} >>> converter_no_subclasses.structure({'a': 1, 'b': 'foo'}, Parent) Parent(a=1) ``` -------------------------------- ### Custom Tagged Union Strategy with functools.partial Source: https://github.com/python-attrs/cattrs/blob/main/docs/strategies.md Demonstrates how to customize the tagged union strategy by specifying a custom tag name using functools.partial. This is useful when the default tag name is not suitable or needs to be consistent across different types. ```python from functools import partial from attrs import define from cattrs.strategies import include_subclasses, configure_tagged_union from cattrs import Converter @define class Parent: a: int @define class Child1(Parent): b: str @define class Child2(Parent): b: int converter = Converter() union_strategy = partial(configure_tagged_union, tag_name="type_name") include_subclasses(Parent, converter, union_strategy=union_strategy) converter.unstructure(Child1(a=1, b="foo"), unstructure_as=Parent) {'a': 1, 'b': 'foo', 'type_name': 'Child1'} converter.structure({'a': 1, 'b': 1, 'type_name': 'Child2'}, Parent) Child2(a=1, b=1) ``` -------------------------------- ### Commit and Push Changes Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Add, commit, and push your changes to your GitHub fork. ```shell $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature ``` -------------------------------- ### Run Linting and Tests Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Check that your changes pass lints and tests, including testing across different Python versions. ```shell $ just lint $ just test $ just --set python python3.10 test # Test on other versions ``` -------------------------------- ### Composing Hooks with an Existing Hook Source: https://github.com/python-attrs/cattrs/blob/main/docs/basics.md Shows how to wrap an existing hook with custom logic using function composition. This allows extending the behavior of default hooks without replacing them entirely. ```python from cattrs import Converter converter = Converter() def my_int_hook(data, type): if not isinstance(data, str): return data try: return int(data) except ValueError: raise ValueError("not an int!") converter.register_structure_hook(int, my_int_hook) # Get the base hook for Model and compose it with our custom int hook base_hook = converter.get_structure_hook(Model) def composing_hook(data, cl): # Here we are composing the base hook with our custom int hook # The base hook will use our custom int hook for int attributes return base_hook(data, cl) converter.register_structure_hook(Model, composing_hook) ``` -------------------------------- ### Structure Data with Fallback Fields Source: https://github.com/python-attrs/cattrs/blob/main/docs/usage.md Demonstrates structuring data into the `MyInternalAttr` class using both the primary field name ('new_field') and a fallback field name ('old_field'). ```python converter.structure({"new_field": "foo"}, MyInternalAttr) ``` ```python converter.structure({"old_field": "foo"}, MyInternalAttr) ``` -------------------------------- ### Using msgspec Converter with Dataclasses Source: https://github.com/python-attrs/cattrs/blob/main/docs/preconf.md Demonstrates how to create a converter for msgspec and obtain a dumps hook for a dataclass. This hook leverages msgspec's direct handling for performance. ```python >>> from cattrs.preconf.msgspec import make_converter >>> @define ... class Test: ... a: int >>> converter = make_converter() >>> dumps = converter.get_dumps_hook(A) >>> dumps(Test(1)) # Will use msgspec directly. b'{"a":1}' ``` -------------------------------- ### Create a New Branch Source: https://github.com/python-attrs/cattrs/blob/main/CONTRIBUTING.md Create a new branch for your bug fix or feature development. ```shell $ git switch -c name-of-your-bugfix-or-feature ``` -------------------------------- ### Using Factory Hooks for Case Conversion Source: https://github.com/python-attrs/cattrs/blob/main/docs/usage.md Demonstrates how to use factory hooks to convert snake_case attributes to camelCase during unstructuring and vice versa. This is useful when integrating with APIs that use different naming conventions. ```python from attr import attrs from cattrs import Converter @attrs class MyClass: my_attribute: int def snake_to_camel(s): parts = s.split("_") return parts[0] + ".".join(x.capitalize() for x in parts[1:]) def camel_to_snake(s): # This is a simplified example and might not cover all edge cases. # A more robust solution would involve a proper parser. parts = [] current_part = "" for char in s: if char.isupper() and current_part: parts.append(current_part) current_part = char.lower() else: current_part += char.lower() if current_part: parts.append(current_part) return "_".join(parts) converter = Converter() converter.register_structure_hook_func(lambda t: isinstance(t, type) and issubclass(t, MyClass), lambda d, t: t(**{camel_to_snake(k): v for k, v in d.items()})) converter.register_unstructure_hook(MyClass, lambda o: {snake_to_camel(k): v for k, v in converter.unstructure(o).items()}) # Example usage: instance = MyClass(my_attribute=10) structured = converter.unstructure(instance) print(f"Unstructured: {structured}") data = {"myAttribute": 20} unstructured_instance = converter.structure(data, MyClass) print(f"Structured: {unstructured_instance}") ``` -------------------------------- ### Structuring with int Hook Source: https://github.com/python-attrs/cattrs/blob/main/docs/defaulthooks.md Demonstrates the behavior of the int hook when attempting to structure a non-integer string. The expected ValueError is propagated. ```python >>> cattrs.structure("not-an-int", int) Traceback (most recent call last): ...ValueError: invalid literal for int() with base 10: 'not-an-int' ``` -------------------------------- ### Structuring a Generic attrs Class Source: https://github.com/python-attrs/cattrs/blob/main/docs/defaulthooks.md Demonstrates how to structure data into a generic attrs class with type parameters. Ensure the class and its type parameters are correctly defined. ```python >>> @define ... class A[T]: ... a: T >>> cattrs.structure({"a": "1"}, A[int]) A(a=1) ``` -------------------------------- ### Registering a Custom Integer Hook Source: https://github.com/python-attrs/cattrs/blob/main/docs/basics.md Demonstrates how to register a custom hook for the `int` class with a `cattrs.Converter` instance. Any subsequent structuring of an `int` will use this custom hook. ```python from cattrs import Converter converter = Converter() def my_int_hook(data, type): if not isinstance(data, str): return data try: return int(data) except ValueError: raise ValueError("not an int!") converter.register_structure_hook(int, my_int_hook) ``` -------------------------------- ### Structuring NewType with Union Passthrough Source: https://github.com/python-attrs/cattrs/blob/main/docs/strategies.md Demonstrates how the union passthrough strategy can structure a NewType when used with a converter. Ensure the converter is configured to use this strategy. ```python >>> from typing import NewType >>> UserId = NewType("UserId", int) >>> converter.loads("12", UserId) 12 ``` -------------------------------- ### Registering a Structuring Hook for Alternative Initializer Source: https://github.com/python-attrs/cattrs/blob/main/docs/recipes.md Statically set a classmethod as the initializer for structuring attrs classes by registering a hook. This is useful when you consistently want to use an alternative creation method. ```python from cattrs import Converter class Point: def __init__(self, x: int, y: int): self.x = x self.y = y @classmethod def from_tuple(cls, t: tuple[int, int]) -> "Point": return cls(*t) converter = Converter() converter.register_structure_hook(Point, lambda d, t: Point.from_tuple(d)) point = converter.structure((1, 2), Point) assert point.x == 1 and point.y == 2 ``` -------------------------------- ### Custom Unstructure Fallback with Pickle Source: https://github.com/python-attrs/cattrs/blob/main/docs/indepth.md Configures a converter to use `pickle.dumps` as the fallback mechanism for unstructuring unsupported types. This allows custom classes to be serialized when no specific unstructure hook is found. ```python >>> from pickle import dumps >>> class Unsupported: ... """An artisinal (non-attrs) class, unsupported by default.""" >>> converter = Converter(unstructure_fallback_factory=lambda _: dumps) >>> instance = Unsupported() >>> converter.unstructure(instance) b'\x80\x04\x95\x18\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04Test\x94\x93\x94)\x81\x94.' ``` -------------------------------- ### Include Subclasses Strategy Source: https://github.com/python-attrs/cattrs/blob/main/docs/strategies.md This strategy allows un/structuring of a base class to an instance of itself or one of its descendants. All subclasses must be defined when the strategy is applied. ```python from cattrs import Converter from cattrs.strategies import include_subclasses class Parent: a: int class Child(Parent): b: str converter = Converter() converter.register_structure_hook(Parent, include_subclasses()) converter.register_unstructure_hook(Parent, include_subclasses()) # Example usage: # child_instance = Child(a=1, b="foo") # unstructured_child = converter.unstructure(child_instance, unstructure_as=Parent) # print(unstructured_child) # Output: {'a': 1, 'b': 'foo'} # structured_child = converter.structure({'a': 1, 'b': 'foo'}, Parent) # print(structured_child) # Output: Child(a=1, b='foo') ``` -------------------------------- ### Registering Type Alias Hooks for Datetime Source: https://github.com/python-attrs/cattrs/blob/main/docs/defaulthooks.md Demonstrates how to register structure and unstructure hooks for a type alias representing datetime. This is useful for custom serialization formats like ISO 8601. Requires Python 3.12+ for type aliases. ```python >>> from datetime import datetime, UTC >>> type IsoDate = datetime >>> converter = cattrs.Converter() >>> converter.register_structure_hook_func( ... lambda t: t is IsoDate, lambda v, _: datetime.fromisoformat(v) ... ) >>> converter.register_unstructure_hook_func( ... lambda t: t is IsoDate, lambda v: v.isoformat() ... ) >>> converter.structure("2022-01-01", IsoDate) datetime.datetime(2022, 1, 1, 0, 0) >>> converter.unstructure(datetime.now(UTC), unstructure_as=IsoDate) '2023-11-20T23:10:46.728394+00:00' ``` -------------------------------- ### Include init=False Fields in Un/Structuring Source: https://github.com/python-attrs/cattrs/blob/main/docs/customizing.md Include attributes defined with `init=False` during the un/structuring process. Generate the un/structure function with `_cattrs_include_init_false=True`. ```python converter.structure(data, MyClass, _cattrs_include_init_false=True) ``` ```python converter.unstructure(obj, _cattrs_include_init_false=True) ``` -------------------------------- ### Structure Union Member using Metadata Source: https://github.com/python-attrs/cattrs/blob/main/docs/unions.md Demonstrates structuring data into a union member (ClassA) by providing a dictionary with '_type' metadata, which the custom structure hook uses for disambiguation. ```python converter.structure({"_type": "ClassA", "a_string": "test"}, Union[ClassA, ClassB]) ``` -------------------------------- ### Registering a Hook Factory to Forbid Extra Keys Source: https://github.com/python-attrs/cattrs/blob/main/docs/customizing.md This snippet demonstrates how to register a hook factory that uses `make_dict_structure_fn` with `_cattrs_forbid_extra_keys=True`. This ensures that when structuring data into an attrs class, any extra keys not defined in the class will raise a `ForbiddenExtraKeysError`. ```python >>> from attrs import define, has >>> from cattrs import Converter >>> from cattrs.gen import make_dict_structure_fn >>> c = Converter() >>> c.register_structure_hook_factory( ... has, ... lambda cl: make_dict_structure_fn(cl, c, _cattrs_forbid_extra_keys=True) ... ) >>> @define ... class E: ... an_int: int >>> c.structure({"an_int": 1, "else": 2}, E) Traceback (most recent call last): ... cattrs.errors.ForbiddenExtraKeysError: Extra fields in constructor for E: else ``` -------------------------------- ### Configure Tagged Union Strategy Source: https://github.com/python-attrs/cattrs/blob/main/docs/strategies.md Use this strategy to un/structure a union of classes by including an additional tag field. The tag field name and value can be customized. A default member can be specified for missing or unknown tags. ```python from cattrs import Converter from cattrs.strategies import configure_tagged_union from typing import Union class Refund: notificationType: str = "REFUND" # ... other fields class OtherAppleNotification: notificationType: str # ... other fields AppleNotification = Union[Refund, OtherAppleNotification] converter = Converter() converter.register_structure_hook( AppleNotification, configure_tagged_union( { "REFUND": Refund, }, # Use the default for all other cases default_member=OtherAppleNotification, # Use the get method of a dictionary to look up the tag tag_generator=dict.get, ), ) converter.register_unstructure_hook( AppleNotification, configure_tagged_union( { "REFUND": Refund, }, default_member=OtherAppleNotification, tag_generator=dict.get, ), ) # Example usage: # unstructured_notification = { # "notificationType": "REFUND", # # ... other fields # } # structured_notification = converter.structure(unstructured_notification, AppleNotification) ``` -------------------------------- ### Restore Default Structure Hook Fallback Factory Source: https://github.com/python-attrs/cattrs/blob/main/docs/migrations.md To restore the pre-25.1.0 behavior of the default structure hook fallback factory, which was changed to more eagerly raise errors for missing hooks, explicitly pass the old hook fallback factory when instantiating the converter. ```python from cattrs.fns import raise_error c = Converter(structure_fallback_factory=lambda _: raise_error) ``` ```python c = BaseConverter(structure_fallback_factory=lambda _: raise_error) ``` -------------------------------- ### Use Field Aliases for Un/Structuring Source: https://github.com/python-attrs/cattrs/blob/main/docs/customizing.md Enable the use of field aliases defined in attrs classes for dictionary keys during un/structuring. Requires generating the un/structure function with `_cattrs_use_alias=True`. ```python converter.structure(data, MyClass, _cattrs_use_alias=True) ``` ```python converter.unstructure(obj, _cattrs_use_alias=True) ``` -------------------------------- ### Override Attribute with omit=False Source: https://github.com/python-attrs/cattrs/blob/main/docs/customizing.md Explicitly include a single attribute with `init=False` for un/structuring by overriding it with `omit=False`. ```python class MyClass(attrs.AttrsClass): x: int y: int = attrs.field(init=False, default=1, metadata={"cattrs": {"omit": False}}) ``` -------------------------------- ### Customizing Subclasses and Attribute Renaming in Unions Source: https://github.com/python-attrs/cattrs/blob/main/docs/strategies.md Shows how to customize the include_subclasses strategy by explicitly defining participating subclasses and renaming attributes during un/structuring. This is useful for complex inheritance hierarchies or when attribute names differ between the data and the model. ```python from attrs import define from cattrs.strategies import include_subclasses from cattrs import Converter, override @define class Parent: a: int @define class Child(Parent): b: str converter = Converter() include_subclasses( Parent, converter, subclasses=(Parent, Child), overrides={"b": override(rename="c")} ) converter.unstructure(Child(a=1, b="foo"), unstructure_as=Parent) {'a': 1, 'c': 'foo'} converter.structure({'a': 1, 'c': 'foo'}, Parent) Child(a=1, b='foo') ```