Skip to content

Commit 7de0564

Browse files
CPython Devleopersyouknowone
authored andcommitted
Update locale from CPython 3.11.2
1 parent cfa9de4 commit 7de0564

File tree

2 files changed

+145
-55
lines changed

2 files changed

+145
-55
lines changed

Lib/locale.py

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"setlocale", "resetlocale", "localeconv", "strcoll", "strxfrm",
2929
"str", "atof", "atoi", "format", "format_string", "currency",
3030
"normalize", "LC_CTYPE", "LC_COLLATE", "LC_TIME", "LC_MONETARY",
31-
"LC_NUMERIC", "LC_ALL", "CHAR_MAX"]
31+
"LC_NUMERIC", "LC_ALL", "CHAR_MAX", "getencoding"]
3232

3333
def _strcoll(a,b):
3434
""" strcoll(string,string) -> int.
@@ -185,8 +185,14 @@ def _format(percent, value, grouping=False, monetary=False, *additional):
185185
formatted = percent % ((value,) + additional)
186186
else:
187187
formatted = percent % value
188+
if percent[-1] in 'eEfFgGdiu':
189+
formatted = _localize(formatted, grouping, monetary)
190+
return formatted
191+
192+
# Transform formatted as locale number according to the locale settings
193+
def _localize(formatted, grouping=False, monetary=False):
188194
# floats and decimal ints need special action!
189-
if percent[-1] in 'eEfFgG':
195+
if '.' in formatted:
190196
seps = 0
191197
parts = formatted.split('.')
192198
if grouping:
@@ -196,7 +202,7 @@ def _format(percent, value, grouping=False, monetary=False, *additional):
196202
formatted = decimal_point.join(parts)
197203
if seps:
198204
formatted = _strip_padding(formatted, seps)
199-
elif percent[-1] in 'diu':
205+
else:
200206
seps = 0
201207
if grouping:
202208
formatted, seps = _group(formatted, monetary=monetary)
@@ -267,7 +273,7 @@ def currency(val, symbol=True, grouping=False, international=False):
267273
raise ValueError("Currency formatting is not possible using "
268274
"the 'C' locale.")
269275

270-
s = _format('%%.%if' % digits, abs(val), grouping, monetary=True)
276+
s = _localize(f'{abs(val):.{digits}f}', grouping, monetary=True)
271277
# '<' and '>' are markers if the sign must be inserted between symbol and value
272278
s = '<' + s + '>'
273279

@@ -279,6 +285,8 @@ def currency(val, symbol=True, grouping=False, international=False):
279285
if precedes:
280286
s = smb + (separated and ' ' or '') + s
281287
else:
288+
if international and smb[-1] == ' ':
289+
smb = smb[:-1]
282290
s = s + (separated and ' ' or '') + smb
283291

284292
sign_pos = conv[val<0 and 'n_sign_posn' or 'p_sign_posn']
@@ -321,6 +329,10 @@ def delocalize(string):
321329
string = string.replace(dd, '.')
322330
return string
323331

332+
def localize(string, grouping=False, monetary=False):
333+
"""Parses a string as locale number according to the locale settings."""
334+
return _localize(string, grouping, monetary)
335+
324336
def atof(string, func=float):
325337
"Parses a string as a float according to the locale settings."
326338
return func(delocalize(string))
@@ -492,6 +504,10 @@ def _parse_localename(localename):
492504
return tuple(code.split('.')[:2])
493505
elif code == 'C':
494506
return None, None
507+
elif code == 'UTF-8':
508+
# On macOS "LC_CTYPE=UTF-8" is a valid locale setting
509+
# for getting UTF-8 handling for text.
510+
return None, 'UTF-8'
495511
raise ValueError('unknown locale: %s' % localename)
496512

497513
def _build_localename(localetuple):
@@ -539,6 +555,12 @@ def getdefaultlocale(envvars=('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE')):
539555
540556
"""
541557

