Skip to content

Commit aff0b5d

Browse files
authored
Merge branch 'main' into ac-decimal/73487-pt2
2 parents 5b9d1f4 + d8a9466 commit aff0b5d

File tree

16 files changed

+272
-98
lines changed

16 files changed

+272
-98
lines changed

Doc/library/decimal.rst

Lines changed: 62 additions & 62 deletions
Large diffs are not rendered by default.

Doc/library/locale.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The :mod:`locale` module defines the following exception and functions:
4242
If *locale* is a pair, it is converted to a locale name using
4343
the locale aliasing engine.
4444
The language code has the same format as a :ref:`locale name <locale_name>`,
45-
but without encoding and ``@``-modifier.
45+
but without encoding.
4646
The language code and encoding can be ``None``.
4747

4848
If *locale* is omitted or ``None``, the current setting for *category* is
@@ -58,6 +58,9 @@ The :mod:`locale` module defines the following exception and functions:
5858
specified in the :envvar:`LANG` environment variable). If the locale is not
5959
changed thereafter, using multithreading should not cause problems.
6060

61+
.. versionchanged:: next
62+
Support language codes with ``@``-modifiers.
63+
6164

6265
.. function:: localeconv()
6366

@@ -366,11 +369,15 @@ The :mod:`locale` module defines the following exception and functions:
366369
values except :const:`LC_ALL`. It defaults to :const:`LC_CTYPE`.
367370

368371
The language code has the same format as a :ref:`locale name <locale_name>`,
369-
but without encoding and ``@``-modifier.
372+
but without encoding.
370373
The language code and encoding may be ``None`` if their values cannot be
371374
determined.
372375
The "C" locale is represented as ``(None, None)``.
373376

377+
.. versionchanged:: next
378+
``@``-modifier are no longer silently removed, but included in
379+
the language code.
380+
374381

375382
.. function:: getpreferredencoding(do_setlocale=True)
376383

Doc/library/string.templatelib.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ To write a t-string, use a ``'t'`` prefix instead of an ``'f'``, like so:
3434
>>> pi = 3.14
3535
>>> t't-strings are new in Python {pi!s}!'
3636
Template(
37-
strings=('t-strings are new in Python ', '.'),
37+
strings=('t-strings are new in Python ', '!'),
3838
interpolations=(Interpolation(3.14, 'pi', 's', ''),)
3939
)
4040

Doc/library/uuid.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ Here are some examples of typical usage of the :mod:`uuid` module::
411411
>>> import uuid
412412

413413
>>> # make a UUID based on the host ID and current time
414-
>>> uuid.uuid1()
414+
>>> uuid.uuid1() # doctest: +SKIP
415415
UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
416416

417417
>>> # make a UUID using an MD5 hash of a namespace UUID and a name
@@ -449,15 +449,24 @@ Here are some examples of typical usage of the :mod:`uuid` module::
449449
>>> uuid.MAX
450450
UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')
451451

452+
>>> # same as UUIDv1 but with fields reordered to improve DB locality
453+
>>> uuid.uuid6() # doctest: +SKIP
454+
UUID('1f0799c0-98b9-62db-92c6-a0d365b91053')
455+
452456
>>> # get UUIDv7 creation (local) time as a timestamp in milliseconds
453457
>>> u = uuid.uuid7()
454458
>>> u.time # doctest: +SKIP
455459
1743936859822
460+
456461
>>> # get UUIDv7 creation (local) time as a datetime object
457462
>>> import datetime as dt
458463
>>> dt.datetime.fromtimestamp(u.time / 1000) # doctest: +SKIP
459464
datetime.datetime(...)
460465

466+
>>> # make a UUID with custom blocks
467+
>>> uuid.uuid8(0x12345678, 0x9abcdef0, 0x11223344)
468+
UUID('00001234-5678-8ef0-8000-000011223344')
469+
461470

462471
.. _uuid-cli-example:
463472

Doc/whatsnew/3.15.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,15 @@ http.cookies
274274
(Contributed by Nick Burns and Senthil Kumaran in :gh:`92936`.)
275275

276276

