Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 36854bb

Browse filesBrowse files
authored
gh-101566: Sync with zipp 3.14. (GH-102018)
1 parent 84181c1 commit 36854bb
Copy full SHA for 36854bb

File tree

6 files changed

+215
-56
lines changed
Filter options

6 files changed

+215
-56
lines changed

‎Lib/test/test_zipfile/_context.py

Copy file name to clipboard
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import contextlib
2+
import time
3+
4+
5+
class DeadlineExceeded(Exception):
6+
pass
7+
8+
9+
class TimedContext(contextlib.ContextDecorator):
10+
"""
11+
A context that will raise DeadlineExceeded if the
12+
max duration is reached during the execution.
13+
14+
>>> TimedContext(1)(time.sleep)(.1)
15+
>>> TimedContext(0)(time.sleep)(.1)
16+
Traceback (most recent call last):
17+
...
18+
tests._context.DeadlineExceeded: (..., 0)
19+
"""
20+
21+
def __init__(self, max_duration: int):
22+
self.max_duration = max_duration
23+
24+
def __enter__(self):
25+
self.start = time.monotonic()
26+
27+
def __exit__(self, *err):
28+
duration = time.monotonic() - self.start
29+
if duration > self.max_duration:
30+
raise DeadlineExceeded(duration, self.max_duration)
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
try:
2+
from func_timeout import func_set_timeout as set_timeout
3+
except ImportError: # pragma: no cover
4+
# provide a fallback that doesn't actually time out
5+
from ._context import TimedContext as set_timeout
6+
7+
8+
__all__ = ['set_timeout']

‎Lib/test/test_zipfile/_itertools.py

Copy file name to clipboardExpand all lines: Lib/test/test_zipfile/_itertools.py
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1+
import itertools
2+
3+
4+
# from jaraco.itertools 6.3.0
5+
class Counter:
6+
"""
7+
Wrap an iterable in an object that stores the count of items
8+
that pass through it.
9+
10+
>>> items = Counter(range(20))
11+
>>> items.count
12+
0
13+
>>> values = list(items)
14+
>>> items.count
15+
20
16+
"""
17+
18+
def __init__(self, i):
19+
self.count = 0
20+
self.iter = zip(itertools.count(1), i)
21+
22+
def __iter__(self):
23+
return self
24+
25+
def __next__(self):
26+
self.count, result = next(self.iter)
27+
return result
28+
29+
130
# from more_itertools v8.13.0
231
def always_iterable(obj, base_type=(str, bytes)):
332
if obj is None:

‎Lib/test/test_zipfile/test_path.py

Copy file name to clipboardExpand all lines: Lib/test/test_zipfile/test_path.py
+84-53Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,25 @@
44
import pathlib
55
import pickle
66
import string
7-
from test.support.script_helper import assert_python_ok
7+
import sys
88
import unittest
99
import zipfile
1010

11-
from ._test_params import parameterize, Invoked
1211
from ._functools import compose
12+
from ._itertools import Counter
1313

14+
from ._test_params import parameterize, Invoked
15+
from ._func_timeout_compat import set_timeout
1416

1517
from test.support.os_helper import temp_dir
1618

1719

18-
# Poor man's technique to consume a (smallish) iterable.
19-
consume = tuple
20-
21-
22-
# from jaraco.itertools 5.0
2320
class jaraco:
2421
class itertools:
25-
class Counter:
26-
def __init__(self, i):
27-
self.count = 0
28-
self._orig_iter = iter(i)
22+
Counter = Counter
2923

30-
def __iter__(self):
31-
return self
3224

33-
def __next__(self):
34-
result = next(self._orig_iter)
35-
self.count += 1
36-
return result
25+
consume = tuple
3726

3827

3928
def add_dirs(zf):
@@ -161,10 +150,10 @@ def test_open_encoding_utf16(self):
161150
u16 = path.joinpath("16.txt")
162151
with u16.open('r', "utf-16") as strm:
163152
data = strm.read()
164-
self.assertEqual(data, "This was utf-16")
153+
assert data == "This was utf-16"
165154
with u16.open(encoding="utf-16") as strm:
166155
data = strm.read()
167-
self.assertEqual(data, "This was utf-16")
156+
assert data == "This was utf-16"
168157

169158
def test_open_encoding_errors(self):
170159
in_memory_file = io.BytesIO()
@@ -177,9 +166,9 @@ def test_open_encoding_errors(self):
177166

178167
# encoding= as a positional argument for gh-101144.
179168
data = u16.read_text("utf-8", errors="ignore")
180-
self.assertEqual(data, "invalid utf-8: .")
169+
assert data == "invalid utf-8: ."
181170
with u16.open("r", "utf-8", errors="surrogateescape") as f:
182-
self.assertEqual(f.read(), "invalid utf-8: \udcff\udcff.")
171+
assert f.read() == "invalid utf-8: \udcff\udcff."
183172

