Skip to content

Commit 6461a91

Browse files
authored
More stdlib updates (#5737)
* update getopt to 3.13.3 * update getpass to 3.13.3 * update timeit to 3.13.3 * update reprlib to 3.13.3 * update fileinput to 3.13.3
2 parents e49e743 + b84d6a3 commit 6461a91

33 files changed

+2133
-74
lines changed

Lib/fileinput.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
sequence must be accessed in strictly sequential order; sequence
5454
access and readline() cannot be mixed.
5555
56-
Optional in-place filtering: if the keyword argument inplace=1 is
56+
Optional in-place filtering: if the keyword argument inplace=True is
5757
passed to input() or to the FileInput constructor, the file is moved
5858
to a backup file and standard output is directed to the input file.
5959
This makes it possible to write a filter that rewrites its input file
@@ -399,7 +399,7 @@ def isstdin(self):
399399

400400

401401
def hook_compressed(filename, mode, *, encoding=None, errors=None):
402-
if encoding is None: # EncodingWarning is emitted in FileInput() already.
402+
if encoding is None and "b" not in mode: # EncodingWarning is emitted in FileInput() already.
403403
encoding = "locale"
404404
ext = os.path.splitext(filename)[1]
405405
if ext == '.gz':

Lib/getpass.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import io
1919
import os
2020
import sys
21-
import warnings
2221

2322
__all__ = ["getpass","getuser","GetPassWarning"]
2423

@@ -118,6 +117,7 @@ def win_getpass(prompt='Password: ', stream=None):
118117

119118

120119
def fallback_getpass(prompt='Password: ', stream=None):
120+
import warnings
121121
warnings.warn("Can not control echo on the terminal.", GetPassWarning,
122122
stacklevel=2)
123123
if not stream:
@@ -156,17 +156,24 @@ def getuser():
156156
157157
First try various environment variables, then the password
158158
database. This works on Windows as long as USERNAME is set.
159+
Any failure to find a username raises OSError.
159160
161+
.. versionchanged:: 3.13
162+
Previously, various exceptions beyond just :exc:`OSError`
163+
were raised.
160164
"""
161165

162166
for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
163167
user = os.environ.get(name)
164168
if user:
165169
return user
166170

167-
# If this fails, the exception will "explain" why
168-
import pwd
169-
return pwd.getpwuid(os.getuid())[0]
171+
try:
172+
import pwd
173+
return pwd.getpwuid(os.getuid())[0]
174+
except (ImportError, KeyError) as e:
175+
raise OSError('No username set in the environment') from e
176+
170177

171178
# Bind the name getpass to the appropriate function
172179
try:

Lib/reprlib.py

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,49 +29,100 @@ def wrapper(self):
2929
wrapper.__name__ = getattr(user_function, '__name__')
3030
wrapper.__qualname__ = getattr(user_function, '__qualname__')
3131
wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
32+
wrapper.__type_params__ = getattr(user_function, '__type_params__', ())
33+
wrapper.__wrapped__ = user_function
3234
return wrapper
3335

3436
return decorating_function
3537

3638
class Repr:
37-
38-
def __init__(self):
39-
self.maxlevel = 6
40-
self.maxtuple = 6
41-
self.maxlist = 6
42-
self.maxarray = 5
43-
self.maxdict = 4
44-
self.maxset = 6
45-
self.maxfrozenset = 6
46-
self.maxdeque = 6
47-
self.maxstring = 30
48-
self.maxlong = 40
49-
self.maxother = 30
39+
_lookup = {
40+
'tuple': 'builtins',
41+
'list': 'builtins',
42+
'array': 'array',
43+
'set': 'builtins',
44+
'frozenset': 'builtins',
45+
'deque': 'collections',
46+
'dict': 'builtins',
47+
'str': 'builtins',
48+
'int': 'builtins'
49+
}
50+
51+
def __init__(
52+
self, *, maxlevel=6, maxtuple=6, maxlist=6, maxarray=5, maxdict=4,
53+
maxset=6, maxfrozenset=6, maxdeque=6, maxstring=30, maxlong=40,
54+
maxother=30, fillvalue='...', indent=None,
55+
):
56+
self.maxlevel = maxlevel
57+
self.maxtuple = maxtuple
58+
self.maxlist = maxlist
59+
self.maxarray = maxarray
60+
self.maxdict = maxdict
61+
self.maxset = maxset
62+
self.maxfrozenset = maxfrozenset
63+
self.maxdeque = maxdeque
64+
self.maxstring = maxstring
65+
self.maxlong = maxlong
66+
self.maxother = maxother
67+
self.fillvalue = fillvalue
68+
self.indent = indent
5069

5170
def repr(self, x):
5271
return self.repr1(x, self.maxlevel)
5372

5473
def repr1(self, x, level):
55-
typename = type(x).__name__
74+
cls = type(x)
75+
typename = cls.__name__
76+
5677
if ' ' in typename:
5778
parts = typename.split()
5879
typename = '_'.join(parts)
59-
if hasattr(self, 'repr_' + typename):
60-
return getattr(self, 'repr_' + typename)(x, level)
61-
else:
62-
return self.repr_instance(x, level)
80+
81+
method = getattr(self, 'repr_' + typename, None)
82+
if method:
83+
# not defined in this class
84+
if typename not in self._lookup:
85+
return method(x, level)
86+
module = getattr(cls, '__module__', None)
87+
# defined in this class and is the module intended
88+
if module == self._lookup[typename]:
89+
return method(x, level)
90+
91+
return self.repr_instance(x, level)
92+
93+
def _join(self, pieces, level):
94+
if self.indent is None:
95+
return ', '.join(pieces)
96+
if not pieces:
97+
return ''
98+
indent = self.indent
99+
if isinstance(indent, int):
100+
if indent < 0:
101+
raise ValueError(
102+
f'Repr.indent cannot be negative int (was {indent!r})'
103+
)
104+
indent *= ' '
105+
try:
106+
sep = ',\n' + (self.maxlevel - level + 1) * indent
107+
except TypeError as error:
108+
raise TypeError(
109+
f'Repr.indent must be a str, int or None, not {type(indent)}'
110+
) from error
111+
return sep.join(('', *pieces, ''))[1:-len(indent) or None]
63112

64113
def _repr_iterable(self, x, level, left, right, maxiter, trail=''):
65114
n = len(x)
66115
if level <= 0 and n:
67-
s = '...'
116+
s = self.fillvalue
68117
else:
69118
newlevel = level - 1
70119
repr1 = self.repr1
71120
pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)]
72-
if n > maxiter: pieces.append('...')
73-
s = ', '.join(pieces)
74-
if n == 1 and trail: right = trail + right
121+
if n > maxiter:
122+
pieces.append(self.fillvalue)
123+
s = self._join(pieces, level)
124+
if n == 1 and trail and self.indent is None:
125+
right = trail + right
75126
return '%s%s%s' % (left, s, right)
76127

77128
def repr_tuple(self, x, level):
@@ -104,17 +155,20 @@ def repr_deque(self, x, level):
104155

105156
def repr_dict(self, x, level):
106157
n = len(x)
107-
if n == 0: return '{}'
108-
if level <= 0: return '{...}'
158+
if n == 0:
159+
return '{}'
160+
if level <= 0:
161+
return '{' + self.fillvalue + '}'
109162
newlevel = level - 1
110163
repr1 = self.repr1
111164
pieces = []
112165
for key in islice(_possibly_sorted(x), self.maxdict):
113166
keyrepr = repr1(key, newlevel)
114167
valrepr = repr1(x[key], newlevel)
115168
pieces.append('%s: %s' % (keyrepr, valrepr))
116-
if n > self.maxdict: pieces.append('...')
117-
s = ', '.join(pieces)
169+
if n > self.maxdict:
170+
pieces.append(self.fillvalue)
171+
s = self._join(pieces, level)
118172
return '{%s}' % (s,)
119173

120174
def repr_str(self, x, level):
@@ -123,15 +177,15 @@ def repr_str(self, x, level):
123177
i = max(0, (self.maxstring-3)//2)
124178
j = max(0, self.maxstring-3-i)
125179
s = builtins.repr(x[:i] + x[len(x)-j:])
126-
s = s[:i] + '...' + s[len(s)-j:]
180+
s = s[:i] + self.fillvalue + s[len(s)-j:]
127181
return s
128182

129183
def repr_int(self, x, level):
130184
s = builtins.repr(x) # XXX Hope this isn't too slow...
131185
if len(s) > self.maxlong:
132186
i = max(0, (self.maxlong-3)//2)
133187
j = max(0, self.maxlong-3-i)
134-
s = s[:i] + '...' + s[len(s)-j:]
188+
s = s[:i] + self.fillvalue + s[len(s)-j:]
135189
return s
136190

137191
def repr_instance(self, x, level):
@@ -144,7 +198,7 @@ def repr_instance(self, x, level):
144198
if len(s) > self.maxother:
145199
i = max(0, (self.maxother-3)//2)
146200
j = max(0, self.maxother-3-i)
147-
s = s[:i] + '...' + s[len(s)-j:]
201+
s = s[:i] + self.fillvalue + s[len(s)-j:]
148202
return s
149203

150204

Lib/test/support/i18n_helper.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import re
2+
import subprocess
3+
import sys
4+
import unittest
5+
from pathlib import Path
6+
from test.support import REPO_ROOT, TEST_HOME_DIR, requires_subprocess
7+
from test.test_tools import skip_if_missing
8+
9+
10+
pygettext = Path(REPO_ROOT) / 'Tools' / 'i18n' / 'pygettext.py'
11+
12+
msgid_pattern = re.compile(r'msgid(.*?)(?:msgid_plural|msgctxt|msgstr)',
13+
re.DOTALL)
14+
msgid_string_pattern = re.compile(r'"((?:\\"|[^"])*)"')
15+
16+
17+
def _generate_po_file(path, *, stdout_only=True):
18+
res = subprocess.run([sys.executable, pygettext,
19+
'--no-location', '-o', '-', path],
20+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
21+
text=True)
22+
if stdout_only:
23+
return res.stdout
24+
return res
25+
26+
27+
def _extract_msgids(po):
28+
msgids = []
29+
for msgid in msgid_pattern.findall(po):
30+
msgid_string = ''.join(msgid_string_pattern.findall(msgid))
31+
msgid_string = msgid_string.replace(r'\"', '"')
32+
if msgid_string:
33+
msgids.append(msgid_string)
34+
return sorted(msgids)
35+
36+
37+
def _get_snapshot_path(module_name):
38+
return Path(TEST_HOME_DIR) / 'translationdata' / module_name / 'msgids.txt'
39+
40+
41+
@requires_subprocess()
42+
class TestTranslationsBase(unittest.TestCase):
43+
44+
def assertMsgidsEqual(self, module):
45+
'''Assert that msgids extracted from a given module match a
46+
snapshot.
47+
48+
'''
49+
skip_if_missing('i18n')
50+
res = _generate_po_file(module.__file__, stdout_only=False)
51+
self.assertEqual(res.returncode, 0)
52+
self.assertEqual(res.stderr, '')
53+
msgids = _extract_msgids(res.stdout)
54+
snapshot_path = _get_snapshot_path(module.__name__)
55+
snapshot = snapshot_path.read_text().splitlines()
56+
self.assertListEqual(msgids, snapshot)
57+
58+
59+
def update_translation_snapshots(module):
60+
contents = _generate_po_file(module.__file__)
61+
msgids = _extract_msgids(contents)
62+
snapshot_path = _get_snapshot_path(module.__name__)
63+
snapshot_path.write_text('\n'.join(msgids))

0 commit comments

Comments
 (0)