558+
import warnings
559+
warnings.warn(
560+
"Use setlocale(), getencoding() and getlocale() instead",
561+
DeprecationWarning, stacklevel=2
562+
)
563+
542564
try:
543565
# check if it's supported by the _locale module
544566
import _locale
@@ -611,55 +633,72 @@ def resetlocale(category=LC_ALL):
611633
getdefaultlocale(). category defaults to LC_ALL.
612634
613635
"""
614-
_setlocale(category, _build_localename(getdefaultlocale()))
636+
import warnings
637+
warnings.warn(
638+
'Use locale.setlocale(locale.LC_ALL, "") instead',
639+
DeprecationWarning, stacklevel=2
640+
)
641+
642+
with warnings.catch_warnings():
643+
warnings.simplefilter('ignore', category=DeprecationWarning)
644+
loc = getdefaultlocale()
645+
646+
_setlocale(category, _build_localename(loc))
647+
648+
649+
try:
650+
from _locale import getencoding
651+
except ImportError:
652+
def getencoding():
653+
if hasattr(sys, 'getandroidapilevel'):
654+
# On Android langinfo.h and CODESET are missing, and UTF-8 is
655+
# always used in mbstowcs() and wcstombs().
656+
return 'utf-8'
657+
encoding = getdefaultlocale()[1]
658+
if encoding is None:
659+
# LANG not set, default to UTF-8
660+
encoding = 'utf-8'
661+
return encoding
615662

616-
if sys.platform.startswith("win"):
617-
# On Win32, this will return the ANSI code page
618-
def getpreferredencoding(do_setlocale = True):
663+
try:
664+
CODESET
665+
except NameError:
666+
def getpreferredencoding(do_setlocale=True):
619667
"""Return the charset that the user is likely using."""
668+
if sys.flags.warn_default_encoding:
669+
import warnings
670+
warnings.warn(
671+
"UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
672+
EncodingWarning, 2)
620673
if sys.flags.utf8_mode:
621-
return 'UTF-8'
622-
import _bootlocale
623-
return _bootlocale.getpreferredencoding(False)
674+
return 'utf-8'
675+
return getencoding()
624676
else:
625677
# On Unix, if CODESET is available, use that.
626-
try:
627-
CODESET
628-
except NameError:
629-
if hasattr(sys, 'getandroidapilevel'):
630-
# On Android langinfo.h and CODESET are missing, and UTF-8 is
631-
# always used in mbstowcs() and wcstombs().
632-
def getpreferredencoding(do_setlocale = True):
633-
return 'UTF-8'
634-
else:
635-
# Fall back to parsing environment variables :-(
636-
def getpreferredencoding(do_setlocale = True):
637-
"""Return the charset that the user is likely using,
638-
by looking at environment variables."""
639-
if sys.flags.utf8_mode:
640-
return 'UTF-8'
641-
res = getdefaultlocale()[1]
642-
if res is None:
643-
# LANG not set, default conservatively to ASCII
644-
res = 'ascii'
645-
return res
646-
else:
647-
def getpreferredencoding(do_setlocale = True):
648-
"""Return the charset that the user is likely using,
649-
according to the system configuration."""
650-
if sys.flags.utf8_mode:
651-
return 'UTF-8'
652-
import _bootlocale
653-
if do_setlocale:
654-
oldloc = setlocale(LC_CTYPE)
655-
try:
656-
setlocale(LC_CTYPE, "")
657-
except Error:
658-
pass
659-
result = _bootlocale.getpreferredencoding(False)
660-
if do_setlocale:
661-
setlocale(LC_CTYPE, oldloc)
662-
return result
678+
def getpreferredencoding(do_setlocale=True):
679+
"""Return the charset that the user is likely using,
680+
according to the system configuration."""
681+
682+
if sys.flags.warn_default_encoding:
683+
import warnings
684+
warnings.warn(
685+
"UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
686+
EncodingWarning, 2)
687+
if sys.flags.utf8_mode:
688+
return 'utf-8'
689+
690+
if not do_setlocale:
691+
return getencoding()
692+
693+
old_loc = setlocale(LC_CTYPE)
694+
try:
695+
try:
696+
setlocale(LC_CTYPE, "")
697+
except Error:
698+
pass
699+
return getencoding()
700+
finally:
701+
setlocale(LC_CTYPE, old_loc)
663702

664703

665704
### Database
@@ -734,6 +773,7 @@ def getpreferredencoding(do_setlocale = True):
734773
for k, v in sorted(locale_encoding_alias.items()):
735774
k = k.replace('_', '')
736775
locale_encoding_alias.setdefault(k, v)
776+
del k, v
737777

738778
#
739779
# The locale_alias table maps lowercase alias names to C locale names

Lib/test/test_locale.py

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from test.support import verbose, is_android
1+
from decimal import Decimal
2+
from test.support import verbose, is_android, is_emscripten, is_wasi
23
from test.support.warnings_helper import check_warnings
34
import unittest
45
import locale
@@ -335,8 +336,7 @@ def test_currency(self):
335336
euro = '\u20ac'
336337
self._test_currency(50000, "50000,00 " + euro)
337338
self._test_currency(50000, "50 000,00 " + euro, grouping=True)
338-
# XXX is the trailing space a bug?
339-
self._test_currency(50000, "50 000,00 EUR ",
339+
self._test_currency(50000, "50 000,00 EUR",
340340
grouping=True, international=True)
341341

342342

@@ -367,7 +367,7 @@ class TestEnUSCollation(BaseLocalizedTest, TestCollation):
367367
locale_type = locale.LC_ALL
368368

369369
def setUp(self):
370-
enc = codecs.lookup(locale.getpreferredencoding(False) or 'ascii').name
370+
enc = codecs.lookup(locale.getencoding() or 'ascii').name
371371
if enc not in ('utf-8', 'iso8859-1', 'cp1252'):
372372
raise unittest.SkipTest('encoding not suitable')
373373
if enc != 'iso8859-1' and (sys.platform == 'darwin' or is_android or
@@ -377,11 +377,19 @@ def setUp(self):
377377

378378
@unittest.skipIf(sys.platform.startswith('aix'),
379379
'bpo-29972: broken test on AIX')
380+
@unittest.skipIf(
381+
is_emscripten or is_wasi,
382+
"musl libc issue on Emscripten/WASI, bpo-46390"
383+
)
380384
def test_strcoll_with_diacritic(self):
381385
self.assertLess(locale.strcoll('à', 'b'), 0)
382386

383387
@unittest.skipIf(sys.platform.startswith('aix'),
384388
'bpo-29972: broken test on AIX')
389+
@unittest.skipIf(
390+
is_emscripten or is_wasi,
391+
"musl libc issue on Emscripten/WASI, bpo-46390"
392+
)
385393
def test_strxfrm_with_diacritic(self):
386394
self.assertLess(locale.strxfrm('à'), locale.strxfrm('b'))
387395

@@ -502,7 +510,7 @@ class TestMiscellaneous(unittest.TestCase):
502510
@unittest.expectedFailure
503511
def test_defaults_UTF8(self):
504512
# Issue #18378: on (at least) macOS setting LC_CTYPE to "UTF-8" is
505-
# valid. Futhermore LC_CTYPE=UTF is used by the UTF-8 locale coercing
513+
# valid. Furthermore LC_CTYPE=UTF is used by the UTF-8 locale coercing
506514
# during interpreter startup (on macOS).
507515
import _locale
508516
import os
@@ -524,7 +532,8 @@ def test_defaults_UTF8(self):
524532

525533
os.environ['LC_CTYPE'] = 'UTF-8'
526534

527-
self.assertEqual(locale.getdefaultlocale(), (None, 'UTF-8'))
535+
with check_warnings(('', DeprecationWarning)):
536+
self.assertEqual(locale.getdefaultlocale(), (None, 'UTF-8'))
528537

529538
finally:
530539
for k in orig_env:
@@ -536,6 +545,14 @@ def test_defaults_UTF8(self):
536545
if orig_getlocale is not None:
537546
_locale._getdefaultlocale = orig_getlocale
538547

548+
def test_getencoding(self):
549+
# Invoke getencoding to make sure it does not cause exceptions.
550+
enc = locale.getencoding()
551+
self.assertIsInstance(enc, str)
552+
self.assertNotEqual(enc, "")
553+
# make sure it is valid
554+
codecs.lookup(enc)
555+
539556
def test_getpreferredencoding(self):
540557
# Invoke getpreferredencoding to make sure it does not cause exceptions.
541558
enc = locale.getpreferredencoding()
@@ -573,7 +590,13 @@ def test_getsetlocale_issue1813(self):
573590
loc = locale.getlocale(locale.LC_CTYPE)
574591
if verbose:
575592
print('testing with %a' % (loc,), end=' ', flush=True)
576-
locale.setlocale(locale.LC_CTYPE, loc)
593+
try:
594+
locale.setlocale(locale.LC_CTYPE, loc)
595+
except locale.Error as exc:
596+
# bpo-37945: setlocale(LC_CTYPE) fails with getlocale(LC_CTYPE)
597+
# and the tr_TR locale on Windows. getlocale() builds a locale
598+
# which is not recognize by setlocale().
599+
self.skipTest(f"setlocale(LC_CTYPE, {loc!r}) failed: {exc!r}")
577600
self.assertEqual(loc, locale.getlocale(locale.LC_CTYPE))
578601

579602
def test_invalid_locale_format_in_localetuple(self):
@@ -639,5 +662,32 @@ def test_atoi(self):
639662
self._test_atoi('50 000', 50000)
640663

641664

665+
class BaseLocalizeTest(BaseLocalizedTest):
666+
667+
def _test_localize(self, value, out, grouping=False):
668+
self.assertEqual(locale.localize(value, grouping=grouping), out)
669+
670+
671+
class TestEnUSLocalize(EnUSCookedTest, BaseLocalizeTest):
672+
673+
def test_localize(self):
674+
self._test_localize('50000.00', '50000.00')
675+
self._test_localize(
676+
'{0:.16f}'.format(Decimal('1.15')), '1.1500000000000000')
677+
678+
679+
class TestCLocalize(CCookedTest, BaseLocalizeTest):
680+
681+
def test_localize(self):
682+
self._test_localize('50000.00', '50000.00')
683+
684+
685+
class TestfrFRLocalize(FrFRCookedTest, BaseLocalizeTest):
686+
687+
def test_localize(self):
688+
self._test_localize('50000.00', '50000,00')
689+
self._test_localize('50000.00', '50 000,00', grouping=True)
690+
691+
642692
if __name__ == '__main__':
643693
unittest.main()

0 commit comments

Comments
 (0)