Source code for dateparser.languages.loader

import threading
from collections import OrderedDict
from collections.abc import Iterable, Iterator
from copy import deepcopy
from importlib import import_module
from typing import Any, ClassVar

import regex as re

from dateparser.data.languages_info import language_locale_dict, language_order

from .locale import Locale

LOCALE_SPLIT_PATTERN = re.compile(r"-(?=[A-Z0-9]+$)")

_LANGUAGE_POSITIONS = {language: i for i, language in enumerate(language_order)}


def _isvalidlocale(locale: str) -> bool:
    language = LOCALE_SPLIT_PATTERN.split(locale)[0]
    if language not in _LANGUAGE_POSITIONS:
        return False
    locales_list = language_locale_dict[language]
    return locale == language or locale in locales_list


[docs] class LocaleDataLoader: """Class that handles loading of locale instances.""" _loaded_languages: ClassVar[dict[str, dict[str, Any]]] = {} _loaded_locales: ClassVar[dict[str, Locale]] = {} _load_lock = threading.Lock()
[docs] def get_locale_map( self, languages: Iterable[str] | None = None, locales: Iterable[str] | None = None, region: str | None = None, use_given_order: bool = False, allow_conflicting_locales: bool = False, ) -> OrderedDict[str, Locale]: """ Get an ordered mapping with locale codes as keys and corresponding locale instances as values. :param languages: A list of language codes, e.g. ['en', 'es', 'zh-Hant']. If locales are not given, languages and region are used to construct locales to load. :type languages: list :param locales: A list of codes of locales which are to be loaded, e.g. ['fr-PF', 'qu-EC', 'af-NA'] :type locales: list :param region: A region code, e.g. 'IN', '001', 'NE'. If locales are not given, languages and region are used to construct locales to load. :type region: str :param use_given_order: If True, the returned mapping is ordered in the order locales are given. :type use_given_order: bool :param allow_conflicting_locales: if True, locales with same language and different region can be loaded. :type allow_conflicting_locales: bool :return: ordered locale code to locale instance mapping """ return OrderedDict( self._load_data( languages=languages, locales=locales, region=region, use_given_order=use_given_order, allow_conflicting_locales=allow_conflicting_locales, ) )
[docs] def get_locales( self, languages: Iterable[str] | None = None, locales: Iterable[str] | None = None, region: str | None = None, use_given_order: bool = False, allow_conflicting_locales: bool = False, ) -> Iterator[Locale]: """ Yield locale instances. :param languages: A list of language codes, e.g. ['en', 'es', 'zh-Hant']. If locales are not given, languages and region are used to construct locales to load. :type languages: list :param locales: A list of codes of locales which are to be loaded, e.g. ['fr-PF', 'qu-EC', 'af-NA'] :type locales: list :param region: A region code, e.g. 'IN', '001', 'NE'. If locales are not given, languages and region are used to construct locales to load. :type region: str :param use_given_order: If True, the returned mapping is ordered in the order locales are given. :type use_given_order: bool :param allow_conflicting_locales: if True, locales with same language and different region can be loaded. :type allow_conflicting_locales: bool :yield: locale instances """ for _, locale in self._load_data( languages=languages, locales=locales, region=region, use_given_order=use_given_order, allow_conflicting_locales=allow_conflicting_locales, ): yield locale
[docs] def get_locale(self, shortname: str) -> Locale: """ Get a locale instance. :param shortname: A locale code, e.g. 'fr-PF', 'qu-EC', 'af-NA'. :type shortname: str :return: locale instance """ return next(iter(self.get_locales(locales=[shortname])))
def _load_data( # noqa: PLR0912 self, languages: Iterable[str] | None = None, locales: Iterable[str] | None = None, region: str | None = None, use_given_order: bool = False, allow_conflicting_locales: bool = False, ) -> Iterator[tuple[str, Locale]]: locale_dict: dict[str, tuple[str, ...]] = {} if locales: invalid_locales = [] for locale in locales: split_locale = LOCALE_SPLIT_PATTERN.split(locale) if len(split_locale) == 1: split_locale.append("") locale_dict[locale] = tuple(split_locale) if not _isvalidlocale(locale): invalid_locales.append(locale) if invalid_locales: raise ValueError( f"Unknown locale(s): {', '.join(map(repr, invalid_locales))}" ) if not allow_conflicting_locales and len(set(locales)) > len( {t[0] for t in locale_dict.values()} ): raise ValueError( "Locales should not have same language and different region" ) else: if languages is None: languages = language_order unsupported_languages = set(languages) - _LANGUAGE_POSITIONS.keys() if unsupported_languages: raise ValueError( f"Unknown language(s): {', '.join(map(repr, unsupported_languages))}" ) if region is None: region = "" for language in languages: locale = f"{language}-{region}" if not region or not _isvalidlocale(locale): locale = language locale_dict[locale] = (language, region) if not use_given_order: locale_dict = dict( sorted(locale_dict.items(), key=lambda x: _LANGUAGE_POSITIONS[x[1][0]]) ) for shortname, lang_reg in locale_dict.items(): with self._load_lock: if shortname not in self._loaded_locales: lang, _reg = lang_reg if lang in self._loaded_languages: locale_obj = Locale( shortname, language_info=deepcopy(self._loaded_languages[lang]), ) else: language_info = ( import_module( "dateparser.data.date_translation_data." + lang ) ).info locale_obj = Locale( shortname, language_info=deepcopy(language_info) ) self._loaded_languages[lang] = language_info # Store only once fully built so concurrent readers never see # a half-initialised locale. self._loaded_locales[shortname] = locale_obj locale_obj = self._loaded_locales[shortname] yield shortname, locale_obj
default_loader = LocaleDataLoader()