Skip to content

Commit a58a50a

Browse files
committed
feat: add NeverNone to always generate an optional field
1 parent e0c6941 commit a58a50a

File tree

3 files changed

+20
-5
lines changed

3 files changed

+20
-5
lines changed

polyfactory/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .exceptions import ConfigurationException
22
from .factories import BaseFactory
3-
from .fields import Fixture, Ignore, PostGenerated, Require, Use
3+
from .fields import Fixture, Ignore, PostGenerated, Require, Use, NeverNone
44
from .persistence import AsyncPersistenceProtocol, SyncPersistenceProtocol
55

66
__all__ = (
@@ -11,6 +11,7 @@
1111
"Ignore",
1212
"PostGenerated",
1313
"Require",
14+
"NeverNone",
1415
"SyncPersistenceProtocol",
1516
"Use",
1617
)

polyfactory/factories/base.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
)
5555
from polyfactory.exceptions import ConfigurationException, MissingBuildKwargException, ParameterException
5656
from polyfactory.field_meta import Null
57-
from polyfactory.fields import Fixture, Ignore, PostGenerated, Require, Use
57+
from polyfactory.fields import Fixture, Ignore, NeverNone, PostGenerated, Require, Use
5858
from polyfactory.utils.helpers import (
5959
flatten_annotation,
6060
get_collection_type,
@@ -334,6 +334,7 @@ def _handle_factory_field( # noqa: PLR0911
334334
if isinstance(field_value, Fixture):
335335
return field_value.to_value()
336336

337+
# if a raw lambda is passed, invoke it
337338
if callable(field_value):
338339
return field_value()
339340

@@ -942,8 +943,12 @@ def should_set_none_value(cls, field_meta: FieldMeta) -> bool:
942943
:returns: A boolean determining whether 'None' should be set for the given field_meta.
943944
944945
"""
946+
field_value = hasattr(cls, field_meta.name) and getattr(cls, field_meta.name)
947+
never_none = field_value and isinstance(field_value, NeverNone)
948+
945949
return (
946950
cls.__allow_none_optionals__
951+
and not never_none
947952
and is_optional(field_meta.annotation)
948953
and create_random_boolean(random=cls.__random__)
949954
)
@@ -1017,13 +1022,15 @@ def _check_declared_fields_exist_in_model(cls) -> None:
10171022
f"{field_name} is declared on the factory {cls.__name__}"
10181023
f" but it is not part of the model {cls.__model__.__name__}"
10191024
)
1020-
if isinstance(field_value, (Use, PostGenerated, Ignore, Require)):
1025+
if isinstance(field_value, (Use, PostGenerated, Ignore, Require, NeverNone)):
10211026
raise ConfigurationException(error_message)
10221027

10231028
@classmethod
10241029
def process_kwargs(cls, **kwargs: Any) -> dict[str, Any]:
10251030
"""Process the given kwargs and generate values for the factory's model.
10261031
1032+
If you need to deeply customize field values, you'll want to override this method.
1033+
10271034
:param kwargs: Any build kwargs.
10281035
10291036
:returns: A dictionary of build results.
@@ -1034,8 +1041,11 @@ def process_kwargs(cls, **kwargs: Any) -> dict[str, Any]:
10341041
for field_meta in cls.get_model_fields():
10351042
field_build_parameters = cls.extract_field_build_parameters(field_meta=field_meta, build_args=kwargs)
10361043
if cls.should_set_field_value(field_meta, **kwargs) and not cls.should_use_default_value(field_meta):
1037-
if hasattr(cls, field_meta.name) and not hasattr(BaseFactory, field_meta.name):
1038-
field_value = getattr(cls, field_meta.name)
1044+
field_value = getattr(cls, field_meta.name, None)
1045+
1046+
# TODO why do we need the BaseFactory check here, only dunder methods which are ignored would trigger this?
1047+
# NeverNone should be treated as a normally-generated field
1048+
if field_value and not hasattr(BaseFactory, field_meta.name) and not isinstance(field_value, NeverNone):
10391049
if isinstance(field_value, Ignore):
10401050
continue
10411051

polyfactory/fields.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class Require:
2222
"""A factory field that marks an attribute as a required build-time kwarg."""
2323

2424

25+
class NeverNone:
26+
"""A factory field that marks as always generated, even if it's an optional"""
27+
28+
2529
class Ignore:
2630
"""A factory field that marks an attribute as ignored."""
2731

0 commit comments

Comments
 (0)