diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2019-05-08 13:45:06 (GMT) |
---|---|---|
committer | Barry Warsaw <barry@python.org> | 2019-05-08 13:45:05 (GMT) |
commit | b2758ff9553d8bebe4e9dd1cb3996212473810e3 (patch) | |
tree | 60ee775ed37a5262b66dec6f4122888da43df2e3 /Lib/zipfile.py | |
parent | 70b80541bb044e8cb7037acaf97f64890fef418e (diff) | |
download | cpython-b2758ff9553d8bebe4e9dd1cb3996212473810e3.zip cpython-b2758ff9553d8bebe4e9dd1cb3996212473810e3.tar.gz cpython-b2758ff9553d8bebe4e9dd1cb3996212473810e3.tar.bz2 |
bpo-36832: add zipfile.Path (#13153)
* bpo-36832: add zipfile.Path
* bpo-36832: add documentation for zipfile.Path
* 📜🤖 Added by blurb_it.
* Remove module reference from blurb.
* Sort the imports
* Update docstrings and docs per recommendations.
* Rely on test.support.temp_dir
* Signal that 'root' is the parameter.
* Correct spelling of 'mod'
* Convert docstring to comment for brevity.
* Fix more errors in the docs
Diffstat (limited to 'Lib/zipfile.py')
-rw-r--r-- | Lib/zipfile.py | 144 |
1 files changed, 139 insertions, 5 deletions
diff --git a/Lib/zipfile.py b/Lib/zipfile.py index 2dc0164..62475c7 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -3,16 +3,18 @@ Read and write ZIP files. XXX references to utf-8 need further investigation. """ +import binascii +import functools +import importlib.util import io import os -import importlib.util -import sys -import time -import stat +import posixpath import shutil +import stat import struct -import binascii +import sys import threading +import time try: import zlib # We may need its compression method @@ -2102,6 +2104,138 @@ class PyZipFile(ZipFile): return (fname, archivename) +class Path: + """ + A pathlib-compatible interface for zip files. + + Consider a zip file with this structure:: + + . + ├── a.txt + └── b + ├── c.txt + └── d + └── e.txt + + >>> data = io.BytesIO() + >>> zf = ZipFile(data, 'w') + >>> zf.writestr('a.txt', 'content of a') + >>> zf.writestr('b/c.txt', 'content of c') + >>> zf.writestr('b/d/e.txt', 'content of e') + >>> zf.filename = 'abcde.zip' + + Path accepts the zipfile object itself or a filename + + >>> root = Path(zf) + + From there, several path operations are available. + + Directory iteration (including the zip file itself): + + >>> a, b = root.iterdir() + >>> a + Path('abcde.zip', 'a.txt') + >>> b + Path('abcde.zip', 'b/') + + name property: + + >>> b.name + 'b' + + join with divide operator: + + >>> c = b / 'c.txt' + >>> c + Path('abcde.zip', 'b/c.txt') + >>> c.name + 'c.txt' + + Read text: + + >>> c.read_text() + 'content of c' + + existence: + + >>> c.exists() + True + >>> (b / 'missing.txt').exists() + False + + Coersion to string: + + >>> str(c) + 'abcde.zip/b/c.txt' + """ + + __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" + + def __init__(self, root, at=""): + self.root = root if isinstance(root, ZipFile) else ZipFile(root) + self.at = at + + @property + def open(self): + return functools.partial(self.root.open, self.at) + + @property + def name(self): + return posixpath.basename(self.at.rstrip("/")) + + def read_text(self, *args, **kwargs): + with self.open() as strm: + return io.TextIOWrapper(strm, *args, **kwargs).read() + + def read_bytes(self): + with self.open() as strm: + return strm.read() + + def _is_child(self, path): + return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") + + def _next(self, at): + return Path(self.root, at) + + def is_dir(self): + return not self.at or self.at.endswith("/") + + def is_file(self): + return not self.is_dir() + + def exists(self): + return self.at in self._names() + + def iterdir(self): + if not self.is_dir(): + raise ValueError("Can't listdir a file") + subs = map(self._next, self._names()) + return filter(self._is_child, subs) + + def __str__(self): + return posixpath.join(self.root.filename, self.at) + + def __repr__(self): + return self.__repr.format(self=self) + + def __truediv__(self, add): + next = posixpath.join(self.at, add) + next_dir = posixpath.join(self.at, add, "") + names = self._names() + return self._next(next_dir if next not in names and next_dir in names else next) + + @staticmethod + def _add_implied_dirs(names): + return names + [ + name + "/" + for name in map(posixpath.dirname, names) + if name and name + "/" not in names + ] + + def _names(self): + return self._add_implied_dirs(self.root.namelist()) + + def main(args=None): import argparse |