given a path with elements separated by posixpath sep generate all parents of that path listparents bd b listparents bd b listparents bdf bd b listparents b listparents given a path with elements separated by posixpath sep generate all elements of that path listancestry bd bd b listancestry bd bd b listancestry bdf bdf bd b listancestry b b listancestry deduplicate an iterable in original order def differenceminuend subtrahend return itertools filterfalsesetsubtrahend contains minuend class initializedstate def initself args kwargs self args args self kwargs kwargs super initargs kwargs def getstateself return self args self kwargs def setstateself state args kwargs state super initargs kwargs class completedirsinitializedstate zipfile zipfile staticmethod def implieddirsnames parents itertools chain fromiterablemapparents names asdirs p posixpath sep for p in parents return dedupedifferenceasdirs names def namelistself names super namelist return names listself implieddirsnames def namesetself return setself namelist def resolvedirself name names self nameset dirname name dirmatch name not in names and dirname in names return dirname if dirmatch else name def getinfoself name try return super getinfoname except keyerror if not name endswith or name not in self nameset raise return zipfile zipinfofilenamename classmethod def makecls source if isinstancesource completedirs return source if not isinstancesource zipfile zipfile return clssource only allow for fastlookup when supplied zipfile is readonly if r not in source mode cls completedirs source class cls return source class fastlookupcompletedirs def namelistself with contextlib suppressattributeerror return self names self names super namelist return self names def namesetself with contextlib suppressattributeerror return self lookup self lookup super nameset return self lookup def extracttextencodingencodingnone args kwargs stacklevel3 so that the caller of the caller see any warning return io textencodingencoding 3 args kwargs class path repr self class nameself root filename r self at r def initself root at self root fastlookup makeroot self at at def eqself other if self class is not other class return notimplemented return self root self at other root other at def hashself return hashself root self at def openself mode r args pwdnone kwargs if self isdir raise isadirectoryerrorself zipmode mode0 if not self exists and zipmode r raise filenotfounderrorself stream self root openself at zipmode pwdpwd if b in mode if args or kwargs raise valueerrorencoding args invalid for binary operation return stream text mode encoding args kwargs extracttextencodingargs kwargs return io textiowrapperstream encoding args kwargs def baseself return pathlib pureposixpathself at or self root filename property def nameself return self base name property def suffixself return self base suffix property def suffixesself return self base suffixes property def stemself return self base stem property def filenameself return pathlib pathself root filename joinpathself at def readtextself args kwargs encoding args kwargs extracttextencodingargs kwargs with self open r encoding args kwargs as strm return strm read def readbytesself with self open rb as strm return strm read def ischildself path return posixpath dirnamepath at rstrip self at rstrip def nextself at return self classself root at def isdirself return not self at or self at endswith def isfileself return self exists and not self isdir def existsself return self at in self root nameset def iterdirself if not self isdir raise valueerrorcan t listdir a file subs mapself next self root namelist return filterself ischild subs def matchself pathpattern return pathlib pureposixpathself at matchpathpattern def issymlinkself return false def globself pattern if not pattern raise valueerrorfunacceptable pattern pattern r prefix re escapeself at matches re compileprefix translatepattern fullmatch return mapself next filtermatches self root namelist def rglobself pattern return self globf pattern def relativetoself other extra return posixpath relpathstrself strother joinpathextra def strself return posixpath joinself root filename self at def reprself return self repr formatselfself def joinpathself other next posixpath joinself at other return self nextself root resolvedirnext truediv joinpath property def parentself if not self at return self filename parent parentat posixpath dirnameself at rstrip if parentat parentat return self nextparentat given a path with elements separated by posixpath sep generate all parents of that path list _parents b d b list _parents b d b list _parents b d f b d b list _parents b list _parents given a path with elements separated by posixpath sep generate all elements of that path list _ancestry b d b d b list _ancestry b d b d b list _ancestry b d f b d f b d b list _ancestry b b list _ancestry deduplicate an iterable in original order return items in minuend not in subtrahend retaining order with o 1 lookup mix in to save the initialization state for pickling a zipfile subclass that ensures that implied directories are always included in the namelist list completedirs _implied_dirs foo bar txt foo bar baz txt foo foo bar list completedirs _implied_dirs foo bar txt foo bar baz txt foo bar foo if the name represents a directory return that name as a directory with the trailing slash supplement getinfo for implied dirs given a source filename or zipfile return an appropriate completedirs subclass only allow for fastlookup when supplied zipfile is read only zipfile subclass to ensure implicit dirs exist and are resolved rapidly stacklevel 3 so that the caller of the caller see any warning 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 mem 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 mem abcde zip a txt b path mem abcde zip b name property b name b join with divide operator c b c txt c path mem abcde zip b c txt c name c txt read text c read_text encoding utf 8 content of c existence c exists true b missing txt exists false coercion to string import os str c replace os sep posixpath sep mem abcde zip b c txt at the root name filename and parent resolve to the zipfile note these attributes are not valid and will raise a valueerror if the zipfile has no filename root name abcde zip str root filename replace os sep posixpath sep mem abcde zip str root parent mem construct a path from a zipfile or filename note when the source is an existing zipfile object its type __class__ will be mutated to a specialized type if the caller wishes to retain the original type the caller should either create a separate zipfile object or pass a filename path zipfile zipfile io bytesio w foo false open this entry as text or binary following the semantics of pathlib path open by passing arguments through to io textiowrapper text mode return whether this path is a symlink always false python cpython 82102
import io
import posixpath
import zipfile
import itertools
import contextlib
import pathlib
import re

from .glob import translate


__all__ = ['Path']


