Repository: litestar-org/polyfactory (2023-10-20 ~ 2023-10-21)

problem_statement: Enhancement: Deprecate collection params in FieldMeta.from_type
### Summary

After https://github.com/litestar-org/polyfactory/pull/407, these are no longer used other than passing onto child instances for https://github.com/litestar-org/polyfactory/blob/main/polyfactory/field_meta.py#L113 collection params.

Removing outright would be a breaking change so propose to
- Change defaults to None
- Remove any internal usage 
- Omit deprecation warning if these are not None and that these will be removed in next major release. This may require adding some utils

### Basic Example

_No response_

### Drawbacks and Impact

_No response_

### Unresolved questions

_No response_

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh Litestar dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

<a href="https://polar.sh/litestar-org/polyfactory/issues/415">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/polyfactory/issues/415/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/polyfactory/issues/415/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->

date: 2023-10-20
patch: diff --git a/polyfactory/factories/attrs_factory.py b/polyfactory/factories/attrs_factory.py
index 84b8e7c..5ab5acd 100644
--- a/polyfactory/factories/attrs_factory.py
+++ b/polyfactory/factories/attrs_factory.py
@@ -61,9 +61,6 @@ class AttrsFactory(BaseFactory[T]):
                     name=field.alias,
                     default=default_value,
                     random=cls.__random__,
-                    randomize_collection_length=cls.__randomize_collection_length__,
-                    min_collection_length=cls.__min_collection_length__,
-                    max_collection_length=cls.__max_collection_length__,
                 ),
             )
 
diff --git a/polyfactory/factories/dataclass_factory.py b/polyfactory/factories/dataclass_factory.py
index ea7ed23..b4c6d2b 100644
--- a/polyfactory/factories/dataclass_factory.py
+++ b/polyfactory/factories/dataclass_factory.py
@@ -49,9 +49,6 @@ class DataclassFactory(Generic[T], BaseFactory[T]):
                     name=field.name,
                     default=default_value,
                     random=cls.__random__,
-                    randomize_collection_length=cls.__randomize_collection_length__,
-                    min_collection_length=cls.__min_collection_length__,
-                    max_collection_length=cls.__max_collection_length__,
                 ),
             )
 
diff --git a/polyfactory/factories/msgspec_factory.py b/polyfactory/factories/msgspec_factory.py
index d97e222..1b579ae 100644
--- a/polyfactory/factories/msgspec_factory.py
+++ b/polyfactory/factories/msgspec_factory.py
@@ -1,13 +1,7 @@
 from __future__ import annotations
 
 from inspect import isclass
-from typing import (
-    TYPE_CHECKING,
-    Any,
-    Callable,
-    Generic,
-    TypeVar,
-)
+from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar
 
 from typing_extensions import get_type_hints
 
@@ -73,9 +67,6 @@ class MsgspecFactory(Generic[T], BaseFactory[T]):
                     name=field.name,
                     default=default_value,
                     random=cls.__random__,
-                    randomize_collection_length=cls.__randomize_collection_length__,
-                    min_collection_length=cls.__min_collection_length__,
-                    max_collection_length=cls.__max_collection_length__,
                 ),
             )
         return fields_meta
diff --git a/polyfactory/factories/pydantic_factory.py b/polyfactory/factories/pydantic_factory.py
index e7b0f67..a6549db 100644
--- a/polyfactory/factories/pydantic_factory.py
+++ b/polyfactory/factories/pydantic_factory.py
@@ -5,31 +5,17 @@ from datetime import timezone
 from functools import partial
 from os.path import realpath
 from pathlib import Path
-from typing import (
-    TYPE_CHECKING,
-    Any,
-    ClassVar,
-    ForwardRef,
-    Generic,
-    Mapping,
-    Tuple,
-    TypeVar,
-    cast,
-)
+from typing import TYPE_CHECKING, Any, ClassVar, ForwardRef, Generic, Mapping, Tuple, TypeVar, cast
 from uuid import NAMESPACE_DNS, uuid1, uuid3, uuid5
 
 from typing_extensions import Literal, get_args, get_origin
 
 from polyfactory.collection_extender import CollectionExtender
