Source: Pydantic-factories

← Rendered

markdown source:

---
description: Pydantic-factories - создание фиктивных данных для pydantic_models
title: Pydantic-factories
tags: pydantic pip tests python
---
Эта библиотека предлагает мощные возможности создания фиктивных данных для моделей и классов данных на основе pydantic. Можно использовать с другими библиотеками, использующими pydantic в качестве основы, например SQLModel и Beanie.

```python
from datetime import date, datetime
from typing import List, Union

from pydantic import BaseModel, UUID4

from pydantic_factories import ModelFactory


class Person(BaseModel):
    id: UUID4
    name: str
    hobbies: List[str]
    age: Union[float, int]
    birthday: Union[datetime, date]


class PersonFactory(ModelFactory):
    __model__ = Person


result = PersonFactory.build()
```

Мы можем создать фиктивный объект данных, соответствующий определению модели класса Person. Это возможно из-за информации о типизации, доступной в модели pydantic и полях модели, которые используются в качестве источника истины для генерации данных. Фабрика анализирует информацию, хранящуюся в модели pydantic, и создает словарь kwargs, который передается методу init класса Person.

Возможности:

- поддерживает как встроенные, так и [[pydantic]] типы.
- поддерживает ограничения поля pydantic
- поддерживает сложные типы полей
- поддерживает настраиваемые поля модели

## Методы

Класс `ModelFactory` предоставляет два метода сборки:

`.build(**kwargs)` — строит один экземпляр модели фабрики
`.batch(size: int, **kwargs)` — построить список размером n экземпляров

```python
result = PersonFactory.build()  # a single Person instance

result = PersonFactory.batch(size=5)  # list[Person, Person, Person, Person, Person]
```

Любые kwargs, которые вы передаете в `.build`, `.batch` или любой из методов, будут иметь приоритет над любыми значениями по умолчанию, определенными в самом фабричном классе.

По умолчанию при создании класса pydantic проверяются kwargs. Чтобы избежать проверки ввода, вы можете использовать параметр `factory_use_construct`.

```python
result = PersonFactory.build(id=5)  # Raises a validation error

result = PersonFactory.build(
    factory_use_construct=True, id=5
)  # Build a Person with invalid id
```

## Поддерживаются встроенные и сконструированные типы

```python
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"
    PIG = "Pig"
    MONKEY = "Monkey"


class Pet(BaseModel):
    name: str
    sound: 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 PersonFactory(ModelFactory):
    __model__ = Person


result = PersonFactory.build()
```

Этот пример также будет работать, хотя для класса Pet не была определена фабрика, это не проблема — фабрика будет динамически сгенерирована для него на лету.

Сложная типизация под атрибутом assets немного сложнее, но фабрика сгенерирует объект python, соответствующий этой сигнатуре, поэтому пройдет проверку.

Обратите внимание: единственное, что фабрики не могут обработать, — это модели, ссылающиеся на самих себя, потому что это может привести к ошибкам рекурсии. В этом случае вам нужно будет обработать конкретное поле, установив для него значения по умолчанию.

## Датаклассы

Эта библиотека работает с любым классом, который наследует класс pydantic BaseModel, включая GenericModel и классы из сторонних библиотек, а также с классами данных — как из стандартной библиотеки python, так и из классов данных pydantic. Фактически, вы можете использовать их взаимозаменяемо.

```python
import dataclasses
from typing import Dict, List

import pydantic
from pydantic_factories import ModelFactory


@pydantic.dataclasses.dataclass
class MyPydanticDataClass:
    name: str


class MyFirstModel(pydantic.BaseModel):
    dataclass: MyPydanticDataClass


@dataclasses.dataclass()
class MyPythonDataClass:
    id: str
    complex_type: Dict[str, Dict[int, List[MyFirstModel]]]


class MySecondModel(pydantic.BaseModel):
    dataclasses: List[MyPythonDataClass]


class MyFactory(ModelFactory):
    __model__ = MySecondModel


result = MyFactory.build()
```

