-
Notifications
You must be signed in to change notification settings - Fork 339
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add factory_boy plugin support (#1497)
- Loading branch information
1 parent
fd8250e
commit 1491f52
Showing
16 changed files
with
861 additions
and
190 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
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
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,97 @@ | ||
.. _factory_plugin: | ||
|
||
Integration with factory_boy | ||
============================ | ||
|
||
.. versionadded:: 15.0.0 | ||
|
||
You no longer require any third-party packages to integrate Mimesis with ``factory_boy``. | ||
|
||
Mimesis requires ``factory_boy`` to be installed, but it's not a hard dependency. | ||
Therefore, you'll need to install it manually: | ||
|
||
.. code-block:: bash | ||
poetry add --group dev factory_boy | ||
Utilization | ||
----------- | ||
|
||
Look at the example below and you’ll understand how it works: | ||
|
||
.. code-block:: python | ||
class Account(object): | ||
def __init__(self, username, email, name, surname, age): | ||
self.username = username | ||
self.email = email | ||
self.name = name | ||
self.surname = surname | ||
self.age = age | ||
Now, use the ``MimesisField`` class to define how fake data is generated: | ||
|
||
.. code-block:: python | ||
import factory | ||
from mimesis.plugins.factory import MimesisField | ||
from account import Account | ||
class AccountFactory(factory.Factory): | ||
class Meta(object): | ||
model = Account | ||
username = MimesisField('username', template='l_d') | ||
name = MimesisField('name', gender='female') | ||
surname = MimesisField('surname', gender='female') | ||
age = MimesisField('age', minimum=18, maximum=90) | ||
email = factory.LazyAttribute( | ||
lambda instance: '{0}@example.org'.format(instance.username) | ||
) | ||
access_token = MimesisField('token', entropy=32) | ||
See `factory_boy <https://factoryboy.readthedocs.io/>`_ documentation for more information about how to use factories. | ||
|
||
|
||
Configuration | ||
------------- | ||
|
||
You can also define custom field handlers for your factories. To do this, you need to | ||
define an attribute named ``field_handlers`` in the ``Params`` class of your factory. | ||
|
||
Just like this: | ||
|
||
.. code-block:: python | ||
import factory | ||
from mimesis.plugins.factory import MimesisField | ||
class FactoryWithCustomFieldHandlers(factory.Factory): | ||
class Meta(object): | ||
model = Guest # Your model here | ||
class Params(object): | ||
field_handlers = [ | ||
("num", lambda rand, **kwargs: rand.randint(1, 99)), | ||
("nick", lambda rand, **kwargs: rand.choice(["john", "alice"])), | ||
] | ||
age = MimesisField("num") | ||
nickname = MimesisField("nick") | ||
See `Custom Field Handlers <https://mimesis.name/en/master/schema.html#custom-field-handlers>`_ for more information | ||
about how to define custom field handlers. | ||
|
||
Factories and pytest | ||
-------------------- | ||
|
||
We also recommend to use `pytest-factoryboy <https://github.com/pytest-dev/pytest-factoryboy>`_. | ||
This way it will be possible to integrate your factories into pytest fixtures. |
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
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,118 @@ | ||
from contextlib import contextmanager | ||
from typing import Any, ClassVar, Iterator | ||
|
||
from mimesis.locales import Locale | ||
from mimesis.schema import Field, RegisterableFieldHandlers | ||
|
||
try: | ||
from factory import declarations | ||
from factory.builder import BuildStep, Resolver | ||
except ImportError: | ||
raise ImportError("This plugin requires factory_boy to be installed.") | ||
|
||
__all__ = ["MimesisField"] | ||
|
||
|
||
class MimesisField(declarations.BaseDeclaration): # type: ignore[misc] | ||
""" | ||
Mimesis integration with FactoryBoy starts here. | ||
This class provides a common interface for FactoryBoy, | ||
but inside it has Mimesis generators. | ||
""" | ||
|
||
_default_locale: ClassVar[Locale] = Locale.EN | ||
_cached_instances: ClassVar[dict[str, Field]] = {} | ||
|
||
def __init__( | ||
self, | ||
field: str, | ||
locale: Locale | None = None, | ||
**kwargs: Any, | ||
) -> None: | ||
""" | ||
Creates a field instance. | ||
The created field is lazy. It also receives build time parameters. | ||
These parameters are not applied yet. | ||
:param field: name to be passed to :class:`~mimesis.schema.Field`. | ||
:param locale: locale to use. This parameter has the highest priority. | ||
:param kwargs: optional parameters that would be passed to ``Field``. | ||
""" | ||
super().__init__() | ||
self.locale = locale | ||
self.kwargs = kwargs | ||
self.field = field | ||
|
||
def evaluate( | ||
self, | ||
instance: Resolver, | ||
step: BuildStep, | ||
extra: dict[str, Any] | None = None, | ||
) -> Any: | ||
"""Evaluates the lazy field. | ||
:param instance: (factory.builder.Resolver): The object holding currently computed attributes. | ||
:param step: (factory.builder.BuildStep): The object holding the current build step. | ||
:param extra: Extra call-time added kwargs that would be passed to ``Field``. | ||
""" | ||
kwargs: dict[str, Any] = {} | ||
kwargs.update(self.kwargs) | ||
kwargs.update(extra or {}) | ||
|
||
field_handlers = step.builder.factory_meta.declarations.get( | ||
"field_handlers", [] | ||
) | ||
|
||
mimesis_field = self._get_cached_instance( | ||
locale=self.locale, | ||
field_handlers=field_handlers, | ||
) | ||
return mimesis_field(self.field, **kwargs) | ||
|
||
@classmethod | ||
@contextmanager | ||
def override_locale(cls, locale: Locale) -> Iterator[None]: | ||
""" | ||
Overrides unspecified locales. | ||
Remember that implicit locales would not be overridden. | ||
""" | ||
old_locale = cls._default_locale | ||
cls._default_locale = locale | ||
yield | ||
cls._default_locale = old_locale | ||
|
||
@classmethod | ||
def _get_cached_instance( | ||
cls, | ||
locale: Locale | None = None, | ||
field_handlers: RegisterableFieldHandlers | None = None, | ||
) -> Field: | ||
"""Returns cached instance. | ||
:param locale: locale to use. | ||
:param field_handlers: custom field handlers. | ||
:return: cached instance of Field. | ||
""" | ||
if locale is None: | ||
locale = cls._default_locale | ||
|
||
field_names = "-".join( | ||
sorted( | ||
dict(field_handlers if field_handlers else []).keys(), | ||
) | ||
) | ||
|
||
key = f"{locale}{field_names}" | ||
|
||
if key not in cls._cached_instances: | ||
field = Field(locale) | ||
|
||
if field_handlers: | ||
field.register_handlers(field_handlers) | ||
|
||
cls._cached_instances[key] = field | ||
|
||
return cls._cached_instances[key] |
Oops, something went wrong.