-from polyfactory.constants import (
-    DEFAULT_RANDOM,
-    MAX_COLLECTION_LENGTH,
-    MIN_COLLECTION_LENGTH,
-    RANDOMIZE_COLLECTION_LENGTH,
-)
+from polyfactory.constants import DEFAULT_RANDOM
 from polyfactory.exceptions import MissingDependencyException
 from polyfactory.factories.base import BaseFactory
 from polyfactory.field_meta import Constraints, FieldMeta, Null
+from polyfactory.utils.deprecation import check_for_deprecated_parameters
 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
@@ -94,9 +80,9 @@ class PydanticFieldMeta(FieldMeta):
         field_info: FieldInfo,
         use_alias: bool,
         random: Random | None,
-        randomize_collection_length: bool = RANDOMIZE_COLLECTION_LENGTH,
-        min_collection_length: int = MIN_COLLECTION_LENGTH,
-        max_collection_length: int = MAX_COLLECTION_LENGTH,
+        randomize_collection_length: bool | None = None,
+        min_collection_length: int | None = None,
+        max_collection_length: int | None = None,
     ) -> PydanticFieldMeta:
         """Create an instance from a pydantic field info.
 
@@ -110,6 +96,14 @@ class PydanticFieldMeta(FieldMeta):
 
         :returns: A PydanticFieldMeta instance.
         """
+        check_for_deprecated_parameters(
+            "2.11.0",
+            parameters=(
+                ("randomize_collection_length", randomize_collection_length),
+                ("min_collection_length", min_collection_length),
+                ("max_collection_length", max_collection_length),
+            ),
+        )
         if callable(field_info.default_factory):
             default_value = field_info.default_factory()
         else:
@@ -128,10 +122,7 @@ class PydanticFieldMeta(FieldMeta):
                 cls.from_field_info(
                     field_info=FieldInfo.from_annotation(arg),
                     field_name=field_name,
-                    max_collection_length=max_collection_length,
-                    min_collection_length=min_collection_length,
                     random=random,
-                    randomize_collection_length=randomize_collection_length,
                     use_alias=use_alias,
                 )
                 for arg in get_args(annotation)
@@ -159,11 +150,8 @@ class PydanticFieldMeta(FieldMeta):
             children=children,
             constraints=cast("Constraints", {k: v for k, v in constraints.items() if v is not None}) or None,
             default=default_value,
-            max_collection_length=max_collection_length,
-            min_collection_length=min_collection_length,
             name=name,
             random=random or DEFAULT_RANDOM,
-            randomize_collection_length=randomize_collection_length,
         )
 
     @classmethod
@@ -171,9 +159,9 @@ class PydanticFieldMeta(FieldMeta):
         cls,
         model_field: ModelField,  # pyright: ignore[reportGeneralTypeIssues]
         use_alias: bool,
-        randomize_collection_length: bool,
-        min_collection_length: int,
-        max_collection_length: int,
+        randomize_collection_length: bool | None = None,
+        min_collection_length: int | None = None,
+        max_collection_length: int | None = None,
         random: Random = DEFAULT_RANDOM,
     ) -> PydanticFieldMeta:
         """Create an instance from a pydantic model field.
@@ -187,6 +175,14 @@ class PydanticFieldMeta(FieldMeta):
         :returns: A PydanticFieldMeta instance.
 
         """
+        check_for_deprecated_parameters(
+            "2.11.0",
+            parameters=(
+                ("randomize_collection_length", randomize_collection_length),
+                ("min_collection_length", min_collection_length),
+                ("max_collection_length", max_collection_length),
+            ),
+        )
         from pydantic import AmqpDsn, AnyHttpUrl, AnyUrl, HttpUrl, KafkaDsn, PostgresDsn, RedisDsn
         from pydantic.fields import DeferredType, Undefined  # type: ignore[attr-defined]
 
@@ -251,9 +247,6 @@ class PydanticFieldMeta(FieldMeta):
 
         children: list[FieldMeta] = []
         if model_field.key_field or model_field.sub_fields:
