Python dataclass_transform: Generated Classes

Published on: August 29, 2026
Reading time: 5 minutes
A detailed view of computer programming code on a screen, showcasing software development.

typing.dataclass_transform lets a library tell static type checkers that a decorator, base class, or metaclass provides dataclass-like behavior. Frameworks that generate __init__, fields, equality, ordering, or declarative models can offer precise typing without requiring users to apply the standard @dataclass decorator.

The marker does not transform classes by itself. The library must implement runtime behavior. This guide covers decorators, base classes, metaclasses, defaults, field specifiers, aliases, keyword-only fields, frozen models, inheritance, static tests, and runtime alignment.

The generated-class problem

def model(cls):
    # dynamically generates __init__
    return cls

@model
class User:
    name: str
    age: int

user = User(name="Ana", age=30)

A framework may generate the constructor at runtime. Without extra information, a checker sees a class without a compatible __init__ and rejects the call.

Marking the decorator

from typing import dataclass_transform

@dataclass_transform()
def model(cls):
    return transform_into_model(cls)

The checker now treats classes decorated with @model as dataclass-like and may synthesize the constructor from annotated fields.

Runtime remains the library’s responsibility

dataclass_transform does not generate methods. If transform_into_model() fails to create __init__, runtime code fails even when static analysis succeeds. The declared transform and implementation must stay aligned.

Typing the decorator

from typing import TypeVar

T = TypeVar("T", bound=type)

@dataclass_transform()
def model(cls: T) -> T:
    return transform_into_model(cls)

The decorator returns the same class statically. More complex APIs may require overloads or additional generic parameters.

Transforming base classes

@dataclass_transform()
class ModelBase:
    pass

class Product(ModelBase):
    id: int
    name: str

product = Product(id=1, name="Keyboard")

Every subclass is treated as dataclass-like. This pattern is common in ORMs, validation libraries, and configuration frameworks.

Transforming metaclasses

@dataclass_transform()
class ModelMeta(type):
    ...

class Model(metaclass=ModelMeta):
    ...

A metaclass may collect annotations, create descriptors, generate methods, and register fields. The marker communicates that behavior to checkers.

eq_default

@dataclass_transform(eq_default=True)
def model(cls):
    ...

eq_default states whether equality methods are generated by default when users do not specify an option.

order_default

@dataclass_transform(order_default=False)
def model(cls):
    ...

This describes whether ordering methods are generated by default. Runtime behavior must match.

kw_only_default

@dataclass_transform(kw_only_default=True)
class ModelBase:
    ...

Fields are treated as keyword-only by default:

class User(ModelBase):
    name: str
    age: int

User(name="Ana", age=30)
# User("Ana", 30) should be rejected

frozen_default

Modern forms can indicate that models are frozen by default. This influences assignment checks and inheritance. If the library promises frozen objects, runtime code must prevent mutation as well.

Field specifiers

Frameworks often provide a custom field function:

def field_value(*, default=..., alias: str | None = None):
    ...

Declare that it has dataclass-like semantics:

@dataclass_transform(field_specifiers=(field_value,))
def model(cls):
    ...

A checker can interpret defaults, factories, aliases, init inclusion, and keyword-only flags from recognized parameters.

Default and default_factory

class Cart(ModelBase):
    items: list[str] = field_value(default_factory=list)
    open: bool = field_value(default=True)

The runtime library must avoid shared mutable defaults and call factories per instance.

Fields excluded from __init__

class Record(ModelBase):
    id: int = field_value(init=False)
    name: str

If the specifier communicates init=False, the checker omits the field from the synthesized constructor. Runtime code must populate it elsewhere.

Parameter aliases

class User(ModelBase):
    full_name: str = field_value(alias="name")

Some frameworks accept User(name="Ana") while storing full_name. Field specifiers can communicate aliases when checker support exists.

Field order

Non-default fields generally need to precede default fields in positional constructors. Keyword-only defaults may relax the rule. Keep synthesized static signatures equal to runtime signatures.

Inheritance

class Entity(ModelBase):
    id: int

class User(Entity):
    name: str

The checker combines base and derived fields. Ordering, defaults, and frozen rules must follow the framework contract.

Field overrides

A subclass may change a field type, default, or options. Define clear policies. Incompatible overrides can break constructor calls and substitutability.

Frozen inheritance

Frozen and non-frozen models require consistent inheritance rules. Do not promise static immutability while allowing silent runtime mutation. Test construction and assignments across supported combinations.

Decorators with arguments

