Skip to content

Commit a3e5f2d

Browse files
committed
Update ten Python library modules to Python 3.15 final
Migrate dependency-ordered linecache, locale, _pydecimal, decimal, random, uuid, wave, _strptime, zipapp and poplib with their canonical tests and WAV fixtures using update_lib. Assisted-by: OpenAI Codex:model version unavailable
1 parent 498adff commit a3e5f2d

20 files changed

Lines changed: 836 additions & 225 deletions

‎Lib/_pydecimal.py‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,16 @@
4747
'HAVE_THREADS',
4848

4949
# C version: compile time choice that enables the coroutine local context
50-
'HAVE_CONTEXTVAR'
50+
'HAVE_CONTEXTVAR',
51+
52+
# Highest version of the spec this module complies with
53+
'SPEC_VERSION',
5154
]
5255

5356
__xname__ = __name__ # sys.modules lookup (--without-threads)
5457
__name__ = 'decimal' # For pickling
55-
__version__ = '1.70' # Highest version of the spec this complies with
56-
# See http://speleotrove.com/decimal/
58+
SPEC_VERSION = '1.70' # Highest version of the spec this complies with
59+
# See https://speleotrove.com/decimal/decarith.html
5760
__libmpdec_version__ = "2.4.2" # compatible libmpdec version
5861

5962
import math as _math
@@ -6404,3 +6407,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
64046407
# _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MODULUS
64056408
_PyHASH_10INV = pow(10, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
64066409
del sys
6410+
6411+
def __getattr__(name):
6412+
if name == "__version__":
6413+
from warnings import _deprecated
6414+
6415+
_deprecated("__version__", remove=(3, 20))
6416+
return SPEC_VERSION
6417+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

‎Lib/_strptime.py‎

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
FUNCTIONS:
99
_getlang -- Figure out what language is being used for the locale
10-
strptime -- Calculates the time struct represented by the passed-in string
10+
_strptime -- Calculates the time struct represented by the passed-in string
1111
1212
"""
1313
import os
@@ -371,7 +371,9 @@ def __init__(self, locale_time=None):
371371
# W is set below by using 'U'
372372
'y': r"(?P<y>\d\d)",
373373
'Y': r"(?P<Y>\d\d\d\d)",
374-
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
374+
# See gh-121237: "z" must support colons for backwards compatibility.
375+
'z': r"(?P<z>([+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)|(?-i:Z))?",
376+
':z': r"(?P<colon_z>([+-]\d\d:[0-5]\d(:[0-5]\d(\.\d{1,6})?)?)|(?-i:Z))?",
375377
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
376378
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
377379
'B': self.__seqToRE(_fixmonths(self.locale_time.f_month[1:]), 'B'),
@@ -380,7 +382,10 @@ def __init__(self, locale_time=None):
380382
'Z': self.__seqToRE((tz for tz_names in self.locale_time.timezone
381383
for tz in tz_names),
382384
'Z'),
383-
'%': '%'}
385+
'n': r'\s*',
386+
't': r'\s*',
387+
'%': '%',
388+
}
384389
if self.locale_time.LC_alt_digits is None:
385390
for d in 'dmyCHIMS':
386391
mapping['O' + d] = r'(?P<%s>\d\d|\d| \d)' % d
@@ -416,6 +421,8 @@ def __init__(self, locale_time=None):
416421
mapping['W'] = mapping['U'].replace('U', 'W')
417422

418423
base.__init__(mapping)
424+
base.__setitem__('D', self.pattern('%m/%d/%y'))
425+
base.__setitem__('F', self.pattern('%Y-%m-%d'))
419426
base.__setitem__('T', self.pattern('%H:%M:%S'))
420427
base.__setitem__('R', self.pattern('%H:%M'))
421428
base.__setitem__('r', self.pattern(self.locale_time.LC_time_ampm))
@@ -457,28 +464,39 @@ def pattern(self, format):
457464
format = re_sub(r'\s+', r'\\s+', format)
458465
format = re_sub(r"'", "['\u02bc]", format) # needed for br_FR
459466
year_in_format = False
460-
day_of_month_in_format = False
467+
day_d_in_format = False
468+
day_e_in_format = False
461469
def repl(m):
462-
format_char = m[1]
463-
match format_char:
470+
directive = m.group()[1:] # exclude `%` symbol
471+
match directive:
464472
case 'Y' | 'y' | 'G':
465473
nonlocal year_in_format
466474
year_in_format = True
467475
case 'd':
468-
nonlocal day_of_month_in_format
469-
day_of_month_in_format = True
470-
return self[format_char]
471-
format = re_sub(r'%[-_0^#]*[0-9]*([OE]?\\?.?)', repl, format)
472-
if day_of_month_in_format and not year_in_format:
473-
import warnings
474-
warnings.warn("""\
476+
nonlocal day_d_in_format
477+
day_d_in_format = True
478+
case 'e':
479+
nonlocal day_e_in_format
480+
day_e_in_format = True
481+
return self[directive]
482+
format = re_sub(r'%[-_0^#]*[0-9]*([OE]?[:\\]?.?)', repl, format)
483+
if not year_in_format:
484+
if day_d_in_format:
485+
raise ValueError(
486+
"Day of month directive '%d' may not be used without "
487+
"a year directive. Parsing dates involving a day of "
488+
"month without a year is ambiguous and fails to parse "
489+
"leap day. Add a year to the input and format. "
490+
"See https://github.com/python/cpython/issues/70647.")
491+
if day_e_in_format:
492+
import warnings
493+
warnings.warn("""\
475494
Parsing dates involving a day of month without a year specified is ambiguous
476-
and fails to parse leap day. The default behavior will change in Python 3.15
477-
to either always raise an exception or to use a different default year (TBD).
478-
To avoid trouble, add a specific year to the input & format.
495+
and fails to parse leap day. '%e' without a year will become an error in Python 3.17.
496+
To avoid trouble, add a specific year to the input and format.
479497
See https://github.com/python/cpython/issues/70647.""",
480-
DeprecationWarning,
481-
skip_file_prefixes=(os.path.dirname(__file__),))
498+
DeprecationWarning,
499+
skip_file_prefixes=(os.path.dirname(__file__),))
482500
return format
483501