-            number_of_args = (
-                random.randint(min_collection_length, max_collection_length) if randomize_collection_length else 1
-            )
             fields_to_iterate = (
                 ([model_field.key_field, *model_field.sub_fields])
                 if model_field.key_field is not None
@@ -271,15 +264,12 @@ class PydanticFieldMeta(FieldMeta):
             if get_origin(outer_type) in (tuple, Tuple) and get_args(outer_type)[-1] == Ellipsis:
                 # pydantic removes ellipses from Tuples in sub_fields
                 type_args += (...,)
-            extended_type_args = CollectionExtender.extend_type_args(annotation, type_args, number_of_args)
+            extended_type_args = CollectionExtender.extend_type_args(annotation, type_args, 1)
             children.extend(
                 PydanticFieldMeta.from_model_field(
                     model_field=type_arg_to_sub_field[arg],
                     use_alias=use_alias,
                     random=random,
-                    randomize_collection_length=randomize_collection_length,
-                    min_collection_length=min_collection_length,
-                    max_collection_length=max_collection_length,
                 )
                 for arg in extended_type_args
             )
@@ -335,9 +325,6 @@ class ModelFactory(Generic[T], BaseFactory[T]):
                         field,
                         use_alias=not cls.__model__.__config__.allow_population_by_field_name,  # type: ignore[attr-defined]
                         random=cls.__random__,
-                        randomize_collection_length=cls.__randomize_collection_length__,
-                        min_collection_length=cls.__min_collection_length__,
-                        max_collection_length=cls.__max_collection_length__,
                     )
                     for field in cls.__model__.__fields__.values()  # type: ignore[attr-defined]
                 ]
@@ -348,9 +335,6 @@ class ModelFactory(Generic[T], BaseFactory[T]):
                         field_name=field_name,
                         random=cls.__random__,
                         use_alias=not cls.__model__.model_config.get("populate_by_name", False),
-                        randomize_collection_length=cls.__randomize_collection_length__,
-                        min_collection_length=cls.__min_collection_length__,
-                        max_collection_length=cls.__max_collection_length__,
                     )
                     for field_name, field_info in cls.__model__.model_fields.items()
                 ]
diff --git a/polyfactory/factories/sqlalchemy_factory.py b/polyfactory/factories/sqlalchemy_factory.py
index 983c3cb..c423d0b 100644
--- a/polyfactory/factories/sqlalchemy_factory.py
+++ b/polyfactory/factories/sqlalchemy_factory.py
@@ -136,9 +136,6 @@ class SQLAlchemyFactory(Generic[T], BaseFactory[T]):
                 annotation=cls.get_type_from_column(column),
                 name=name,
                 random=cls.__random__,
-                randomize_collection_length=cls.__randomize_collection_length__,
-                min_collection_length=cls.__min_collection_length__,
-                max_collection_length=cls.__max_collection_length__,
             )
             for name, column in table.columns.items()
             if cls.should_column_be_set(column)
@@ -152,9 +149,6 @@ class SQLAlchemyFactory(Generic[T], BaseFactory[T]):
                         name=name,
                         annotation=annotation,
                         random=cls.__random__,
-                        randomize_collection_length=cls.__randomize_collection_length__,
-                        min_collection_length=cls.__min_collection_length__,
-                        max_collection_length=cls.__max_collection_length__,
                     ),
                 )
 
diff --git a/polyfactory/factories/typed_dict_factory.py b/polyfactory/factories/typed_dict_factory.py
index ec22ce2..80eb78d 100644
--- a/polyfactory/factories/typed_dict_factory.py
+++ b/polyfactory/factories/typed_dict_factory.py
@@ -41,9 +41,6 @@ class TypedDictFactory(Generic[TypedDictT], BaseFactory[TypedDictT]):
                 random=DEFAULT_RANDOM,
                 name=field_name,
                 default=getattr(cls.__model__, field_name, Null),
-                randomize_collection_length=cls.__randomize_collection_length__,
-                min_collection_length=cls.__min_collection_length__,
-                max_collection_length=cls.__max_collection_length__,
             )
             for field_name, annotation in model_type_hints.items()
         ]
