Skip to content
Merged
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
7 changes: 5 additions & 2 deletions pydantic_resolve/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,8 +588,11 @@ def _prepare_class_context(self, kls: type, kls_name: str) -> FieldContext:
object_fields_without_resolver = [a[0] for a in object_fields if a[0] not in fields_with_resolver]

# Scan expose and collect (__pydantic_resolve_xxx__)
expose_dict = getattr(kls, const.EXPOSE_TO_DESCENDANT, {})
collect_dict = getattr(kls, const.COLLECTOR_CONFIGURATION, {})
# Read from kls.__dict__ to skip inherited attributes — a subclass that
# doesn't override __pydantic_resolve_expose__ would otherwise re-register
# the parent's aliases and falsely trigger the conflict check (#292).
expose_dict = kls.__dict__.get(const.EXPOSE_TO_DESCENDANT, {})
collect_dict = kls.__dict__.get(const.COLLECTOR_CONFIGURATION, {})
self._validate_expose(expose_dict, kls_name)

return {
Expand Down
31 changes: 31 additions & 0 deletions tests/analysis/test_analysis_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,37 @@ def test_expose_conflict():
Analytic().scan(ExposeConflictModel)


# ============ Test 4b: Inherited expose_dict must not double-register ============
# See #292: a subclass that inherits __pydantic_resolve_expose__ without
# overriding it used to re-register the parent's aliases via getattr-MRO
# lookup, falsely raising "Expose alias name conflicts" when both classes
# appear in the same resolve tree.
class ExposeParent(BaseModel):
__pydantic_resolve_expose__ = {'id': 'parent_id'}
id: int
siblings: list['ExposeChild'] = []


class ExposeChild(BaseModel):
name: str


ExposeParent.model_rebuild()


def test_inherited_expose_does_not_conflict():
"""A subclass that doesn't override __pydantic_resolve_expose__ should
not re-register the parent's aliases when walked in the same tree."""
# Should not raise — ExposeChild inherits the field but contributes no
# expose declaration of its own.
result = Analytic().scan(ExposeParent)
prefix = 'tests.analysis.test_analysis_edge_cases'
parent_expose = result[f'{prefix}.ExposeParent']['expose_dict']
assert parent_expose == {'id': 'parent_id'}
child_expose = result[f'{prefix}.ExposeChild']['expose_dict']
assert child_expose == {}


# ============ Test 5: Inheritance chain ============
class BaseModelWithResolve(BaseModel):
id: int
Expand Down