Repository: litestar-org/polyfactory
Pull request #411 (2023-10-18 05:40:20)

feat: support annotation with Constraints
<!--
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
- [ ] Pre-Commit Checks were ran and passed
- [ ] Tests were ran and passed

### Description
<!--
Please describe your pull request for new release changelog purposes
-->

- This PR enables annotating any types with `Constraints` to specify the constraints. It is important to note that this does NOT mean that these constraints will be validated at runtime, but that `polyfactory` will take them into account when creating the model instances.

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

[![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=411&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=411&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=411&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=411&resolved=false&types=VULNERABILITY) [![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=411&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=411&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=411&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=411&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=starlite-api_polyfactory&pullRequest=411&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=411&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=411&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=411&resolved=false&types=CODE_SMELL)

[![100.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100-16px.png '100.0%')](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=411&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=411&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=411&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=411&metric=new_duplicated_lines_density&view=list)


github-actions[bot]: Documentation preview will be available shortly at https://litestar-org.github.io/polyfactory-docs-preview/411
guacs: > nit: probably use double backticks?

Isn't that usually done in documentation though?
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=starlite-api_polyfactory&pullRequest=411)

[![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=411&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=411&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=411&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=411&resolved=false&types=VULNERABILITY) [![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=411&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=411&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=411&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=411&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=starlite-api_polyfactory&pullRequest=411&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=411&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=411&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=starlite-api_polyfactory&pullRequest=411&resolved=false&types=CODE_SMELL)

[![100.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/100-16px.png '100.0%')](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=411&metric=new_coverage&view=list) [100.0% Coverage](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=411&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=411&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=starlite-api_polyfactory&pullRequest=411&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/polyfactory/field_meta.py b/polyfactory/field_meta.py
index a0d8bc9..763caca 100644
--- a/polyfactory/field_meta.py
+++ b/polyfactory/field_meta.py
@@ -3,7 +3,7 @@ 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 typing_extensions import Mapping, get_args, get_origin
 
 from polyfactory.collection_extender import CollectionExtender
 from polyfactory.constants import (
@@ -183,6 +183,10 @@ class FieldMeta:
                     constraints["pattern"] = "[[:digit:]]"
             elif is_dataclass(value) and (value_dict := asdict(value)) and ("allowed_schemes" in value_dict):
                 constraints["url"] = {k: v for k, v in value_dict.items() if v is not None}
+            # This is to support `Constraints`, but we can't do a isinstance with `Constraints` since isinstance
+            # checks with `TypedDict` is not supported.
+            elif isinstance(value, Mapping):
+                constraints.update(value)
             else:
                 constraints.update(
                     {


Test patch:
diff --git a/tests/constraints/test_get_field_value_constraints.py b/tests/constraints/test_get_field_value_constraints.py
index a9566fa..91e0900 100644
--- a/tests/constraints/test_get_field_value_constraints.py
+++ b/tests/constraints/test_get_field_value_constraints.py
@@ -4,6 +4,7 @@ from random import Random
 from typing import FrozenSet, List, Set, Tuple, Type, Union, cast
 
 import pytest
+from typing_extensions import Annotated
 
 from polyfactory.factories.base import BaseFactory
 from polyfactory.field_meta import Constraints, FieldMeta
@@ -57,3 +58,11 @@ def test_date() -> None:
 
     assert value >= ge_date
     assert value <= le_date
+
+
+def test_constraints_parsing() -> None:
+    constraints = Constraints(min_length=10)
+    annotation = Annotated[str, constraints]
+    field_meta = FieldMeta.from_type(annotation)
+
+    assert field_meta.constraints == constraints