diff --git a/polyfactory/field_meta.py b/polyfactory/field_meta.py
index 763caca..766a604 100644
--- a/polyfactory/field_meta.py
+++ b/polyfactory/field_meta.py
@@ -6,13 +6,8 @@ from typing import TYPE_CHECKING, Any, Literal, Pattern, TypedDict, cast
 from typing_extensions import Mapping, get_args, get_origin
 
 from polyfactory.collection_extender import CollectionExtender
-from polyfactory.constants import (
-    DEFAULT_RANDOM,
-    MAX_COLLECTION_LENGTH,
-    MIN_COLLECTION_LENGTH,
-    RANDOMIZE_COLLECTION_LENGTH,
-    TYPE_MAPPING,
-)
+from polyfactory.constants import DEFAULT_RANDOM, TYPE_MAPPING
+from polyfactory.utils.deprecation import check_for_deprecated_parameters
 from polyfactory.utils.helpers import normalize_annotation, unwrap_annotated, unwrap_args, unwrap_new_type
 from polyfactory.utils.predicates import is_annotated, is_any_annotated
 
@@ -110,9 +105,9 @@ class FieldMeta:
         name: str = "",
         default: Any = Null,
         constraints: Constraints | None = None,
-        randomize_collection_length: bool = RANDOMIZE_COLLECTION_LENGTH,
-        min_collection_length: int = MIN_COLLECTION_LENGTH,
-        max_collection_length: int = MAX_COLLECTION_LENGTH,
+        randomize_collection_length: bool | None = None,
+        min_collection_length: int | None = None,
+        max_collection_length: int | None = None,
         children: list[FieldMeta] | None = None,
     ) -> Self:
         """Builder method to create a FieldMeta from a type annotation.
@@ -128,6 +123,14 @@ class FieldMeta:
 
         :returns: A field meta instance.
         """
+        check_for_deprecated_parameters(
+            "2.11.0",
+            parameters=(
+                ("randomize_collection_length", randomize_collection_length),
+                ("min_collection_length", min_collection_length),
+                ("max_collection_length", max_collection_length),
+            ),
+        )
         field_type = normalize_annotation(annotation, random=random)
 
         if not constraints and is_annotated(annotation):
@@ -156,9 +159,6 @@ class FieldMeta:
                 FieldMeta.from_type(
                     annotation=unwrap_new_type(arg),
                     random=random,
-                    randomize_collection_length=randomize_collection_length,
-                    min_collection_length=min_collection_length,
-                    max_collection_length=max_collection_length,
                 )
                 for arg in extended_type_args
             ]
