Repository: litestar-org/polyfactory (python)

Issue:
Use defaults values from BaseModel in ModelFactory
### Summary

I think it would be great if `ModelFactory` can use defaults values from `BaseModel`

### Basic Example

For example I have class `AppSettings`, where I set `app_title` as APP_TITLE by default
```
from pydantic import BaseModel, Field
from polyfactory.factories.pydantic_factory import ModelFactory

APP_TITLE = 'title'


class AppSettings(BaseModel):
    app_title: str = Field(APP_TITLE)

    some_another_field: int
 ```

And now I can specify `app_title` as `APP_TITLE` in `ModelFactory` if only I set it model
```
class AppSettingsFactory(ModelFactory):
    __model__ = AppSettings
    app_title = APP_TITLE
```

So I think it would be great to have an extra param for using defaults in `ModelFactory`. Maybe something like this
```
class AppSettingsFactory(ModelFactory):
    __model__ = AppSettings
    __use_defaults__ = True
```

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


Pull request description:
feat: allow use of the default value based on configuration
### Description
<!--
Please describe your pull request for new release changelog purposes
-->

- This PR allows users to configure whether to use the default value for a field or not based on the `__use_defaults__` field on the class.
- Users can also have more fine-grained (i.e. per field) control by overriding `BaseFactory.should_use_default_value` classmethod.

### Close Issue(s)

- Closes #466.


Patch:
diff --git a/docs/usage/configuration.rst b/docs/usage/configuration.rst
index d38d53c..2905556 100644
--- a/docs/usage/configuration.rst
+++ b/docs/usage/configuration.rst
@@ -92,7 +92,7 @@ Set of fields that allow you to generate a collection with random lengths. By de
     :language: python
 
 Allow None Optionals
-----------------------------
+--------------------
 
 Allow `None` to be generated as a value for types marked as optional. When set to `True`, the outputted value will be randomly chosen between `None` and other allowed types. By default, this is set to `True`.
 
@@ -113,3 +113,20 @@ Any other field definition will not be checked.
 .. literalinclude:: /examples/configuration/test_example_8.py
     :caption: Enable Check Factory Fields
     :language: python
+
+Use Default Values
+------------------
+
+If ``__use_default__`` is set to ``True``, then the default value will be used instead of creating a random value
+for a given field, provided there's a default value for that field.
+
+By default, ``__use_default__`` is set to ``False.`` If you need more fine grained control, you can override the
+:meth:`~polyfactory.factories.base.BaseFactory.should_use_default_value` classmethod.
+
+.. note::
+    Setting ``__use_default__`` has no effect for ``TypedDictFactory`` since you cannot set default values for
+    ``TypedDict``.
+
+.. literalinclude:: /examples/configuration/test_example_9.py
+    :caption: Use Default Values
+    :language: python
diff --git a/polyfactory/factories/base.py b/polyfactory/factories/base.py
index 242b73c..3893b7b 100644
--- a/polyfactory/factories/base.py
+++ b/polyfactory/factories/base.py
@@ -23,6 +23,8 @@ from os.path import realpath
 from pathlib import Path
 from random import Random
 
+from polyfactory.field_meta import Null
+
 try:
     from types import NoneType
 except ImportError:
@@ -167,6 +169,10 @@ class BaseFactory(ABC, Generic[T]):
     """
     An integer value that defines maximum length of a collection.
     """
+    __use_defaults__: ClassVar[bool] = False
+    """
+    Flag indicating whether to use the default value on a specific field, if provided.
+    """
 
     # cached attributes
     _fields_metadata: list[FieldMeta]
@@ -774,6 +780,20 @@ class BaseFactory(ABC, Generic[T]):
             and create_random_boolean(random=cls.__random__)
         )
 
