Skip to content

Commit d37c531

Browse files
jbrockmendeljreback
authored andcommitted
[REF] Move remaining locale functions to _config.localization (#25861)
1 parent d8642e9 commit d37c531

File tree

9 files changed

+158
-146
lines changed

9 files changed

+158
-146
lines changed

pandas/_config/__init__.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
"""
22
pandas._config is considered explicitly upstream of everything else in pandas,
33
should have no intra-pandas dependencies.
4+
5+
importing `dates` and `display` ensures that keys needed by _libs
6+
are initialized.
47
"""
5-
__all__ = ["config", "get_option", "set_option", "reset_option",
6-
"describe_option", "option_context", "options"]
8+
__all__ = ["config", "detect_console_encoding", "get_option", "set_option",
9+
"reset_option", "describe_option", "option_context", "options"]
710
from pandas._config import config
11+
from pandas._config import dates # noqa:F401
812
from pandas._config.config import (
913
describe_option, get_option, option_context, options, reset_option,
1014
set_option)
15+
from pandas._config.display import detect_console_encoding

pandas/_config/dates.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
config for datetime formatting
3+
"""
4+
from pandas._config import config as cf
5+
6+
pc_date_dayfirst_doc = """
7+
: boolean
8+
When True, prints and parses dates with the day first, eg 20/01/2005
9+
"""
10+
11+
pc_date_yearfirst_doc = """
12+
: boolean
13+
When True, prints and parses dates with the year first, eg 2005/01/20
14+
"""
15+
16+
with cf.config_prefix('display'):
17+
# Needed upstream of `_libs` because these are used in tslibs.parsing
18+
cf.register_option('date_dayfirst', False, pc_date_dayfirst_doc,
19+
validator=cf.is_bool)
20+
cf.register_option('date_yearfirst', False, pc_date_yearfirst_doc,
21+
validator=cf.is_bool)

pandas/_config/display.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Unopinionated display configuration.
3+
"""
4+
import locale
5+
import sys
6+
7+
from pandas._config import config as cf
8+
9+
# -----------------------------------------------------------------------------
10+
# Global formatting options
11+
_initial_defencoding = None
12+
13+
14+
def detect_console_encoding():
15+
"""
16+
Try to find the most capable encoding supported by the console.
17+
slightly modified from the way IPython handles the same issue.
18+
"""
19+
global _initial_defencoding
20+
21+
encoding = None
22+
try:
23+
encoding = sys.stdout.encoding or sys.stdin.encoding
24+
except (AttributeError, IOError):
25+
pass
26+
27+
# try again for something better
28+
if not encoding or 'ascii' in encoding.lower():
29+
try:
30+
encoding = locale.getpreferredencoding()
31+
except Exception:
32+
pass
33+
34+
# when all else fails. this will usually be "ascii"
35+
if not encoding or 'ascii' in encoding.lower():
36+
encoding = sys.getdefaultencoding()
37+
38+
# GH#3360, save the reported defencoding at import time
39+
# MPL backends may change it. Make available for debugging.
40+
if not _initial_defencoding:
41+
_initial_defencoding = sys.getdefaultencoding()
42+
43+
return encoding
44+
45+
46+
pc_encoding_doc = """
47+
: str/unicode
48+
Defaults to the detected encoding of the console.
49+
Specifies the encoding to be used for strings returned by to_string,
50+
these are generally strings meant to be displayed on the console.
51+
"""
52+
53+
with cf.config_prefix('display'):
54+
cf.register_option('encoding', detect_console_encoding(), pc_encoding_doc,
55+
validator=cf.is_text)

pandas/_config/localization.py

+69
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"""
66
from contextlib import contextmanager
77
import locale
8+
import re
9+
import subprocess
10+
11+
from pandas._config.config import options
812

913

1014
@contextmanager
@@ -91,3 +95,68 @@ def _valid_locales(locales, normalize):
9195
normalizer = lambda x: x.strip()
9296

9397
return list(filter(can_set_locale, map(normalizer, locales)))
98+
99+
100+
def _default_locale_getter():
101+
try:
102+
raw_locales = subprocess.check_output(['locale -a'], shell=True)
103+
except subprocess.CalledProcessError as e:
104+
raise type(e)("{exception}, the 'locale -a' command cannot be found "
105+
"on your system".format(exception=e))
106+
return raw_locales
107+
108+
109+
def get_locales(prefix=None, normalize=True,
110+
locale_getter=_default_locale_getter):
111+
"""
112+
Get all the locales that are available on the system.
113+
114+
Parameters
115+
----------
116+
prefix : str
117+
If not ``None`` then return only those locales with the prefix
118+
provided. For example to get all English language locales (those that
119+
start with ``"en"``), pass ``prefix="en"``.
120+
normalize : bool
121+
Call ``locale.normalize`` on the resulting list of available locales.
122+
If ``True``, only locales that can be set without throwing an
123+
``Exception`` are returned.
124+
locale_getter : callable
125+
The function to use to retrieve the current locales. This should return
126+
a string with each locale separated by a newline character.
127+
128+
Returns
129+
-------
130+
locales : list of strings
131+
A list of locale strings that can be set with ``locale.setlocale()``.
132+
For example::
133+
134+
locale.setlocale(locale.LC_ALL, locale_string)
135+
136+
On error will return None (no locale available, e.g. Windows)
137+
138+
"""
139+
try:
140+
raw_locales = locale_getter()
141+
except Exception:
142+
return None
143+
144+
try:
145+
# raw_locales is "\n" separated list of locales
146+
# it may contain non-decodable parts, so split
147+
# extract what we can and then rejoin.
148+
raw_locales = raw_locales.split(b'\n')
149+
out_locales = []
150+
for x in raw_locales:
151+
out_locales.append(str(
152+
x, encoding=options.display.encoding))
153+
154+
except TypeError:
155+
pass
156+
157+
if prefix is None:
158+
return _valid_locales(out_locales, normalize)
159+
160+
pattern = re.compile('{prefix}.*'.format(prefix=prefix))
161+
found = pattern.findall('\n'.join(out_locales))
162+
return _valid_locales(found, normalize)

pandas/core/config_init.py

-24
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
is_bool, is_callable, is_instance_factory, is_int, is_one_of_factory,
1515
is_text)
1616

17-
from pandas.io.formats.console import detect_console_encoding
1817
from pandas.io.formats.terminal import is_terminal
1918

2019
# compute
@@ -110,16 +109,6 @@ def use_numexpr_cb(key):
110109
pandas objects (if it is available).
111110
"""
112111

113-
pc_date_dayfirst_doc = """
114-
: boolean
115-
When True, prints and parses dates with the day first, eg 20/01/2005
116-
"""
117-
118-
pc_date_yearfirst_doc = """
119-
: boolean
120-
When True, prints and parses dates with the year first, eg 2005/01/20
121-
"""
122-
123112
pc_pprint_nest_depth = """
124113
: int
125114
Controls the number of nested levels to process when pretty-printing
@@ -131,13 +120,6 @@ def use_numexpr_cb(key):
131120
elements in outer levels within groups)
132121
"""
133122

134-
pc_encoding_doc = """
135-
: str/unicode
136-
Defaults to the detected encoding of the console.
137-
Specifies the encoding to be used for strings returned by to_string,
138-
these are generally strings meant to be displayed on the console.
139-
"""
140-
141123
float_format_doc = """
142124
: callable
143125
The callable should accept a floating point number and return
@@ -331,16 +313,10 @@ def table_schema_cb(key):
331313
validator=is_text)
332314
cf.register_option('notebook_repr_html', True, pc_nb_repr_h_doc,
333315
validator=is_bool)
334-
cf.register_option('date_dayfirst', False, pc_date_dayfirst_doc,
335-
validator=is_bool)
336-
cf.register_option('date_yearfirst', False, pc_date_yearfirst_doc,
337-
validator=is_bool)
338316
cf.register_option('pprint_nest_depth', 3, pc_pprint_nest_depth,
339317
validator=is_int)
340318
cf.register_option('multi_sparse', True, pc_multi_sparse_doc,
341319
validator=is_bool)
342-
cf.register_option('encoding', detect_console_encoding(), pc_encoding_doc,
343-
validator=is_text)
344320
cf.register_option('expand_frame_repr', True, pc_expand_repr_doc)
345321
cf.register_option('show_dimensions', 'truncate', pc_show_dimensions_doc,
346322
validator=is_one_of_factory([True, False, 'truncate']))

pandas/io/formats/console.py

-39
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,8 @@
22
Internal module for console introspection
33
"""
44

5-
import locale
6-
import sys
7-
85
from pandas.io.formats.terminal import get_terminal_size
96

10-
# -----------------------------------------------------------------------------
11-
# Global formatting options
12-
_initial_defencoding = None
13-
14-
15-
def detect_console_encoding():
16-
"""
17-
Try to find the most capable encoding supported by the console.
18-
slightly modified from the way IPython handles the same issue.
19-
"""
20-
global _initial_defencoding
21-
22-
encoding = None
23-
try:
24-
encoding = sys.stdout.encoding or sys.stdin.encoding
25-
except (AttributeError, IOError):
26-
pass
27-
28-
# try again for something better
29-
if not encoding or 'ascii' in encoding.lower():
30-
try:
31-
encoding = locale.getpreferredencoding()
32-
except Exception:
33-
pass
34-
35-
# when all else fails. this will usually be "ascii"
36-
if not encoding or 'ascii' in encoding.lower():
37-
encoding = sys.getdefaultencoding()
38-
39-
# GH3360, save the reported defencoding at import time
40-
# MPL backends may change it. Make available for debugging.
41-
if not _initial_defencoding:
42-
_initial_defencoding = sys.getdefaultencoding()
43-
44-
return encoding
45-
467

478
def get_console_size():
489
"""Return console size as tuple = (width, height).

pandas/tests/config/test_localization.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55

66
import pytest
77

8-
from pandas._config.localization import can_set_locale, set_locale
8+
from pandas._config.localization import can_set_locale, get_locales, set_locale
99

1010
from pandas.compat import is_platform_windows
1111

12-
# TODO: move get_locales into localization, making `tm` import unnecessary.
13-
# This is blocked by the need for core.config to be moved to _config.
14-
import pandas.util.testing as tm
15-
16-
_all_locales = tm.get_locales() or []
12+
_all_locales = get_locales() or []
1713
_current_locale = locale.getlocale()
1814

1915
# Don't run any of these tests if we are on Windows or have no locales.
@@ -55,7 +51,7 @@ def test_get_locales_at_least_one():
5551
@_skip_if_only_one_locale
5652
def test_get_locales_prefix():
5753
first_locale = _all_locales[0]
58-
assert len(tm.get_locales(prefix=first_locale[:2])) > 0
54+
assert len(get_locales(prefix=first_locale[:2])) > 0
5955

6056

6157
@_skip_if_only_one_locale

pandas/tests/io/formats/test_console.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import pytest
44

5-
from pandas.io.formats.console import detect_console_encoding
5+
from pandas._config import detect_console_encoding
6+
67
from pandas.io.formats.terminal import _get_terminal_size_tput
78

89

0 commit comments

Comments
 (0)