Repository: litestar-org/polyfactory
Pull request #396 (2023-09-30 06:46:36)

refactor: move creation of pydantic provider map
[//]: # "By submitting this pull request, you agree to:"
[//]: # "- follow [Litestar's Code of Conduct](https://github.com/litestar-org/.github/blob/main/CODE_OF_CONDUCT.md)"
[//]: # "- follow [Litestar's contribution guidelines](https://github.com/litestar-org/.github/blob/main/CONTRIBUTING.md)"
[//]: # "- follow the [PSFs's Code of Conduct](https://www.python.org/psf/conduct/)"

### Pull Request Checklist

- [ ] New code has 100% test coverage
- [ ] (If applicable) The prose documentation has been updated to reflect the changes introduced by this PR
- [ ] (If applicable) The reference documentation has been updated to reflect the changes introduced by this PR
- [x] Pre-Commit Checks were ran and passed
- [x] Tests were ran and passed

### Description
[//]: # "Please describe your pull request for new release changelog purposes"

- move creation of pydantic provider map

sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=starlite-api_polyfactory&pullRequest=396)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=VULNERABILITY) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=VULNERABILITY) [1 Vulnerability](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=396&resolved=false&types=CODE_SMELL)

[![94.1%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '94.1%')](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=396&metric=new_coverage&view=list) [94.1% Coverage](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=396&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=396&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=396&metric=new_duplicated_lines_density&view=list)



![idea](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png 'idea') Catch issues before they fail your Quality Gate with our IDE extension ![sonarlint](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png 'sonarlint') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarcloud-welcome)
github-actions[bot]: Documentation preview will be available shortly at https://litestar-org.github.io/polyfactory-docs-preview/396

Patch:
diff --git a/polyfactory/factories/base.py b/polyfactory/factories/base.py
index e7c5efd..3a37677 100644
--- a/polyfactory/factories/base.py
+++ b/polyfactory/factories/base.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from collections import Counter, abc, deque
 from contextlib import suppress
-from datetime import date, datetime, time, timedelta, timezone
+from datetime import date, datetime, time, timedelta
 from decimal import Decimal
 from enum import EnumMeta
 from functools import partial
@@ -35,7 +35,7 @@ from typing import (
     TypeVar,
     cast,
 )
-from uuid import NAMESPACE_DNS, UUID, uuid1, uuid3, uuid5
+from uuid import UUID
 
 from faker import Faker
 from typing_extensions import get_args
@@ -89,80 +89,6 @@ if TYPE_CHECKING:
     from polyfactory.persistence import AsyncPersistenceProtocol, SyncPersistenceProtocol
 
 
-def _create_pydantic_type_map(cls: type[BaseFactory[Any]]) -> dict[type, Callable[[], Any]]:
-    """Creates a mapping of pydantic types to mock data functions.
-
-    :param cls: The base factory class.
-    :return: A dict mapping types to callables.
-    """
-    try:
-        import pydantic
-
-        mapping = {
-            pydantic.ByteSize: cls.__faker__.pyint,
-            pydantic.PositiveInt: cls.__faker__.pyint,
-            pydantic.NegativeFloat: lambda: cls.__random__.uniform(-100, -1),
-            pydantic.NegativeInt: lambda: cls.__faker__.pyint() * -1,
-            pydantic.PositiveFloat: cls.__faker__.pyint,
-            pydantic.NonPositiveFloat: lambda: cls.__random__.uniform(-100, 0),
-            pydantic.NonNegativeInt: cls.__faker__.pyint,
-            pydantic.StrictInt: cls.__faker__.pyint,
-            pydantic.StrictBool: cls.__faker__.pybool,
-            pydantic.StrictBytes: partial(create_random_bytes, cls.__random__),
-            pydantic.StrictFloat: cls.__faker__.pyfloat,
-            pydantic.StrictStr: cls.__faker__.pystr,
-            pydantic.EmailStr: cls.__faker__.free_email,
-            pydantic.NameEmail: cls.__faker__.free_email,
-            pydantic.Json: cls.__faker__.json,
-            pydantic.PaymentCardNumber: cls.__faker__.credit_card_number,
-            pydantic.AnyUrl: cls.__faker__.url,
-            pydantic.AnyHttpUrl: cls.__faker__.url,
-            pydantic.HttpUrl: cls.__faker__.url,
-            pydantic.SecretBytes: partial(create_random_bytes, cls.__random__),
-            pydantic.SecretStr: cls.__faker__.pystr,
-            pydantic.IPvAnyAddress: cls.__faker__.ipv4,
-            pydantic.IPvAnyInterface: cls.__faker__.ipv4,
-            pydantic.IPvAnyNetwork: lambda: cls.__faker__.ipv4(network=True),
-            pydantic.PastDate: cls.__faker__.past_date,
-            pydantic.FutureDate: cls.__faker__.future_date,
-        }
-
-        if pydantic.VERSION.startswith("1"):
-            # v1 only values - these will raise an exception in v2
-            # in pydantic v2 these are all aliases for Annotated with a constraint.
-            # we therefore do not need them in v2
-            mapping.update(
-                {
-                    pydantic.PyObject: lambda: "decimal.Decimal",
-                    pydantic.AmqpDsn: lambda: "amqps://example.com",
-                    pydantic.KafkaDsn: lambda: "kafka://localhost:9092",
-                    pydantic.PostgresDsn: lambda: "postgresql://user:secret@localhost",
-                    pydantic.RedisDsn: lambda: "redis://localhost:6379/0",
-                    pydantic.FilePath: lambda: Path(realpath(__file__)),
-                    pydantic.DirectoryPath: lambda: Path(realpath(__file__)).parent,
-                    pydantic.UUID1: uuid1,
-                    pydantic.UUID3: lambda: uuid3(NAMESPACE_DNS, cls.__faker__.pystr()),
-                    pydantic.UUID4: cls.__faker__.uuid4,
-                    pydantic.UUID5: lambda: uuid5(NAMESPACE_DNS, cls.__faker__.pystr()),
-                    pydantic.color.Color: cls.__faker__.hex_color,  # pyright: ignore[reportGeneralTypeIssues]
-                },
-            )
-        else:
-            mapping.update(
-                {
-                    pydantic.PastDatetime: cls.__faker__.past_datetime,
-                    pydantic.FutureDatetime: cls.__faker__.future_datetime,
-                    pydantic.AwareDatetime: partial(cls.__faker__.date_time, timezone.utc),
-                    pydantic.NaiveDatetime: cls.__faker__.date_time,
-                },
-            )
-
-    except ImportError:
-        mapping = {}
-
-    return mapping
-
-
 T = TypeVar("T")
 F = TypeVar("F", bound="BaseFactory[Any]")
 
@@ -488,7 +414,6 @@ class BaseFactory(ABC, Generic[T]):
             Callable: _create_generic_fn,
             abc.Callable: _create_generic_fn,
             Counter: lambda: Counter(cls.__faker__.pystr()),
-            **_create_pydantic_type_map(cls),
         }
 
     @classmethod