diff --git a/polyfactory/utils/deprecation.py b/polyfactory/utils/deprecation.py
new file mode 100644
index 0000000..576c4ac
--- /dev/null
+++ b/polyfactory/utils/deprecation.py
@@ -0,0 +1,149 @@
+from __future__ import annotations
+
+import inspect
+from functools import wraps
+from typing import Any, Callable, Literal, TypeVar
+from warnings import warn
+
+from typing_extensions import ParamSpec
+
+__all__ = ("deprecated", "warn_deprecation", "check_for_deprecated_parameters")
+
+
+T = TypeVar("T")
+P = ParamSpec("P")
+DeprecatedKind = Literal["function", "method", "classmethod", "attribute", "property", "class", "parameter", "import"]
+
+
+def warn_deprecation(
+    version: str,
+    deprecated_name: str,
+    kind: DeprecatedKind,
+    *,
+    removal_in: str | None = None,
+    alternative: str | None = None,
+    info: str | None = None,
+    pending: bool = False,
+) -> None:
+    """Warn about a call to a (soon to be) deprecated function.
+
+    Args:
+        version: Polyfactory version where the deprecation will occur.
+        deprecated_name: Name of the deprecated function.
+        removal_in: Polyfactory version where the deprecated function will be removed.
+        alternative: Name of a function that should be used instead.
+        info: Additional information.
+        pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``.
+        kind: Type of the deprecated thing.
+    """
+    parts = []
+
+    if kind == "import":
+        access_type = "Import of"
+    elif kind in {"function", "method"}:
+        access_type = "Call to"
+    else:
+        access_type = "Use of"
+
+    if pending:
+        parts.append(f"{access_type} {kind} awaiting deprecation {deprecated_name!r}")
+    else:
+        parts.append(f"{access_type} deprecated {kind} {deprecated_name!r}")
+
+    parts.extend(
+        (
+            f"Deprecated in polyfactory {version}",
+            f"This {kind} will be removed in {removal_in or 'the next major version'}",
+        )  # noqa: COM812
+    )
+    if alternative:
+        parts.append(f"Use {alternative!r} instead")
+
+    if info:
+        parts.append(info)
+
+    text = ". ".join(parts)
+    warning_class = PendingDeprecationWarning if pending else DeprecationWarning
+
+    warn(text, warning_class, stacklevel=2)
+
+
+def deprecated(
+    version: str,
+    *,
+    removal_in: str | None = None,
+    alternative: str | None = None,
+    info: str | None = None,
+    pending: bool = False,
+    kind: Literal["function", "method", "classmethod", "property"] | None = None,
+) -> Callable[[Callable[P, T]], Callable[P, T]]:
+    """Create a decorator wrapping a function, method or property with a warning call about a (pending) deprecation.
+
+    Args:
+        version: Polyfactory version where the deprecation will occur.
+        removal_in: Polyfactory version where the deprecated function will be removed.
+        alternative: Name of a function that should be used instead.
+        info: Additional information.
+        pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``.
+        kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure
+            out if it's a function or method.
+
+    Returns:
+        A decorator wrapping the function call with a warning
+    """
+
+    def decorator(func: Callable[P, T]) -> Callable[P, T]:
+        @wraps(func)
+        def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
+            warn_deprecation(
+                version=version,
+                deprecated_name=func.__name__,
+                info=info,
+                alternative=alternative,
+                pending=pending,
+                removal_in=removal_in,
+                kind=kind or ("method" if inspect.ismethod(func) else "function"),
+            )
+            return func(*args, **kwargs)
+
+        return wrapped
+
+    return decorator
+
+
+def check_for_deprecated_parameters(
+    version: str,
+    *,
+    parameters: tuple[tuple[str, Any], ...],
+    default_value: Any = None,
+    removal_in: str | None = None,
+    alternative: str | None = None,
+    info: str | None = None,
+    pending: bool = False,
+) -> None:
+    """Warn about a call to a (soon to be) deprecated argument to a function.
+
+    Args:
+        version: Polyfactory version where the deprecation will occur.
+        parameters: Parameters to trigger warning if used.
+        default_value: Default value for parameter to detect if supplied or not.
+        removal_in: Polyfactory version where the deprecated function will be removed.
+        alternative: Name of a function that should be used instead.
+        info: Additional information.
+        pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``.
+        kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure
+            out if it's a function or method.
+    """
+    for parameter_name, value in parameters:
+        if value == default_value:
+            continue
+
+        warn_deprecation(
+            version=version,
+            deprecated_name=parameter_name,
+            info=info,
+            alternative=alternative,
+            pending=pending,
+            removal_in=removal_in,
+            kind="parameter",
+        )

test_patch: diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py
new file mode 100644
index 0000000..3abed14
--- /dev/null
+++ b/tests/utils/test_deprecation.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+import pytest
+
+from polyfactory.utils.deprecation import check_for_deprecated_parameters
+
+
+def test_parameter_deprecation() -> None:
+    def my_func(a: int, b: int | None = None) -> None:
+        check_for_deprecated_parameters("5", parameters=(("b", b),))
+
+    with pytest.warns(
+        DeprecationWarning,
+        match="Use of deprecated parameter 'b'. Deprecated in polyfactory 5. This parameter will be removed in the next major version",
+    ):
+        my_func(1, 2)


----

problem_statement: Bug: Required fields in TypedDicts throw exceptions
### Description

Hello, thanks for this super useful library! I was trying out polyfactory with some TypedDicts and it throws an exception. Not sure if this should be classified as a bug or a feature request, but wanted to bring it to your attention in any case.

### URL to code causing the issue

_No response_

### MCVE

```python
from typing_extensions import TypedDict, Required
from polyfactory.factories import TypedDictFactory

class Foo(TypedDict, total=False):
    name: Required[str]
    bar: str

class FooFactory(TypedDictFactory[Foo]):
    __model__ = Foo
```


