Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anypath abstractmethods #266

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 187 additions & 3 deletions cloudpathlib/anypath.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
from __future__ import absolute_import
import os
from abc import ABC
from abc import ABCMeta, abstractmethod
from pathlib import Path
from typing import Union
from typing import Generator, Iterable, List, Sequence, Tuple, Union

from .cloudpath import InvalidPrefixError, CloudPath
from .exceptions import AnyPathTypeError


class AnyPath(ABC):
class AnyPathMeta(ABCMeta):
def __init__(cls, name, bases, dic):
# Copy docstring from pathlib.Path
for attr in dir(cls):
if (
not attr.startswith("_")
and hasattr(Path, attr)
and getattr(getattr(Path, attr), "__doc__", None)
):
docstring = getattr(Path, attr).__doc__ + " _(Docstring copied from pathlib.Path)_"
getattr(cls, attr).__doc__ = docstring
if isinstance(getattr(cls, attr), property):
# Properties have __doc__ duplicated under fget, and at least some parsers
# read it from there.
getattr(cls, attr).fget.__doc__ = docstring


class AnyPath(metaclass=AnyPathMeta):
"""Polymorphic virtual superclass for CloudPath and pathlib.Path. Constructing an instance will
automatically dispatch to CloudPath or Path based on the input. It also supports both
isinstance and issubclass checks.
Expand Down Expand Up @@ -43,6 +61,172 @@ def _validate(cls, value) -> Union[CloudPath, Path]:
# Note __new__ is static method and not a class method
return cls.__new__(cls, value)

@property
@abstractmethod
def anchor(self) -> str:
pass

@property
@abstractmethod
def name(self) -> str:
pass

@property
@abstractmethod
def suffix(self) -> str:
pass

@property
@abstractmethod
def suffixes(self) -> List[str]:
pass

@property
@abstractmethod
def stem(self) -> str:
pass

@property
@abstractmethod
def parts(self) -> Tuple[str]:
pass

@property
@abstractmethod
def parent(self) -> "AnyPath":
pass

@property
@abstractmethod
def parents(self) -> Sequence["AnyPath"]:
pass

@property
@abstractmethod
def drive(self) -> str:
pass

@abstractmethod
def absolute(self) -> "AnyPath":
pass

@abstractmethod
def as_uri(self) -> str:
pass

@abstractmethod
def exists(self) -> bool:
pass

@abstractmethod
def glob(self, pattern: str) -> Generator["AnyPath", None, None]:
pass

@abstractmethod
def is_absolute(self) -> bool:
pass

@abstractmethod
def is_dir(self) -> bool:
pass

@abstractmethod
def is_file(self) -> bool:
pass

@abstractmethod
def is_relative_to(self, other) -> bool:
pass

@abstractmethod
def iterdir(self) -> Iterable["AnyPath"]:
pass

@abstractmethod
def joinpath(self, *args) -> "AnyPath":
pass

@abstractmethod
def match(self, path_pattern: str) -> bool:
pass

@abstractmethod
def mkdir(self, parents: bool = False, exist_ok: bool = False) -> None:
"""docstring? has mode for pathlib"""
pass

@abstractmethod
def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None, **kwargs):
pass

@abstractmethod
def read_bytes(self) -> bytes:
pass

@abstractmethod
def read_text(self, *args, **kwargs) -> str:
pass

@abstractmethod
def relative_to(self, other) -> Path:
pass

@abstractmethod
def rename(self, target: "AnyPath") -> "AnyPath":
"""Add docstring as behavior for CloudPath differs"""
pass

@abstractmethod
def replace(self, target) -> "AnyPath":
"""Add docstring as behavior for CloudPath differs"""
pass

@abstractmethod
def resolve(self) -> "AnyPath":
pass

@abstractmethod
def rglob(self, pattern: str) -> Generator["AnyPath", None, None]:
pass

@abstractmethod
def rmdir(self) -> None:
pass

@abstractmethod
def samefile(self, other_path) -> bool:
pass

@abstractmethod
def stat(self):
"""docstring? has mode for pathlib"""
pass

@abstractmethod
def touch(self, exist_ok: bool = True) -> None:
"""docstring? has mode for pathlib"""
pass

@abstractmethod
def unlink(self, missing_ok=False) -> None:
pass

@abstractmethod
def with_name(self, name: str) -> "AnyPath":
pass

@abstractmethod
def with_suffix(self, suffix: str) -> "AnyPath":
pass

@abstractmethod
def write_bytes(self, data: bytes) -> int:
pass

@abstractmethod
def write_text(self, data: str, encoding=None, errors=None) -> int:
pass


AnyPath.register(CloudPath) # type: ignore
AnyPath.register(Path)
Expand Down
14 changes: 7 additions & 7 deletions cloudpathlib/cloudpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def exists(self) -> bool:
def fspath(self) -> str:
return self.__fspath__()

def _glob_checks(self, pattern):
def _glob_checks(self, pattern: str):
if ".." in pattern:
raise CloudPathNotImplementedError(
"Relative paths with '..' not supported in glob patterns."
Expand All @@ -346,15 +346,15 @@ def _glob(self, selector):
for p in selector.select_from(root):
yield self.client.CloudPath(f"{self.cloud_prefix}{self.drive}{p}")

def glob(self, pattern):
def glob(self, pattern: str):
self._glob_checks(pattern)

pattern_parts = PurePosixPath(pattern).parts
selector = _make_selector(tuple(pattern_parts), _posix_flavour)

yield from self._glob(selector)

def rglob(self, pattern):
def rglob(self, pattern: str):
self._glob_checks(pattern)

pattern_parts = PurePosixPath(pattern).parts
Expand Down Expand Up @@ -566,7 +566,7 @@ def relative_to(self, other):
)
return self._path.relative_to(other._path)

def is_relative_to(self, other):
def is_relative_to(self, other) -> bool:
try:
self.relative_to(other)
return True
Expand Down Expand Up @@ -612,10 +612,10 @@ def suffix(self):
def suffixes(self):
return self._dispatch_to_path("suffixes")

def with_name(self, name):
def with_name(self, name: str):
return self._dispatch_to_path("with_name", name)

def with_suffix(self, suffix):
def with_suffix(self, suffix: str):
return self._dispatch_to_path("with_suffix", suffix)

# ====================== DISPATCHED TO LOCAL CACHE FOR CONCRETE PATHS ======================
Expand Down Expand Up @@ -1001,7 +1001,7 @@ def _filter_children(self, rel_to):
}

@staticmethod
def _is_relative_to(maybe_child, maybe_parent):
def _is_relative_to(maybe_child, maybe_parent) -> bool:
try:
maybe_child.relative_to(maybe_parent)
return True
Expand Down