Repository: litestar-org/polyfactory
Pull request #309 (2023-07-21 14:22:13)

chore(internal): fix annotated field resolution
This PR fixes a bug where generatic types with Annotated where not parsed for constraints.


Patch:
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 9992251..3cd1149 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -6,10 +6,9 @@ Setting up the environment
 
 1. Install `poetry <https://python-poetry.org/>`_.
 2. Run ``poetry install --extras full`` to create a `virtual environment <https://docs.python.org/3/tutorial/venv.html>`_
-    and install
-   the dependencies.
+   and install the dependencies.
 3. If you're working on the documentation and need to build it locally, install the extra dependencies with ``poetry
-    install --with docs --extras full``.
+   install --with docs --extras full``.
 4. Install `pre-commit <https://pre-commit.com/>`_.
 5. Run ``pre-commit install`` to install pre-commit hooks.
 
@@ -27,8 +26,10 @@ Workflow
 5. Once you are ready, add a PR in the main repo.
 6. Create a pull request to the main repository with an explanation of your changes.
 
-.. note:: The test suite requires having an instance of MongoDB available. You can launch one using the root level
-          docker-compose config with ``docker-compose up --detach``, or by any other means.
+.. note::
+    The test suite requires having an instance of MongoDB available. You can launch one
+    using the root level docker-compose config with ``docker-compose up --detach``, or
+    by any other means.
 
 
 Project documentation
diff --git a/polyfactory/factories/pydantic_factory.py b/polyfactory/factories/pydantic_factory.py
index 8ce981b..100deb5 100644
--- a/polyfactory/factories/pydantic_factory.py
+++ b/polyfactory/factories/pydantic_factory.py
@@ -69,6 +69,9 @@ class PydanticFieldMeta(FieldMeta):
         :param field_info: A pydantic FieldInfo instance.
         :param use_alias: Whether to use the field alias.
         :param random: A random.Random instance.
+        :param randomize_collection_length: Whether to randomize collection length.
+        :param min_collection_length: Minimum collection length.
+        :param max_collection_length: Maximum collection length.
 
         :returns: A PydanticFieldMeta instance.
         """
diff --git a/polyfactory/field_meta.py b/polyfactory/field_meta.py
index 288fd7a..8f6f26f 100644
--- a/polyfactory/field_meta.py
+++ b/polyfactory/field_meta.py
@@ -3,6 +3,8 @@ from __future__ import annotations
 from dataclasses import asdict, is_dataclass
 from typing import TYPE_CHECKING, Any, Literal, Pattern, TypedDict, cast
 
+from typing_extensions import get_args, get_origin
+
 from polyfactory.collection_extender import CollectionExtender
 from polyfactory.constants import (
     DEFAULT_RANDOM,
@@ -131,14 +133,21 @@ class FieldMeta:
             _, metadata = unwrap_annotated(annotation, random=random)
             constraints = cls.parse_constraints(metadata)
 
+        if not any(is_annotated(arg) for arg in get_args(annotation)):
+            annotation = TYPE_MAPPING[field_type] if field_type in TYPE_MAPPING else field_type
+        elif (origin := get_origin(annotation)) and origin in TYPE_MAPPING:  # pragma: no cover
+            container = TYPE_MAPPING[origin]
+            annotation = container[get_args(annotation)]  # type: ignore
+
         field = cls(
-            annotation=TYPE_MAPPING[field_type] if field_type in TYPE_MAPPING else field_type,
+            annotation=annotation,
             random=random,
             name=name,
             default=default,
             children=None,
             constraints=constraints,
         )
+
         if field.type_args:
             if randomize_collection_length:
                 number_of_args = random.randint(min_collection_length, max_collection_length)


Test patch:
diff --git a/tests/test_annotated_fields.py b/tests/test_annotated_fields.py
index e3574c8..f4abbc3 100644
--- a/tests/test_annotated_fields.py
+++ b/tests/test_annotated_fields.py
@@ -1,7 +1,7 @@
 from dataclasses import dataclass
-from typing import Literal, Union
+from typing import Literal, Tuple, Union
 
-from annotated_types import LowerCase, UpperCase
+from annotated_types import Ge, Le, LowerCase, UpperCase
 from pydantic import BaseModel, Field
 from typing_extensions import Annotated
 
@@ -47,3 +47,23 @@ def test_predicated_fields() -> None:
         __model__ = PredicatedMusician
 
     assert PredicatedMusicianFactory.build()
+
+
+def test_tuple_with_annotated_constraints() -> None:
+    class Location(BaseModel):
+        long_lat: Tuple[Annotated[float, Ge(-180), Le(180)], Annotated[float, Ge(-90), Le(90)]]
+
+    class LocationFactory(ModelFactory[Location]):
+        __model__ = Location
+
+    assert LocationFactory.build()
+
+
+def test_legacy_tuple_with_annotated_constraints() -> None:
+    class Location(BaseModel):
+        long_lat: Tuple[Annotated[float, Ge(-180), Le(180)], Annotated[float, Ge(-90), Le(90)]]
+
+    class LocationFactory(ModelFactory[Location]):
+        __model__ = Location
+
+    assert LocationFactory.build()