Skip to content

Commit 2171b6c

Browse files
committed
Avoid copying into a dict
1 parent 57c7819 commit 2171b6c

1 file changed

Lines changed: 22 additions & 9 deletions

File tree

babel/localedata.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,23 @@
2121
from collections.abc import Iterator, Mapping, MutableMapping
2222
from functools import lru_cache
2323
from itertools import chain
24-
from typing import Any, MutableMapping, TypeVar
24+
from typing import TYPE_CHECKING, Any, MutableMapping, TypeVar
2525

2626
_Key = TypeVar('_Key',)
2727
_Value = TypeVar('_Value')
2828

29+
if TYPE_CHECKING:
30+
from abc import abstractmethod
31+
from typing import runtime_checkable
32+
33+
@runtime_checkable
34+
class CopyableMutableMapping(MutableMapping[_Key, _Value]):
35+
"""An abstract base class for copyable mutable mappings."""
36+
37+
@abstractmethod
38+
def copy(self) -> CopyableMutableMapping[_Key, _Value]:
39+
...
40+
2941
_cache: dict[str, Any] = {}
3042
_cache_lock = threading.RLock()
3143
_dirname = os.path.join(os.path.dirname(__file__), 'locale-data')
@@ -164,7 +176,7 @@ def merge(dict1: MutableMapping[Any, Any], dict2: Mapping[Any, Any]) -> None:
164176
for key, val2 in dict2.items():
165177
if val2 is not None:
166178
val1 = dict1.get(key)
167-
if isinstance(val2, dict):
179+
if isinstance(val2, MutableMapping):
168180
if val1 is None:
169181
val1 = {}
170182
if isinstance(val1, Alias):
@@ -195,7 +207,7 @@ def __init__(self, keys: tuple[str, ...]) -> None:
195207
def __repr__(self) -> str:
196208
return f"<{type(self).__name__} {self.keys!r}>"
197209

198-
def resolve(self, data: Mapping[_Key, _Value]) -> dict[_Key, _Value]:
210+
def resolve(self, data: CopyableMutableMapping[_Key, _Value]) -> CopyableMutableMapping[_Key, _Value]:
199211
"""Resolve the alias based on the given data.
200212
201213
This is done recursively, so if one alias resolves to a second alias,
@@ -212,18 +224,18 @@ def resolve(self, data: Mapping[_Key, _Value]) -> dict[_Key, _Value]:
212224
elif isinstance(data, tuple):
213225
alias, others = data
214226
data = alias.resolve(base)
215-
return dict(data)
227+
return data
216228

217229
class LocaleDataDict(MutableMapping[_Key, _Value]):
218230
"""Dictionary wrapper that automatically resolves aliases to the actual
219231
values.
220232
"""
221233

222-
def __init__(self, data: MutableMapping[_Key, _Value], base: Mapping[_Key, _Value] | None = None):
223-
self._data = dict(data) # Storing as a dict allows copy() to work
234+
def __init__(self, data: CopyableMutableMapping[_Key, _Value], base: CopyableMutableMapping[_Key, _Value] | None = None):
235+
self._data = data
224236
if base is None:
225237
base = data
226-
self.base = dict(base)
238+
self.base = base
227239

228240
def __len__(self) -> int:
229241
return len(self._data)
@@ -239,8 +251,9 @@ def __getitem__(self, key: _Key) -> _Value:
239251
alias, others = val
240252
val = alias.resolve(self.base).copy()
241253
merge(val, others)
242-
if isinstance(val, dict): # Return a nested alias-resolving dict
243-
val = LocaleDataDict(val, base=self.base)
254+
if isinstance(val, MutableMapping) and not isinstance(val, LocaleDataDict):
255+
# Return a nested alias-resolving dict
256+
val = LocaleDataDict(val, base=self.base) # type: ignore # assume copyable
244257
if val is not orig:
245258
self._data[key] = val # type: ignore # Cache the resolved value
246259
return val # type: ignore

0 commit comments

Comments
 (0)