Problem
datamodel-codegen emits subordinate types (`Creative`, `Package`, `MediaBuyDelivery`, etc.) once per response file. The public alias in `adcp.types` resolves to one specific emission, but the response type's field references a different local emission with the same name.
Example:
```python
from adcp.types import Creative
→ adcp.types.generated_poc.creative.get_creative_delivery_response.Creative
from adcp.types.generated_poc.creative.list_creatives_response import (
ListCreativesResponse,
)
ListCreativesResponse.creatives: Sequence[Creative]
↑ this Creative is adcp.types.generated_poc.creative.list_creatives_response.Creative
— a DIFFERENT class with the same name
```
These two `Creative` classes are nominally identical but type-distinct from mypy's perspective. So an adopter pattern:
```python
from adcp.types import Creative
class _Ext(Creative):
extra: str | None = Field(default=None, exclude=True)
class _ExtListResp(ListCreativesResponse):
creatives: list[_Ext] # mypy [assignment] — type-identity mismatch
```
…fails under mypy --strict regardless of #624's Sequence widening, because `_Ext` is a subclass of one `Creative` and the parent expects a different one.
Affected response types (likely incomplete)
Each of these has a field whose element type is locally re-emitted rather than imported from the canonical module:
- `ListCreativesResponse.creatives` → `Creative`
- `GetCreativeDeliveryResponse.creatives` → `Creative`
- `GetMediaBuyDeliveryResponse.media_buy_deliveries` → `MediaBuyDelivery`
- `GetMediaBuysResponse.media_buys` → `MediaBuy`
- `GetSignalsResponse.signals` → `Signal`
(These are the same response types where #624's Sequence widening produces incomplete coverage — the widening is necessary but not sufficient because of this codegen-side issue.)
Proposed fix
Two paths:
A) Codegen-side: prefer cross-file import over local re-emission. When datamodel-codegen sees that `Creative` is already defined in a sibling module (e.g. `creative/creative_item.py` or `core/creative.py`), import it rather than re-emit. Same shape, same type identity, override compatibility restored.
B) Post-processor: rewrite all `X` references in a generated file to import from the canonical module. A new function in `scripts/post_generate_fixes.py` that, for each multi-emitted type on an allowlist, replaces local class definitions with `from .canonical_module import X` and keeps the rest of the file pointing at the imported reference.
Approach A is structurally cleaner; approach B is more contained and safer if the canonical-module mapping is unambiguous.
Companion to #624
This issue tracks the type-identity half of the adopter override-pattern friction. #624 (Sequence widening) is required first — without it, even cases where the type identity is correct would still hit list-invariance `[assignment]` errors. After both land, the full Critical Pattern #1 override should typecheck cleanly under mypy --strict with zero `# type: ignore` lines.
How surfaced
While extending the regression test for #624 to cover required-list overrides (`tests/type_checks/extend_response_with_sequence.py`), the override of `ListCreativesResponse.creatives: list[_InternalCreative]` still failed with `[assignment]` even after Sequence widening. Spike traced the failure to type identity, not variance.
Related
Problem
datamodel-codegen emits subordinate types (`Creative`, `Package`, `MediaBuyDelivery`, etc.) once per response file. The public alias in `adcp.types` resolves to one specific emission, but the response type's field references a different local emission with the same name.
Example:
```python
from adcp.types import Creative
→ adcp.types.generated_poc.creative.get_creative_delivery_response.Creative
from adcp.types.generated_poc.creative.list_creatives_response import (
ListCreativesResponse,
)
ListCreativesResponse.creatives: Sequence[Creative]
↑ this Creative is adcp.types.generated_poc.creative.list_creatives_response.Creative
— a DIFFERENT class with the same name
```
These two `Creative` classes are nominally identical but type-distinct from mypy's perspective. So an adopter pattern:
```python
from adcp.types import Creative
class _Ext(Creative):
extra: str | None = Field(default=None, exclude=True)
class _ExtListResp(ListCreativesResponse):
creatives: list[_Ext] # mypy [assignment] — type-identity mismatch
```
…fails under mypy --strict regardless of #624's Sequence widening, because `_Ext` is a subclass of one `Creative` and the parent expects a different one.
Affected response types (likely incomplete)
Each of these has a field whose element type is locally re-emitted rather than imported from the canonical module:
(These are the same response types where #624's Sequence widening produces incomplete coverage — the widening is necessary but not sufficient because of this codegen-side issue.)
Proposed fix
Two paths:
A) Codegen-side: prefer cross-file import over local re-emission. When datamodel-codegen sees that `Creative` is already defined in a sibling module (e.g. `creative/creative_item.py` or `core/creative.py`), import it rather than re-emit. Same shape, same type identity, override compatibility restored.
B) Post-processor: rewrite all `X` references in a generated file to import from the canonical module. A new function in `scripts/post_generate_fixes.py` that, for each multi-emitted type on an allowlist, replaces local class definitions with `from .canonical_module import X` and keeps the rest of the file pointing at the imported reference.
Approach A is structurally cleaner; approach B is more contained and safer if the canonical-module mapping is unambiguous.
Companion to #624
This issue tracks the type-identity half of the adopter override-pattern friction. #624 (Sequence widening) is required first — without it, even cases where the type identity is correct would still hit list-invariance `[assignment]` errors. After both land, the full Critical Pattern #1 override should typecheck cleanly under mypy --strict with zero `# type: ignore` lines.
How surfaced
While extending the regression test for #624 to cover required-list overrides (`tests/type_checks/extend_response_with_sequence.py`), the override of `ListCreativesResponse.creatives: list[_InternalCreative]` still failed with `[assignment]` even after Sequence widening. Spike traced the failure to type identity, not variance.
Related