mrkeyoor.com_
Sun 20 Sept 14:47 UTC
PyPIUtilsupdated 20 Sept 2026

cattrs review

cattrs 26.1.0 converts dictionaries and other plain Python values into typed attrs classes, dataclasses, TypedDicts, named tuples, collections, and unions, then turns those objects back into values a serializer can handle. Conversion policy belongs to a `Converter`, which lets one class use different field names or formats at separate boundaries. The current release adds a `tomllib` preset and lets `Annotated[T, override(...)]` alter generated hooks per field. It requires Python 3.10 or newer.

Verdict

cattrs 26.1.0 took 0.2 seconds and 1 MB in our sandbox, with typed pure-Python code and no audit findings, making it a low-cost addition for apps that already own typed models. Install Pydantic instead when validation rules and schema generation are the main requirement.

We installed it

Lab card: what happened when we installed cattrsScreenshot of cattrs documentation
Install✓ · 0.2s3 packages on disk · 1 MB
Importimport cattr in 0.18s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does cattrs install cleanly?

Yes. In a fresh container with an empty cache, pip install cattrs finished in 0.2s, leaving 3 packages and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does cattrs need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import cattr succeeded in 0.18s, and the package ships py.typed for type checkers.

cattrs or dacite: which should you use?

dacite: Choose it for dictionary-to-dataclass construction when serialization back to plain values is outside the job. cattrs 26.1.0 took 0.2 seconds and 1 MB in our sandbox, with typed pure-Python code and no audit findings, making it a low-cost addition for apps that already own typed models.

When should you not use cattrs?

Use Pydantic if the model layer must own runtime constraints, settings, JSON Schema, and OpenAPI generation. cattrs concentrates on conversion according to type information and registered hooks.

API stability4/5cattrs 26.1.0 keeps the long-standing `Converter`, `structure`, `unstructure`, generated-hook, and hook-registration interfaces. The project publishes migration notes and a compatibility policy, but observable details do move between releases. Recent examples include Python 3.9 support ending, abstract set output changing to `frozenset`, and fixes to alias metadata used by generated functions.
Docs4/5The official cattrs 26.1.0 documentation gives working examples for basic conversion, custom hooks, factories, unions, strategies, validation errors, generated functions, and each serializer preset. It also states where automatic union selection stops. The harder combinations, especially multiple factories affecting generated code, sometimes require moving between the narrative guide, API reference, and changelog.
Maintenance4/5The python-attrs/cattrs repository was pushed on August 9, 2026 and is not archived. GitHub reports 1,050 stars and 89 open issues and pull requests. Version 26.1.0 shipped in February with new TOML support, field-level overrides, enum work, and fixes for nested generics, aliases, and tagged unions, showing active work on both behavior and performance.
Ecosystem3/5PyPI counted 14,073,810 cattrs downloads in the latest week, while GitHub reports 1,050 stars. Its built-in type coverage spans attrs, dataclasses, TypedDicts, named tuples, generics, collections, and unions. Presets connect those models to several JSON implementations, MessagePack, CBOR, BSON, YAML, and TOML, though third-party framework integrations are fewer than Pydantic offers.

Use it if

  • You already model data with attrs classes or dataclasses and need nested input converted without putting parsing methods on the models.
  • The same class must use one representation in an HTTP API and another in a cache, queue, or file.
  • Your boundary contains typed containers, generics, aliases, defaults, or unions that need explicit conversion rules.
  • You want ready-made converters for stdlib JSON, orjson, ujson, msgpack, cbor2, BSON, YAML, tomlkit, or tomllib.
Skip it if

Setup reality

Our install of cattrs 26.1.0 finished in 0.2 seconds inside a fresh Python 3.12 Bookworm container. The environment ended with 3 packages and 1 MB on disk. pip-audit found 0 known vulnerabilities. The distribution is pure Python, requires Python 3.10+, includes py.typed, and carries the MIT license. Importing the older compatibility module name, cattr, worked in 0.18 seconds.

The convenience functions use one process-wide converter. Registering a hook on that object changes later calls anywhere in the process, so services with multiple payload formats should construct separate Converter instances. Decide how to treat extra keys, aliases, omitted defaults, and detailed errors before the first request reaches the boundary. Import order is a poor configuration system.

Version 26.1.0 understands supported annotated containers and class forms, but it does not invent a transport representation for every scalar. Register both structure and unstructure hooks for domain values, or start with a preset whose serializer matches the wire format. The new tomllib preset reads TOML using the standard library. It cannot write TOML because tomllib itself has no dump function.

Generated hooks are cached after cattrs first sees a type. Install hook factories and union strategies before workers process that type. An untagged union works only when the members can be distinguished from required fields; otherwise add a tag or a discriminator. Field metadata supplied through Annotated[T, override(...)] is useful for a single representation, while separate converters remain easier to reason about when one model crosses several boundaries.

Patterns

Convert nested values in both directions structure-and-unstructure

from attrs import define
from cattrs import structure, unstructure

@define
class User:
    id: int
    tags: list[str]

u = structure({"id": 1, "tags": ["a", "b"]}, User)
print(u)              # User(id=1, tags=['a', 'b'])
print(unstructure(u))  # {'id': 1, 'tags': ['a', 'b']}

These helpers share the global converter. Use your own `Converter` once the application needs custom policy or plugin isolation.

Set boundary policy on one converter own-converter

from cattrs import Converter

converter = Converter(forbid_extra_keys=True, omit_if_default=True)

