Skip to content

Commit f5a854e

Browse files
committed
feat: add NeverNone to always generate an optional field
1 parent 36395d9 commit f5a854e

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

@@ -946,8 +947,12 @@ def should_set_none_value(cls, field_meta: FieldMeta) -> bool:
946947
:returns: A boolean determining whether 'None' should be set for the given field_meta.
947948
948949
"""
950+
field_value = hasattr(cls, field_meta.name) and getattr(cls, field_meta.name)
951+
never_none = field_value and isinstance(field_value, NeverNone)
952+
949953
return (
950954
cls.__allow_none_optionals__
955+
and not never_none
951956
and is_optional(field_meta.annotation)
952957
and create_random_boolean(random=cls.__random__)
953958
)
@@ -1021,13 +1026,15 @@ def _check_declared_fields_exist_in_model(cls) -> None:
10211026
f"{field_name} is declared on the factory {cls.__name__}"
10221027
f" but it is not part of the model {cls.__model__.__name__}"
10231028
)
1024-
if isinstance(field_value, (Use, PostGenerated, Ignore, Require)):
1029+
if isinstance(field_value, (Use, PostGenerated, Ignore, Require, NeverNone)):
10251030
raise ConfigurationException(error_message)
10261031

10271032
@classmethod
10281033
def process_kwargs(cls, **kwargs: Any) -> dict[str, Any]:
10291034
"""Process the given kwargs and generate values for the factory's model.
10301035
1036+
If you need to deeply customize field values, you'll want to override this method.
1037+
10311038
:param kwargs: Any build kwargs.
10321039
10331040
:returns: A dictionary of build results.
@@ -1038,8 +1045,11 @@ def process_kwargs(cls, **kwargs: Any) -> dict[str, Any]:
10381045
for field_meta in cls.get_model_fields():
10391046
field_build_parameters = cls.extract_field_build_parameters(field_meta=field_meta, build_args=kwargs)
10401047
if cls.should_set_field_value(field_meta, **kwargs) and not cls.should_use_default_value(field_meta):
1041-
if hasattr(cls, field_meta.name) and not hasattr(BaseFactory, field_meta.name):
1042-
field_value = getattr(cls, field_meta.name)
1048+
field_value = getattr(cls, field_meta.name, None)
1049+
1050+
# TODO why do we need the BaseFactory check here, only dunder methods which are ignored would trigger this?
1051+
# NeverNone should be treated as a normally-generated field
1052+
if field_value and not hasattr(BaseFactory, field_meta.name) and not isinstance(field_value, NeverNone):
10431053
if isinstance(field_value, Ignore):
10441054
continue
10451055

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)