Skip to content

Commit f692b5f

Browse files
author
Sylvain MARIE
committed
Added a get_current_locale function, compliant with setlocale, to avoid misuses of getlocale (see pandas-dev#46595). Added tests.
1 parent 59cd733 commit f692b5f

File tree

2 files changed

+74
-12
lines changed

2 files changed

+74
-12
lines changed

pandas/_config/localization.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@
1717
from pandas._config.config import options
1818

1919

20+
def get_current_locale(lc_var: int = locale.LC_ALL):
21+
"""
22+
Return the current locale associated with category ``lc_var``.
23+
This is a convenience method to avoid misuses of ``getlocale``. Indeed the
24+
result returned by ``getlocale`` can not always be used to set back the locale
25+
using ``setlocale``. See GH#46595
26+
27+
Parameters
28+
----------
29+
lc_var : int, default `locale.LC_ALL`
30+
The category of the locale being set.
31+
32+
Returns
33+
-------
34+
35+
"""
36+
# `getlocale` does not always play well with `setLocale`, avoid using it
37+
# return locale.getlocale()
38+
39+
# Using `setlocale` with no second argument returns the current locale
40+
return locale.setlocale(lc_var)
41+
42+
2043
@contextmanager
2144
def set_locale(
2245
new_locale: str | tuple[str, str], lc_var: int = locale.LC_ALL
@@ -39,8 +62,8 @@ def set_locale(
3962
particular locale, without globally setting the locale. This probably isn't
4063
thread-safe.
4164
"""
42-
# getlocale is wrong, see GH#46595
43-
current_locale = locale.setlocale(lc_var)
65+
# getlocale is not always compliant with setlocale, see GH#46595
66+
current_locale = get_current_locale(lc_var=lc_var)
4467

4568
try:
4669
locale.setlocale(lc_var, new_locale)

pandas/tests/config/test_localization.py

+49-10
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,74 @@
66

77
from pandas._config.localization import (
88
can_set_locale,
9+
get_current_locale,
910
get_locales,
1011
set_locale,
1112
)
1213

13-
from pandas.compat import is_platform_windows
14-
1514
import pandas as pd
1615

1716
_all_locales = get_locales() or []
1817
_current_locale = locale.setlocale(locale.LC_ALL) # getlocale() is wrong, see GH#46595
1918

20-
# Don't run any of these tests if we are on Windows or have no locales.
21-
pytestmark = pytest.mark.skipif(
22-
is_platform_windows() or not _all_locales, reason="Need non-Windows and locales"
23-
)
19+
# Don't run any of these tests if we have no locales.
20+
pytestmark = pytest.mark.skipif(not _all_locales, reason="Need locales")
2421

2522
_skip_if_only_one_locale = pytest.mark.skipif(
2623
len(_all_locales) <= 1, reason="Need multiple locales for meaningful test"
2724
)
2825

2926

30-
def test_can_set_locale_valid_set():
27+
@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
28+
def test_get_current_locale(lc_var):
29+
# Make sure that `get_current_locale` uses setlocale
30+
before_locale = get_current_locale(lc_var)
31+
assert before_locale == locale.setlocale(lc_var)
32+
33+
34+
@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
35+
def test_can_set_current_locale(lc_var):
36+
# Can set the current locale
37+
before_locale = get_current_locale(lc_var)
38+
assert can_set_locale(before_locale, lc_var=lc_var)
39+
after_locale = get_current_locale(lc_var)
40+
assert before_locale == after_locale
41+
42+
43+
@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
44+
def test_can_set_locale_valid_set(lc_var):
3145
# Can set the default locale.
32-
assert can_set_locale("")
46+
before_locale = get_current_locale(lc_var)
47+
assert can_set_locale("", lc_var=lc_var)
48+
after_locale = get_current_locale(lc_var)
49+
assert before_locale == after_locale
3350

3451

35-
def test_can_set_locale_invalid_set():
52+
@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
53+
def test_can_set_locale_invalid_set(lc_var):
3654
# Cannot set an invalid locale.
37-
assert not can_set_locale("non-existent_locale")
55+
before_locale = get_current_locale(lc_var)
56+
assert not can_set_locale("non-existent_locale", lc_var=lc_var)
57+
after_locale = get_current_locale(lc_var)
58+
assert before_locale == after_locale
59+
60+
61+
@pytest.mark.parametrize(
62+
"lang,enc",
63+
[
64+
("it_CH", "UTF-8"),
65+
("en_US", "ascii"),
66+
("zh_CN", "GB2312"),
67+
("it_IT", "ISO-8859-1"),
68+
],
69+
)
70+
@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
71+
def test_can_set_locale_no_leak(lang, enc, lc_var):
72+
# Test that can_set_locale does not leak even when returning False. See GH#46595
73+
before_locale = get_current_locale(lc_var)
74+
can_set_locale((lang, enc), locale.LC_ALL)
75+
after_locale = get_current_locale(lc_var)
76+
assert before_locale == after_locale
3877

3978

4079
def test_can_set_locale_invalid_get(monkeypatch):

0 commit comments

Comments
 (0)