20
20
import threading
21
21
from collections .abc import Iterator , Mapping
22
22
from itertools import chain
23
- from typing import Any , MutableMapping , TypeVar
23
+ from typing import TYPE_CHECKING , Any , MutableMapping , TypeVar
24
24
25
25
_Key = TypeVar ('_Key' ,)
26
26
_Value = TypeVar ('_Value' )
27
27
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
+
28
40
_cache : dict [str , Any ] = {}
29
41
_cache_lock = threading .RLock ()
30
42
_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:
167
179
for key , val2 in dict2 .items ():
168
180
if val2 is not None :
169
181
val1 = dict1 .get (key )
170
- if isinstance (val2 , dict ):
182
+ if isinstance (val2 , MutableMapping ):
171
183
if val1 is None :
172
184
val1 = {}
173
185
if isinstance (val1 , Alias ):
@@ -198,7 +210,7 @@ def __init__(self, keys: tuple[str, ...]) -> None:
198
210
def __repr__ (self ) -> str :
199
211
return f"<{ type (self ).__name__ } { self .keys !r} >"
200
212
201
- def resolve (self , data : Mapping [_Key , _Value ]) -> dict [_Key , _Value ]:
213
+ def resolve (self , data : CopyableMutableMapping [_Key , _Value ]) -> CopyableMutableMapping [_Key , _Value ]:
202
214
"""Resolve the alias based on the given data.
203
215
204
216
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]:
215
227
elif isinstance (data , tuple ):
216
228
alias , others = data
217
229
data = alias .resolve (base )
218
- return dict ( data )
230
+ return data
219
231
220
232
class LocaleDataDict (MutableMapping [_Key , _Value ]):
221
233
"""Dictionary wrapper that automatically resolves aliases to the actual
222
234
values.
223
235
"""
224
236
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
227
239
if base is None :
228
240
base = data
229
- self .base = dict ( base )
241
+ self .base = base
230
242
231
243
def __len__ (self ) -> int :
232
244
return len (self ._data )
@@ -242,8 +254,9 @@ def __getitem__(self, key: _Key) -> _Value:
242
254
alias , others = val
243
255
val = alias .resolve (self .base ).copy ()
244
256
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
247
260
if val is not orig :
248
261
self ._data [key ] = val # type: ignore # Cache the resolved value
249
262
return val # type: ignore
0 commit comments