Skip to content

Commit

Permalink
Add factory_boy plugin support
Browse files Browse the repository at this point in the history
  • Loading branch information
lk-geimfari committed Feb 20, 2024
1 parent fd8250e commit 2a50c69
Show file tree
Hide file tree
Showing 11 changed files with 581 additions and 21 deletions.
2 changes: 1 addition & 1 deletion mimesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
"__license__",
]

__version__ = "14.0.0"
__version__ = "15.0.0"

Check warning on line 149 in mimesis/__init__.py

View check run for this annotation

Codecov / codecov/patch

mimesis/__init__.py#L149

Added line #L149 was not covered by tests
__title__ = "mimesis"
__description__ = "Mimesis: Fake Data Generator."
__url__ = "https://github.com/lk-geimfari/mimesis"
Expand Down
118 changes: 118 additions & 0 deletions mimesis/plugins/factory.py
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.")

Check warning on line 11 in mimesis/plugins/factory.py

View check run for this annotation

Codecov / codecov/patch

mimesis/plugins/factory.py#L10-L11

Added lines #L10 - L11 were not covered by tests

__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]
46 changes: 27 additions & 19 deletions mimesis/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import json
import pickle
import re
import typing as t
from typing import Any, Callable, Sequence

Check warning on line 8 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L8

Added line #L8 was not covered by tests

from mimesis.exceptions import (
AliasesTypeError,
Expand All @@ -21,12 +21,20 @@
from mimesis.random import Random
from mimesis.types import JSON, CallableSchema, Key, MissingSeed, Seed

__all__ = ["BaseField", "Field", "Fieldset", "Schema"]

FieldCache = dict[str, t.Callable[[t.Any], t.Any]]
FieldHandler = t.Callable[[Random, t.Any], t.Any]
__all__ = [

Check warning on line 24 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L24

Added line #L24 was not covered by tests
"BaseField",
"Field",
"Fieldset",
"Schema",
"FieldHandler",
"RegisterableFieldHandler",
"RegisterableFieldHandlers",
]

FieldCache = dict[str, Callable[[Any], Any]]
FieldHandler = Callable[[Random, Any], Any]

Check warning on line 35 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L34-L35

Added lines #L34 - L35 were not covered by tests
RegisterableFieldHandler = tuple[str, FieldHandler]
RegisterableFieldHandlers = t.Sequence[RegisterableFieldHandler]
RegisterableFieldHandlers = Sequence[RegisterableFieldHandler]

Check warning on line 37 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L37

Added line #L37 was not covered by tests


class BaseField:
Expand Down Expand Up @@ -62,7 +70,7 @@ def get_random_instance(self) -> Random:
"""
return self._generic.random

def _explicit_lookup(self, name: str) -> t.Any:
def _explicit_lookup(self, name: str) -> Any:

Check warning on line 73 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L73

Added line #L73 was not covered by tests
"""An explicit method lookup.
This method is called when the field
Expand All @@ -79,7 +87,7 @@ def _explicit_lookup(self, name: str) -> t.Any:
except AttributeError:
raise FieldError(name)

def _fuzzy_lookup(self, name: str) -> t.Any:
def _fuzzy_lookup(self, name: str) -> Any:

Check warning on line 90 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L90

Added line #L90 was not covered by tests
"""A fuzzy method lookup.
This method is called when the field definition
Expand All @@ -97,7 +105,7 @@ def _fuzzy_lookup(self, name: str) -> t.Any:

raise FieldError(name)

def _lookup_method(self, name: str) -> t.Any:
def _lookup_method(self, name: str) -> Any:

Check warning on line 108 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L108

Added line #L108 was not covered by tests
"""Lookup method by the field name.
:param name: The field name.
Expand Down Expand Up @@ -137,8 +145,8 @@ def perform(
self,
name: str | None = None,
key: Key = None,
**kwargs: t.Any,
) -> t.Any:
**kwargs: Any,
) -> Any:
"""Performs the value of the field by its name.
It takes any string that represents the name of any method of
Expand Down Expand Up @@ -224,7 +232,7 @@ def register_handler(self, field_name: str, field_handler: FieldHandler) -> None

def handle(
self, field_name: str | None = None
) -> t.Callable[[FieldHandler], FieldHandler]:
) -> Callable[[FieldHandler], FieldHandler]:
"""Decorator for registering a custom field handler.
You can use this decorator only for functions,
Expand Down Expand Up @@ -261,7 +269,7 @@ def unregister_handler(self, field_name: str) -> None:

self._handlers.pop(field_name, None)

def unregister_handlers(self, field_names: t.Sequence[str] = ()) -> None:
def unregister_handlers(self, field_names: Sequence[str] = ()) -> None:

Check warning on line 272 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L272

Added line #L272 was not covered by tests
"""Unregister a field handlers with given names.
:param field_names: Names of the fields.
Expand Down Expand Up @@ -305,7 +313,7 @@ class Field(BaseField):
Dogtag_1836
"""

def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
def __call__(self, *args: Any, **kwargs: Any) -> Any:

Check warning on line 316 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L316

Added line #L316 was not covered by tests
return self.perform(*args, **kwargs)


Expand Down Expand Up @@ -338,7 +346,7 @@ class Fieldset(BaseField):
fieldset_default_iterations: int = 10
fieldset_iterations_kwarg: str = "i"

def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
def __init__(self, *args: Any, **kwargs: Any) -> None:

Check warning on line 349 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L349

Added line #L349 was not covered by tests
"""Initialize fieldset.
Accepts additional keyword argument **i** which is used
Expand All @@ -353,7 +361,7 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
)
super().__init__(*args, **kwargs)

def __call__(self, *args: t.Any, **kwargs: t.Any) -> list[t.Any]:
def __call__(self, *args: Any, **kwargs: Any) -> list[Any]:

Check warning on line 364 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L364

Added line #L364 was not covered by tests
"""Perform fieldset.
:param args: Arguments for field.
Expand Down Expand Up @@ -399,7 +407,7 @@ def __init__(self, schema: CallableSchema, iterations: int = 10) -> None:
else:
raise SchemaError()

def to_csv(self, file_path: str, **kwargs: t.Any) -> None:
def to_csv(self, file_path: str, **kwargs: Any) -> None:

Check warning on line 410 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L410

Added line #L410 was not covered by tests
"""Export a schema as a CSV file.
:param file_path: The file path.
Expand All @@ -412,7 +420,7 @@ def to_csv(self, file_path: str, **kwargs: t.Any) -> None:
dict_writer.writeheader()
dict_writer.writerows(data)

def to_json(self, file_path: str, **kwargs: t.Any) -> None:
def to_json(self, file_path: str, **kwargs: Any) -> None:

Check warning on line 423 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L423

Added line #L423 was not covered by tests
"""Export a schema as a JSON file.
:param file_path: File a path.
Expand All @@ -421,7 +429,7 @@ def to_json(self, file_path: str, **kwargs: t.Any) -> None:
with open(file_path, "w", encoding="utf-8") as fp:
json.dump(self.create(), fp, **kwargs)

def to_pickle(self, file_path: str, **kwargs: t.Any) -> None:
def to_pickle(self, file_path: str, **kwargs: Any) -> None:

Check warning on line 432 in mimesis/schema.py

View check run for this annotation

Codecov / codecov/patch

mimesis/schema.py#L432

Added line #L432 was not covered by tests
"""Export a schema as the pickled representation of the object to the file.
:param file_path: The file path.
Expand Down
Loading

0 comments on commit 2a50c69

Please sign in to comment.