Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
zip.py184 linesDownload Raw Back to implementations
1import os2import zipfile3 4import fsspec5from fsspec.archive import AbstractArchiveFileSystem6 7 8class ZipFileSystem(AbstractArchiveFileSystem):9    """Read/Write contents of ZIP archive as a file-system10 11    Keeps file object open while instance lives.12 13    This class is pickleable, but not necessarily thread-safe14    """15 16    root_marker = ""17    protocol = "zip"18    cachable = False19 20    def __init__(21        self,22        fo="",23        mode="r",24        target_protocol=None,25        target_options=None,26        compression=zipfile.ZIP_STORED,27        allowZip64=True,28        compresslevel=None,29        **kwargs,30    ):31        """32        Parameters33        ----------34        fo: str or file-like35            Contains ZIP, and must exist. If a str, will fetch file using36            :meth:`~fsspec.open_files`, which must return one file exactly.37        mode: str38            Accept: "r", "w", "a"39        target_protocol: str (optional)40            If ``fo`` is a string, this value can be used to override the41            FS protocol inferred from a URL42        target_options: dict (optional)43            Kwargs passed when instantiating the target FS, if ``fo`` is44            a string.45        compression, allowZip64, compresslevel: passed to ZipFile46            Only relevant when creating a ZIP47        """48        super().__init__(self, **kwargs)49        if mode not in set("rwa"):50            raise ValueError(f"mode '{mode}' no understood")51        self.mode = mode52        if isinstance(fo, (str, os.PathLike)):53            if mode == "a":54                m = "r+b"55            else:56                m = mode + "b"57            fo = fsspec.open(58                fo, mode=m, protocol=target_protocol, **(target_options or {})59            )60        self.force_zip_64 = allowZip6461        self.of = fo62        self.fo = fo.__enter__()  # the whole instance is a context63        self.zip = zipfile.ZipFile(64            self.fo,65            mode=mode,66            compression=compression,67            allowZip64=allowZip64,68            compresslevel=compresslevel,69        )70        self.dir_cache = None71 72    @classmethod73    def _strip_protocol(cls, path):74        # zip file paths are always relative to the archive root75        return super()._strip_protocol(path).lstrip("/")76 77    def __del__(self):78        if hasattr(self, "zip"):79            self.close()80            del self.zip81        if hasattr(self, "of") and hasattr(self.of, "__exit__"):82            self.of.__exit__(None, None, None)83 84    def close(self):85        """Commits any write changes to the file. Done on ``del`` too."""86        self.zip.close()87 88    def _get_dirs(self):89        if self.dir_cache is None or self.mode in set("wa"):90            # when writing, dir_cache is always in the ZipFile's attributes,91            # not read from the file.92            files = self.zip.infolist()93            self.dir_cache = {94                dirname.rstrip("/"): {95                    "name": dirname.rstrip("/"),96                    "size": 0,97                    "type": "directory",98                }99                for dirname in self._all_dirnames(self.zip.namelist())100            }101            for z in files:102                f = {s: getattr(z, s, None) for s in zipfile.ZipInfo.__slots__}103                f.update(104                    {105                        "name": z.filename.rstrip("/"),106                        "size": z.file_size,107                        "type": ("directory" if z.is_dir() else "file"),108                    }109                )110                self.dir_cache[f["name"]] = f111 112    def pipe_file(self, path, value, **kwargs):113        # override upstream, because we know the exact file size in this case114        self.zip.writestr(path, value, **kwargs)115 116    def _open(117        self,118        path,119        mode="rb",120        block_size=None,121        autocommit=True,122        cache_options=None,123        **kwargs,124    ):125        path = self._strip_protocol(path)126        if "r" in mode and self.mode in set("wa"):127            if self.exists(path):128                raise OSError("ZipFS can only be open for reading or writing, not both")129            raise FileNotFoundError(path)130        if "r" in self.mode and "w" in mode:131            raise OSError("ZipFS can only be open for reading or writing, not both")132        out = self.zip.open(path, mode.strip("b"), force_zip64=self.force_zip_64)133        if "r" in mode:134            info = self.info(path)135            out.size = info["size"]136            out.name = info["name"]137        return out138 139    def find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs):140        if maxdepth is not None and maxdepth < 1:141            raise ValueError("maxdepth must be at least 1")142 143        def to_parts(_path: str):144            return list(filter(None, _path.replace("\\", "/").split("/")))145 146        if not isinstance(path, str):147            path = str(path)148 149        # Remove the leading slash, as the zip file paths are always150        # given without a leading slash151        path = path.lstrip("/")152        path_parts = to_parts(path)153        path_depth = len(path_parts)154 155        self._get_dirs()156 157        result = {}158        # To match posix find, if an exact file name is given, we should159        # return only that file160        if path in self.dir_cache and self.dir_cache[path]["type"] == "file":161            result[path] = self.dir_cache[path]162            return result if detail else [path]163 164        for file_path, file_info in self.dir_cache.items():165            if len(file_parts := to_parts(file_path)) < path_depth or any(166                a != b for a, b in zip(path_parts, file_parts)167            ):168                # skip parent folders and mismatching paths169                continue170 171            if file_info["type"] == "directory":172                if withdirs and file_path not in result:173                    result[file_path.strip("/")] = file_info174                continue175 176            if file_path not in result:177                result[file_path] = file_info if detail else None178 179        if maxdepth:180            result = {181                k: v for k, v in result.items() if k.count("/") < maxdepth + path_depth182            }183        return result if detail else sorted(result)184 
codekingpro/portable-devtools · Team Ai