def _parents(path):
    
    return itertools.islice(_ancestry(path), 1, None)


def _ancestry(path):
    
    path = path.rstrip(posixpath.sep)
    while path and path != posixpath.sep:
        yield path
        path, tail = posixpath.split(path)


_dedupe = dict.fromkeys



def _difference(minuend, subtrahend):
    
    return itertools.filterfalse(set(subtrahend).__contains__, minuend)


class InitializedState:
    

    def __init__(self, *args, **kwargs):
        self.__args = args
        self.__kwargs = kwargs
        super().__init__(*args, **kwargs)

    def __getstate__(self):
        return self.__args, self.__kwargs

    def __setstate__(self, state):
        args, kwargs = state
        super().__init__(*args, **kwargs)


class CompleteDirs(InitializedState, zipfile.ZipFile):
    

    @staticmethod
    def _implied_dirs(names):
        parents = itertools.chain.from_iterable(map(_parents, names))
        as_dirs = (p + posixpath.sep for p in parents)
        return _dedupe(_difference(as_dirs, names))

    def namelist(self):
        names = super().namelist()
        return names + list(self._implied_dirs(names))

    def _name_set(self):
        return set(self.namelist())

    def resolve_dir(self, name):
        
        names = self._name_set()
        dirname = name + '/'
        dir_match = name not in names and dirname in names
        return dirname if dir_match else name

    def getinfo(self, name):
        
        try:
            return super().getinfo(name)
        except KeyError:
            if not name.endswith('/') or name not in self._name_set():
                raise
            return zipfile.ZipInfo(filename=name)

    @classmethod
    def make(cls, source):
        
        if isinstance(source, CompleteDirs):
            return source

        if not isinstance(source, zipfile.ZipFile):
            return cls(source)

        
        if 'r' not in source.mode:
            cls = CompleteDirs

        source.__class__ = cls
        return source


class FastLookup(CompleteDirs):
    

    def namelist(self):
        with contextlib.suppress(AttributeError):
            return self.__names
        self.__names = super().namelist()
        return self.__names

    def _name_set(self):
        with contextlib.suppress(AttributeError):
            return self.__lookup
        self.__lookup = super()._name_set()
        return self.__lookup


def _extract_text_encoding(encoding=None, *args, **kwargs):
    
    return io.text_encoding(encoding, 3), args, kwargs


class Path:
    

    __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"

    def __init__(self, root, at=""):
        
        self.root = FastLookup.make(root)
        self.at = at

    def __eq__(self, other):
        
        if self.__class__ is not other.__class__:
            return NotImplemented
        return (self.root, self.at) == (other.root, other.at)

    def __hash__(self):
        return hash((self.root, self.at))

    def open(self, mode='r', *args, pwd=None, **kwargs):
        
        if self.is_dir():
            raise IsADirectoryError(self)
        zip_mode = mode[0]
        if not self.exists() and zip_mode == 'r':
            raise FileNotFoundError(self)
        stream = self.root.open(self.at, zip_mode, pwd=pwd)
        if 'b' in mode:
            if args or kwargs:
                raise ValueError("encoding args invalid for binary operation")
            return stream
        
        encoding, args, kwargs = _extract_text_encoding(*args, **kwargs)
        return io.TextIOWrapper(stream, encoding, *args, **kwargs)

    def _base(self):
        return pathlib.PurePosixPath(self.at or self.root.filename)

    @property
    def name(self):
        return self._base().name

    @property
    def suffix(self):
        return self._base().suffix

    @property
    def suffixes(self):
        return self._base().suffixes

    @property
    def stem(self):
        return self._base().stem

    @property
    def filename(self):
        return pathlib.Path(self.root.filename).joinpath(self.at)

    def read_text(self, *args, **kwargs):
        encoding, args, kwargs = _extract_text_encoding(*args, **kwargs)
        with self.open('r', encoding, *args, **kwargs) as strm:
            return strm.read()

    def read_bytes(self):
        with self.open('rb') as strm:
            return strm.read()

    def _is_child(self, path):
        return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")

    def _next(self, at):
        return self.__class__(self.root, at)

    def is_dir(self):
        return not self.at or self.at.endswith("/")

    def is_file(self):
        return self.exists() and not self.is_dir()

    def exists(self):
        return self.at in self.root._name_set()

    def iterdir(self):
        if not self.is_dir():
            raise ValueError("Can't listdir a file")
        subs = map(self._next, self.root.namelist())
        return filter(self._is_child, subs)

    def match(self, path_pattern):
        return pathlib.PurePosixPath(self.at).match(path_pattern)

    def is_symlink(self):
        
        return False

    def glob(self, pattern):
        if not pattern:
            raise ValueError(f"Unacceptable pattern: {pattern!r}")

        prefix = re.escape(self.at)
        matches = re.compile(prefix + translate(pattern)).fullmatch
        return map(self._next, filter(matches, self.root.namelist()))

    def rglob(self, pattern):
        return self.glob(f'**/{pattern}')

    def relative_to(self, other, *extra):
        return posixpath.relpath(str(self), str(other.joinpath(*extra)))

    def __str__(self):
        return posixpath.join(self.root.filename, self.at)

    def __repr__(self):
        return self.__repr.format(self=self)

    def joinpath(self, *other):
        next = posixpath.join(self.at, *other)
        return self._next(self.root.resolve_dir(next))

    __truediv__ = joinpath

    @property
    def parent(self):
        if not self.at:
            return self.filename.parent
        parent_at = posixpath.dirname(self.at.rstrip('/'))
        if parent_at:
            parent_at += '/'
        return self._next(parent_at)