Source code for mimesis.providers.base
"""Base data provider."""
import contextlib
import json
import operator
import typing as t
from functools import reduce
from pathlib import Path
from mimesis.exceptions import NonEnumerableError
from mimesis.locales import Locale, validate_locale
from mimesis.random import Random, get_random_item
from mimesis.types import JSON, Seed
__all__ = ["BaseDataProvider", "BaseProvider"]
[docs]class BaseProvider:
"""This is a base class for all providers."""
class Meta:
name: str
[docs] def __init__(self, *, seed: Seed = None, **kwargs: t.Any) -> None:
"""Initialize attributes.
Keep in mind, that locale-independent data providers will work
only with keyword-only arguments since version 5.0.0.
:param seed: Seed for random.
When set to `None` the current system time is used.
"""
self.seed = seed
self.random = Random()
self.reseed(seed)
[docs] def reseed(self, seed: Seed = None) -> None:
"""Reseed the internal random generator.
In case we use the default seed, we need to create a per instance
random generator, in this case two providers with the same seed
will always return the same values.
:param seed: Seed for random.
When set to `None` the current system time is used.
"""
self.seed = seed
self.random.seed(seed)
[docs] def validate_enum(self, item: t.Any, enum: t.Any) -> t.Any:
"""Validate enum parameter of method in subclasses of BaseProvider.
:param item: Item of enum object.
:param enum: Enum object.
:return: Value of item.
:raises NonEnumerableError: if ``item`` not in ``enum``.
"""
if item is None:
result = get_random_item(enum, self.random)
elif item and isinstance(item, enum):
result = item
else:
raise NonEnumerableError(enum)
return result.value
def __str__(self) -> str:
"""Human-readable representation of locale."""
return self.__class__.__name__
[docs]class BaseDataProvider(BaseProvider):
"""This is a base class for all data providers."""
[docs] def __init__(self, locale: Locale = Locale.DEFAULT, seed: Seed = None) -> None:
"""Initialize attributes for data providers.
:param locale: Current locale.
:param seed: Seed to all the random functions.
"""
super().__init__(seed=seed)
self._data: JSON = {}
self._datafile: str = ""
self._setup_locale(locale)
self._data_dir = Path(__file__).parent.parent.joinpath("data")
def _setup_locale(self, locale: Locale = Locale.DEFAULT) -> None:
"""Set up locale after pre-check.
:param str locale: Locale
:raises UnsupportedLocale: When locale not supported.
:return: Nothing.
"""
locale_obj = validate_locale(locale)
self.locale = locale_obj.value
def _update_dict(self, initial: JSON, other: JSON) -> JSON:
"""Recursively update a dictionary.
:param initial: Dict to update.
:param other: Dict to update from.
:return: Updated dict.
"""
for key, value in other.items():
if isinstance(value, dict):
r = self._update_dict(initial.get(key, {}), value)
initial[key] = r
else:
initial[key] = other[key]
return initial
def _load_datafile(self, datafile: str = "") -> None:
"""Loads the content from the JSON.
:param datafile: The name of file.
:return: The content of the file.
:raises UnsupportedLocale: Raises if locale is unsupported.
"""
locale = self.locale
data_dir = self._data_dir
if not datafile:
datafile = self._datafile
def get_data(locale_name: str) -> t.Any:
"""Pull JSON data from file.
:param locale_name: Locale name.
:return: Content of JSON file as dict.
"""
file_path = Path(data_dir).joinpath(locale_name, datafile)
with open(file_path, encoding="utf8") as f:
return json.load(f)
locale_separator = "-"
master_locale = locale.split(locale_separator).pop(0)
data = get_data(master_locale)
if locale_separator in locale:
data = self._update_dict(data, get_data(locale))
self._data = data
[docs] def get_current_locale(self) -> str:
"""Get current locale.
If locale is not defined then this method will always return ``en``,
because ``en`` is default locale for all providers, excluding builtins.
:return: Current locale.
"""
# noinspection PyTypeChecker
return self.locale
def _override_locale(self, locale: Locale = Locale.DEFAULT) -> None:
"""Overrides current locale with passed and pull data for new locale.
:param locale: Locale
:return: Nothing.
"""
self._setup_locale(locale)
self._load_datafile()
[docs] @contextlib.contextmanager
def override_locale(
self,
locale: Locale,
) -> t.Generator["BaseDataProvider", None, None]:
"""Context manager which allows overriding current locale.
Temporarily overrides current locale for
locale-dependent providers.
:param locale: Locale.
:return: Provider with overridden locale.
"""
try:
origin_locale = Locale(self.locale)
self._override_locale(locale)
try:
yield self
finally:
self._override_locale(origin_locale)
except AttributeError:
raise ValueError(f"«{self.__class__.__name__}» has not locale dependent")
def __str__(self) -> str:
"""Human-readable representation of locale."""
locale = Locale(getattr(self, "locale", Locale.DEFAULT))
return f"{self.__class__.__name__} <{locale}>"