-
-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
428 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from dataclasses import dataclass | ||
|
||
from polyfactory import Use | ||
from polyfactory.factories import DataclassFactory | ||
|
||
|
||
@dataclass | ||
class Person: | ||
name: str | ||
age: float | ||
height: float | ||
weight: float | ||
|
||
|
||
class PersonFactory(DataclassFactory[Person]): | ||
__model__ = Person | ||
__random_seed__ = 1 | ||
|
||
name = Use(DataclassFactory.__random__.choice, ["John", "Alice", "George"]) | ||
|
||
|
||
def test_random_seed() -> None: | ||
# the outcome of 'factory.__random__.choice' is deterministic, because Random has been seeded with a set value. | ||
assert PersonFactory.build().name == "John" | ||
assert PersonFactory.build().name == "George" | ||
assert PersonFactory.build().name == "John" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from dataclasses import dataclass | ||
from random import Random | ||
|
||
from polyfactory import Use | ||
from polyfactory.factories import DataclassFactory | ||
|
||
|
||
@dataclass | ||
class Person: | ||
name: str | ||
age: float | ||
height: float | ||
weight: float | ||
|
||
|
||
class PersonFactory(DataclassFactory[Person]): | ||
__model__ = Person | ||
__random__ = Random(10) | ||
|
||
name = Use(DataclassFactory.__random__.choice, ["John", "Alice", "George"]) | ||
|
||
|
||
def test_setting_random() -> None: | ||
# the outcome of 'factory.__random__.choice' is deterministic, because Random is configured with a set value. | ||
assert PersonFactory.build().name == "Alice" | ||
assert PersonFactory.build().name == "John" | ||
assert PersonFactory.build().name == "Alice" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from dataclasses import dataclass | ||
|
||
from faker import Faker | ||
|
||
from polyfactory.factories import DataclassFactory | ||
|
||
|
||
@dataclass | ||
class Person: | ||
name: str | ||
age: float | ||
height: float | ||
weight: float | ||
|
||
|
||
class PersonFactory(DataclassFactory[Person]): | ||
__model__ = Person | ||
__faker__ = Faker(locale="es_ES") | ||
|
||
__random_seed__ = 10 | ||
|
||
@classmethod | ||
def name(cls) -> str: | ||
return cls.__faker__.name() | ||
|
||
|
||
def test_setting_faker() -> None: | ||
# the outcome of faker deterministic because we seeded random, and it uses a german locale. | ||
assert PersonFactory.build().name == "Asunción Céspedes" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from asyncio import sleep | ||
from dataclasses import dataclass | ||
from typing import List, Dict | ||
from uuid import UUID | ||
|
||
from polyfactory import SyncPersistenceProtocol, AsyncPersistenceProtocol | ||
from polyfactory.factories import DataclassFactory | ||
|
||
|
||
@dataclass | ||
class Person: | ||
id: UUID | ||
name: str | ||
|
||
|
||
# we will use a dictionary to persist values for the example | ||
mock_db: Dict[UUID, Person] = {} | ||
|
||
|
||
class SyncPersistenceHandler(SyncPersistenceProtocol[Person]): | ||
def save(self, data: Person) -> Person: | ||
# do stuff here to persist the value, such as use an ORM or ODM, cache in redis etc. | ||
# in our case we simply save it in the dictionary. | ||
mock_db[data.id] = data | ||
return data | ||
|
||
def save_many(self, data: List[Person]) -> List[Person]: | ||
# same as for save, here we should store the list in persistence. | ||
# in this case, we use the same dictionary. | ||
for person in data: | ||
mock_db[person.id] = person | ||
return data | ||
|
||
|
||
class AsyncPersistenceHandler(AsyncPersistenceProtocol[Person]): | ||
async def save(self, data: Person) -> Person: | ||
# do stuff here to persist the value using an async method, such as an async ORM or ODM. | ||
# in our case we simply save it in the dictionary and add a minimal sleep to mock async. | ||
mock_db[data.id] = data | ||
await sleep(0.0001) | ||
return data | ||
|
||
async def save_many(self, data: List[Person]) -> List[Person]: | ||
# same as for the async save, here we should store the list in persistence using async logic. | ||
# we again store in dict, and mock async using sleep. | ||
for person in data: | ||
mock_db[person.id] = person | ||
await sleep(0.0001) | ||
return data | ||
|
||
|
||
class PersonFactory(DataclassFactory[Person]): | ||
__model__ = Person | ||
__sync_persistence__ = SyncPersistenceHandler | ||
__async_persistence__ = AsyncPersistenceHandler | ||
|
||
|
||
def test_sync_persistence_build() -> None: | ||
person_instance = PersonFactory.create_sync() | ||
assert mock_db[person_instance.id] is person_instance | ||
|
||
|
||
def test_sync_persistence_batch() -> None: | ||
person_batch = PersonFactory.create_batch_sync(10) | ||
for person_instance in person_batch: | ||
assert mock_db[person_instance.id] is person_instance | ||
|
||
|
||
async def test_async_persistence_build() -> None: | ||
person_instance = await PersonFactory.create_async() | ||
assert mock_db[person_instance.id] is person_instance | ||
|
||
|
||
async def test_async_persistence_batch() -> None: | ||
person_batch = await PersonFactory.create_batch_async(10) | ||
for person_instance in person_batch: | ||
assert mock_db[person_instance.id] is person_instance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
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]): | ||
__model__ = Pet | ||
__set_as_default_factory_for_type__ = True | ||
|
||
name = Use(DataclassFactory.__random__.choice, ["Roxy", "Spammy", "Moshe"]) | ||
|
||
|
||
class PersonFactory(DataclassFactory[Person]): | ||
__model__ = Person | ||
|
||
|
||
def test_default_pet_factory() -> None: | ||
person_instance = PersonFactory.build() | ||
assert len(person_instance.pets) > 0 | ||
assert person_instance.pets[0].name in ["Roxy", "Spammy", "Moshe"] |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
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]]] | ||
|
||
|
||
class PersonFactory(DataclassFactory[Person]): | ||
__model__ = Person | ||
|
||
|
||
def test_dynamic_factory_generation() -> None: | ||
person_instance = PersonFactory.build() | ||
assert len(person_instance.pets) > 0 | ||
assert isinstance(person_instance.pets[0], Pet) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from dataclasses import dataclass | ||
from enum import Enum | ||
|
||
from polyfactory.factories import DataclassFactory | ||
|
||
|
||
class Species(str, Enum): | ||
CAT = "Cat" | ||
DOG = "Dog" | ||
|
||
|
||
@dataclass | ||
class Pet: | ||
name: str | ||
species: Species | ||
sound: str | ||
|
||
|
||
def test_imperative_factory_creation() -> None: | ||
pet_factory = DataclassFactory.create_factory(model=Pet) | ||
pet_instance = pet_factory.build() | ||
assert isinstance(pet_instance, Pet) |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
Factory Configuration | ||
===================== | ||
|
||
Factories can be configured by setting special dunder (double underscore) class attributes. | ||
You can read the reference for these in the API reference for :class:`BaseFactory <polyfactory.factories.BaseFactory>`. | ||
Below we discuss some configuration options in some depth. | ||
|
||
Seeding Randomness | ||
------------------ | ||
|
||
.. literalinclude:: /examples/configuration/test_example_1.py | ||
:caption: Seeding the factory's 'random.Random' | ||
:language: python | ||
|
||
Seeding randomness allows you to control the random generation of values produced by the factory. This affects all 'random.Random' | ||
methods as well as faker. | ||
|
||
Setting Random | ||
-------------- | ||
|
||
.. literalinclude:: /examples/configuration/test_example_2.py | ||
:caption: Setting the factory's 'random.Random' | ||
:language: python | ||
|
||
This configuration option is functionally identical to the previous, with the difference being that here we are setting | ||
the actual instance of 'random.Random'. This is useful when embedding factories inside more complex logic, such as in | ||
other libraries, as well as when factories are being dynamically generated. | ||
|
||
Setting Faker | ||
------------- | ||
|
||
.. literalinclude:: /examples/configuration/test_example_3.py | ||
:caption: Setting the factory's 'Faker' | ||
:language: python | ||
|
||
In the above example we are setting the factory's instance of ``Faker`` and configure its locale to Spanish. Because | ||
we are also setting the random seed value, the results of the test are deterministic. | ||
|
||
.. note:: | ||
To understand why we are using a classmethod here, see the documentation about :doc:`factory fields <./fields.rst>`. | ||
|
||
Persistence Handlers | ||
-------------------- | ||
|
||
Factory classes have four optional persistence methods: | ||
|
||
- ``.create_sync(**kwargs)`` - builds and persists a single instance of the factory's model synchronously | ||
- ``.create_batch_sync(size: int, **kwargs)`` - builds and persists a list of size n instances synchronously | ||
- ``.create_async(**kwargs)`` - builds and persists a single instance of the factory's model asynchronously | ||
- ``.create_batch_async(size: int, **kwargs)`` - builds and persists a list of size n instances asynchronously | ||
|
||
To use these methods, you must first specify a sync and/or async persistence handlers for the factory: | ||
|
||
.. literalinclude:: /examples/configuration/test_example_4.py | ||
:caption: Defining and using persistence handlers. | ||
:language: python | ||
|
||
With the persistence handlers in place, you can now use all persistence methods. | ||
|
||
.. note:: | ||
You do not need to define both persistence handlers. If you will only use sync or async persistence, you only need | ||
to define the respective handler to use these methods. | ||
|
||
Defining Default Factories | ||
-------------------------- | ||
|
||
As explained in the section about dynamic factory generation in :doc:`declaring factories <./declaring_factories.rst>`, | ||
factories generate new factories for supported types dynamically. This process requires no intervention from the user. | ||
Once a factory is generated, it is then cached and reused - when the same type is used. | ||
|
||
For example, when build is called for the ``PersonFactory`` below, a ``PetFactory`` will be dynamically generated and reused: | ||
|
||
.. literalinclude:: /examples/defining_factories/test_example_5.py | ||
:caption: Dynamic factory generation | ||
:language: python | ||
|
||
You can also control the default factory for a type by declaring a factory as the type default: | ||
|
||
.. literalinclude:: /examples/configuration/test_example_5.py | ||
:caption: Setting a factory as a type default. | ||
:language: python |
Oops, something went wrong.