184173
# encoding= both positional and keyword is an error; gh-101144.
185174
with self.assertRaisesRegex(TypeError, "encoding"):
@@ -191,24 +180,21 @@ def test_open_encoding_errors(self):
191180
with self.assertRaises(UnicodeDecodeError):
192181
f.read()
193182

194-
def test_encoding_warnings(self):
183+
@unittest.skipIf(
184+
not getattr(sys.flags, 'warn_default_encoding', 0),
185+
"Requires warn_default_encoding",
186+
)
187+
@pass_alpharep
188+
def test_encoding_warnings(self, alpharep):
195189
"""EncodingWarning must blame the read_text and open calls."""
196-
code = '''\
197-
import io, zipfile
198-
with zipfile.ZipFile(io.BytesIO(), "w") as zf:
199-
zf.filename = '<test_encoding_warnings in memory zip file>'
200-
zf.writestr("path/file.txt", b"Spanish Inquisition")
201-
root = zipfile.Path(zf)
202-
(path,) = root.iterdir()
203-
file_path = path.joinpath("file.txt")
204-
unused = file_path.read_text() # should warn
205-
file_path.open("r").close() # should warn
206-
'''
207-
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
208-
warnings = proc.err.splitlines()
209-
self.assertEqual(len(warnings), 2, proc.err)
210-
self.assertRegex(warnings[0], rb"^<string>:8: EncodingWarning:")
211-
self.assertRegex(warnings[1], rb"^<string>:9: EncodingWarning:")
190+
assert sys.flags.warn_default_encoding
191+
root = zipfile.Path(alpharep)
192+
with self.assertWarns(EncodingWarning) as wc:
193+
root.joinpath("a.txt").read_text()
194+
assert __file__ == wc.filename
195+
with self.assertWarns(EncodingWarning) as wc:
196+
root.joinpath("a.txt").open("r").close()
197+
assert __file__ == wc.filename
212198

213199
def test_open_write(self):
214200
"""
@@ -250,7 +236,8 @@ def test_read(self, alpharep):
250236
root = zipfile.Path(alpharep)
251237
a, b, g = root.iterdir()
252238
assert a.read_text(encoding="utf-8") == "content of a"
253-
a.read_text("utf-8") # No positional arg TypeError per gh-101144.
239+
# Also check positional encoding arg (gh-101144).
240+
assert a.read_text("utf-8") == "content of a"
254241
assert a.read_bytes() == b"content of a"
255242

256243
@pass_alpharep
@@ -275,19 +262,6 @@ def test_traverse_truediv(self, alpharep):
275262
e = root / "b" / "d" / "e.txt"
276263
assert e.read_text(encoding="utf-8") == "content of e"
277264

278-
@pass_alpharep
279-
def test_traverse_simplediv(self, alpharep):
280-
"""
281-
Disable the __future__.division when testing traversal.
282-
"""
283-
code = compile(
284-
source="zipfile.Path(alpharep) / 'a'",
285-
filename="(test)",
286-
mode="eval",
287-
dont_inherit=True,
288-
)
289-
eval(code)
290-
291265
@pass_alpharep
292266
def test_pathlike_construction(self, alpharep):
293267
"""
@@ -356,7 +330,7 @@ def test_joinpath_constant_time(self):
356330
# Check the file iterated all items
357331
assert entries.count == self.HUGE_ZIPFILE_NUM_ENTRIES
358332

359-
# @func_timeout.func_set_timeout(3)
333+
@set_timeout(3)
360334
def test_implied_dirs_performance(self):
361335
data = ['/'.join(string.ascii_lowercase + str(n)) for n in range(10000)]
362336
zipfile.CompleteDirs._implied_dirs(data)
@@ -472,6 +446,52 @@ def test_root_unnamed(self, alpharep):
472446
assert sub.name == "b"
473447
assert sub.parent
474448