@dataclass_transform()
def model(*, frozen: bool = False, kw_only: bool = False):
    def apply(cls):
        return transform(cls, frozen=frozen, kw_only=kw_only)
    return apply

@model(frozen=True)
class Configuration:
    host: str

Option names and literal values must follow patterns understood by checkers. In some APIs, Literal[True] overloads improve precision.

Optional parentheses

A decorator usable as both @model and @model(...) may need overloads. Test both forms. Excessively dynamic decorator APIs are difficult for static tools to model.

Comparison with @dataclass

Use @dataclass directly when it meets the need. dataclass_transform is aimed at framework authors who add validation, ORM behavior, conversion, descriptors, registration, or different field semantics.

Comparison with Protocol

Protocol describes capabilities an object already has. dataclass_transform tells the checker that a tool synthesizes methods and constructors. They solve different problems and may be used together.

Runtime introspection

The marker may expose metadata such as __dataclass_transform__. That does not make transformed classes real dataclasses, and dataclasses.is_dataclass() may return false.

Schema generation

The framework must maintain its own field model, defaults, validators, and schema APIs. dataclass_transform improves static typing but does not provide serialization or reflection behavior.

Static tests

from typing import assert_type

user = User(name="Ana", age=30)
assert_type(user.name, str)
assert_type(user.age, int)

Add negative cases for missing fields, extra names, wrong types, forbidden positional calls, and mutation of frozen models.

Runtime tests

Run the same matrix at runtime. A dangerous mismatch occurs when the checker accepts a call that fails, or rejects a valid call. Test inspect.signature(), construction, defaults, factories, equality, and inheritance.

Checker compatibility

Mypy and pyright may implement details at different times. Follow the typing specification, avoid checker-specific magic where possible, and run a suite across supported tools.

Python compatibility

Use typing_extensions.dataclass_transform on older interpreters. Importing the marker from typing_extensions does not change the runtime transformation logic.

Common mistakes

  • Assuming the marker generates methods: the library must implement them.
  • Advertising defaults that differ at runtime: signatures diverge.
  • Forgetting field_specifiers: custom field functions remain opaque.
  • Skipping inheritance tests: field order and frozen behavior can break.
  • Designing an overly dynamic decorator: checkers cannot model it reliably.
  • Using it when @dataclass is enough: maintenance increases without benefit.

Complete example: mini framework

from dataclasses import dataclass, field
from typing import dataclass_transform


def attribute(*, default=..., default_factory=...):
    if default_factory is not ...:
        return field(default_factory=default_factory)
    if default is not ...:
        return field(default=default)
    return field()

@dataclass_transform(field_specifiers=(attribute,))
def model(cls=None, *, frozen: bool = False):
    def apply(target):
        return dataclass(target, frozen=frozen)
    if cls is None:
        return apply
    return apply(cls)

@model(frozen=True)
class Product:
    id: int
    name: str
    tags: list[str] = attribute(default_factory=list)

product = Product(id=1, name="Keyboard")

Runtime behavior delegates to a real dataclass. The marker tells checkers that the custom decorator follows dataclass semantics. Real frameworks can add validation, aliases, and conversion.

When to use it

Use dataclass_transform when authoring a library that synthesizes constructors and fields from annotations. Application developers rarely need it directly. For ordinary models, dataclasses, attrs, Pydantic, or normal classes are usually sufficient.

Conclusion

dataclass_transform bridges declarative frameworks and static analyzers. It describes synthesized methods, constructor parameters, defaults, field specifiers, keyword-only behavior, and frozen models without enforcing one runtime implementation.

The official Python dataclass_transform documentation defines the parameters. Keep runtime behavior aligned with the contract, declare field specifiers, and protect the user experience with both static and runtime tests.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python TypeVarTuple: Variadic Generics

    Learn Python TypeVarTuple to preserve heterogeneous tuples, model dimensions, and build generics with variable type parameters.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    assert_type and reveal_type: Test Type Inference

    Learn Python assert_type and reveal_type to inspect inference, test typed APIs, and prevent static typing regressions.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python get_type_hints: Resolve Annotations

    Learn Python get_type_hints to resolve forward references, preserve Annotated metadata, and inspect functions and classes safely.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python runtime_checkable: Protocol at Runtime

    Learn Python runtime_checkable to test Protocols with isinstance, understand its limits, and design safer structural contracts.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python Unpack: Typed kwargs and Variadics

    Learn Python Unpack to type **kwargs with TypedDict, expand variadic tuples, and preserve precise callable signatures.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Required and NotRequired: Optional TypedDict Keys

    Learn Python Required and NotRequired to control mandatory and optional TypedDict keys without confusing absence with None.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026