Skip to content

Commit a934695

Browse files
authored
Merge pull request #547 from python/main
Sync Fork from Upstream Repo
2 parents d62099e + 3b56b3b commit a934695

14 files changed

+177
-123
lines changed

Lib/contextlib.py

+59-45
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,20 @@ def __init__(self, func, args, kwds):
113113
# for the class instead.
114114
# See http://bugs.python.org/issue19404 for more details.
115115

116-
117-
class _GeneratorContextManager(_GeneratorContextManagerBase,
118-
AbstractContextManager,
119-
ContextDecorator):
120-
"""Helper for @contextmanager decorator."""
121-
122116
def _recreate_cm(self):
123-
# _GCM instances are one-shot context managers, so the
117+
# _GCMB instances are one-shot context managers, so the
124118
# CM must be recreated each time a decorated function is
125119
# called
126120
return self.__class__(self.func, self.args, self.kwds)
127121

122+
123+
class _GeneratorContextManager(
124+
_GeneratorContextManagerBase,
125+
AbstractContextManager,
126+
ContextDecorator,
127+
):
128+
"""Helper for @contextmanager decorator."""
129+
128130
def __enter__(self):
129131
# do not keep args and kwds alive unnecessarily
130132
# they are only needed for recreation, which is not possible anymore
@@ -134,8 +136,8 @@ def __enter__(self):
134136
except StopIteration:
135137
raise RuntimeError("generator didn't yield") from None
136138

137-
def __exit__(self, type, value, traceback):
138-
if type is None:
139+
def __exit__(self, typ, value, traceback):
140+
if typ is None:
139141
try:
140142
next(self.gen)
141143
except StopIteration:
@@ -146,9 +148,9 @@ def __exit__(self, type, value, traceback):
146148
if value is None:
147149
# Need to force instantiation so we can reliably
148150
# tell if we get the same exception back
149-
value = type()
151+
value = typ()
150152
try:
151-
self.gen.throw(type, value, traceback)
153+
self.gen.throw(typ, value, traceback)
152154
except StopIteration as exc:
153155
# Suppress StopIteration *unless* it's the same exception that
154156
# was passed to throw(). This prevents a StopIteration
@@ -158,81 +160,93 @@ def __exit__(self, type, value, traceback):
158160
# Don't re-raise the passed in exception. (issue27122)
159161
if exc is value:
160162
return False
161-
# Likewise, avoid suppressing if a StopIteration exception
163+
# Avoid suppressing if a StopIteration exception
162164
# was passed to throw() and later wrapped into a RuntimeError
163-
# (see PEP 479).
164-
if type is StopIteration and exc.__cause__ is value:
165+
# (see PEP 479 for sync generators; async generators also
166+
# have this behavior). But do this only if the exception wrapped
167+
# by the RuntimeError is actually Stop(Async)Iteration (see
168+
# issue29692).
169+
if (
170+
isinstance(value, StopIteration)
171+
and exc.__cause__ is value
172+
):
165173
return False
166174
raise
167-
except:
175+
except BaseException as exc:
168176
# only re-raise if it's *not* the exception that was
169177
# passed to throw(), because __exit__() must not raise
170178
# an exception unless __exit__() itself failed. But throw()
171179
# has to raise the exception to signal propagation, so this
172180
# fixes the impedance mismatch between the throw() protocol
173181
# and the __exit__() protocol.
174-
#
175-
# This cannot use 'except BaseException as exc' (as in the
176-
# async implementation) to maintain compatibility with
177-
# Python 2, where old-style class exceptions are not caught
178-
# by 'except BaseException'.
179-
if sys.exc_info()[1] is value:
180-
return False
181-
raise
182+
if exc is not value:
183+
raise
184+
return False
182185
raise RuntimeError("generator didn't stop after throw()")
183186

184-
185-
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
186-
AbstractAsyncContextManager,
187-
AsyncContextDecorator):
188-
"""Helper for @asynccontextmanager."""
189-
190-
def _recreate_cm(self):
191-
# _AGCM instances are one-shot context managers, so the
192-
# ACM must be recreated each time a decorated function is
193-
# called
194-
return self.__class__(self.func, self.args, self.kwds)
187+
class _AsyncGeneratorContextManager(
188+
_GeneratorContextManagerBase,
189+
AbstractAsyncContextManager,
190+
AsyncContextDecorator,
191+
):
192+
"""Helper for @asynccontextmanager decorator."""
195193

