Defining Factory Fields
The factory api is designed to be as semantic and simple as possible, lets look at several examples that assume we have the following models:
from datetime import date, datetime
from enum import Enum
from pydantic import BaseModel, UUID4
from typing import Any, Dict, List, Union
from pydantic_factories import ModelFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
class Pet(BaseModel):
name: str
species: Species
class Person(BaseModel):
id: UUID4
name: str
hobbies: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
pets: List[Pet]
assets: List[Dict[str, Dict[str, Any]]]
pet = Pet(name="Roxy", sound="woof woof", species=Species.DOG)
class PersonFactory(ModelFactory):
__model__ = Person
pets = [pet]
In this case when we call PersonFactory.build()
the result will be randomly generated, except the pets list, which
will be the hardcoded default we defined.
Use
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 datetime import date, datetime
from pydantic import BaseModel, UUID4
from typing import Any, Dict, List, Union
from enum import Enum
from pydantic_factories import ModelFactory, Use
from random import choice
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
class Pet(BaseModel):
name: str
species: Species
class Person(BaseModel):
id: UUID4
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(ModelFactory):
__model__ = Pet
name = Use(choice, ["Ralph", "Roxy"])
species = Use(choice, list(Species))
class PersonFactory(ModelFactory):
__model__ = Person
pets = Use(PetFactory.batch, size=2)
The signature for use is: cb: Callable, *args, **defaults
, it can receive any sync callable. In the above example, we
used the choice
function from the standard library's random
package, and the batch method of PetFactory
.
You do not need to use the Use
field, you can place callables (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 enum import Enum
from pydantic import BaseModel
from random import choice
from pydantic_factories import ModelFactory
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
class Pet(BaseModel):
name: str
species: Species
class PetFactory(ModelFactory):
__model__ = Pet
name = lambda: choice(["Ralph", "Roxy"]) # noqa: E731
species = lambda: choice(list(Species)) # noqa: E731
Use
is merely a semantic abstraction that makes the factory cleaner and simpler to understand.
Global factory registration
Sometimes you want to alter how a model is built by default. It is especially useful for a model that is used a lot across the project. In this case updating attributes to reference specific factory everywhere can be quite cumbersome. Instead
you can rely on auto registering models by setting the __auto_register__
attribute`.
from datetime import date, datetime
from pydantic import BaseModel, UUID4
from typing import Any, Dict, List, Union
from enum import Enum
from pydantic_factories import ModelFactory
from random import choice
class Species(str, Enum):
CAT = "Cat"
DOG = "Dog"
class Pet(BaseModel):
name: str
species: Species
class Person(BaseModel):
id: UUID4
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(ModelFactory):
__model__ = Pet
__auto_register__ = True
name = lambda: choice(["Ralph", "Roxy"]) # noqa: E731
species = Species.DOG
class PersonFactory(ModelFactory):
__model__ = Person
Here if we call PersonFactory.build()
the result will be randomly generated except the pet list which will
contain a dog with the name Ralph
or Roxy
. Notice that in this case we didn't have to define the pets
attribute in the PersonFactory
because we have registered PetFactory
as the default factory for the Pet
model.
PostGenerated
It 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 pydantic import BaseModel
from pydantic_factories import ModelFactory, PostGenerated
from random import randint
from datetime import datetime, timedelta
def add_timedelta(name: str, values: dict, *args, **kwds):
delta = timedelta(days=randint(0, 12), seconds=randint(13, 13000))
return values["from_dt"] + delta
class MyModel(BaseModel):
from_dt: datetime
to_dt: datetime
class MyFactory(ModelFactory):
__model__ = MyModel
to_dt = PostGenerated(add_timedelta)
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.
Ignore
Ignore
is another field exported by this library, and its used - as its name implies - to designate a given attribute
as ignored:
from typing import TypeVar
from odmantic import EmbeddedModel, Model
from pydantic_factories import ModelFactory, Ignore
T = TypeVar("T", Model, EmbeddedModel)
class OdmanticModelFactory(ModelFactory[T]):
id = Ignore()
The above example is basically the extension included in pydantic-factories
for the
library ODMantic, which is a pydantic based mongo ODM.
For ODMantic models, the id
attribute should not be set by the factory, but rather handled by the odmantic logic
itself. Thus, the id
field is marked as ignored.
When you ignore an attribute using Ignore
, it will be completely ignored by the factory - that is, it will not be set
as a kwarg passed to pydantic at all.
Require
The Require
field in turn specifies that a particular attribute is a required kwarg. That is, if a kwarg with a
value for this particular attribute is not passed when calling factory.build()
, a MissingBuildKwargError
will be
raised.
What is the use case for this? For example, lets say we have a document called Article
which we store in some DB and
is represented using a non-pydantic model, say, an elastic-dsl
document. We then need to store in our pydantic object
a reference to an id for this article. This value should not be some mock value, but must rather be an actual id passed
to the factory. Thus, we can define this attribute as required:
from pydantic import BaseModel
from pydantic_factories import ModelFactory, Require
from uuid import UUID
class ArticleProxy(BaseModel):
article_id: UUID
...
class ArticleProxyFactory(ModelFactory):
__model__ = ArticleProxy
article_id = Require()
If we call factory.build()
without passing a value for article_id, an error will be raised.
Fixture
The Fixture
field is a special field meant to be used with factories that have been decorated using
register_fixture. For example:
from typing import Optional, List, Union
from datetime import datetime, date
from pydantic import BaseModel, UUID4
from pydantic_factories import ModelFactory, Fixture
from pydantic_factories.plugins.pytest_plugin import register_fixture
class Person(BaseModel):
id: UUID4
name: str
hobbies: Optional[List[str]]
nicks: List[str]
age: Union[float, int]
birthday: Union[datetime, date]
class ClassRoom(BaseModel):
teacher: Person
pupils: List[Person]
@register_fixture(name="my_fixture")
class PersonFactory(ModelFactory):
__model__ = Person
class ClassRoomFactory(ModelFactory):
teacher = Fixture(PersonFactory, name="Jenny Osterman")
pupils = Fixture(PersonFactory, size=20)
If we tried to use PersonFactory
now normally it wouldn't work because pytest fixtures can only be called by pytest.
Thus we can use Fixture
. As you can see above, this field can accept kwargs that are passed to the factory's
underlying build or batch methods, and an optional size
kwarg. If size
is given, than a batch is returned, otherwise
the normal build method is used.