diff --git a/babel/localedata.py b/babel/localedata.py index 4648e6626..02d796b27 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -263,15 +263,19 @@ def __iter__(self) -> Iterator[str | int | None]: def __getitem__(self, key: str | int | None) -> Any: orig = val = self._data[key] + resolved = False if isinstance(val, Alias): # resolve an alias val = val.resolve(self.base) + resolved = True if isinstance(val, tuple): # Merge a partial dict with an alias alias, others = val val = alias.resolve(self.base).copy() merge(val, others) + resolved = True if isinstance(val, dict): # Return a nested alias-resolving dict val = LocaleDataDict(val, base=self.base) - if val is not orig: + # Don't memoize resolved aliases: the backing dict may be shared with a parent locale (issue #1234). + if val is not orig and not resolved: self._data[key] = val return val diff --git a/tests/test_localedata.py b/tests/test_localedata.py index 42810b992..5c68e7a45 100644 --- a/tests/test_localedata.py +++ b/tests/test_localedata.py @@ -10,6 +10,7 @@ # individuals. For the exact contribution history, see the revision # history and logs, available at https://github.com/python-babel/babel/commits/master/. +import datetime import os import pickle import random @@ -62,6 +63,19 @@ def test_load(): assert localedata.load('en_US') is localedata.load('en_US') +def test_resolving_alias_does_not_corrupt_shared_data(): + # Resolving an alias in one locale must not leak into a sibling locale (issue #1234). + from babel.dates import format_date + + localedata._cache.clear() + date = datetime.date(2024, 10, 15) + # 'he' aliases its stand-alone wide months to the Hebrew format ones. + hebrew = format_date(date, 'LLLL', 'he') + german = format_date(date, 'LLLL', 'de') + assert german != hebrew + assert german == 'Oktober' + + def test_load_inheritance(monkeypatch): from babel.localedata import _cache