Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,14 @@ def _proc_pax(self, tarfile):
# Fetch the next header.
try:
next = self._fromtarfile(tarfile, dircheck=False)
except EOFHeaderError:
if self.type == XGLTYPE:
# If this is a global header at the end of the archive
# (no regular members follow), let the EOFHeaderError
# propagate so the caller handles end-of-archive normally.
tarfile.offset = tarfile.fileobj.tell() - BLOCKSIZE
raise
raise SubsequentHeaderError("end of file header") from None
except HeaderError as e:
raise SubsequentHeaderError(str(e)) from None

Expand Down
25 changes: 24 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,10 @@ async def sleep(delay, result=None):
'''a = [x async for x in (x async for x in arange(5))][1]''',
'''a, = [1 for x in {x async for x in arange(1)}]''',
'''a = [await sleep(0, x) async for x in arange(2)][1]''',
'''a = [await sleep(0, 1) for _ in [0]][0]''',
'''a = {await sleep(0, 1) for _ in [0]}.pop()''',
'''a = {0: await sleep(0, 1) for _ in [0]}[0]''',
'''a = (lambda x=[await sleep(0, 1) for _ in [0]]: x)()[0]''',
# gh-121637: Make sure we correctly handle the case where the
# async code is optimized away
'''assert not await sleep(0); a = 1''',
Expand Down Expand Up @@ -610,7 +614,26 @@ async def __aexit__(self, *exc_info):
'''def f():
async with Lock() as l:
a = 1
'''
''',
'''class C:
[await x for x in y]
''',
'''class C:
[x async for x in arange(10)]
''',
'''async def f():
class C:
[await x for x in y]
''',
'''lambda: [await x for x in y]''',
'''class C:
def f(self, x=[await y for y in z]):
pass
''',
'''type T = [await x for x in y]''',
'''async def f[T=[await x for x in y]]():
pass
''',
]
for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
Expand Down
113 changes: 113 additions & 0 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ast
import contextlib
import copy
import inspect
Expand Down Expand Up @@ -407,6 +408,118 @@ async def bar():
with self.subTest(code=code), self.assertRaises(SyntaxError):
compile(code, "<test>", "exec")

def test_async_comprehension_scope(self):
# List/set/dict comprehensions with await or async for are allowed
# only in async functions, or at module level with top-level await.
allowed = [
"async def f():\n [await x for x in y]",
"async def f():\n {await x for x in y}",
"async def f():\n {k: await x for k, x in y}",
"async def f():\n [x async for x in y]",
"async def f():\n {x async for x in y}",
"async def f():\n {k: x async for k, x in y}",
"async def f():\n [[await x for x in y] for y in z]",
# Defaults, bases, and genexp iterables are evaluated in the
# enclosing scope.
"async def outer():\n async def f(x=[await y for y in z]): pass",
"async def f():\n class C([await x for x in y]): pass",
"async def f():\n (x for x in [await y for y in z])",
"async def f():\n (x for x in [y async for y in z])",
]
for code in allowed:
with self.subTest(code=code):
compile(code, "<test>", "exec")

# Generator expressions with await are async genexps and may appear
# outside async functions. A listcomp nested in a genexp body is also
# allowed (the genexp becomes an async generator).
for code in [
"(await x for x in y)",
"def f():\n (await x for x in y)",
"class C:\n (await x for x in y)",
"lambda: (await x for x in y)",
"([await x for x in y] for y in z)",
"def f():\n ([await x for x in y] for y in z)",
"class C:\n ([await x for x in y] for y in z)",
"async def f():\n ([await x for x in y] for y in z)",
]:
with self.subTest(code=code):
compile(code, "<test>", "exec")

err = "asynchronous comprehension outside of an asynchronous function"
invalid = [
"[await x for x in y]",
"{await x for x in y}",
"{k: await x for k, x in y}",
"[x async for x in y]",
"{x async for x in y}",
"{k: x async for k, x in y}",
"[[await x for x in y] for y in z]",
"[[x async for x in y] for y in z]",
"def f():\n [await x for x in y]",
"def f():\n [x async for x in y]",
"async def f():\n def g():\n [await x for x in y]",
"class C:\n [await x for x in y]",
"class C:\n {await x for x in y}",
"class C:\n {k: await x for k, x in y}",
"class C:\n [x async for x in y]",
"class C:\n [[await x for x in y] for y in z]",
"async def f():\n class C:\n x = [await y for y in z]",
"async def f():\n class C:\n x = [y async for y in z]",
# Lambdas are never async, even inside async def.
"lambda: [await x for x in y]",
"async def f():\n lambda: [await x for x in y]",
"class C:\n f = lambda: [await x for x in y]",
# Defaults, bases, and genexp iterables run in the enclosing scope.
"(x for x in [await y for y in z])",
"(x for x in [y async for y in z])",
"def f():\n (x for x in [await y for y in z])",
"async def f(x=[await y for y in z]): pass",
"def f(x=[await y for y in z]): pass",
"class C:\n def f(self, x=[await y for y in z]): pass",
"class C([await x for x in y]): pass",
# Type aliases and type-parameter scopes.
"type T = [await x for x in y]",
"type T = [x async for x in y]",
"def f[T=[await x for x in y]](): pass",
"def f[T: [await x for x in y]](): pass",
"async def f[T=[await x for x in y]](): pass",
"async def f(x: [await y for y in z]): pass",
]
for code in invalid:
with self.subTest(code=code):
support.check_syntax_error(self, code, err)

support.check_syntax_error(
self, "await x", "'await' outside function")
support.check_syntax_error(
self, "class C:\n await x", "'await' outside function")
support.check_syntax_error(
self, "def f():\n await x", "'await' outside async function")

flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
for code in [
"[await x for x in y]",
"async def f(x=[await y for y in z]): pass",
"class C([await x for x in y]): pass",
"f'{[await x for x in y]}'",
"(x for x in [await y for y in z])",
"(x for x in [y async for y in z])",
]:
with self.subTest(code=code, tla=True):
compile(code, "<test>", "exec", flags=flags)
still_invalid = [
"lambda: [await x for x in y]",
"def f():\n (x for x in [await y for y in z])",
"class C:\n def f(self, x=[await y for y in z]): pass",
"type T = [await x for x in y]",
"async def f[T=[await x for x in y]](): pass",
]
for code in still_invalid:
with self.subTest(code=code, tla=True):
with self.assertRaisesRegex(SyntaxError, err):
compile(code, "<test>", "exec", flags=flags)

def test_badsyntax_2(self):
samples = [
"""def foo():
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2499,6 +2499,35 @@ def test_pax_global_header(self):
finally:
tar.close()

def test_pax_global_header_empty_archive(self):
# An archive that contains only a global header and no regular
# members should be opened successfully (gh-149578).
pax_headers = {"foo": "bar"}

# Create a PAX archive with global headers but no file entries.
with tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT,
pax_headers=pax_headers):
pass

# Reading the archive should work and preserve global headers.
with tarfile.open(tmpname) as tar:
self.assertEqual(tar.pax_headers, pax_headers)
self.assertEqual(tar.getmembers(), [])

# Appending to the archive should work.
with tarfile.open(tmpname, "a") as tar:
self.assertEqual(tar.pax_headers, pax_headers)
self.assertEqual(tar.getmembers(), [])
tar.addfile(tarfile.TarInfo("test"))

# Verify the appended member is present and global headers
# are preserved.
with tarfile.open(tmpname) as tar:
self.assertEqual(tar.pax_headers, pax_headers)
members = tar.getmembers()
self.assertEqual(len(members), 1)
self.assertEqual(members[0].name, "test")

def test_pax_extended_header(self):
# The fields from the pax header have priority over the
# TarInfo.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :func:`tarfile.open` failing with :exc:`~tarfile.ReadError` when
opening a PAX format tar archive that contains only global headers and
no regular members.
Loading