]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
Remove localedata writeback shared-cache-data
authorAarni Koskela <akx@iki.fi>
Wed, 29 Jul 2026 14:29:50 +0000 (17:29 +0300)
committerAarni Koskela <akx@iki.fi>
Wed, 29 Jul 2026 14:29:50 +0000 (17:29 +0300)
Fixes #1234

babel/localedata.py
tests/test_localedata.py

index 4648e66266c93616c42a1a9707fc87dd609825bf..0600a81744116a79b9742e20c87466776a2679cc 100644 (file)
@@ -262,7 +262,7 @@ class LocaleDataDict(abc.MutableMapping):
         return iter(self._data)
 
     def __getitem__(self, key: str | int | None) -> Any:
-        orig = val = self._data[key]
+        val = self._data[key]
         if isinstance(val, Alias):  # resolve an alias
             val = val.resolve(self.base)
         if isinstance(val, tuple):  # Merge a partial dict with an alias
@@ -271,8 +271,6 @@ class LocaleDataDict(abc.MutableMapping):
             merge(val, others)
         if isinstance(val, dict):  # Return a nested alias-resolving dict
             val = LocaleDataDict(val, base=self.base)
-        if val is not orig:
-            self._data[key] = val
         return val
 
     def __setitem__(self, key: str | int | None, value: Any) -> None:
index 42810b992b46369876d7f963d0b69b02f788c0ac..030bb90aa798bf74dfae685722d96e759735368e 100644 (file)
@@ -55,6 +55,8 @@ def test_merge_with_alias_and_resolve():
     assert d1 == {'x': {'a': 1, 'b': 12, 'c': 3, 'd': 14}, 'y': (alias, {'b': 22, 'e': 25})}
     d = localedata.LocaleDataDict(d1)
     assert dict(d.items()) == {'x': {'a': 1, 'b': 12, 'c': 3, 'd': 14}, 'y': {'a': 1, 'b': 22, 'c': 3, 'd': 14, 'e': 25}}
+    # Resolving the partial alias must not have written the result back into the underlying data (GH-1234)
+    assert d1['y'] == (alias, {'b': 22, 'e': 25})
 
 
 def test_load():
@@ -62,6 +64,43 @@ def test_load():
     assert localedata.load('en_US') is localedata.load('en_US')
 
 
+def _calendar_snapshot(name):
+    locale = Locale.parse(name)
+    return {
+        'months': dict(locale.months['stand-alone']['wide']),
+        'months_abbr': dict(locale.months['format']['abbreviated']),
+        'days': dict(locale.days['stand-alone']['wide']),
+        'quarters': dict(locale.quarters['stand-alone']['wide']),
+        'eras': dict(locale.eras['wide']),
+    }
+
+
+@pytest.mark.parametrize('reverse', (False, True))
+def test_no_cross_locale_contamination(reverse):
+    """
+    Alias-heavy calendar data (e.g. what `format_date(..., 'LLLL')` reads)
+    must be identical whether a locale is loaded into a cold cache or after
+    other locales have already been read: e.g.
+    * reading 'he' must not turn Norwegian month names Hebrew
+    * reading 'aa' must not turn everyone else's stand-alone quarters into root's 'Q1' placeholders.
+
+    Regression test for https://github.com/python-babel/babel/issues/1234
+    """
+    locales = ('aa', 'de', 'fr', 'he', 'ja', 'no')
+    cold = {}
+    for name in locales:
+        localedata._cache.clear()
+        cold[name] = _calendar_snapshot(name)
+
+    assert cold['he']['months'] != cold['de']['months']  # Sanity check
+
+    localedata._cache.clear()
+    warm = {name: _calendar_snapshot(name) for name in (reversed(locales) if reverse else locales)}
+    assert warm == cold
+    # Repeated reads in the warmed-up state must stay correct too
+    assert {name: _calendar_snapshot(name) for name in locales} == cold
+
+
 def test_load_inheritance(monkeypatch):
     from babel.localedata import _cache