277+
locale
278+
------
279+
280+
* :func:`~locale.setlocale` now supports language codes with ``@``-modifiers.
281+
``@``-modifier are no longer silently removed in :func:`~locale.getlocale`,
282+
but included in the language code.
283+
(Contributed by Serhiy Storchaka in :gh:`137729`.)
284+
285+
277286
math
278287
----
279288

Lib/html/parser.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,8 @@ def goahead(self, end):
271271
j -= len(suffix)
272272
break
273273
self.handle_comment(rawdata[i+4:j])
274-
elif startswith("<![CDATA[", i):
275-
if self._support_cdata:
276-
self.unknown_decl(rawdata[i+3:])
277-
else:
278-
self.handle_comment(rawdata[i+1:])
274+
elif startswith("<![CDATA[", i) and self._support_cdata:
275+
self.unknown_decl(rawdata[i+3:])
279276
elif rawdata[i:i+9].lower() == '<!doctype':
280277
self.handle_decl(rawdata[i+2:])
281278
elif startswith("<!", i):
@@ -350,15 +347,12 @@ def parse_html_declaration(self, i):
350347
if rawdata[i:i+4] == '<!--':
351348
# this case is actually already handled in goahead()
352349
return self.parse_comment(i)
353-
elif rawdata[i:i+9] == '<![CDATA[':
354-
if self._support_cdata:
355-
j = rawdata.find(']]>', i+9)
356-
if j < 0:
357-
return -1
358-
self.unknown_decl(rawdata[i+3: j])
359-
return j + 3
360-
else:
361-
return self.parse_bogus_comment(i)
350+
elif rawdata[i:i+9] == '<![CDATA[' and self._support_cdata:
351+
j = rawdata.find(']]>', i+9)
352+
if j < 0:
353+
return -1
354+
self.unknown_decl(rawdata[i+3: j])
355+
return j + 3
362356
elif rawdata[i:i+9].lower() == '<!doctype':
363357
# find the closing >
364358
gtpos = rawdata.find('>', i+9)

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ def cache_from_source(path, debug_override=None, *, optimization=None):
297297
# Strip initial drive from a Windows path. We know we have an absolute
298298
# path here, so the second part of the check rules out a POSIX path that
299299
# happens to contain a colon at the second character.
300-
if head[1] == ':' and head[0] not in path_separators:
300+
# Slicing avoids issues with an empty (or short) `head`.
301+
if head[1:2] == ':' and head[0:1] not in path_separators:
301302
head = head[2:]
302303

303304
# Strip initial path separator from `head` to complete the conversion

Lib/locale.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -375,12 +375,14 @@ def _replace_encoding(code, encoding):
375375
def _append_modifier(code, modifier):
376376
if modifier == 'euro':
377377
if '.' not in code:
378-
return code + '.ISO8859-15'
378+
# Linux appears to require keeping the "@euro" modifier in place,
379+
# even when using the ".ISO8859-15" encoding.
380+
return code + '.ISO8859-15@euro'
379381
_, _, encoding = code.partition('.')
380-
if encoding in ('ISO8859-15', 'UTF-8'):
382+
if encoding == 'UTF-8':
381383
return code
382384
if encoding == 'ISO8859-1':
383-
return _replace_encoding(code, 'ISO8859-15')
385+
code = _replace_encoding(code, 'ISO8859-15')
384386
return code + '@' + modifier
385387

386388
def normalize(localename):
@@ -485,13 +487,18 @@ def _parse_localename(localename):
485487
# Deal with locale modifiers
486488
code, modifier = code.split('@', 1)
487489
if modifier == 'euro' and '.' not in code:
488-
# Assume Latin-9 for @euro locales. This is bogus,
489-
# since some systems may use other encodings for these
490-
# locales. Also, we ignore other modifiers.
491-
return code, 'iso-8859-15'
490+
# Assume ISO8859-15 for @euro locales. Do note that some systems
491+
# may use other encodings for these locales, so this may not always
492+
# be correct.
493+
return code + '@euro', 'ISO8859-15'
494+
else:
495+
modifier = ''
492496