### Steps to reproduce

```bash
Run `FooFactory.build()`, and I get the exception: `TypeError: Required accepts only a single type. Got (<class 'str'>,).`
```


### Screenshots

_No response_

### Logs

_No response_

### Release Version

Version: 2.10.0

### Platform

- [X] Linux
- [ ] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh Litestar dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

<a href="https://polar.sh/litestar-org/polyfactory/issues/421">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/polyfactory/issues/421/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/polyfactory/issues/421/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->

date: 2023-10-21
patch: diff --git a/polyfactory/factories/typed_dict_factory.py b/polyfactory/factories/typed_dict_factory.py
index 80eb78d..2a3ea1b 100644
--- a/polyfactory/factories/typed_dict_factory.py
+++ b/polyfactory/factories/typed_dict_factory.py
@@ -1,8 +1,16 @@
 from __future__ import annotations
 
-from typing import Any, Generic, TypeVar
-
-from typing_extensions import TypeGuard, _TypedDictMeta, get_type_hints, is_typeddict  # type: ignore[attr-defined]
+from typing import Any, Generic, TypeVar, get_args
+
+from typing_extensions import (  # type: ignore[attr-defined]
+    NotRequired,
+    Required,
+    TypeGuard,
+    _TypedDictMeta,  # pyright: ignore[reportGeneralTypeIssues]
+    get_origin,
+    get_type_hints,
+    is_typeddict,
+)
 
 from polyfactory.constants import DEFAULT_RANDOM
 from polyfactory.factories.base import BaseFactory
