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 rejectedfrozen_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: strIf 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: strThe 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: strOption 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.







