Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/adcp/types/canonical_creative.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,13 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
Binding happens AFTER class creation, so pydantic never considers the
name a field candidate; ``model_fields``, the JSON schema and the wire
are all unaffected. Measured scope: one such validator, on one model.


The rebuild does not raise on an unresolved forward reference. A
subclass may annotate a field with a model defined later in its module
(``creatives: list["CreativeAsset"]``); plain pydantic defers such a
class until ``model_rebuild()``, and a raising rebuild here would turn
that ordinary pattern into an error at class creation.
"""

removed = [name for name in cls.model_fields if is_legacy_creative_identity_key(name)]
Expand All @@ -698,7 +705,7 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
cls.__annotations__.pop(name, None)
setattr(cls, name, None)
if removed:
cls.model_rebuild(force=True)
cls.model_rebuild(force=True, raise_errors=False)

@model_validator(mode="before")
@classmethod
Expand Down
22 changes: 22 additions & 0 deletions tests/test_canonical_removed_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,25 @@ def on(line: int) -> list[str]:
f"{checker} does not know {name} on the wire model {class_name} either, "
f"so the refusal above proves nothing: {on(line)}"
)


def test_a_subclass_may_forward_reference_a_later_model() -> None:
"""The removal's rebuild defers an unresolved forward reference, as pydantic does.

A subclass that re-inherits a removed field triggers the forced rebuild; if
that rebuild raises, annotating a field with a model defined further down the
module fails at class creation instead of at the caller's ``model_rebuild()``.
"""
from adcp.types import PackageRequest

class WithForwardRef(PackageRequest):
later: list[Later] | None = None

class Later(BaseModel):
name: str

assert not WithForwardRef.__pydantic_complete__
WithForwardRef.model_rebuild()

assert "later" in WithForwardRef.model_fields
assert not set(_removed_names(WithForwardRef)) & set(WithForwardRef.model_fields)
Loading