Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/babel/localedata.py: 27%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2babel.localedata
3~~~~~~~~~~~~~~~~
5Low-level locale data access.
7:note: The `Locale` class, which uses this module under the hood, provides a
8 more convenient interface for accessing the locale data.
10:copyright: (c) 2013-2025 by the Babel Team.
11:license: BSD, see LICENSE for more details.
12"""
14from __future__ import annotations
16import os
17import pickle
18import re
19import sys
20import threading
21from collections import abc
22from collections.abc import Iterator, Mapping, MutableMapping
23from functools import lru_cache
24from itertools import chain
25from typing import Any
27_cache: dict[str, Any] = {}
28_cache_lock = threading.RLock()
29_dirname = os.path.join(os.path.dirname(__file__), 'locale-data')
30_windows_reserved_name_re = re.compile("^(con|prn|aux|nul|com[0-9]|lpt[0-9])$", re.I)
33def normalize_locale(name: str) -> str | None:
34 """Normalize a locale ID by stripping spaces and apply proper casing.
36 Returns the normalized locale ID string or `None` if the ID is not
37 recognized.
38 """
39 if not name or not isinstance(name, str):
40 return None
41 name = name.strip().lower()
42 for locale_id in chain.from_iterable([_cache, locale_identifiers()]):
43 if name == locale_id.lower():
44 return locale_id
47def resolve_locale_filename(name: os.PathLike[str] | str) -> str:
48 """
49 Resolve a locale identifier to a `.dat` path on disk.
50 """
52 # Clean up any possible relative paths.
53 name = os.path.basename(name)
55 # Ensure we're not left with one of the Windows reserved names.
56 if sys.platform == "win32" and _windows_reserved_name_re.match(os.path.splitext(name)[0]):
57 raise ValueError(f"Name {name} is invalid on Windows")
59 # Build the path.
60 return os.path.join(_dirname, f"{name}.dat")
63def exists(name: str) -> bool:
64 """Check whether locale data is available for the given locale.
66 Returns `True` if it exists, `False` otherwise.
68 :param name: the locale identifier string
69 """
70 if not name or not isinstance(name, str):
71 return False
72 if name in _cache:
73 return True
74 file_found = os.path.exists(resolve_locale_filename(name))
75 return True if file_found else bool(normalize_locale(name))
78@lru_cache(maxsize=None)
79def locale_identifiers() -> list[str]:
80 """Return a list of all locale identifiers for which locale data is
81 available.
83 This data is cached after the first invocation.
84 You can clear the cache by calling `locale_identifiers.cache_clear()`.
86 .. versionadded:: 0.8.1
88 :return: a list of locale identifiers (strings)
89 """
90 return [
91 stem
92 for stem, extension in (
93 os.path.splitext(filename) for filename in os.listdir(_dirname)
94 )
95 if extension == '.dat' and stem != 'root'
96 ]
99def _is_non_likely_script(name: str) -> bool:
100 """Return whether the locale is of the form ``lang_Script``,
101 and the script is not the likely script for the language.
103 This implements the behavior of the ``nonlikelyScript`` value of the
104 ``localRules`` attribute for parent locales added in CLDR 45.
105 """
106 from babel.core import get_global, parse_locale
108 try:
109 lang, territory, script, variant, *rest = parse_locale(name)
110 except ValueError:
111 return False
113 if lang and script and not territory and not variant and not rest:
114 likely_subtag = get_global('likely_subtags').get(lang)
115 _, _, likely_script, *_ = parse_locale(likely_subtag)
116 return script != likely_script
117 return False
120def load(name: os.PathLike[str] | str, merge_inherited: bool = True) -> dict[str, Any]:
121 """Load the locale data for the given locale.
123 The locale data is a dictionary that contains much of the data defined by
124 the Common Locale Data Repository (CLDR). This data is stored as a
125 collection of pickle files inside the ``babel`` package.
127 >>> d = load('en_US')
128 >>> d['languages']['sv']
129 'Swedish'
131 Note that the results are cached, and subsequent requests for the same
132 locale return the same dictionary:
134 >>> d1 = load('en_US')
135 >>> d2 = load('en_US')
136 >>> d1 is d2
137 True
139 :param name: the locale identifier string (or "root")
140 :param merge_inherited: whether the inherited data should be merged into
141 the data of the requested locale
142 :raise `IOError`: if no locale data file is found for the given locale
143 identifier, or one of the locales it inherits from
144 """
145 name = os.path.basename(name)
146 _cache_lock.acquire()
147 try:
148 data = _cache.get(name)
149 if not data:
150 # Load inherited data
151 if name == 'root' or not merge_inherited:
152 data = {}
153 else:
154 from babel.core import get_global
156 parent = get_global('parent_exceptions').get(name)
157 if not parent:
158 if _is_non_likely_script(name):
159 parent = 'root'
160 else:
161 parts = name.split('_')
162 parent = "root" if len(parts) == 1 else "_".join(parts[:-1])
163 data = load(parent).copy()
164 filename = resolve_locale_filename(name)
165 with open(filename, 'rb') as fileobj:
166 if name != 'root' and merge_inherited:
167 merge(data, pickle.load(fileobj))
168 else:
169 data = pickle.load(fileobj)
170 _cache[name] = data
171 return data
172 finally:
173 _cache_lock.release()
176def merge(dict1: MutableMapping[Any, Any], dict2: Mapping[Any, Any]) -> None:
177 """Merge the data from `dict2` into the `dict1` dictionary, making copies
178 of nested dictionaries.
180 >>> d = {1: 'foo', 3: 'baz'}
181 >>> merge(d, {1: 'Foo', 2: 'Bar'})
182 >>> sorted(d.items())
183 [(1, 'Foo'), (2, 'Bar'), (3, 'baz')]
185 :param dict1: the dictionary to merge into
186 :param dict2: the dictionary containing the data that should be merged
187 """
188 for key, val2 in dict2.items():
189 if val2 is not None:
190 val1 = dict1.get(key)
191 if isinstance(val2, dict):
192 if val1 is None:
193 val1 = {}
194 if isinstance(val1, Alias):
195 val1 = (val1, val2)
196 elif isinstance(val1, tuple):
197 alias, others = val1
198 others = others.copy()
199 merge(others, val2)
200 val1 = (alias, others)
201 else:
202 val1 = val1.copy()
203 merge(val1, val2)
204 else:
205 val1 = val2
206 dict1[key] = val1
209class Alias:
210 """Representation of an alias in the locale data.
212 An alias is a value that refers to some other part of the locale data,
213 as specified by the `keys`.
214 """
216 def __init__(self, keys: tuple[str, ...]) -> None:
217 self.keys = tuple(keys)
219 def __repr__(self) -> str:
220 return f"<{type(self).__name__} {self.keys!r}>"
222 def resolve(self, data: Mapping[str | int | None, Any]) -> Mapping[str | int | None, Any]:
223 """Resolve the alias based on the given data.
225 This is done recursively, so if one alias resolves to a second alias,
226 that second alias will also be resolved.
228 :param data: the locale data
229 :type data: `dict`
230 """
231 base = data
232 for key in self.keys:
233 data = data[key]
234 if isinstance(data, Alias):
235 data = data.resolve(base)
236 elif isinstance(data, tuple):
237 alias, others = data
238 data = alias.resolve(base)
239 return data
242class LocaleDataDict(abc.MutableMapping):
243 """Dictionary wrapper that automatically resolves aliases to the actual
244 values.
245 """
247 def __init__(
248 self,
249 data: MutableMapping[str | int | None, Any],
250 base: Mapping[str | int | None, Any] | None = None,
251 ):
252 self._data = data
253 if base is None:
254 base = data
255 self.base = base
257 def __len__(self) -> int:
258 return len(self._data)
260 def __iter__(self) -> Iterator[str | int | None]:
261 return iter(self._data)
263 def __getitem__(self, key: str | int | None) -> Any:
264 orig = val = self._data[key]
265 if isinstance(val, Alias): # resolve an alias
266 val = val.resolve(self.base)
267 if isinstance(val, tuple): # Merge a partial dict with an alias
268 alias, others = val
269 val = alias.resolve(self.base).copy()
270 merge(val, others)
271 if isinstance(val, dict): # Return a nested alias-resolving dict
272 val = LocaleDataDict(val, base=self.base)
273 if val is not orig:
274 self._data[key] = val
275 return val
277 def __setitem__(self, key: str | int | None, value: Any) -> None:
278 self._data[key] = value
280 def __delitem__(self, key: str | int | None) -> None:
281 del self._data[key]
283 def copy(self) -> LocaleDataDict:
284 return LocaleDataDict(self._data.copy(), base=self.base)