diff --git a/polyfactory/factories/pydantic_factory.py b/polyfactory/factories/pydantic_factory.py
index 8d4a45d..e7b0f67 100644
--- a/polyfactory/factories/pydantic_factory.py
+++ b/polyfactory/factories/pydantic_factory.py
@@ -1,6 +1,10 @@
 from __future__ import annotations
 
 from contextlib import suppress
+from datetime import timezone
+from functools import partial
+from os.path import realpath
+from pathlib import Path
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -12,6 +16,7 @@ from typing import (
     TypeVar,
     cast,
 )
+from uuid import NAMESPACE_DNS, uuid1, uuid3, uuid5
 
 from typing_extensions import Literal, get_args, get_origin
 
@@ -27,8 +32,10 @@ from polyfactory.factories.base import BaseFactory
 from polyfactory.field_meta import Constraints, FieldMeta, Null
 from polyfactory.utils.helpers import unwrap_new_type, unwrap_optional
 from polyfactory.utils.predicates import is_optional, is_safe_subclass, is_union
+from polyfactory.value_generators.primitives import create_random_bytes
 
 try:
+    import pydantic
     from pydantic import VERSION, BaseModel, Json
     from pydantic.fields import FieldInfo
 except ImportError as e:
@@ -47,7 +54,7 @@ with suppress(ImportError):
 if TYPE_CHECKING:
     from random import Random
 
-    from typing_extensions import NotRequired, TypeGuard
+    from typing_extensions import Callable, NotRequired, TypeGuard
 
 T = TypeVar("T", bound=BaseModel)
 
@@ -405,3 +412,67 @@ class ModelFactory(Generic[T], BaseFactory[T]):
         return field_meta.name not in kwargs and (
             not field_meta.name.startswith("_") or cls.is_custom_root_field(field_meta)
         )