449+
@pass_alpharep
450+
def test_match_and_glob(self, alpharep):
451+
root = zipfile.Path(alpharep)
452+
assert not root.match("*.txt")
453+
454+
assert list(root.glob("b/c.*")) == [zipfile.Path(alpharep, "b/c.txt")]
455+
456+
files = root.glob("**/*.txt")
457+
assert all(each.match("*.txt") for each in files)
458+
459+
assert list(root.glob("**/*.txt")) == list(root.rglob("*.txt"))
460+
461+
def test_glob_empty(self):
462+
root = zipfile.Path(zipfile.ZipFile(io.BytesIO(), 'w'))
463+
with self.assertRaises(ValueError):
464+
root.glob('')
465+
466+
@pass_alpharep
467+
def test_eq_hash(self, alpharep):
468+
root = zipfile.Path(alpharep)
469+
assert root == zipfile.Path(alpharep)
470+
471+
assert root != (root / "a.txt")
472+
assert (root / "a.txt") == (root / "a.txt")
473+
474+
root = zipfile.Path(alpharep)
475+
assert root in {root}
476+
477+
@pass_alpharep
478+
def test_is_symlink(self, alpharep):
479+
"""
480+
See python/cpython#82102 for symlink support beyond this object.
481+
"""
482+
483+
root = zipfile.Path(alpharep)
484+
assert not root.is_symlink()
485+
486+
@pass_alpharep
487+
def test_relative_to(self, alpharep):
488+
root = zipfile.Path(alpharep)
489+
relative = root.joinpath("b", "c.txt").relative_to(root / "b")
490+
assert str(relative) == "c.txt"
491+
492+
relative = root.joinpath("b", "d", "e.txt").relative_to(root / "b")
493+
assert str(relative) == "d/e.txt"
494+
475495
@pass_alpharep
476496
def test_inheritance(self, alpharep):
477497
cls = type('PathChild', (zipfile.Path,), {})
@@ -493,3 +513,14 @@ def test_pickle(self, alpharep, path_type, subpath):
493513
restored_1 = pickle.loads(saved_1)
494514
first, *rest = restored_1.iterdir()
495515
assert first.read_text().startswith('content of ')
516+
517+
@pass_alpharep
518+
def test_extract_orig_with_implied_dirs(self, alpharep):
519+
"""
520+
A zip file wrapped in a Path should extract even with implied dirs.
521+
"""
522+
source_path = self.zipfile_ondisk(alpharep)
523+
zf = zipfile.ZipFile(source_path)
524+
# wrap the zipfile for its side effect
525+
zipfile.Path(zf)
526+
zf.extractall(source_path.parent)

‎Lib/zipfile/_path.py

Copy file name to clipboardExpand all lines: Lib/zipfile/_path.py
+60-3Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import itertools
55
import contextlib
66
import pathlib
7+
import re
8+
import fnmatch
79

810

911
__all__ = ['Path']
@@ -93,7 +95,7 @@ def _implied_dirs(names):
9395
return _dedupe(_difference(as_dirs, names))
9496

9597
def namelist(self):
96-
names = super(CompleteDirs, self).namelist()
98+
names = super().namelist()
9799
return names + list(self._implied_dirs(names))
98100

99101
def _name_set(self):
@@ -109,6 +111,17 @@ def resolve_dir(self, name):
109111
dir_match = name not in names and dirname in names
110112
return dirname if dir_match else name
111113

114+
def getinfo(self, name):
115+
"""
116+
Supplement getinfo for implied dirs.
117+
"""
118+
try:
119+
return super().getinfo(name)
120+
except KeyError:
121+
if not name.endswith('/') or name not in self._name_set():
122+
raise
123+
return zipfile.ZipInfo(filename=name)
124+
112125
@classmethod
113126
def make(cls, source):
114127
"""
@@ -138,13 +151,13 @@ class FastLookup(CompleteDirs):
138151
def namelist(self):
139152
with contextlib.suppress(AttributeError):
140153
return self.__names
141-
self.__names = super(FastLookup, self).namelist()
154+
self.__names = super().namelist()
142155
return self.__names
143156

144157
def _name_set(self):
145158
with contextlib.suppress(AttributeError):
146159
return self.__lookup
147-
self.__lookup = super(FastLookup, self)._name_set()
160+
self.__lookup = super()._name_set()
148161
return self.__lookup
149162

150163

@@ -246,6 +259,18 @@ def __init__(self, root, at=""):
246259
self.root = FastLookup.make(root)
247260
self.at = at
248261

262+
def __eq__(self, other):
263+
"""
264+
>>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo'
265+
False
266+
"""
267+
if self.__class__ is not other.__class__:
268+
return NotImplemented
269+
return (self.root, self.at) == (other.root, other.at)
270+
271+
def __hash__(self):
272+
return hash((self.root, self.at))
273+
249274
def open(self, mode='r', *args, pwd=None, **kwargs):
250275
"""
251276
Open this entry as text or binary following the semantics
@@ -316,6 +341,38 @@ def iterdir(self):
316341
subs = map(self._next, self.root.namelist())
317342
return filter(self._is_child, subs)
318343

344+
def match(self, path_pattern):
345+
return pathlib.Path(self.at).match(path_pattern)
346+
347+
def is_symlink(self):
348+
"""
349+
Return whether this path is a symlink. Always false (python/cpython#82102).
350+
"""
351+
return False
352+
353+
def _descendants(self):
354+
for child in self.iterdir():
355+
yield child
356+
if child.is_dir():
357+
yield from child._descendants()
358+
359+
def glob(self, pattern):
360+
if not pattern:
361+
raise ValueError(f"Unacceptable pattern: {pattern!r}")
362+
363+
matches = re.compile(fnmatch.translate(pattern)).fullmatch
364+
return (
365+
child
366+
for child in self._descendants()
367+
if matches(str(child.relative_to(self)))
368+
)
369+
370+
def rglob(self, pattern):
371+
return self.glob(f'**/{pattern}')
372+
373+
def relative_to(self, other, *extra):
374+
return posixpath.relpath(str(self), str(other.joinpath(*extra)))
375+
319376
def __str__(self):
320377
return posixpath.join(self.root.filename, self.at)
321378

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.