484502
def compile(self, format):
@@ -514,9 +532,10 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
514532

515533

516534
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
517-
"""Return a 2-tuple consisting of a time struct and an int containing
518-
the number of microseconds based on the input string and the
519-
format string."""
535+
"""Return a 3-tuple consisting of a tuple with time components,
536+
an int containing the number of microseconds, and an int
537+
containing the microseconds part of the GMT offset, based on the
538+
input string and the format string."""
520539

521540
for index, arg in enumerate([data_string, format]):
522541
if not isinstance(arg, str):
@@ -555,8 +574,17 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
555574
raise ValueError("time data %r does not match format %r" %
556575
(data_string, format))
557576
if len(data_string) != found.end():
558-
raise ValueError("unconverted data remains: %s" %
559-
data_string[found.end():])
577+
rest = data_string[found.end():]
578+
# Specific check for '%:z' directive
579+
if (
580+
"colon_z" in found.re.groupindex
581+
and found.group("colon_z") is not None
582+
and rest[0] != ":"
583+
):
584+
raise ValueError(
585+
f"Missing colon in %:z before '{rest}', got '{data_string}'"
586+
)
587+
raise ValueError("unconverted data remains: %s" % rest)
560588

561589
iso_year = year = None
562590
month = day = 1
@@ -616,18 +644,18 @@ def parse_int(s):
616644
hour = parse_int(found_dict['I'])
617645
ampm = found_dict.get('p', '').lower()
618646
# If there was no AM/PM indicator, we'll treat this like AM
619-
if ampm in ('', locale_time.am_pm[0]):
620-
# We're in AM so the hour is correct unless we're
621-
# looking at 12 midnight.
622-
# 12 midnight == 12 AM == hour 0
623-
if hour == 12:
624-
hour = 0
625-
elif ampm == locale_time.am_pm[1]:
647+
if ampm == locale_time.am_pm[1]:
626648
# We're in PM so we need to add 12 to the hour unless
627649
# we're looking at 12 noon.
628650
# 12 noon == 12 PM == hour 12
629651
if hour != 12:
630652
hour += 12
653+
else:
654+
# We're in AM so the hour is correct unless we're
655+
# looking at 12 midnight.
656+
# 12 midnight == 12 AM == hour 0
657+
if hour == 12:
658+
hour = 0
631659
elif group_key == 'M':
632660
minute = parse_int(found_dict['M'])
633661
elif group_key == 'S':
@@ -662,29 +690,30 @@ def parse_int(s):
662690
week_of_year_start = 0
663691
elif group_key == 'V':
664692
iso_week = int(found_dict['V'])
665-
elif group_key == 'z':
666-
z = found_dict['z']
667-
if z == 'Z':
668-
gmtoff = 0
669-
else:
670-
if z[3] == ':':
671-
z = z[:3] + z[4:]
672-
if len(z) > 5:
673-
if z[5] != ':':
674-
msg = f"Inconsistent use of : in {found_dict['z']}"
675-
raise ValueError(msg)
676-
z = z[:5] + z[6:]
677-
hours = int(z[1:3])
678-
minutes = int(z[3:5])
679-
seconds = int(z[5:7] or 0)
680-
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
681-
gmtoff_remainder = z[8:]
682-
# Pad to always return microseconds.
683-
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
684-
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
685-
if z.startswith("-"):
686-
gmtoff = -gmtoff
687-
gmtoff_fraction = -gmtoff_fraction
693+
elif group_key in ('z', 'colon_z'):
694+
z = found_dict[group_key]
695+
if z:
696+
if z == 'Z':
697+
gmtoff = 0
698+
else:
699+
if z[3] == ':':
700+
z = z[:3] + z[4:]
701+
if len(z) > 5:
702+
if z[5] != ':':
703+
msg = f"Inconsistent use of : in {found_dict[group_key]}"
704+
raise ValueError(msg)
705+
z = z[:5] + z[6:]
706+
hours = int(z[1:3])
707+
minutes = int(z[3:5])
708+
seconds = int(z[5:7] or 0)
709+
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
710+
gmtoff_remainder = z[8:]
711+
# Pad to always return microseconds.
712+
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
713+
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
714+
if z.startswith("-"):
715+
gmtoff = -gmtoff
716+
gmtoff_fraction = -gmtoff_fraction
688717
elif group_key == 'Z':
689718
# Since -1 is default value only need to worry about setting tz if
690719
# it can be something other than -1.