@@ -35,13 +43,19 @@ class TypedDictFactory(Generic[TypedDictT], BaseFactory[TypedDictT]):
         """
         model_type_hints = get_type_hints(cls.__model__, include_extras=True)
 
-        fields_meta: list["FieldMeta"] = [
-            FieldMeta.from_type(
-                annotation=annotation,
-                random=DEFAULT_RANDOM,
-                name=field_name,
-                default=getattr(cls.__model__, field_name, Null),
+        field_metas: list[FieldMeta] = []
+        for field_name, annotation in model_type_hints.items():
+            origin = get_origin(annotation)
+            if origin in (Required, NotRequired):
+                annotation = get_args(annotation)[0]  # noqa: PLW2901
+
+            field_metas.append(
+                FieldMeta.from_type(
+                    annotation=annotation,
+                    random=DEFAULT_RANDOM,
+                    name=field_name,
+                    default=getattr(cls.__model__, field_name, Null),
+                ),
             )
-            for field_name, annotation in model_type_hints.items()
-        ]
-        return fields_meta
+
+        return field_metas

test_patch: diff --git a/tests/constraints/test_int_constraints.py b/tests/constraints/test_int_constraints.py
index 7a963d8..67edb30 100644
--- a/tests/constraints/test_int_constraints.py
+++ b/tests/constraints/test_int_constraints.py
@@ -1,7 +1,7 @@
 from random import Random
 
 import pytest
-from hypothesis import assume, given
+from hypothesis import HealthCheck, assume, given, settings
 from hypothesis.strategies import integers
 
 from polyfactory.exceptions import ParameterException
@@ -55,6 +55,7 @@ def test_handle_constrained_int_handles_lt(maximum: int) -> None:
     assert result < maximum
 
 
+@settings(suppress_health_check=(HealthCheck.filter_too_much,))
 @given(
     integers(min_value=1, max_value=10),
     integers(min_value=1, max_value=10),
diff --git a/tests/test_typeddict_factory.py b/tests/test_typeddict_factory.py
index 9d3a313..c9a3267 100644
--- a/tests/test_typeddict_factory.py
+++ b/tests/test_typeddict_factory.py
@@ -1,7 +1,8 @@
 from typing import Dict, List, Optional
 
+from annotated_types import Ge
 from pydantic import BaseModel
-from typing_extensions import TypedDict
+from typing_extensions import Annotated, NotRequired, Required, TypedDict
 
 from polyfactory.factories import TypedDictFactory
 from polyfactory.factories.pydantic_factory import ModelFactory
@@ -44,3 +45,25 @@ def test_factory_model_with_typeddict_attribute_value() -> None:
     assert result.td["name"]
     assert result.td["list_field"][0]
     assert type(result.td["int_field"]) in (type(None), int)
+
+
+def test_typeddict_with_required_and_non_required_fields() -> None:
+    class TypedDictModel(TypedDict):
+        id: Required[int]
+        name: NotRequired[str]
+        annotated: Required[Annotated[int, Ge(100)]]
+        list_field: List[Dict[str, int]]
+        optional_int: Required[Optional[int]]
+
+    class TypedDictModelFactory(TypedDictFactory[TypedDictModel]):
+        __model__ = TypedDictModel
+
+    result = TypedDictModelFactory.build()
+
+    assert isinstance(result["id"], int)
+    assert isinstance(result["annotated"], int)
+    assert result["annotated"] >= 100
+    assert isinstance(result["list_field"], list)
+    assert isinstance(result["optional_int"], (type(None), int))
+    assert "name" in result
+    assert isinstance(result["name"], str)


----

problem_statement: Bug: Can't consistently override optional fields with nested models
### Description

When trying to build a nested set of models, if one of them is optional / can be `None`, then there's a chance the factory will assign `None` to the corresponding field even if an explicit value is being passed when calling `build` (see example).

### URL to code causing the issue

_No response_

### MCVE

```python
from dataclasses import dataclass
from polyfactory.factories import DataclassFactory

@dataclass
class Bar:
    baz: str

@dataclass
class Foo:
    bar: Bar | None

class FooFactory(DataclassFactory):
    __model__ = Foo

for _ in range(1000):
    foo = FooFactory.build(bar={'baz': 'TEST'})
    # foo.bar should consistently be present
    assert foo.bar is not None
```


### Steps to reproduce

_No response_

### Screenshots

_No response_

### Logs

_No response_

### Release Version

2.10.0

### Platform

- [ ] Linux
- [X] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh Litestar dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

<a href="https://polar.sh/litestar-org/polyfactory/issues/419">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/polyfactory/issues/419/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/polyfactory/issues/419/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->

date: 2023-10-21
patch: diff --git a/polyfactory/factories/base.py b/polyfactory/factories/base.py
index aecb9cf..67ff305 100644
--- a/polyfactory/factories/base.py
+++ b/polyfactory/factories/base.py
@@ -559,7 +559,7 @@ class BaseFactory(ABC, Generic[T]):
         if cls.is_ignored_type(field_meta.annotation):
             return None
 
-        if cls.should_set_none_value(field_meta=field_meta):
+        if field_build_parameters is None and cls.should_set_none_value(field_meta=field_meta):
             return None
 
         unwrapped_annotation = unwrap_annotation(field_meta.annotation, random=cls.__random__)

test_patch: diff --git a/tests/test_passing_build_args_to_child_factories.py b/tests/test_passing_build_args_to_child_factories.py
index 50cfeb2..1ba4ff2 100644
--- a/tests/test_passing_build_args_to_child_factories.py
+++ b/tests/test_passing_build_args_to_child_factories.py
@@ -3,6 +3,7 @@ from typing import List, Mapping, Optional
 from pydantic import BaseModel
 
 from polyfactory.factories.pydantic_factory import ModelFactory
+from polyfactory.field_meta import FieldMeta
 
 
 class Address(BaseModel):
@@ -176,3 +177,22 @@ def test_factory_with_partial_kwargs_deep_in_tree() -> None:
     build_result = DFactory.build(factory_use_construct=False, **{"c": {"b": {"a": {"name": "test"}}}})
     assert build_result
     assert build_result.c.b.a.name == "test"
+
+
+def test_factory_with_nested_optional_field_overrides_in_dict() -> None:
+    class MyChildModel(BaseModel):
+        name: str
+
+    class MyParentModel(BaseModel):
+        child: Optional[MyChildModel]
+
+    class MyParentModelFactory(ModelFactory):
+        __model__ = MyParentModel
+
+        @classmethod
+        def should_set_none_value(cls, field_meta: FieldMeta) -> bool:
+            return True
+
+    result = MyParentModelFactory.build(child={"name": "test"})
+    assert result.child is not None
+    assert result.child.name == "test"