+    @classmethod
+    def should_use_default_value(cls, field_meta: FieldMeta) -> bool:
+        """Determine whether to use the default value for the given field.
+
+        :param field_meta: FieldMeta instance.
+
+        :notes:
+            - This method is distinct to allow overriding.
+
+        :returns: A boolean determining whether the default value should be used for the given field_meta.
+
+        """
+        return cls.__use_defaults__ and field_meta.default is not Null
+
     @classmethod
     def should_set_field_value(cls, field_meta: FieldMeta, **kwargs: Any) -> bool:
         """Determine whether to set a value for a given field_name.
@@ -846,7 +866,7 @@ class BaseFactory(ABC, Generic[T]):
 
         for field_meta in cls.get_model_fields():
             field_build_parameters = cls.extract_field_build_parameters(field_meta=field_meta, build_args=kwargs)
-            if cls.should_set_field_value(field_meta, **kwargs):
+            if cls.should_set_field_value(field_meta, **kwargs) and not cls.should_use_default_value(field_meta):
                 if hasattr(cls, field_meta.name) and not hasattr(BaseFactory, field_meta.name):
                     field_value = getattr(cls, field_meta.name)
                     if isinstance(field_value, Ignore):


Test patch:
diff --git a/docs/examples/configuration/test_example_9.py b/docs/examples/configuration/test_example_9.py
new file mode 100644
index 0000000..50371cd
--- /dev/null
+++ b/docs/examples/configuration/test_example_9.py
@@ -0,0 +1,28 @@
+from dataclasses import dataclass
+from enum import Enum
+
+from polyfactory.factories import DataclassFactory
+
+
+class Species(str, Enum):
+    CAT = "Cat"
+    DOG = "Dog"
+
+
+@dataclass
+class Pet:
+    name: str
+    sound: str = "meow"
+    species: Species = Species.CAT
+
+
+class PetFactory(DataclassFactory[Pet]):
+    __model__ = Pet
+    __use_defaults__ = True
+
+
+def test_use_default() -> None:
+    pet = PetFactory.build()
+
+    assert pet.species == Species.CAT
+    assert pet.sound == "meow"
diff --git a/tests/test_attrs_factory.py b/tests/test_attrs_factory.py
index fbbae97..be5b8d6 100644
--- a/tests/test_attrs_factory.py
+++ b/tests/test_attrs_factory.py
@@ -191,3 +191,31 @@ def test_with_init_false() -> None:
         __model__ = Foo
 
     assert FooFactory.build()
+
+
+def test_use_default_with_callable_default() -> None:
+    @define
+    class Foo:
+        default_field: int = attrs.field(default=attrs.Factory(lambda: 10, takes_self=False))
+
+    class FooFactory(AttrsFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
+
+
+def test_use_default_with_non_callable_default() -> None:
+    @define
+    class Foo:
+        default_field: int = attrs.field(default=10)
+
+    class FooFactory(AttrsFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
diff --git a/tests/test_dataclass_factory.py b/tests/test_dataclass_factory.py
index eb06199..6dd7e1b 100644
--- a/tests/test_dataclass_factory.py
+++ b/tests/test_dataclass_factory.py
@@ -1,3 +1,4 @@
+import dataclasses
 from dataclasses import dataclass as vanilla_dataclass
 from dataclasses import field
 from types import ModuleType
@@ -205,3 +206,31 @@ def test_dataclass_with_init_false() -> None:
     result = MyFactory.build()
 
     assert result
+
+
+def test_use_default_with_callable_default() -> None:
+    @vanilla_dataclass
+    class Foo:
+        default_field: int = dataclasses.field(default_factory=lambda: 10)
+
+    class FooFactory(DataclassFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
+
+
+def test_use_default_with_non_callable_default() -> None:
+    @vanilla_dataclass
+    class Foo:
+        default_field: int = 10
+
+    class FooFactory(DataclassFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
diff --git a/tests/test_msgspec_factory.py b/tests/test_msgspec_factory.py
index 02ebe60..de2f981 100644
--- a/tests/test_msgspec_factory.py
+++ b/tests/test_msgspec_factory.py
@@ -249,3 +249,29 @@ def test_mapping_with_constrained_item_types() -> None:
     validated_foo = msgspec.convert(structs.asdict(foo), Foo)
 
     assert validated_foo == foo
+
+
+def test_use_default_with_callable_default() -> None:
+    class Foo(Struct):
+        default_field: int = msgspec.field(default_factory=lambda: 10)
+
+    class FooFactory(MsgspecFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
+
+
+def test_use_default_with_non_callable_default() -> None:
+    class Foo(Struct):
+        default_field: int = 10
+
+    class FooFactory(MsgspecFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
diff --git a/tests/test_pydantic_factory.py b/tests/test_pydantic_factory.py
index b15a7d7..f04fb1f 100644
--- a/tests/test_pydantic_factory.py
+++ b/tests/test_pydantic_factory.py
@@ -107,3 +107,29 @@ def test_mapping_with_annotated_item_types() -> None:
         __model__ = Foo
 
     assert FooFactory.build()
+
+
+def test_use_default_with_callable_default() -> None:
+    class Foo(BaseModel):
+        default_field: int = Field(default_factory=lambda: 10)
+
+    class FooFactory(ModelFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10
+
+
+def test_use_default_with_non_callable_default() -> None:
+    class Foo(BaseModel):
+        default_field: int = Field(default=10)
+
+    class FooFactory(ModelFactory[Foo]):
+        __model__ = Foo
+        __use_defaults__ = True
+
+    foo = FooFactory.build()
+
+    assert foo.default_field == 10