При создании фиктивных значений для полей с типом optional, если фабрика определена с `__allow_none_Options__ = True`, значение поля будет либо значением, либо `None` — в зависимости от случайного решения. Это работает, даже если необязательное типирование глубоко вложено, за исключением классов данных — типизация только поверхностно оценивается для классов данных, и поэтому всегда предполагается, что они требуют значения. Если вы хотите иметь значение `None`, в данном конкретном случае вы должны сделать это вручную, настроив обратный вызов Use для конкретного поля.

## Конфигурирование фабрики

Конфигурация `ModelFactory` выполняется с использованием переменных класса:

`__model__`: обязательная переменная, определяющая модель фабрики. Он принимает любой класс, который расширяет `BaseModel` pydantic, включая классы из других библиотек. Если эта переменная не установлена, будет возбуждено исключение `ConfigurationException.`

`__faker__`: необязательная переменная, указывающая настроенный пользователем экземпляр фиктивных данных. Если эта переменная не установлена, фабрика по умолчанию будет использовать ванильный фейкер.

`__sync_persistence__`: необязательная переменная, указывающая обработчик для синхронно сохраняемых данных. Если эта переменная не установлена, методы фабрики `.create_sync` и `.create_batch_sync` использовать нельзя.

`__async_persistence__`: необязательная переменная, определяющая обработчик асинхронно сохраняемых данных. Если эта переменная не задана, методы фабрики `.create_async` и `.create_batch_async` использовать нельзя.

`__allow_none_optionals__`: необязательная переменная, определяющая, должна ли фабрика случайным образом устанавливать значения `None` для необязательных полей или всегда устанавливать для них значения. Это `True` по умолчанию.

```python
from faker import Faker
from pydantic_factories import ModelFactory

from app.models import Person
from .persistence import AsyncPersistenceHandler, SyncPersistenceHandler

Faker.seed(5)
my_faker = Faker("en-EN")


class PersonFactory(ModelFactory):
    __model__ = Person
    __faker__ = my_faker
    __sync_persistence__ = SyncPersistenceHandler
    __async_persistence__ = AsyncPersistenceHandler
    __allow_none_optionals__ = False
    ...
```

Для генерации детерменистических данных используйте метод `ModelFactory.seed_random`. Это передаст начальное значение как `Faker`, так и вызову случайного метода, гарантируя, что данные будут одинаковыми между вызовами. Особенно полезно для тестирования.

## Определение атрибутов

```python
from datetime import date, datetime
from enum import Enum
from pydantic import BaseModel, UUID4
from typing import Any, Dict, List, Union


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]]]
```

Первый способ - непосредственно при создании

```python
pet = Pet(name="Roxy", sound="woof woof", species=Species.DOG)


class PersonFactory(ModelFactory):
    __model__ = Person

    pets = [pet]
```

В этом случае, когда мы вызываем `PersonFactory.build()`, результат будет сгенерирован случайным образом, за исключением списка питомцев, который будет жестко запрограммированным значением по умолчанию, которое мы определили.

### Поле Use

Другой вариант - определить фабрику для Pet, где мы ограничиваем выбор диапазоном, который нам нравится.

```python
from enum import Enum
from pydantic_factories import ModelFactory, Use
from random import choice

from .models import Pet, Person


class Species(str, Enum):
    CAT = "Cat"
    DOG = "Dog"


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)
```

или так (не используя Use):

```python
class PetFactory(ModelFactory):
    __model__ = Pet

    name = lambda: choice(["Ralph", "Roxy"])
    species = lambda: choice(list(Species))
```

### Поле PostGenerated

Позволяет создавать поля на основе уже сгенерированных значений других (не созданных таким способом) полей. В большинстве случаев этого шаблона лучше избегать.

```python
from pydantic import BaseModel
from pydantic_factories import ModelFactory, PostGenerated
from random import randint


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)
```

### Поле Ignore

Используется для обозначения данного атрибута как игнорируемого.

