Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
local.py518 linesDownload Raw Back to implementations
1import datetime2import io3import logging4import os5import os.path as osp6import shutil7import stat8import tempfile9from functools import lru_cache10 11from fsspec import AbstractFileSystem12from fsspec.compression import compr13from fsspec.core import get_compression14from fsspec.utils import isfilelike, stringify_path15 16logger = logging.getLogger("fsspec.local")17 18 19class LocalFileSystem(AbstractFileSystem):20    """Interface to files on local storage21 22    Parameters23    ----------24    auto_mkdir: bool25        Whether, when opening a file, the directory containing it should26        be created (if it doesn't already exist). This is assumed by pyarrow27        code.28    """29 30    root_marker = "/"31    protocol = "file", "local"32    local_file = True33 34    def __init__(self, auto_mkdir=False, **kwargs):35        super().__init__(**kwargs)36        self.auto_mkdir = auto_mkdir37 38    @property39    def fsid(self):40        return "local"41 42    def mkdir(self, path, create_parents=True, **kwargs):43        path = self._strip_protocol(path)44        if self.exists(path):45            raise FileExistsError(path)46        if create_parents:47            self.makedirs(path, exist_ok=True)48        else:49            os.mkdir(path, **kwargs)50 51    def makedirs(self, path, exist_ok=False):52        path = self._strip_protocol(path)53        os.makedirs(path, exist_ok=exist_ok)54 55    def rmdir(self, path):56        path = self._strip_protocol(path)57        os.rmdir(path)58 59    def ls(self, path, detail=False, **kwargs):60        path = self._strip_protocol(path)61        path_info = self.info(path)62        infos = []63        if path_info["type"] == "directory":64            with os.scandir(path) as it:65                for f in it:66                    try:67                        # Only get the info if requested since it is a bit expensive (the stat call inside)68                        # The strip_protocol is also used in info() and calls make_path_posix to always return posix paths69                        info = self.info(f) if detail else self._strip_protocol(f.path)70                        infos.append(info)71                    except FileNotFoundError:72                        pass73        else:74            infos = [path_info] if detail else [path_info["name"]]75 76        return infos77 78    def info(self, path, **kwargs):79        if isinstance(path, os.DirEntry):80            # scandir DirEntry81            out = path.stat(follow_symlinks=False)82            link = path.is_symlink()83            if path.is_dir(follow_symlinks=False):84                t = "directory"85            elif path.is_file(follow_symlinks=False):86                t = "file"87            else:88                t = "other"89 90            size = out.st_size91            if link:92                try:93                    out2 = path.stat(follow_symlinks=True)94                    size = out2.st_size95                except OSError:96                    size = 097            path = self._strip_protocol(path.path)98        else:99            # str or path-like100            path = self._strip_protocol(path)101            out = os.stat(path, follow_symlinks=False)102            link = stat.S_ISLNK(out.st_mode)103            if link:104                out = os.stat(path, follow_symlinks=True)105            size = out.st_size106            if stat.S_ISDIR(out.st_mode):107                t = "directory"108            elif stat.S_ISREG(out.st_mode):109                t = "file"110            else:111                t = "other"112 113        # Check for the 'st_birthtime' attribute, which is not always present; fallback to st_ctime114        created_time = getattr(out, "st_birthtime", out.st_ctime)115 116        result = {117            "name": path,118            "size": size,119            "type": t,120            "created": created_time,121            "islink": link,122        }123        for field in ["mode", "uid", "gid", "mtime", "ino", "nlink"]:124            result[field] = getattr(out, f"st_{field}")125        if link:126            result["destination"] = os.readlink(path)127        return result128 129    def lexists(self, path, **kwargs):130        return osp.lexists(path)131 132    def cp_file(self, path1, path2, **kwargs):133        path1 = self._strip_protocol(path1)134        path2 = self._strip_protocol(path2)135        if self.auto_mkdir:136            self.makedirs(self._parent(path2), exist_ok=True)137        if self.isfile(path1):138            shutil.copyfile(path1, path2)139        elif self.isdir(path1):140            self.mkdirs(path2, exist_ok=True)141        else:142            raise FileNotFoundError(path1)143 144    def isfile(self, path):145        path = self._strip_protocol(path)146        return os.path.isfile(path)147 148    def isdir(self, path):149        path = self._strip_protocol(path)150        return os.path.isdir(path)151 152    def get_file(self, path1, path2, callback=None, **kwargs):153        if isfilelike(path2):154            with open(path1, "rb") as f:155                shutil.copyfileobj(f, path2)156        else:157            return self.cp_file(path1, path2, **kwargs)158 159    def put_file(self, path1, path2, callback=None, **kwargs):160        return self.cp_file(path1, path2, **kwargs)161 162    def mv(self, path1, path2, recursive: bool = True, **kwargs):163        """Move files/directories164        For the specific case of local, all ops on directories are recursive and165        the recursive= kwarg is ignored.166        """167        path1 = self._strip_protocol(path1)168        path2 = self._strip_protocol(path2)169 170        if self.auto_mkdir:171            self.makedirs(self._parent(path2), exist_ok=True)172 173        shutil.move(path1, path2)174 175    def link(self, src, dst, **kwargs):176        src = self._strip_protocol(src)177        dst = self._strip_protocol(dst)178        os.link(src, dst, **kwargs)179 180    def symlink(self, src, dst, **kwargs):181        src = self._strip_protocol(src)182        dst = self._strip_protocol(dst)183        os.symlink(src, dst, **kwargs)184 185    def islink(self, path) -> bool:186        return os.path.islink(self._strip_protocol(path))187 188    def rm_file(self, path):189        os.remove(self._strip_protocol(path))190 191    def rm(self, path, recursive=False, maxdepth=None):192        if not isinstance(path, list):193            path = [path]194 195        for p in path:196            p = self._strip_protocol(p)197            if self.isdir(p):198                if not recursive:199                    raise ValueError("Cannot delete directory, set recursive=True")200                if osp.abspath(p) == os.getcwd():201                    raise ValueError("Cannot delete current working directory")202                shutil.rmtree(p)203            else:204                os.remove(p)205 206    def unstrip_protocol(self, name):207        protocol = self.protocol if isinstance(self.protocol, str) else self.protocol[0]208        name = self._strip_protocol(name)  # normalise for local/win/...209        return f"{protocol}://{name}"210 211    def _open(self, path, mode="rb", block_size=None, **kwargs):212        path = self._strip_protocol(path)213        if self.auto_mkdir and "w" in mode:214            self.makedirs(self._parent(path), exist_ok=True)215        return LocalFileOpener(path, mode, fs=self, **kwargs)216 217    def touch(self, path, truncate=True, **kwargs):218        path = self._strip_protocol(path)219        if self.auto_mkdir:220            self.makedirs(self._parent(path), exist_ok=True)221        if self.exists(path):222            os.utime(path, None)223        else:224            open(path, "a").close()225        if truncate:226            os.truncate(path, 0)227 228    def created(self, path):229        info = self.info(path=path)230        return datetime.datetime.fromtimestamp(231            info["created"], tz=datetime.timezone.utc232        )233 234    def modified(self, path):235        info = self.info(path=path)236        return datetime.datetime.fromtimestamp(info["mtime"], tz=datetime.timezone.utc)237 238    @classmethod239    def _parent(cls, path):240        path = cls._strip_protocol(path)241        if os.sep == "/":242            # posix native243            return path.rsplit("/", 1)[0] or "/"244        else:245            # NT246            path_ = path.rsplit("/", 1)[0]247            if len(path_) <= 3:248                if path_[1:2] == ":":249                    # nt root (something like c:/)250                    return path_[0] + ":/"251            # More cases may be required here252            return path_253 254    @classmethod255    def _strip_protocol(cls, path):256        path = stringify_path(path)257        protos = (cls.protocol,) if isinstance(cls.protocol, str) else cls.protocol258        prefixes = (protocol + sep for protocol in protos for sep in ("://", ":"))259        for prefix in prefixes:260            if path.startswith(prefix):261                path = path.removeprefix(prefix)262                break263 264        path = make_path_posix(path)265        if os.sep != "/":266            # This code-path is a stripped down version of267            # > drive, path = ntpath.splitdrive(path)268            if path[1:2] == ":":269                # Absolute drive-letter path, e.g. X:\Windows270                # Relative path with drive, e.g. X:Windows271                drive, path = path[:2], path[2:]272            elif path[:2] == "//":273                # UNC drives, e.g. \\server\share or \\?\UNC\server\share274                # Device drives, e.g. \\.\device or \\?\device275                if (index1 := path.find("/", 2)) == -1 or (276                    index2 := path.find("/", index1 + 1)277                ) == -1:278                    drive, path = path, ""279                else:280                    drive, path = path[:index2], path[index2:]281            else:282                # Relative path, e.g. Windows283                drive = ""284 285            path = path.rstrip("/") or cls.root_marker286            return drive + path287 288        else:289            return path.rstrip("/") or cls.root_marker290 291    def _isfilestore(self):292        # Inheriting from DaskFileSystem makes this False (S3, etc. were)293        # the original motivation. But we are a posix-like file system.294        # See https://github.com/dask/dask/issues/5526295        return True296 297    def chmod(self, path, mode):298        path = stringify_path(path)299        return os.chmod(path, mode)300 301 302def make_path_posix(path):303    """Make path generic and absolute for current OS"""304    if not isinstance(path, str):305        if isinstance(path, (list, set, tuple)):306            return type(path)(make_path_posix(p) for p in path)307        else:308            path = stringify_path(path)309            if not isinstance(path, str):310                raise TypeError(f"could not convert {path!r} to string")311    if os.sep == "/":312        # Native posix313        if path.startswith("/"):314            # most common fast case for posix315            return path316        elif path.startswith("~"):317            return osp.expanduser(path)318        elif path.startswith("./"):319            path = path[2:]320        elif path == ".":321            path = ""322        return f"{os.getcwd()}/{path}"323    else:324        # NT handling325        if path[0:1] == "/" and path[2:3] == ":":326            # path is like "/c:/local/path"327            path = path[1:]328        if path[1:2] == ":":329            # windows full path like "C:\\local\\path"330            if len(path) <= 3:331                # nt root (something like c:/)332                return path[0] + ":/"333            path = path.replace("\\", "/")334            return path335        elif path[0:1] == "~":336            return make_path_posix(osp.expanduser(path))337        elif path.startswith(("\\\\", "//")):338            # windows UNC/DFS-style paths339            return "//" + path[2:].replace("\\", "/")340        elif path.startswith(("\\", "/")):341            # windows relative path with root342            path = path.replace("\\", "/")343            return f"{osp.splitdrive(os.getcwd())[0]}{path}"344        else:345            path = path.replace("\\", "/")346            if path.startswith("./"):347                path = path[2:]348            elif path == ".":349                path = ""350            return f"{make_path_posix(os.getcwd())}/{path}"351 352 353def trailing_sep(path):354    """Return True if the path ends with a path separator.355 356    A forward slash is always considered a path separator, even on Operating357    Systems that normally use a backslash.358    """359    # TODO: if all incoming paths were posix-compliant then separator would360    # always be a forward slash, simplifying this function.361    # See https://github.com/fsspec/filesystem_spec/pull/1250362    return path.endswith(os.sep) or (os.altsep is not None and path.endswith(os.altsep))363 364 365@lru_cache(maxsize=1)366def get_umask(mask: int = 0o666) -> int:367    """Get the current umask.368 369    Follows https://stackoverflow.com/a/44130549 to get the umask.370    Temporarily sets the umask to the given value, and then resets it to the371    original value.372    """373    value = os.umask(mask)374    os.umask(value)375    return value376 377 378class LocalFileOpener(io.IOBase):379    def __init__(380        self, path, mode, autocommit=True, fs=None, compression=None, **kwargs381    ):382        logger.debug("open file: %s", path)383        self.path = path384        self.mode = mode385        self.fs = fs386        self.f = None387        self.autocommit = autocommit388        self.compression = get_compression(path, compression)389        self.blocksize = io.DEFAULT_BUFFER_SIZE390        self._open()391 392    def _open(self):393        if self.f is None or self.f.closed:394            if self.autocommit or "w" not in self.mode:395                self.f = open(self.path, mode=self.mode)396                if self.compression:397                    compress = compr[self.compression]398                    self.f = compress(self.f, mode=self.mode)399            else:400                # TODO: check if path is writable?401                i, name = tempfile.mkstemp()402                os.close(i)  # we want normal open and normal buffered file403                self.temp = name404                self.f = open(name, mode=self.mode)405            if "w" not in self.mode:406                self.size = self.f.seek(0, 2)407                self.f.seek(0)408                self.f.size = self.size409 410    def _fetch_range(self, start, end):411        # probably only used by cached FS412        if "r" not in self.mode:413            raise ValueError414        self._open()415        self.f.seek(start)416        return self.f.read(end - start)417 418    def __setstate__(self, state):419        self.f = None420        loc = state.pop("loc", None)421        self.__dict__.update(state)422        if "r" in state["mode"]:423            self.f = None424            self._open()425            self.f.seek(loc)426 427    def __getstate__(self):428        d = self.__dict__.copy()429        d.pop("f")430        if "r" in self.mode:431            d["loc"] = self.f.tell()432        else:433            if not self.f.closed:434                raise ValueError("Cannot serialise open write-mode local file")435        return d436 437    def commit(self):438        if self.autocommit:439            raise RuntimeError("Can only commit if not already set to autocommit")440        try:441            shutil.move(self.temp, self.path)442        except PermissionError as e:443            # shutil.move raises PermissionError if os.rename444            # and the default copy2 fallback with shutil.copystats fail.445            # The file should be there nonetheless, but without copied permissions.446            # If it doesn't exist, there was no permission to create the file.447            if not os.path.exists(self.path):448                raise e449        else:450            # If PermissionError is not raised, permissions can be set.451            try:452                mask = 0o666453                os.chmod(self.path, mask & ~get_umask(mask))454            except RuntimeError:455                pass456 457    def discard(self):458        if self.autocommit:459            raise RuntimeError("Cannot discard if set to autocommit")460        os.remove(self.temp)461 462    def readable(self) -> bool:463        return True464 465    def writable(self) -> bool:466        return "r" not in self.mode467 468    def read(self, *args, **kwargs):469        return self.f.read(*args, **kwargs)470 471    def write(self, *args, **kwargs):472        return self.f.write(*args, **kwargs)473 474    def tell(self, *args, **kwargs):475        return self.f.tell(*args, **kwargs)476 477    def seek(self, *args, **kwargs):478        return self.f.seek(*args, **kwargs)479 480    def seekable(self, *args, **kwargs):481        return self.f.seekable(*args, **kwargs)482 483    def readline(self, *args, **kwargs):484        return self.f.readline(*args, **kwargs)485 486    def readlines(self, *args, **kwargs):487        return self.f.readlines(*args, **kwargs)488 489    def close(self):490        return self.f.close()491 492    def truncate(self, size=None) -> int:493        return self.f.truncate(size)494 495    @property496    def closed(self):497        return self.f.closed498 499    def fileno(self):500        return self.raw.fileno()501 502    def flush(self) -> None:503        self.f.flush()504 505    def __iter__(self):506        return self.f.__iter__()507 508    def __getattr__(self, item):509        return getattr(self.f, item)510 511    def __enter__(self):512        self._incontext = True513        return self514 515    def __exit__(self, exc_type, exc_value, traceback):516        self._incontext = False517        self.f.__exit__(exc_type, exc_value, traceback)518 
codekingpro/portable-devtools · Team Ai