+
+    @classmethod
+    def get_provider_map(cls) -> dict[Any, Callable[[], Any]]:
+        mapping = {
+            pydantic.ByteSize: cls.__faker__.pyint,
+            pydantic.PositiveInt: cls.__faker__.pyint,
+            pydantic.NegativeFloat: lambda: cls.__random__.uniform(-100, -1),
+            pydantic.NegativeInt: lambda: cls.__faker__.pyint() * -1,
+            pydantic.PositiveFloat: cls.__faker__.pyint,
+            pydantic.NonPositiveFloat: lambda: cls.__random__.uniform(-100, 0),
+            pydantic.NonNegativeInt: cls.__faker__.pyint,
+            pydantic.StrictInt: cls.__faker__.pyint,
+            pydantic.StrictBool: cls.__faker__.pybool,
+            pydantic.StrictBytes: partial(create_random_bytes, cls.__random__),
+            pydantic.StrictFloat: cls.__faker__.pyfloat,
+            pydantic.StrictStr: cls.__faker__.pystr,
+            pydantic.EmailStr: cls.__faker__.free_email,
+            pydantic.NameEmail: cls.__faker__.free_email,
+            pydantic.Json: cls.__faker__.json,
+            pydantic.PaymentCardNumber: cls.__faker__.credit_card_number,
+            pydantic.AnyUrl: cls.__faker__.url,
+            pydantic.AnyHttpUrl: cls.__faker__.url,
+            pydantic.HttpUrl: cls.__faker__.url,
+            pydantic.SecretBytes: partial(create_random_bytes, cls.__random__),
+            pydantic.SecretStr: cls.__faker__.pystr,
+            pydantic.IPvAnyAddress: cls.__faker__.ipv4,
+            pydantic.IPvAnyInterface: cls.__faker__.ipv4,
+            pydantic.IPvAnyNetwork: lambda: cls.__faker__.ipv4(network=True),
+            pydantic.PastDate: cls.__faker__.past_date,
+            pydantic.FutureDate: cls.__faker__.future_date,
+        }
+
+        if pydantic.VERSION.startswith("1"):
+            # v1 only values - these will raise an exception in v2
+            # in pydantic v2 these are all aliases for Annotated with a constraint.
+            # we therefore do not need them in v2
+            mapping.update(
+                {
+                    pydantic.PyObject: lambda: "decimal.Decimal",
+                    pydantic.AmqpDsn: lambda: "amqps://example.com",
+                    pydantic.KafkaDsn: lambda: "kafka://localhost:9092",
+                    pydantic.PostgresDsn: lambda: "postgresql://user:secret@localhost",
+                    pydantic.RedisDsn: lambda: "redis://localhost:6379/0",
+                    pydantic.FilePath: lambda: Path(realpath(__file__)),
+                    pydantic.DirectoryPath: lambda: Path(realpath(__file__)).parent,
+                    pydantic.UUID1: uuid1,
+                    pydantic.UUID3: lambda: uuid3(NAMESPACE_DNS, cls.__faker__.pystr()),
+                    pydantic.UUID4: cls.__faker__.uuid4,
+                    pydantic.UUID5: lambda: uuid5(NAMESPACE_DNS, cls.__faker__.pystr()),
+                    pydantic.color.Color: cls.__faker__.hex_color,  # pyright: ignore[reportGeneralTypeIssues]
+                },
+            )
+        else:
+            mapping.update(
+                {
+                    pydantic.PastDatetime: cls.__faker__.past_datetime,
+                    pydantic.FutureDatetime: cls.__faker__.future_datetime,
+                    pydantic.AwareDatetime: partial(cls.__faker__.date_time, timezone.utc),
+                    pydantic.NaiveDatetime: cls.__faker__.date_time,
+                },
+            )
+
+        mapping.update(super().get_provider_map())
+        return mapping


Test patch:
diff --git a/tests/test_provider_map.py b/tests/test_provider_map.py
index 0dd42bb..07fd538 100644
--- a/tests/test_provider_map.py
+++ b/tests/test_provider_map.py
@@ -1,13 +1,11 @@
 from typing import Any
 
-from polyfactory.factories.base import BaseFactory, _create_pydantic_type_map
+from polyfactory.factories.base import BaseFactory
 
 
 def test_provider_map() -> None:
     provider_map = BaseFactory.get_provider_map()
     provider_map.pop(Any)
-    for key in _create_pydantic_type_map(BaseFactory):  # type: ignore[type-abstract]
-        provider_map.pop(key)
 
     for type_, handler in provider_map.items():
         value = handler()