Handling Custom Types#
Sometimes you need to handle custom types, either from 3rd party libraries or from your own codebase. To achieve this, you can extend what’s referred as the providers_map:
from dataclasses import dataclass
from typing import Any, Dict, Type
from uuid import UUID
from polyfactory.factories import DataclassFactory
# we created a special class we will use in our code
class CustomSecret:
def __init__(self, value: str) -> None:
self.value = value
def __repr__(self) -> str:
return "*" * len(self.value)
def __str__(self) -> str:
return "*" * len(self.value)
@dataclass
class Person:
id: UUID
secret: CustomSecret
# by default the factory class cannot handle unknown types,
# so we need to override the provider map to add it:
class PersonFactory(DataclassFactory[Person]):
@classmethod
def get_provider_map(cls) -> Dict[Type, Any]:
providers_map = super().get_provider_map()
return {
CustomSecret: lambda: CustomSecret("jeronimo"),
**providers_map,
}
def test_custom_secret_creation() -> None:
person_instance = PersonFactory.build()
assert isinstance(person_instance.secret, CustomSecret)
assert repr(person_instance.secret) == "*" * len("jeronimo")
assert str(person_instance.secret) == "*" * len("jeronimo")
assert person_instance.secret.value == "jeronimo"
In the above example we override the get_provider_map class method, which maps types to callables. Each callable in the map returns an appropriate mock value for the type. In the above example, an instance of CustomSecret with the hardcoded string ‘jeronimo’.
Creating Custom Base Factories#
The above works great when you need to do this in a localised fashion, but if you need to use some custom types in many places it will lead to unnecessary duplication. The solution for this is to create a custom base factory, in this case for handling dataclasses:
from dataclasses import dataclass
from typing import Any, Dict, Generic, Type, TypeVar
from uuid import UUID
from polyfactory.factories import DataclassFactory
# we created a special class we will use in our code
class CustomSecret:
def __init__(self, value: str) -> None:
self.value = value
def __repr__(self) -> str:
return "*" * len(self.value)
def __str__(self) -> str:
return "*" * len(self.value)
T = TypeVar("T")
# we create a custom base factory to handle dataclasses, with an extended provider map
class CustomDataclassFactory(Generic[T], DataclassFactory[T]):
__is_base_factory__ = True
@classmethod
def get_provider_map(cls) -> Dict[Type, Any]:
providers_map = super().get_provider_map()
return {
CustomSecret: lambda: CustomSecret("jeronimo"),
**providers_map,
}
@dataclass
class Person:
id: UUID
secret: CustomSecret
# we use our CustomDataclassFactory as a base for the PersonFactory
class PersonFactory(CustomDataclassFactory[Person]):
...
def test_custom_dataclass_base_factory() -> None:
person_instance = PersonFactory.build()
assert isinstance(person_instance.secret, CustomSecret)
assert repr(person_instance.secret) == "*" * len("jeronimo")
assert str(person_instance.secret) == "*" * len("jeronimo")
assert person_instance.secret.value == "jeronimo"
Note
If extra configs values are defined for custom base classes, then __config_keys__
should be extended so
that these values are correctly passed onto to concrete factories.