text: # -*- coding: utf-8 -*-
  
  """Base data provider."""
  
  import contextlib
  import functools
  import json
  import operator
  from functools import reduce
  from pathlib import Path
  from typing import Any, Generator, List, Optional
  
  from mimesis.exceptions import NonEnumerableError
  from mimesis.locales import Locale, validate_locale
  from mimesis.random import Random, get_random_item, random
  from mimesis.types import JSON, Seed
  
  __all__ = ["BaseDataProvider", "BaseProvider"]
  
  
  class BaseProvider:
      """This is a base class for all providers."""
  
      def __init__(self, *, seed: Seed = None, **kwargs: 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
  
          if seed is not None:
              self.reseed(seed)
  
      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.
          """
          if self.random is random:
              self.random = Random()
  
          self.seed = seed
          self.random.seed(self.seed)
  
      def validate_enum(self, item: Any, enum: Any) -> 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__
  
  
  class BaseDataProvider(BaseProvider):
      """This is a base class for all data providers."""
  
      _LOCALE_SEPARATOR = "-"
  
      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 = ""
          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 extract(self, keys: List[str], default: Optional[Any] = None) -> Any:
          """Extracts nested values from JSON file by list of keys.
  
          :param keys: List of keys (order extremely matters).
          :param default: Default value.
          :return: Data.
          """
  
          if not keys:
              raise ValueError("List of keys cannot be empty.")
  
          try:
              if len(keys) == 1:
                  return self._data[keys[0]]
              return reduce(operator.getitem, keys, self._data)
          except (TypeError, KeyError):
              return default
  
      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
  
      @functools.lru_cache(maxsize=None)
      def _load_datafile(self, datafile: str = "") -> None:
          """Pull the content from the JSON and memorize one.
  
          Opens JSON file ``file`` in the folder ``data/locale``
          and get content from the file and memorize ones using lru_cache.
  
          :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) -> 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, "r", encoding="utf8") as f:
                  return json.load(f)
  
          master_locale = locale.split(self._LOCALE_SEPARATOR).pop(0)
          data = get_data(master_locale)
  
          if self._LOCALE_SEPARATOR in locale:
              data = self._update_dict(data, get_data(locale))
  
          self._data = data
  
      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.
          """
          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.cache_clear()
          self._load_datafile()
  
      @contextlib.contextmanager
      def override_locale(
          self,
          locale: Locale,
      ) -> 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(
                  "«{}» has not locale dependent".format(self.__class__.__name__)
              )
  
      def __str__(self) -> str:
          """Human-readable representation of locale."""
          locale = getattr(self, "locale", Locale.DEFAULT)
          return "{} <{}>".format(self.__class__.__name__, Locale(locale))
  