‎Lib/decimal.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@
100100

101101
try:
102102
from _decimal import *
103-
from _decimal import __version__ # noqa: F401
104103
from _decimal import __libmpdec_version__ # noqa: F401
104+
from _decimal import __getattr__ # noqa: F401
105105
except ImportError:
106106
import _pydecimal
107107
import sys

‎Lib/linecache.py‎

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -224,21 +224,58 @@ def lazycache(filename, module_globals):
224224
def _make_lazycache_entry(filename, module_globals):
225225
if not filename or (filename.startswith('<') and filename.endswith('>')):
226226
return None
227-
# Try for a __loader__, if available
228-
if module_globals and '__name__' in module_globals:
229-
spec = module_globals.get('__spec__')
230-
name = getattr(spec, 'name', None) or module_globals['__name__']
231-
loader = getattr(spec, 'loader', None)
232-
if loader is None:
233-
loader = module_globals.get('__loader__')
234-
get_source = getattr(loader, 'get_source', None)
235-
236-
if name and get_source:
237-
def get_lines(name=name, *args, **kwargs):
238-
return get_source(name, *args, **kwargs)
239-
return (get_lines,)
240-
return None
241227

228+
if module_globals is not None and not isinstance(module_globals, dict):
229+
raise TypeError(f'module_globals must be a dict, not {type(module_globals).__qualname__}')
230+
if not module_globals or '__name__' not in module_globals:
231+
return None
232+
233+
spec = module_globals.get('__spec__')
234+
name = getattr(spec, 'name', None) or module_globals['__name__']
235+
if name is None:
236+
return None
237+
238+
loader = _bless_my_loader(module_globals)
239+
if loader is None:
240+
return None
241+
242+
get_source = getattr(loader, 'get_source', None)
243+
if get_source is None:
244+
return None
245+
246+
def get_lines(name=name, *args, **kwargs):
247+
return get_source(name, *args, **kwargs)
248+
return (get_lines,)
249+
250+
def _bless_my_loader(module_globals):
251+
# Similar to _bless_my_loader() in importlib._bootstrap_external,
252+
# but always emits warnings instead of errors.
253+
loader = module_globals.get('__loader__')
254+
if loader is None and '__spec__' not in module_globals:
255+
return None
256+
spec = module_globals.get('__spec__')
257+
258+
# The __main__ module has __spec__ = None.
259+
if spec is None and module_globals.get('__name__') == '__main__':
260+
return loader
261+
262+
spec_loader = getattr(spec, 'loader', None)
263+
if spec_loader is None:
264+
import warnings
265+
warnings.warn(
266+
'Module globals is missing a __spec__.loader',
267+
DeprecationWarning)
268+
return loader
269+
270+
assert spec_loader is not None
271+
if loader is not None and loader != spec_loader:
272+
import warnings
273+
warnings.warn(
274+
'Module globals; __loader__ != __spec__.loader',
275+
DeprecationWarning)
276+
return loader
277+
278+
return spec_loader
242279

243280

244281
def _register_code(code, string, name):

0 commit comments

Comments
 (0)