# forbid_extra_keys: unknown dict keys raise ForbiddenExtraKeysError
# omit_if_default: fields equal to their default are left out of output

`forbid_extra_keys` rejects unexpected mapping keys, while `omit_if_default` affects output. The generated converter caches functions per type.

Teach a converter an ISO datetime datetime-hooks

from datetime import datetime, timezone
from cattrs import Converter

converter = Converter()
converter.register_unstructure_hook(datetime, lambda dt: dt.isoformat())
converter.register_structure_hook(
    datetime, lambda v, _: datetime.fromisoformat(v)
)

converter.structure("2026-08-06T10:00:00+00:00", datetime)

The structure callback receives the input plus its requested type. The reverse callback receives the Python object, so both directions need registration.

Give API fields different names rename-fields

from attrs import define
from cattrs import Converter, override
from cattrs.gen import make_dict_structure_fn, make_dict_unstructure_fn

@define
class Account:
    account_id: int
    display_name: str

c = Converter()
renames = {
    "account_id": override(rename="accountId"),
    "display_name": override(rename="displayName"),
}
c.register_unstructure_hook(Account, make_dict_unstructure_fn(Account, c, **renames))
c.register_structure_hook(Account, make_dict_structure_fn(Account, c, **renames))

c.unstructure(Account(1, "Ann"))  # {'accountId': 1, 'displayName': 'Ann'}

Install a generated hook for input and another for output. Registering only one direction produces a mapping that cannot round-trip.

Serialize through the orjson preset preconf-orjson

# pip install 'cattrs[orjson]'
from attrs import define
from cattrs.preconf.orjson import make_converter

@define
class Event:
    name: str

c = make_converter()
raw = c.dumps(Event("deploy"))     # b'{"name":"deploy"}'
print(c.loads(raw, Event))

The `orjson` extra supplies the serializer dependency. Presets add `dumps` and `loads` and include format-specific handling for common scalar values.

Discriminate a union with a tag tagged-union

from attrs import define
from cattrs import Converter
from cattrs.strategies import configure_tagged_union

@define
class Cat:
    lives: int

@define
class Dog:
    breed: str

c = Converter()
configure_tagged_union(Cat | Dog, c, tag_name="kind")

c.structure({"kind": "Cat", "lives": 9}, Cat | Dog)

Default tag values are class names. Set `tag_generator` when wire values must stay independent of Python class names.

Turn grouped failures into field paths readable-errors

from attrs import define
from cattrs import Converter, transform_error
from cattrs.errors import ClassValidationError

@define
class Point:
    x: int
    y: int

c = Converter()
try:
    c.structure({"x": "nope"}, Point)
except ClassValidationError as exc:
    for line in transform_error(exc):
        print(line)

Detailed validation can collect several nested failures in an `ExceptionGroup`; `transform_error` renders paths such as `@ $.x`.

Keep a secret out of output omit-and-rename-one-field

from attrs import define, field
from cattrs import Converter, override
from cattrs.gen import make_dict_unstructure_fn

@define
class Session:
    user: str
    token: str = field(default="")

c = Converter()
c.register_unstructure_hook(
    Session, make_dict_unstructure_fn(Session, c, token=override(omit=True))
)
c.unstructure(Session("ann", "secret"))  # {'user': 'ann'}

This generated hook changes unstructuring only. Input policy must be configured separately if the same field should also be rejected or renamed.

Apply one output rule to attrs classes hook-factory-all-classes

from attrs import has
from cattrs import Converter
from cattrs.gen import make_dict_unstructure_fn

c = Converter()
c.register_unstructure_hook_factory(
    has,
    lambda cls, conv: make_dict_unstructure_fn(
        cls, conv, _cattrs_omit_if_default=True
    ),
)

A hook factory runs the first time each matching class is used, then its result is cached. Register factories before converting those classes.

Recognize subclasses through a base type include-subclasses

from attrs import define
from cattrs import Converter
from cattrs.strategies import include_subclasses

@define
class Shape:
    name: str

@define
class Circle(Shape):
    radius: float

c = Converter()
include_subclasses(Shape, c)

c.structure({"name": "c", "radius": 1.0}, Shape)  # Circle(...)

`include_subclasses` sees classes already imported into `__subclasses__()`. Import implementations first and tag members whose fields overlap.

Load a dataclass and a TypedDict dataclasses-and-typeddict

from dataclasses import dataclass
from typing import TypedDict
from cattrs import Converter

@dataclass
class Item:
    sku: str
    qty: int

class Row(TypedDict):
    sku: str
    qty: int

c = Converter()
c.structure({"sku": "a", "qty": 2}, Item)
c.structure({"sku": "a", "qty": 2}, Row)

Dataclasses and TypedDicts use the same converter machinery as attrs models; an attrs base class is not required at the data boundary.

Raise the first conversion error disable-detailed-validation

from cattrs import Converter

fast = Converter(detailed_validation=False)
# raises the first underlying error (ValueError, KeyError, ...)
# instead of collecting an ExceptionGroup

With detailed validation disabled, cattrs stops at the first underlying exception and omits the grouped field-path report. Use it only when that diagnostic loss is acceptable.

Alternatives

PackageRegistryPick it when
dacitePyPIChoose it for dictionary-to-dataclass construction when serialization back to plain values is outside the job.
mashumaroPyPIChoose it when generated methods attached to models fit better than conversion policy held in separate objects.
pydanticPyPIChoose it when validation constraints, schemas, settings, and framework support should come with the model API.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.