196194
async def __aenter__(self):
195+
# do not keep args and kwds alive unnecessarily
196+
# they are only needed for recreation, which is not possible anymore
197+
del self.args, self.kwds, self.func
197198
try:
198-
return await self.gen.__anext__()
199+
return await anext(self.gen)
199200
except StopAsyncIteration:
200201
raise RuntimeError("generator didn't yield") from None
201202

202203
async def __aexit__(self, typ, value, traceback):
203204
if typ is None:
204205
try:
205-
await self.gen.__anext__()
206+
await anext(self.gen)
206207
except StopAsyncIteration:
207-
return
208+
return False
208209
else:
209210
raise RuntimeError("generator didn't stop")
210211
else:
211212
if value is None:
213+
# Need to force instantiation so we can reliably
214+
# tell if we get the same exception back
212215
value = typ()
213-
# See _GeneratorContextManager.__exit__ for comments on subtleties
214-
# in this implementation
215216
try:
216217
await self.gen.athrow(typ, value, traceback)
217-
raise RuntimeError("generator didn't stop after athrow()")
218218
except StopAsyncIteration as exc:
219+
# Suppress StopIteration *unless* it's the same exception that
220+
# was passed to throw(). This prevents a StopIteration
221+
# raised inside the "with" statement from being suppressed.
219222
return exc is not value
220223
except RuntimeError as exc:
224+
# Don't re-raise the passed in exception. (issue27122)
221225
if exc is value:
222226
return False
223-
# Avoid suppressing if a StopIteration exception
224-
# was passed to throw() and later wrapped into a RuntimeError
227+
# Avoid suppressing if a Stop(Async)Iteration exception
228+
# was passed to athrow() and later wrapped into a RuntimeError
225229
# (see PEP 479 for sync generators; async generators also
226230
# have this behavior). But do this only if the exception wrapped
227231
# by the RuntimeError is actually Stop(Async)Iteration (see
228232
# issue29692).
229-
if isinstance(value, (StopIteration, StopAsyncIteration)):
230-
if exc.__cause__ is value:
231-
return False
233+
if (
234+
isinstance(value, (StopIteration, StopAsyncIteration))
235+
and exc.__cause__ is value
236+
):
237+
return False
232238
raise
233239
except BaseException as exc:
240+
# only re-raise if it's *not* the exception that was
241+
# passed to throw(), because __exit__() must not raise
242+
# an exception unless __exit__() itself failed. But throw()
243+
# has to raise the exception to signal propagation, so this
244+
# fixes the impedance mismatch between the throw() protocol
245+
# and the __exit__() protocol.
234246
if exc is not value:
235247
raise
248+
return False
249+
raise RuntimeError("generator didn't stop after athrow()")
236250

237251

238252
def contextmanager(func):

Lib/imghdr.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ def what(file, h=None):
3535
tests = []
3636

3737
def test_jpeg(h, f):
38-
"""JPEG data in JFIF or Exif format"""
38+
"""JPEG data with JFIF or Exif markers; and raw JPEG"""
3939
if h[6:10] in (b'JFIF', b'Exif'):
4040
return 'jpeg'
41+
elif h[:4] == b'\xff\xd8\xff\xdb':
42+
return 'jpeg'
4143

4244
tests.append(test_jpeg)
4345

Lib/os.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -704,9 +704,11 @@ def __len__(self):
704704
return len(self._data)
705705

706706
def __repr__(self):
707-
return 'environ({{{}}})'.format(', '.join(
708-
('{!r}: {!r}'.format(self.decodekey(key), self.decodevalue(value))
709-
for key, value in self._data.items())))
707+
formatted_items = ", ".join(
708+
f"{self.decodekey(key)!r}: {self.decodevalue(value)!r}"
709+
for key, value in self._data.items()
710+
)
711+
return f"environ({{{formatted_items}}})"
710712

