| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff 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) | ||
| Original file line number | Diff line number | Diff 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'] | ||
| Original file line number | Diff line number | Diff 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 | + | ||
| 1 | 30 | # from more_itertools v8.13.0 | |
| 2 | 31 | def always_iterable(obj, base_type=(str, bytes)): | |
| 3 | 32 | if obj is None: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,36 +4,25 @@ | |||
| 4 | 4 | import pathlib | |
| 5 | 5 | import pickle | |
| 6 | 6 | import string | |
| 7 | - from test.support.script_helper import assert_python_ok | ||
| 7 | + import sys | ||
| 8 | 8 | import unittest | |
| 9 | 9 | import zipfile | |
| 10 | 10 | ||
| 11 | - from ._test_params import parameterize, Invoked | ||
| 12 | 11 | from ._functools import compose | |
| 12 | + from ._itertools import Counter | ||
| 13 | 13 | ||
| 14 | + from ._test_params import parameterize, Invoked | ||
| 15 | + from ._func_timeout_compat import set_timeout | ||
| 14 | 16 | ||
| 15 | 17 | from test.support.os_helper import temp_dir | |
| 16 | 18 | ||
| 17 | 19 | ||
| 18 | - # Poor man's technique to consume a (smallish) iterable. | ||
| 19 | - consume = tuple | ||
| 20 | - | ||
| 21 | - | ||
| 22 | - # from jaraco.itertools 5.0 | ||
| 23 | 20 | class jaraco: | |
| 24 | 21 | class itertools: | |
| 25 | - class Counter: | ||
| 26 | - def __init__(self, i): | ||
| 27 | - self.count = 0 | ||
| 28 | - self._orig_iter = iter(i) | ||
| 22 | + Counter = Counter | ||
| 29 | 23 | ||
| 30 | - def __iter__(self): | ||
| 31 | - return self | ||
| 32 | 24 | ||
| 33 | - def __next__(self): | ||
| 34 | - result = next(self._orig_iter) | ||
| 35 | - self.count += 1 | ||
| 36 | - return result | ||
| 25 | + consume = tuple | ||
| 37 | 26 | ||
| 38 | 27 | ||
| 39 | 28 | def add_dirs(zf): | |
@@ -161,10 +150,10 @@ def test_open_encoding_utf16(self): | |||
| 161 | 150 | u16 = path.joinpath("16.txt") | |
| 162 | 151 | with u16.open('r', "utf-16") as strm: | |
| 163 | 152 | data = strm.read() | |
| 164 | - self.assertEqual(data, "This was utf-16") | ||
| 153 | + assert data == "This was utf-16" | ||
| 165 | 154 | with u16.open(encoding="utf-16") as strm: | |
| 166 | 155 | data = strm.read() | |
| 167 | - self.assertEqual(data, "This was utf-16") | ||
| 156 | + assert data == "This was utf-16" | ||
| 168 | 157 | ||
| 169 | 158 | def test_open_encoding_errors(self): | |
| 170 | 159 | in_memory_file = io.BytesIO() | |
@@ -177,9 +166,9 @@ def test_open_encoding_errors(self): | |||
| 177 | 166 | ||
| 178 | 167 | # encoding= as a positional argument for gh-101144. | |
| 179 | 168 | data = u16.read_text("utf-8", errors="ignore") | |
| 180 | - self.assertEqual(data, "invalid utf-8: .") | ||
| 169 | + assert data == "invalid utf-8: ." | ||
| 181 | 170 | 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." | ||
| 183 | 172 | ||
| 184 | 173 | # encoding= both positional and keyword is an error; gh-101144. | |
| 185 | 174 | with self.assertRaisesRegex(TypeError, "encoding"): | |
@@ -191,24 +180,21 @@ def test_open_encoding_errors(self): | |||
| 191 | 180 | with self.assertRaises(UnicodeDecodeError): | |
| 192 | 181 | f.read() | |
| 193 | 182 | ||
| 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): | ||
| 195 | 189 | """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 | ||
| 212 | 198 | ||
| 213 | 199 | def test_open_write(self): | |
| 214 | 200 | """ | |
@@ -250,7 +236,8 @@ def test_read(self, alpharep): | |||
| 250 | 236 | root = zipfile.Path(alpharep) | |
| 251 | 237 | a, b, g = root.iterdir() | |
| 252 | 238 | 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" | ||
| 254 | 241 | assert a.read_bytes() == b"content of a" | |
| 255 | 242 | ||
| 256 | 243 | @pass_alpharep | |
@@ -275,19 +262,6 @@ def test_traverse_truediv(self, alpharep): | |||
| 275 | 262 | e = root / "b" / "d" / "e.txt" | |
| 276 | 263 | assert e.read_text(encoding="utf-8") == "content of e" | |
| 277 | 264 | ||
| 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 | - | ||
| 291 | 265 | @pass_alpharep | |
| 292 | 266 | def test_pathlike_construction(self, alpharep): | |
| 293 | 267 | """ | |
@@ -356,7 +330,7 @@ def test_joinpath_constant_time(self): | |||
| 356 | 330 | # Check the file iterated all items | |
| 357 | 331 | assert entries.count == self.HUGE_ZIPFILE_NUM_ENTRIES | |
| 358 | 332 | ||
| 359 | - # @func_timeout.func_set_timeout(3) | ||
| 333 | + @set_timeout(3) | ||
| 360 | 334 | def test_implied_dirs_performance(self): | |
| 361 | 335 | data = ['/'.join(string.ascii_lowercase + str(n)) for n in range(10000)] | |
| 362 | 336 | zipfile.CompleteDirs._implied_dirs(data) | |
@@ -472,6 +446,52 @@ def test_root_unnamed(self, alpharep): | |||
| 472 | 446 | assert sub.name == "b" | |
| 473 | 447 | assert sub.parent | |
| 474 | 448 | ||
| 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 | + | ||
| 475 | 495 | @pass_alpharep | |
| 476 | 496 | def test_inheritance(self, alpharep): | |
| 477 | 497 | cls = type('PathChild', (zipfile.Path,), {}) | |
@@ -493,3 +513,14 @@ def test_pickle(self, alpharep, path_type, subpath): | |||
| 493 | 513 | restored_1 = pickle.loads(saved_1) | |
| 494 | 514 | first, *rest = restored_1.iterdir() | |
| 495 | 515 | 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) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,8 @@ | |||
| 4 | 4 | import itertools | |
| 5 | 5 | import contextlib | |
| 6 | 6 | import pathlib | |
| 7 | + import re | ||
| 8 | + import fnmatch | ||
| 7 | 9 | ||
| 8 | 10 | ||
| 9 | 11 | __all__ = ['Path'] | |
@@ -93,7 +95,7 @@ def _implied_dirs(names): | |||
| 93 | 95 | return _dedupe(_difference(as_dirs, names)) | |
| 94 | 96 | ||
| 95 | 97 | def namelist(self): | |
| 96 | - names = super(CompleteDirs, self).namelist() | ||
| 98 | + names = super().namelist() | ||
| 97 | 99 | return names + list(self._implied_dirs(names)) | |
| 98 | 100 | ||
| 99 | 101 | def _name_set(self): | |
@@ -109,6 +111,17 @@ def resolve_dir(self, name): | |||
| 109 | 111 | dir_match = name not in names and dirname in names | |
| 110 | 112 | return dirname if dir_match else name | |
| 111 | 113 | ||
| 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 | + | ||
| 112 | 125 | @classmethod | |
| 113 | 126 | def make(cls, source): | |
| 114 | 127 | """ | |
@@ -138,13 +151,13 @@ class FastLookup(CompleteDirs): | |||
| 138 | 151 | def namelist(self): | |
| 139 | 152 | with contextlib.suppress(AttributeError): | |
| 140 | 153 | return self.__names | |
| 141 | - self.__names = super(FastLookup, self).namelist() | ||
| 154 | + self.__names = super().namelist() | ||
| 142 | 155 | return self.__names | |
| 143 | 156 | ||
| 144 | 157 | def _name_set(self): | |
| 145 | 158 | with contextlib.suppress(AttributeError): | |
| 146 | 159 | return self.__lookup | |
| 147 | - self.__lookup = super(FastLookup, self)._name_set() | ||
| 160 | + self.__lookup = super()._name_set() | ||
| 148 | 161 | return self.__lookup | |
| 149 | 162 | ||
| 150 | 163 | ||
@@ -246,6 +259,18 @@ def __init__(self, root, at=""): | |||
| 246 | 259 | self.root = FastLookup.make(root) | |
| 247 | 260 | self.at = at | |
| 248 | 261 | ||
| 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 | + | ||
| 249 | 274 | def open(self, mode='r', *args, pwd=None, **kwargs): | |
| 250 | 275 | """ | |
| 251 | 276 | Open this entry as text or binary following the semantics | |
@@ -316,6 +341,38 @@ def iterdir(self): | |||
| 316 | 341 | subs = map(self._next, self.root.namelist()) | |
| 317 | 342 | return filter(self._is_child, subs) | |
| 318 | 343 | ||
| 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 | + | ||
| 319 | 376 | def __str__(self): | |
| 320 | 377 | return posixpath.join(self.root.filename, self.at) | |
| 321 | 378 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments