Skip to content

Commit 870d3c9

Browse files
committed
Avoid copying into a dict
1 parent a651608 commit 870d3c9

File tree

1 file changed

+22
-9
lines changed

1 file changed

+22
-9
lines changed

babel/localedata.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,23 @@
2020
import threading
2121
from collections.abc import Iterator, Mapping
2222
from itertools import chain
23-
from typing import Any, MutableMapping, TypeVar
23+
from typing import TYPE_CHECKING, Any, MutableMapping, TypeVar
2424

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

28+
if TYPE_CHECKING:
29+
from abc import abstractmethod
30+
from typing import runtime_checkable
31+
32+
@runtime_checkable
33+
class CopyableMutableMapping(MutableMapping[_Key, _Value]):
34+
"""An abstract base class for copyable mutable mappings."""
35+
36+
@abstractmethod
37+
def copy(self) -> CopyableMutableMapping[_Key, _Value]:
38+
...
39+
2840
_cache: dict[str, Any] = {}
2941
_cache_lock = threading.RLock()
3042
_dirname = os.path.join(os.path.dirname(__file__), 'locale-data')
@@ -167,7 +179,7 @@ def merge(dict1: MutableMapping[Any, Any], dict2: Mapping[Any, Any]) -> None:
167179
for key, val2 in dict2.items():
168180
if val2 is not None:
169181
val1 = dict1.get(key)
170-
if isinstance(val2, dict):
182+
if isinstance(val2, MutableMapping):
171183
if val1 is None:
172184
val1 = {}
173185
if isinstance(val1, Alias):
@@ -198,7 +210,7 @@ def __init__(self, keys: tuple[str, ...]) -> None:
198210
def __repr__(self) -> str:
199211
return f"<{type(self).__name__} {self.keys!r}>"
200212

201-
def resolve(self, data: Mapping[_Key, _Value]) -> dict[_Key, _Value]:
213+
def resolve(self, data: CopyableMutableMapping[_Key, _Value]) -> CopyableMutableMapping[_Key, _Value]:
202214
"""Resolve the alias based on the given data.
203215
204216
This is done recursively, so if one alias resolves to a second alias,
@@ -215,18 +227,18 @@ def resolve(self, data: Mapping[_Key, _Value]) -> dict[_Key, _Value]:
215227
elif isinstance(data, tuple):
216228
alias, others = data
217229
data = alias.resolve(base)
218-
return dict(data)
230+
return data
219231

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

225-
def __init__(self, data: MutableMapping[_Key, _Value], base: Mapping[_Key, _Value] | None = None):
226-
self._data = dict(data) # Storing as a dict allows copy() to work
237+
def __init__(self, data: CopyableMutableMapping[_Key, _Value], base: CopyableMutableMapping[_Key, _Value] | None = None):
238+
self._data = data
227239
if base is None:
228240
base = data
229-
self.base = dict(base)
241+
self.base = base
230242

231243
def __len__(self) -> int:
232244
return len(self._data)
@@ -242,8 +254,9 @@ def __getitem__(self, key: _Key) -> _Value:
242254
alias, others = val
243255
val = alias.resolve(self.base).copy()
244256
merge(val, others)
245-
if isinstance(val, dict): # Return a nested alias-resolving dict
246-
val = LocaleDataDict(val, base=self.base)
257+
if isinstance(val, MutableMapping) and not isinstance(val, LocaleDataDict):
258+
# Return a nested alias-resolving dict
259+
val = LocaleDataDict(val, base=self.base) # type: ignore # assume copyable
247260
if val is not orig:
248261
self._data[key] = val # type: ignore # Cache the resolved value
249262
return val # type: ignore

0 commit comments

Comments
 (0)