Factory Fields¶
The factory api is designed to be as semantic and simple as possible, and by default it requires no customization to mock data. Nonetheless, when you do need to customize and control the data being generated, polyfactory has you covered. Lets look at a few examples:
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, List, Union
from uuid import UUID
from polyfactory.factories import DataclassFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
@dataclass
class Pet:
name: str
species: Species
sound: str
@dataclass
class Person:
id: UUID
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
pets: List[Pet]
assets: List[Dict[str, Dict[str, Any]]]
pet_instance = Pet(name="Roxy", sound="woof woof", species=Species.DOG)
class PersonFactory(DataclassFactory[Person]):
pets = [pet_instance]
def test_is_pet_instance() -> None:
person_instance = PersonFactory.build()
assert len(person_instance.pets) == 1
assert person_instance.pets[0] == pet_instance
In the example above, the call to PersonFactory.build()
results in a Person
where all values are randomly
generated, except the pets
list, which will be the hardcoded value we defined.
The Use
Field¶
This though is often not desirable. We could instead, define a factory for Pet where we restrict the choices to a range we like. For example:
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, List, Union
from uuid import UUID
from polyfactory import Use
from polyfactory.factories import DataclassFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
@dataclass
class Pet:
name: str
species: Species
sound: str
@dataclass
class Person:
id: UUID
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
pets: List[Pet]
assets: List[Dict[str, Dict[str, Any]]]
class PetFactory(DataclassFactory[Pet]):
name = Use(DataclassFactory.__random__.choice, ["Ralph", "Roxy"])
species = Use(DataclassFactory.__random__.choice, list(Species))
class PersonFactory(DataclassFactory[Person]):
pets = Use(PetFactory.batch, size=2)
def test_pet_choices() -> None:
person_instance = PersonFactory.build()
assert len(person_instance.pets) == 2
assert all(pet.name in ["Ralph", "Roxy"] for pet in person_instance.pets)
The Use
class is merely a semantic abstraction that makes the factory cleaner and simpler
to understand, you can in fact use any callable (including classes) as values for a factory’s attribute directly, and
these will be invoked at build-time. Thus, you could for example re-write the above PetFactory like so:
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, List, Union
from uuid import UUID
from polyfactory import Use
from polyfactory.factories import DataclassFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
@dataclass
class Pet:
name: str
species: Species
sound: str
@dataclass
class Person:
id: UUID
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
pets: List[Pet]
assets: List[Dict[str, Dict[str, Any]]]
class PetFactory(DataclassFactory[Pet]):
name = lambda: DataclassFactory.__random__.choice(["Ralph", "Roxy"])
species = lambda: DataclassFactory.__random__.choice(list(Species))
class PersonFactory(DataclassFactory[Person]):
pets = Use(PetFactory.batch, size=2)
def test_pet_choices() -> None:
person_instance = PersonFactory.build()
assert len(person_instance.pets) == 2
assert all(pet.name in ["Ralph", "Roxy"] for pet in person_instance.pets)
Or you can use a class method, which will give you easy and nice access to the factory itself:
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, List, Union
from uuid import UUID
from polyfactory import Use
from polyfactory.factories import DataclassFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
@dataclass
class Pet:
name: str
species: Species
sound: str
@dataclass
class Person:
id: UUID
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
pets: List[Pet]
assets: List[Dict[str, Dict[str, Any]]]
class PetFactory(DataclassFactory[Pet]):
@classmethod
def name(cls) -> str:
return cls.__random__.choice(["Ralph", "Roxy"])
@classmethod
def species(cls) -> str:
return cls.__random__.choice(list(Species))
class PersonFactory(DataclassFactory[Person]):
pets = Use(PetFactory.batch, size=2)
def test_pet_choices() -> None:
person_instance = PersonFactory.build()
assert len(person_instance.pets) == 2
assert all(pet.name in ["Ralph", "Roxy"] for pet in person_instance.pets)
Note
All the above examples used DataclassFactory.__random__.choice
, and this is intentional. While you can use
random.choice
or any other function exported from the stdlib random library, the factory class has its own instance
of random.Random
attached under cls.__random__
. This instance can be affected by random seeding in several ways, e.g.
calling the factory seeding method, which will be scoped only to this instance. Thus, for consistent results when seeding
randomness, its important to use the factory random.Random
instance rather than the global one from the stdlib.
The Ignore
Field¶
Ignore
is used to designate an attribute as ignored, which means it will be completely
ignored by the factory:
from typing import TypedDict
from polyfactory import Ignore
from polyfactory.factories import TypedDictFactory
class Person(TypedDict):
id: int
name: str
class PersonFactory(TypedDictFactory[Person]):
id = Ignore()
def test_id_is_ignored() -> None:
person_instance = PersonFactory.build()
assert person_instance.get("name")
assert person_instance.get("id") is None
The Require
Field¶
The Require
class is used to designate a given attribute as a required kwarg. This means that the
factory will require passing a value for this attribute as a kwarg to the build method, or an exception will be raised:
from typing import TypedDict
import pytest
from polyfactory import Require
from polyfactory.exceptions import MissingBuildKwargException
from polyfactory.factories import TypedDictFactory
class Person(TypedDict):
id: int
name: str
class PersonFactory(TypedDictFactory[Person]):
id = Require()
def test_id_is_required() -> None:
# this will not raise an exception
person_instance = PersonFactory.build(id=1)
assert person_instance.get("name")
assert person_instance.get("id") == 1
# but when no kwarg is passed, an exception will be raised:
with pytest.raises(MissingBuildKwargException):
PersonFactory.build()
The PostGenerated
Field¶
The PostGenerated
class allows for post generating fields based on already generated
values of other (non post generated) fields. In most cases this pattern is best avoided, but for the few valid cases
the PostGenerated helper is provided. For example:
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Any, Dict
from polyfactory import PostGenerated
from polyfactory.factories import DataclassFactory
def add_timedelta(name: str, values: Dict[str, datetime], *args: Any, **kwargs: Any) -> datetime:
delta = timedelta(days=1)
return values["from_dt"] + delta
@dataclass
class DatetimeRange:
to_dt: datetime
from_dt: datetime = field(default_factory=datetime.now)
class DatetimeRangeFactory(DataclassFactory[DatetimeRange]):
to_dt = PostGenerated(add_timedelta)
def test_post_generated() -> None:
date_range_instance = DatetimeRangeFactory.build()
assert date_range_instance.to_dt == date_range_instance.from_dt + timedelta(days=1)
The signature for use is: cb: Callable, *args, **defaults
it can receive any sync callable. The signature for the
callable should be: name: str, values: dict[str, Any], *args, **defaults
. The already generated values are mapped by
name in the values dictionary.
Factories as Fields¶
Factories themselves can be used as fields. In this usage, build parameters will be passed to the declared factory.
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import Any, Dict, List, Union
from uuid import UUID
from polyfactory.factories import DataclassFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
@dataclass
class Pet:
name: str
species: Species
sound: str
@dataclass
class Person:
id: UUID
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
pet: Pet
assets: List[Dict[str, Dict[str, Any]]]
class PetFactory(DataclassFactory[Pet]):
name = lambda: DataclassFactory.__random__.choice(["Ralph", "Roxy"])
class PersonFactory(DataclassFactory[Person]):
pet = PetFactory
def test_subfactory() -> None:
person_instance = PersonFactory.build()
assert isinstance(person_instance.pet, Pet)
assert person_instance.pet.name in ["Ralph", "Roxy"]
person_instance_with_pet_name = PersonFactory.build(pet={"name": "Winston"})
assert person_instance_with_pet_name.pet.name == "Winston"