Repository: litestar-org/polyfactory
Pull request #220 (2023-05-27 10:15:09)

Include missing types in creating constrained field values
[//]: # "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)"

### Pull Request Checklist

[//]: # "Please review the [Litestar contribution guidelines](https://github.com/litestar-org/.github/blob/main/CONTRIBUTING.md) for this repository."

- [x ] 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

### Description

[//]: # "Please describe your pull request for new release changelog purposes"
Include the missing types in the creation of constrained field values which includes bytes, date, and tuples.

### Close Issue(s)

[//]: # "Please add in issue numbers this pull request will close, if applicable"
[//]: # "Examples: Fixes #4321 or Closes #1234"

This resolves the issue described in #217 

Goldziher: @all-contributors add @guacs code
allcontributors[bot]: @Goldziher 

I've put up [a pull request](https://github.com/litestar-org/polyfactory/pull/221) to add @guacs! :tada:

Patch:
diff --git a/polyfactory/factories/base.py b/polyfactory/factories/base.py
index 98f303f..989d9bb 100644
--- a/polyfactory/factories/base.py
+++ b/polyfactory/factories/base.py
@@ -529,9 +529,20 @@ class BaseFactory(ABC, Generic[T]):
             is_safe_subclass(annotation, set)
             or is_safe_subclass(annotation, list)
             or is_safe_subclass(annotation, frozenset)
+            or is_safe_subclass(annotation, tuple)
         ):
-            result = handle_constrained_collection(
-                collection_type=list if is_safe_subclass(annotation, list) else set,  # pyright: ignore
+            collection_type: type[list] | type[set] | type[tuple] | type[frozenset]
+            if is_safe_subclass(annotation, list):
+                collection_type = list
+            elif is_safe_subclass(annotation, set):
+                collection_type = set
+            elif is_safe_subclass(annotation, tuple):
+                collection_type = tuple
+            else:
+                collection_type = frozenset
+
+            return handle_constrained_collection(
+                collection_type=collection_type,  # type: ignore
                 factory=cls,
                 field_meta=field_meta.children[0] if field_meta.children else field_meta,
                 item_type=constraints.get("item_type"),
@@ -539,7 +550,6 @@ class BaseFactory(ABC, Generic[T]):
                 min_items=constraints.get("min_length"),
                 unique_items=constraints.get("unique_items", False),
             )
-            return frozenset(result) if is_safe_subclass(annotation, frozenset) else result
 
         if is_safe_subclass(annotation, date):
             return handle_constrained_date(
@@ -580,7 +590,7 @@ class BaseFactory(ABC, Generic[T]):
             return cls.__random__.choice(list(unwrapped_annotation))  # pyright: ignore
 
         if field_meta.constraints and (
-            unwrapped_annotation in (float, int, Decimal, str, list, tuple, set, frozenset)
+            unwrapped_annotation in (float, int, Decimal, bytes, str, list, tuple, set, frozenset, date)
             or unwrapped_annotation not in cls.get_provider_map()
         ):
             return cls.get_constrained_field_value(annotation=unwrapped_annotation, field_meta=field_meta)


Test patch:
diff --git a/tests/constraints/test_get_field_value_constraints.py b/tests/constraints/test_get_field_value_constraints.py
new file mode 100644
index 0000000..785bdb9
--- /dev/null
+++ b/tests/constraints/test_get_field_value_constraints.py
@@ -0,0 +1,53 @@
+from datetime import date, datetime, timedelta
+from decimal import Decimal
+from typing import Type, Union, cast, List, Set, FrozenSet, Tuple
+
+import pytest
+
+from polyfactory.factories.base import BaseFactory
+from polyfactory.field_meta import Constraints, FieldMeta
+
+
+@pytest.mark.parametrize("t", (int, float, Decimal))
+def test_numbers(t: Type[Union[int, float, Decimal]]) -> None:
+    constraints: Constraints = {"ge": 1, "le": 20}
+    field_meta = FieldMeta.from_type(t, "foo", constraints=constraints)
+    value = BaseFactory.get_field_value(field_meta)
+
+    assert value >= constraints["ge"]
+    assert value <= constraints["le"]
+
+
+@pytest.mark.parametrize("t", (str, bytes))
+def test_str_and_bytes(t: Type[Union[str, bytes]]) -> None:
+    constraints: Constraints = {"min_length": 20, "max_length": 45}
+    field_meta = FieldMeta.from_type(t, "foo", constraints=constraints)
+    value = BaseFactory.get_field_value(field_meta)
+
+    assert len(value) >= constraints["min_length"]
+    assert len(value) <= constraints["max_length"]
+
+
+@pytest.mark.parametrize("t", (List[int], Set[int], Tuple[int], FrozenSet[int]))
+def test_collections(t: Type[Union[Tuple, List, Set, FrozenSet]]) -> None:
+    constraints: Constraints = {
+        "min_length": 2,
+        "max_length": 10,
+    }
+    field_meta = FieldMeta.from_type(t, "foo", constraints=constraints)
+    value = BaseFactory.get_field_value(field_meta)
+
+    assert len(value) >= constraints["min_length"]
+    assert len(value) <= constraints["max_length"]
+
+
+def test_date() -> None:
+    ge_date = datetime.today().date()
+    le_date = ge_date + timedelta(days=10)
+    constraints = {"ge": ge_date, "le": le_date}
+
+    field_meta = FieldMeta.from_type(date, "foo", constraints=cast(Constraints, constraints))
+    value = BaseFactory.get_field_value(field_meta)
+
+    assert value >= ge_date
+    assert value <= le_date