493497
if '.' in code:
494-
return tuple(code.split('.')[:2])
498+
code, encoding = code.split('.')[:2]
499+
if modifier:
500+
code += '@' + modifier
501+
return code, encoding
495502
elif code == 'C':
496503
return None, None
497504
elif code == 'UTF-8':
@@ -516,7 +523,14 @@ def _build_localename(localetuple):
516523
if encoding is None:
517524
return language
518525
else:
519-
return language + '.' + encoding
526+
if '@' in language:
527+
language, modifier = language.split('@', 1)
528+
else:
529+
modifier = ''
530+
localename = language + '.' + encoding
531+
if modifier:
532+
localename += '@' + modifier
533+
return localename
520534
except (TypeError, ValueError):
521535
raise TypeError('Locale must be None, a string, or an iterable of '
522536
'two strings -- language code, encoding.') from None
@@ -888,6 +902,12 @@ def getpreferredencoding(do_setlocale=True):
888902
# SS 2025-06-10:
889903
# Remove 'c.utf8' -> 'en_US.UTF-8' because 'en_US.UTF-8' does not exist
890904
# on all platforms.
905+
#
906+
# SS 2025-07-30:
907+
# Remove conflicts with GNU libc.
908+
#
909+
# removed 'el_gr@euro'
910+
# removed 'uz_uz@cyrillic'
891911

892912
locale_alias = {
893913
'a3': 'az_AZ.KOI8-C',
@@ -1021,7 +1041,6 @@ def getpreferredencoding(do_setlocale=True):
10211041
'el': 'el_GR.ISO8859-7',
10221042
'el_cy': 'el_CY.ISO8859-7',
10231043
'el_gr': 'el_GR.ISO8859-7',
1024-
'el_gr@euro': 'el_GR.ISO8859-15',
10251044
'en': 'en_US.ISO8859-1',
10261045
'en_ag': 'en_AG.UTF-8',
10271046
'en_au': 'en_AU.ISO8859-1',
@@ -1456,7 +1475,6 @@ def getpreferredencoding(do_setlocale=True):
14561475
'ur_pk': 'ur_PK.CP1256',
14571476
'uz': 'uz_UZ.UTF-8',
14581477
'uz_uz': 'uz_UZ.UTF-8',
1459-
'uz_uz@cyrillic': 'uz_UZ.UTF-8',
14601478
've': 've_ZA.UTF-8',
14611479
've_za': 've_ZA.UTF-8',
14621480
'vi': 'vi_VN.TCVN',

Lib/test/test_htmlparser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ def test_eof_in_cdata(self, content):
791791
self._run_check('<![CDATA[' + content,
792792
[('unknown decl', 'CDATA[' + content)])
793793
self._run_check('<![CDATA[' + content,
794-
[('comment', '![CDATA[' + content)],
794+
[('comment', '[CDATA[' + content)],
795795
collector=EventCollector(autocdata=True))
796796
self._run_check('<svg><text y="100"><![CDATA[' + content,
797797
[('starttag', 'svg', []),

Lib/test/test_importlib/test_util.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,18 @@ def test_cache_from_source_respects_pycache_prefix_relative(self):
580580
self.util.cache_from_source(path, optimization=''),
581581
os.path.normpath(expect))
582582

583+
@unittest.skipIf(sys.implementation.cache_tag is None,
584+
'requires sys.implementation.cache_tag to not be None')
585+
def test_cache_from_source_in_root_with_pycache_prefix(self):
586+
# Regression test for gh-82916
587+
pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
588+
path = 'qux.py'
589+
expect = os.path.join(os.path.sep, 'tmp', 'bytecode',
590+
f'qux.{self.tag}.pyc')
591+
with util.temporary_pycache_prefix(pycache_prefix):
592+
with os_helper.change_cwd('/'):
593+
self.assertEqual(self.util.cache_from_source(path), expect)
594+
583595
@unittest.skipIf(sys.implementation.cache_tag is None,
584596
'requires sys.implementation.cache_tag to not be None')
585597
def test_source_from_cache_inside_pycache_prefix(self):

0 commit comments

Comments
 (0)