summaryrefslogtreecommitdiff
path: root/Lib/zipfile.py
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2019-05-08 09:45:06 -0400
committerBarry Warsaw <barry@python.org>2019-05-08 09:45:05 -0400
commitb2758ff9553d8bebe4e9dd1cb3996212473810e3 (patch)
tree60ee775ed37a5262b66dec6f4122888da43df2e3 /Lib/zipfile.py
parent70b80541bb044e8cb7037acaf97f64890fef418e (diff)
downloadcpython-git-b2758ff9553d8bebe4e9dd1cb3996212473810e3.tar.gz
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.py144
1 files changed, 139 insertions, 5 deletions
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 2dc0164721..62475c701f 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