```python
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()
```

### Поле Require

Указывает, что конкретный атрибут является обязательным kwarg. То есть, если kwarg со значением для этого конкретного атрибута не передается при вызове `factory.build()`, будет поднята ошибка `MissingBuildKwargError`.

Каков вариант использования для этого? Например, предположим, что у нас есть документ под названием Article, который мы храним в какой-либо БД и который представлен с использованием не-pydantic модели, скажем, документа с эластичной DSL. Затем нам нужно сохранить в нашем объекте pydantic ссылку на идентификатор этой статьи. Это значение не должно быть каким-то фиктивным значением, а должно быть фактическим идентификатором, переданным на фабрику. Таким образом, мы можем определить этот атрибут по мере необходимости:

```python
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()
```

### Persistence

ModelFactory имеет четыре метода сохранения:

- `.create_sync(**kwargs)` — синхронно создает и сохраняет один экземпляр модели фабрики.
- `.create_batch_sync(size: int, **kwargs)` — синхронно создает и сохраняет список размером n экземпляров.
- `.create_async(**kwargs)` — асинхронно создает и сохраняет один экземпляр модели фабрики.
- `.create_batch_async(size: int, **kwargs)` — асинхронно создает и сохраняет список размером n экземпляров.

Чтобы использовать эти методы, вы должны сначала указать синнные и/или асинхронные обработчики для фабрики:

```python
# persistence.py
from typing import TypeVar, List

from pydantic import BaseModel
from pydantic_factories import SyncPersistenceProtocol

T = TypeVar("T", bound=BaseModel)


class SyncPersistenceHandler(SyncPersistenceProtocol[T]):
    def save(self, data: T) -> T:
        ...  # do stuff

    def save_many(self, data: List[T]) -> List[T]:
        ...  # do stuff


class AsyncPersistenceHandler(AsyncPersistenceProtocol[T]):
    async def save(self, data: T) -> T:
        ...  # do stuff

    async def save_many(self, data: List[T]) -> List[T]:
        ...  # do stuff
```

Затем вы можете указать один или оба этих обработчика в вашей фабрике:

```python
from pydantic_factories import ModelFactory

from app.models import Person
from .persistence import AsyncPersistenceHandler, SyncPersistenceHandler


class PersonFactory(ModelFactory):
    __model__ = Person
    __sync_persistence__ = SyncPersistenceHandler
    __async_persistence__ = AsyncPersistenceHandler
```

Или создайте свою собственную базовую фабрику и повторно используйте ее в различных фабриках:

```python
from pydantic_factories import ModelFactory

from app.models import Person
from .persistence import AsyncPersistenceHandler, SyncPersistenceHandler


class BaseModelFactory(ModelFactory):
    __sync_persistence__ = SyncPersistenceHandler
    __async_persistence__ = AsyncPersistenceHandler


class PersonFactory(BaseModelFactory):
    __model__ = Person
```

Обратите внимание: вам не нужно определять какой-либо или оба обработчика сохранения. Если вы будете использовать только синхронное или асинхронное сохранение, вам нужно только определить соответствующий обработчик для использования этих методов.

## Дополнительно - [создание фабричных методов](https://github.com/Goldziher/pydantic-factories#create-factory-method)

Если вы предпочитаете императивное создание фабрики, вы можете сделать это с помощью метода `ModelFactory.create_factory`. Этот метод получает следующие аргументы:

- `model` - модель для фабрики.
- `base` - необязательный базовый класс фабрики. По умолчанию используется фабричный класс, для которого вызывается метод.
- `kwargs` - словарь аргументов, соответствующих переменным класса, принятым `ModelFactory`.
Вы также можете переопределить атрибут `__model__` дочерней фабрики, чтобы указать используемую модель и `kwargs`

Пример

