From e5dabe152d5e0dedf5b1f382950bdcbb61de8143 Mon Sep 17 00:00:00 2001 From: Alexander Viklund Date: Sun, 10 May 2026 12:06:19 +0200 Subject: [PATCH] Fix: Support pydantic examples in nested type annotations --- polyfactory/factories/pydantic_factory.py | 26 ++++++++++++++--- tests/test_pydantic_factory.py | 35 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/polyfactory/factories/pydantic_factory.py b/polyfactory/factories/pydantic_factory.py index 7a1e5ca5..596eeea8 100644 --- a/polyfactory/factories/pydantic_factory.py +++ b/polyfactory/factories/pydantic_factory.py @@ -713,11 +713,29 @@ def get_field_value_coverage( if cls.is_ignored_type(field_meta.annotation): return + field_meta = cast("PydanticFieldMeta", field_meta) if cls.__use_examples__: - examples = getattr(field_meta, "examples", None) or [] - if len(examples) > 0: - yield CoverageContainer(examples) - return + + def examples_coverage(field_meta: PydanticFieldMeta) -> Iterable[Any]: + examples = field_meta.examples or [] + if len(examples) > 0: + yield CoverageContainer(examples) + return + if field_meta.children: + # NoneType children aren't included in field_meta.children, + # so to ensure that they are covered we need to check for them explicitly + if NoneType in field_meta.type_args: + yield None + for child in field_meta.children: + child = cast("PydanticFieldMeta", child) + yield from examples_coverage(child) + return + yield from super(ModelFactory, cls).get_field_value_coverage( + field_meta, field_build_parameters, build_context + ) + + yield from examples_coverage(field_meta) + return yield from super().get_field_value_coverage(field_meta, field_build_parameters, build_context) diff --git a/tests/test_pydantic_factory.py b/tests/test_pydantic_factory.py index 1bc17110..64da4c34 100644 --- a/tests/test_pydantic_factory.py +++ b/tests/test_pydantic_factory.py @@ -308,6 +308,41 @@ class FooFactory(ModelFactory[Foo]): assert {instance.number for instance in instances} == set(example_numbers) +@pytest.mark.skipif(_IS_PYDANTIC_V1, reason="Pydantic 1 doesn't support examples") +def test_factory_use_examples_coverage_when_examples_in_annotations() -> None: + class TopLevel(BaseModel): + f: Annotated[int, Field(examples=[1, 2, 3, 4])] + + class WithOptional(BaseModel): + f: Optional[Annotated[int, Field(examples=[1, 2, 3, 4])]] + + class OrNone(BaseModel): + f: Union[Annotated[int, Field(examples=[1, 2, 3, 4])], None] + + class ExamplesInBothSubtypes(BaseModel): + f: Union[Annotated[int, Field(examples=[1, 2])], Annotated[int, Field(examples=[3, 4])], None] + + class OuterMostExampleIsRespected(BaseModel): + f: Annotated[ + Union[Annotated[int, Field(examples=[1, 2])], Annotated[int, Field(examples=[3, 4])], None], + Field(examples=[6, 7, 8, 9]), + ] + + for cls, expected in ( + (TopLevel, [1, 2, 3, 4]), + (WithOptional, [1, 2, 3, 4, None]), + (OrNone, [1, 2, 3, 4, None]), + (ExamplesInBothSubtypes, [1, 2, 3, 4, None]), + (OuterMostExampleIsRespected, [6, 7, 8, 9]), + ): + + class ClsFactory(ModelFactory[cls]): # type: ignore[valid-type] + __use_examples__ = True + + instances = list(ClsFactory.coverage()) + assert {instance.f for instance in instances} == set(expected) # type: ignore[attr-defined] + + def test_factory_use_construct_nested() -> None: class Child(BaseModel): a: int = Field(ge=0)