Skip to content

Choose a tag to compare

@github-actions github-actions released this 03 Aug 18:18
· 23 commits to main since this release
a75da25

Breaking Changes

Code Generation Changes

  • msgspec empty object default on nested Struct now builds the model - An empty object ({}) default whose type is a nested Struct reference now renders a conversion factory instead of a plain dict factory, changing the runtime default from an empty dict to an actual model instance. No CLI flag is required to trigger this. Regenerating existing schemas that have empty-object defaults for nested Struct fields will produce different output and different default values (#3668)
# Before
nested: Nested | UnsetType = field(default_factory=dict)

# After
nested: Nested | UnsetType = field(
    default_factory=lambda: convert({}, type=Nested)
)
  • Pydantic v2 dataclass optional nested-model default factory - Under --use-default-factory-for-optional-nested-models, optional nested Pydantic dataclass fields now emit Field(default_factory=<Nested>) where they previously did not, because the field now resolves Pydantic dataclass references. Output for these fields changes on regeneration when that flag is enabled (#3668)
# After (with --use-default-factory-for-optional-nested-models)
nested: Nested | None = Field(default_factory=Nested)
  • Nested mapping defaults now render as typed constructors - Fields whose type is a generated model (dataclass, msgspec Struct, pydantic dataclass/BaseModel) and whose default is an object/mapping are now emitted as a nested-model constructor default factory instead of a raw dict literal. This changes both the generated source and the runtime default value (a real model instance instead of a dict), so regenerating existing schemas can produce different output. The constructor form is only used when every required argument of the nested model is satisfied and the mapping is not recursive; otherwise the previous dict-literal default is preserved. (#3669)
# Before
@dataclass
class Model:
    inner: Inner | None = field(default_factory=lambda: {'v': float('inf')})

# After
@dataclass
class Model:
    inner: Inner | None = field(default_factory=lambda: Inner(v=float('inf')))
  • Deprecated decorator detection now requires an exact name match - The check that decides whether a @deprecated class decorator is already present changed from a text prefix match (decorator.startswith("@deprecated")) to an exact unqualified-name match (is_named_python_decorator(decorator, "deprecated")). If a schema marks a class as deprecated and you also apply a custom class decorator that merely shares the deprecated prefix (e.g. @deprecated_custom), the generator previously treated it as the deprecated decorator and skipped emitting one; it now recognizes them as distinct and adds the real @deprecated('... is deprecated.') decorator along with the typing_extensions.deprecated import. Generated output for these cases changes accordingly. Idempotency for the standard @deprecated('...') decorator is unchanged. (#3685)
# Input: deprecated class + custom decorator "@deprecated_custom"

# Before
@deprecated_custom
@dataclass
class LegacyUser:
    ...

# After
@deprecated_custom
@deprecated('LegacyUser is deprecated.')
@dataclass
class LegacyUser:
    ...

What's Changed

New Contributors

Full Changelog: 0.71.0...0.72.0