711713
def copy(self):
712714
return dict(self)

Lib/test/imghdrdata/python-raw.jpg

525 Bytes
Loading

Lib/test/test_contextlib.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,22 @@ def woohoo():
126126
self.assertEqual(state, [1, 42, 999])
127127

128128
def test_contextmanager_except_stopiter(self):
129-
stop_exc = StopIteration('spam')
130129
@contextmanager
131130
def woohoo():
132131
yield
133-
try:
134-
with self.assertWarnsRegex(DeprecationWarning,
135-
"StopIteration"):
136-
with woohoo():
137-
raise stop_exc
138-
except Exception as ex:
139-
self.assertIs(ex, stop_exc)
140-
else:
141-
self.fail('StopIteration was suppressed')
132+
133+
class StopIterationSubclass(StopIteration):
134+
pass
135+
136+
for stop_exc in (StopIteration('spam'), StopIterationSubclass('spam')):
137+
with self.subTest(type=type(stop_exc)):
138+
try:
139+
with woohoo():
140+
raise stop_exc
141+
except Exception as ex:
142+
self.assertIs(ex, stop_exc)
143+
else:
144+
self.fail(f'{stop_exc} was suppressed')
142145

143146
def test_contextmanager_except_pep479(self):
144147
code = """\

Lib/test/test_contextlib_async.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,18 @@ async def test_contextmanager_except_stopiter(self):
209209
async def woohoo():
210210
yield
211211

212-
for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
212+
class StopIterationSubclass(StopIteration):
213+
pass
214+
215+
class StopAsyncIterationSubclass(StopAsyncIteration):
216+
pass
217+
218+
for stop_exc in (
219+
StopIteration('spam'),
220+
StopAsyncIteration('ham'),
221+
StopIterationSubclass('spam'),
222+
StopAsyncIterationSubclass('spam')
223+
):
213224
with self.subTest(type=type(stop_exc)):
214225
try:
215226
async with woohoo():

Lib/test/test_imghdr.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
('python.pgm', 'pgm'),
1717
('python.pbm', 'pbm'),
1818
('python.jpg', 'jpeg'),
19+
('python-raw.jpg', 'jpeg'), # raw JPEG without JFIF/EXIF markers
1920
('python.ras', 'rast'),
2021
('python.sgi', 'rgb'),
2122
('python.tiff', 'tiff'),

Lib/test/test_os.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1029,9 +1029,11 @@ def test_items(self):
10291029
def test___repr__(self):
10301030
"""Check that the repr() of os.environ looks like environ({...})."""
10311031
env = os.environ
1032-
self.assertEqual(repr(env), 'environ({{{}}})'.format(', '.join(
1033-
'{!r}: {!r}'.format(key, value)
1034-
for key, value in env.items())))
1032+
formatted_items = ", ".join(
1033+
f"{key!r}: {value!r}"
1034+
for key, value in env.items()
1035+
)
1036+
self.assertEqual(repr(env), f"environ({{{formatted_items}}})")
10351037

10361038
def test_get_exec_path(self):
10371039
defpath_list = os.defpath.split(os.pathsep)

Lib/test/test_shutil.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
TESTFN_SRC = TESTFN + "_SRC"
3838
TESTFN_DST = TESTFN + "_DST"
3939
MACOS = sys.platform.startswith("darwin")
40+
SOLARIS = sys.platform.startswith("sunos")
4041
AIX = sys.platform[:3] == 'aix'
4142
try:
4243
import grp
@@ -1249,7 +1250,7 @@ def test_copyfile_same_file(self):
12491250
# Make sure file is not corrupted.
12501251
self.assertEqual(read_file(src_file), 'foo')
12511252

1252-
@unittest.skipIf(MACOS or _winapi, 'On MACOS and Windows the errors are not confusing (though different)')
1253+
@unittest.skipIf(MACOS or SOLARIS or _winapi, 'On MACOS, Solaris and Windows the errors are not confusing (though different)')
12531254
def test_copyfile_nonexistent_dir(self):
12541255
# Issue 43219
12551256
src_dir = self.mkdtemp()

0 commit comments

Comments
 (0)