```python
from datetime import date, datetime
from enum import Enum
from pydantic import BaseModel, UUID4
from typing import Any, Dict, List, TypeVar, Union, Generic, Optional
from pydantic_factories import ModelFactory


class Species(str, Enum):
    CAT = "Cat"
    DOG = "Dog"


class PetBase(BaseModel):
    name: str
    species: Species


class Pet(PetBase):
    id: UUID4


class PetCreate(PetBase):
    pass


class PetUpdate(PetBase):
    pass


class PersonBase(BaseModel):

    name: str
    hobbies: List[str]
    age: Union[float, int]
    birthday: Union[datetime, date]
    pets: List[Pet]
    assets: List[Dict[str, Dict[str, Any]]]


class PersonCreate(PersonBase):
    pass


class Person(PersonBase):
    id: UUID4


class PersonUpdate(PersonBase):
    pass


def test_factory():
    class PersonFactory(ModelFactory):
        __model__ = Person

    person = PersonFactory.build()

    assert person.pets != []


ModelType = TypeVar("ModelType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)


class BUILDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    def __init__(
        self,
        model: ModelType = None,
        create_schema: Optional[CreateSchemaType] = None,
        update_schema: Optional[UpdateSchemaType] = None,
    ):
        self.model = model
        self.create_model = create_schema
        self.update_model = update_schema

    def build_object(self) -> ModelType:
        object_Factory = ModelFactory.create_factory(self.model)
        return object_Factory.build()

    def build_create_object(self) -> CreateSchemaType:
        object_Factory = ModelFactory.create_factory(self.create_model)
        return object_Factory.build()

    def build_update_object(self) -> UpdateSchemaType:
        object_Factory = ModelFactory.create_factory(self.update_model)
        return object_Factory.build()


class BUILDPet(BUILDBase[Pet, PetCreate, PetUpdate]):
    def build_object(self) -> Pet:
        object_Factory = ModelFactory.create_factory(self.model, name="Fido")
        return object_Factory.build()

    def build_create_object(self) -> PetCreate:
        object_Factory = ModelFactory.create_factory(self.create_model, name="Rover")
        return object_Factory.build()

    def build_update_object(self) -> PetUpdate:
        object_Factory = ModelFactory.create_factory(self.update_model, name="Spot")
        return object_Factory.build()


def test_factory_create():

    person_factory = BUILDBase(Person, PersonCreate, PersonUpdate)

    pet_factory = BUILDPet(Pet, PetCreate, PetUpdate)

    create_person = person_factory.build_create_object()
    update_person = person_factory.build_update_object()

    pet = pet_factory.build_object()
    create_pet = pet_factory.build_create_object()
    update_pet = pet_factory.build_update_object()

    assert create_person != None
    assert update_person != None

    assert pet.name == "Fido"
    assert create_pet.name == "Rover"
    assert update_pet.name == "Spot"
```

## Дополнительно

Любой класс, производный от `BaseModel` pydantic, может использоваться как `__model__` фабрики. Для большинства сторонних библиотек, например. `SQLModel`, эта библиотека будет работать как есть из коробки.

Есть API для [[ormar]]

Смотри еще:

- [гитхаб](https://github.com/Goldziher/pydantic-factories)
- [[pydantic]]
- [[pytest]]
- [[mock-libraries]]
- [[mock]]
- [[faker]]
- [[тестирование]]


[pydantic]: pydantic "Pydantic"
[ormar]: ormar "Ormar"
[pytest]: pytest "Pytest"
[mock-libraries]: mock-libraries "Либы для создания моков"
[mock]: mock "Mock-тесты"
[faker]: faker "Faker - пакет для создания фейковых данных для тестов"
[тестирование]: ../lists/%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 "Основные принципы тестровния"


[pydantic]: pydantic "Pydantic"
[ormar]: ormar "Ormar"
[pydantic]: pydantic "Pydantic"
[pytest]: pytest "Pytest"
[mock-libraries]: mock-libraries "Либы для создания моков"
[mock]: mock "Mock-тесты"
[faker]: faker "Faker - пакет для создания фейковых данных для тестов"
[тестирование]: ../lists/тестирование "Основные принципы тестровния"