diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
index 3325483..906d290 100644
--- a/.github/workflows/python-publish.yml
+++ b/.github/workflows/python-publish.yml
@@ -19,7 +19,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install wheel twine "gqylpy_exception>=2.1"
+ pip install setuptools wheel twine "gqylpy_exception>=3.0.1"
- name: Build package
run: python setup.py bdist_wheel
- name: Publish package
diff --git a/README.md b/README.md
index 862d4c5..f00f2b5 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@
# systempath
-> (OOP) Operating system paths and files.
-> Let Python operating system paths and files become Simple, Simpler, Simplest, Humanization, Unification, Flawless.
+> Object-oriented operation of files and system paths.
+> Make Python operation of files and system paths become simple, simpler, simplest, humane, unified, and flawless.
pip3 install systempath
diff --git a/requirements.txt b/requirements.txt
index 5e7f913..a2b9e33 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1 @@
-gqylpy_exception>=2.1
+gqylpy_exception>=3.0.1
diff --git a/setup.py b/setup.py
index 1548501..291320d 100644
--- a/setup.py
+++ b/setup.py
@@ -20,7 +20,7 @@
license='Apache 2.0',
url='http://gqylpy.com',
project_urls={'Source': source},
- description='Operating system paths and files.',
+ description='Object-oriented operation of files and system paths.',
long_description=open('README.md', encoding='utf8').read(),
long_description_content_type='text/markdown',
packages=[i.__name__],
diff --git a/systempath/__init__.py b/systempath/__init__.py
index 7da9a0c..d00fd4a 100644
--- a/systempath/__init__.py
+++ b/systempath/__init__.py
@@ -1,7 +1,7 @@
-"""(OOP) Operating system paths and files.
+"""Object-oriented operation of files and system paths.
-Let Python operating system paths and files become Simple, Simpler, Simplest,
-Humanization, Unification, Flawless.
+Make Python operation of files and system paths become simple, simpler,
+simplest, humane, unified, and flawless.
>>> from systempath import SystemPath, Directory, File
@@ -18,7 +18,7 @@
>>> file.open.rb().read()
b'GQYLPY \xe6\x94\xb9\xe5\x8f\x98\xe4\xb8\x96\xe7\x95\x8c'
- @version: 1.0.14
+ @version: 1.1
@author: 竹永康
@source: https://github.com/gqylpy/systempath
@@ -46,10 +46,17 @@
Callable, Iterator
)
+if sys.version_info >= (3, 10):
+ from typing import TypeAlias
+else:
+ TypeAlias = TypeVar("TypeAlias")
+
__all__ = ['SystemPath', 'Path', 'Directory', 'File', 'Open', 'Content', 'tree']
-BytesOrStr = TypeVar('BytesOrStr', bytes, str)
-PathLink = BytesOrStr
+BytesOrStr: TypeAlias = TypeVar('BytesOrStr', bytes, str)
+PathLink: TypeAlias = BytesOrStr
+PathType: TypeAlias = \
+ TypeVar('PathType', 'Path', 'Directory', 'File', 'SystemPath')
SystemPathNotFoundError: Type[ge.GqylpyError] = ge.SystemPathNotFoundError
NotAPathError: Type[ge.GqylpyError] = ge.NotAPathError
@@ -130,25 +137,47 @@ def __init__(
def __bytes__(self) -> bytes:
"""Return the path of type bytes."""
- def __eq__(self, other: ['Path', PathLink], /) -> bool:
+ def __eq__(self, other: [PathType, PathLink], /) -> bool:
"""Return True if the absolute path of the path instance is equal to the
absolute path of another path instance (can also be a path link
character) else False."""
- def __ne__(self, other: ['Path', PathLink], /) -> bool:
- return not self.__eq__(other)
-
def __bool__(self) -> bool:
return self.exists
+ def __fspath__(self) -> PathLink:
+ return self.name
+
+ def __truediv__(self, subpath: Union[PathType, PathLink], /) -> PathType:
+ """
+ Concatenate paths, where the right path can be an instance of a path or
+ a path link.Return a new concatenated path instance, whose
+ characteristics are inherited from the left path.
+
+ When `self.strict` is set to True, an exact instance of a directory or
+ file is returned. Otherwise, an instance of `SystemPath` is generally
+ returned.
+ """
+
+ def __add__(self, subpath: Union[PathType, PathLink], /) -> PathType:
+ return self / subpath
+
+ def __rtruediv__(self, dirpath: PathLink, /) -> PathType:
+ """Concatenate paths, where the left path is a path link. Return a new
+ concatenated path instance, whose characteristics are inherited from the
+ right path."""
+
+ def __radd__(self, dirpath: PathLink, /) -> PathType:
+ return dirpath / self
+
@property
def basename(self) -> BytesOrStr:
- return os.path.basename(self.name)
+ return os.path.basename(self)
@property
def dirname(self) -> 'Directory':
return Directory(
- os.path.dirname(self.name),
+ os.path.dirname(self),
strict =self.strict,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
@@ -164,84 +193,84 @@ def dirnamel(self, level: int) -> 'Directory':
)
@property
- def abspath(self) -> 'Path':
+ def abspath(self) -> PathType:
return self.__class__(
- os.path.abspath(self.name),
+ os.path.abspath(self),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def realpath(self, *, strict: Optional[bool] = None) -> 'Path':
+ def realpath(self, *, strict: Optional[bool] = None) -> PathType:
return self.__class__(
- os.path.realpath(self.name, strict=strict),
+ os.path.realpath(self, strict=strict),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def relpath(self, start: Optional[PathLink] = None) -> 'Path':
+ def relpath(self, start: Optional[PathLink] = None) -> PathType:
return self.__class__(
- os.path.relpath(self.name, start=start),
+ os.path.relpath(self, start=start),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def normpath(self) -> 'Path':
+ def normpath(self) -> PathType:
return self.__class__(
- os.path.normpath(self.name),
+ os.path.normpath(self),
strict =self.strict,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
- def expanduser(self) -> 'Path':
+ def expanduser(self) -> PathType:
return self.__class__(
- os.path.expanduser(self.name),
+ os.path.expanduser(self),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def expandvars(self) -> 'Path':
+ def expandvars(self) -> PathType:
return self.__class__(
- os.path.expandvars(self.name),
+ os.path.expandvars(self),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
def split(self) -> Tuple[PathLink, BytesOrStr]:
- return os.path.split(self.name)
+ return os.path.split(self)
def splitdrive(self) -> Tuple[BytesOrStr, PathLink]:
- return os.path.splitdrive(self.name)
+ return os.path.splitdrive(self)
@property
def isabs(self) -> bool:
- return os.path.isabs(self.name)
+ return os.path.isabs(self)
@property
def exists(self) -> bool:
- return os.path.exists(self.name)
+ return os.path.exists(self)
@property
def lexists(self) -> bool:
"""Like `self.exists`, but do not follow symbolic links, return True for
broken symbolic links."""
- return os.path.lexists(self.name)
+ return os.path.lexists(self)
@property
def isdir(self) -> bool:
- return os.path.isdir(self.name)
+ return os.path.isdir(self)
@property
def isfile(self) -> bool:
- return os.path.isfile(self.name)
+ return os.path.isfile(self)
@property
def islink(self) -> bool:
- return os.path.islink(self.name)
+ return os.path.islink(self)
@property
def ismount(self) -> bool:
- return os.path.ismount(self.name)
+ return os.path.ismount(self)
@property
def is_block_device(self) -> bool:
@@ -355,10 +384,10 @@ def replace(self, dst: PathLink, /) -> PathLink:
def move(
self,
- dst: Union['Path', PathLink],
+ dst: Union[PathType, PathLink],
/, *,
copy_function: Optional[Callable[[PathLink, PathLink], None]] = None
- ) -> Union['Path', PathLink]:
+ ) -> Union[PathType, PathLink]:
"""
Move the file or directory to another location, similar to the Unix
system `mv` command. Call `shutil.move` internally.
@@ -378,8 +407,8 @@ def move(
"""
def copystat(
- self, dst: Union['Path', PathLink], /
- ) -> Union['Path', PathLink]:
+ self, dst: Union[PathType, PathLink], /
+ ) -> Union[PathType, PathLink]:
"""
Copy the metadata of the file or directory to another file or directory,
call `shutil.copystat` internally.
@@ -402,8 +431,8 @@ def copystat(
"""
def copymode(
- self, dst: Union['Path', PathLink], /
- ) -> Union['Path', PathLink]:
+ self, dst: Union[PathType, PathLink], /
+ ) -> Union[PathType, PathLink]:
"""
Copy the mode bits of the file or directory to another file or
directory, call `shutil.copymode` internally.
@@ -423,8 +452,8 @@ def copymode(
"""
def symlink(
- self, dst: Union['Path', PathLink], /
- ) -> Union['Path', PathLink]:
+ self, dst: Union[PathType, PathLink], /
+ ) -> Union[PathType, PathLink]:
"""
Create a symbolic link to the file or directory, call `os.symlink`
internally.
@@ -482,16 +511,16 @@ def lstat(self) -> os.stat_result:
def getsize(self) -> int:
"""Get the size of the file, return 0 if the path is a directory."""
- return os.path.getsize(self.name)
+ return os.path.getsize(self)
def getctime(self) -> float:
- return os.path.getctime(self.name)
+ return os.path.getctime(self)
def getmtime(self) -> float:
- return os.path.getmtime(self.name)
+ return os.path.getmtime(self)
def getatime(self) -> float:
- return os.path.getatime(self.name)
+ return os.path.getatime(self)
def chmod(self, mode: int, /) -> None:
"""
@@ -759,10 +788,8 @@ class Directory(Path):
"""Pass a directory path link to get a directory object, which you can then
use to do anything a directory can do."""
- def __getitem__(
- self, name: BytesOrStr
- ) -> Union['SystemPath', 'Directory', 'File', Path]:
- path: PathLink = os.path.join(self.name, name)
+ def __getitem__(self, name: BytesOrStr) -> PathType:
+ path: PathLink = os.path.join(self, name)
if self.strict:
if os.path.isdir(path):
@@ -776,7 +803,7 @@ def __getitem__(
return SystemPath(path)
def __delitem__(self, name: BytesOrStr) -> None:
- Path(os.path.join(self.name, name)).delete()
+ Path(os.path.join(self, name)).delete()
def __iter__(self) -> Iterator[Union['Directory', 'File', Path]]:
return self.subpaths
@@ -1003,7 +1030,7 @@ def rmtree(
def chdir(self) -> None:
"""Change the working directory of the current process to the directory.
"""
- os.chdir(self.name)
+ os.chdir(self)
class File(Path):
@@ -1037,11 +1064,11 @@ def contents(self, content: ['Content', bytes]) -> None:
and `Content.__ior__` only."""
def splitext(self) -> Tuple[BytesOrStr, BytesOrStr]:
- return os.path.splitext(self.name)
+ return os.path.splitext(self)
@property
def extension(self) -> BytesOrStr:
- return os.path.splitext(self.name)[1]
+ return os.path.splitext(self)[1]
def copy(self, dst: Union['File', PathLink], /) -> Union['File', PathLink]:
"""
@@ -1104,7 +1131,7 @@ def link(self, dst: Union['File', PathLink], /) -> Union['File', PathLink]:
"""
def truncate(self, length: int) -> None:
- os.truncate(self.name, length)
+ os.truncate(self, length)
def clear(self) -> None:
self.truncate(0)
@@ -1470,9 +1497,6 @@ def __eq__(self, other: Union['Content', bytes], /) -> bool:
another file (or a bytes object). If they all point to the same file
then direct return True."""
- def __ne__(self, other: Union['Content', bytes], /) -> bool:
- return not self.__eq__(other)
-
def __contains__(self, subcontent: bytes, /) -> bool:
return self.contains(subcontent)
diff --git a/systempath/i systempath.py b/systempath/i systempath.py
index 1ab9425..6e87548 100644
--- a/systempath/i systempath.py
+++ b/systempath/i systempath.py
@@ -86,19 +86,34 @@ def getpwuid(_): raise NotImplementedError
Iterator, Iterable, NoReturn, Any
)
+if sys.version_info >= (3, 9):
+ from typing import Annotated
+else:
+ class Annotated(metaclass=type('', (type,), {
+ '__new__': lambda *a: type.__new__(*a)()
+ })):
+ def __getitem__(self, *a): ...
+
+if sys.version_info >= (3, 10):
+ from typing import TypeAlias
+else:
+ TypeAlias = TypeVar("TypeAlias")
+
import gqylpy_exception as ge
-PathLink = BytesOrStr = Union[bytes, str]
-Closure = TypeVar('Closure', bound=Callable)
+BytesOrStr: TypeAlias = Union[bytes, str]
+PathLink: TypeAlias = BytesOrStr
+PathType: TypeAlias = Union['Path', 'Directory', 'File', 'SystemPath']
+Closure: TypeAlias = TypeVar('Closure', bound=Callable)
-OpenMode = Literal[
+OpenMode: TypeAlias = Annotated[Literal[
'rb', 'rb_plus', 'rt', 'rt_plus', 'r', 'r_plus',
'wb', 'wb_plus', 'wt', 'wt_plus', 'w', 'w_plus',
'ab', 'ab_plus', 'at', 'at_plus', 'a', 'a_plus',
'xb', 'xb_plus', 'xt', 'xt_plus', 'x', 'x_plus'
-]
+], 'The file open mode.']
-EncodingErrorHandlingMode = Literal[
+EncodingErrorHandlingMode: TypeAlias = Annotated[Literal[
'strict',
'ignore',
'replace',
@@ -106,9 +121,9 @@ def getpwuid(_): raise NotImplementedError
'xmlcharrefreplace',
'backslashreplace',
'namereplace'
-]
+], 'The error handling modes for encoding and decoding (strictness).']
-__unique__: Final = object()
+__unique__: Final[Annotated[object, 'A unique object.']] = object()
class MasqueradeClass(type):
@@ -218,7 +233,7 @@ def dst2abs(func: Callable) -> Closure:
# of the source is used as the parent path of the destination instead of
# using the current working directory, different from the traditional way.
@functools.wraps(func)
- def core(path: 'Path', dst: PathLink) -> PathLink:
+ def core(path: PathType, dst: PathLink) -> PathLink:
try:
singlename: bool = basename(dst) == dst
except TypeError:
@@ -228,37 +243,29 @@ def core(path: 'Path', dst: PathLink) -> PathLink:
) from None
if singlename:
try:
- dst: PathLink = join(dirname(path.name), dst)
- except TypeError:
- dst: PathLink = join(dirname(
- path.name.decode() if dst.__class__ is str
- else path.name.encode()
- ), dst)
+ dst: PathLink = join(dirname(path), dst)
+ except TypeError as e:
+ if dst.__class__ is bytes:
+ name: bytes = path.name.encode()
+ elif dst.__class__ is str:
+ name: str = path.name.decode()
+ else:
+ raise e from None
+ dst: PathLink = join(dirname(name), dst)
func(path, dst)
path.name = dst
return dst
return core
-def dst2path(func: Callable) -> Closure:
- # If the destination path is instance of `Path` then convert to path link.
- @functools.wraps(func)
- def core(
- path: 'Path', dst: Union['Path', PathLink], **kw
- ) -> Union['Path', PathLink]:
- func(path, dst.name if isinstance(dst, path.__class__) else dst, **kw)
- return dst
- return core
-
-
def joinpath(func: Callable) -> Closure:
global BytesOrStr
# Compatible with Python earlier versions.
@functools.wraps(func)
- def core(path: 'Path', name: BytesOrStr, /) -> Any:
+ def core(path: PathType, name: BytesOrStr, /) -> Any:
try:
- name: PathLink = join(path.name, name)
+ name: PathLink = join(path, name)
except TypeError:
if name.__class__ is bytes:
name: str = name.decode()
@@ -266,7 +273,7 @@ def core(path: 'Path', name: BytesOrStr, /) -> Any:
name: bytes = name.encode()
else:
raise
- name: PathLink = join(path.name, name)
+ name: PathLink = join(path, name)
return func(path, name)
return core
@@ -280,7 +287,7 @@ def ignore_error(e) -> bool:
)
-def testpath(testfunc: Callable[[int], bool], path: 'Path') -> bool:
+def testpath(testfunc: Callable[[int], bool], path: PathType) -> bool:
try:
return testfunc(path.stat.st_mode)
except OSError as e:
@@ -307,7 +314,7 @@ def __new__(
)
if strict and not exists(name):
raise ge.SystemPathNotFoundError(
- f'system path {repr(name)} does not exist.'
+ f'system path {name!r} does not exist.'
)
return object.__new__(cls)
@@ -329,13 +336,12 @@ def __str__(self) -> str:
return self.name if self.name.__class__ is str else repr(self.name)
def __repr__(self) -> str:
- return f'<{__package__}.{self.__class__.__name__} ' \
- f'name={repr(self.name)}>'
+ return f'<{__package__}.{self.__class__.__name__} name={self.name!r}>'
def __bytes__(self) -> bytes:
return self.name if self.name.__class__ is bytes else self.name.encode()
- def __eq__(self, other: ['Path', PathLink], /) -> bool:
+ def __eq__(self, other: [PathType, PathLink], /) -> bool:
if self is other:
return True
@@ -358,26 +364,91 @@ def __eq__(self, other: ['Path', PathLink], /) -> bool:
self.__class__ == other_type,
self.__class__ in (Path, SystemPath),
other_type in (Path, SystemPath)
- )) and abspath(self.name) == other_path and self.dir_fd == other_dir_fd
+ )) and abspath(self) == other_path and self.dir_fd == other_dir_fd
def __bool__(self) -> bool:
return self.exists
+ def __fspath__(self) -> PathLink:
+ return self.name
+
+ def __truediv__(self, subpath: Union[PathType, PathLink], /) -> PathType:
+ if isinstance(subpath, Path):
+ subpath: PathLink = subpath.name
+ try:
+ joined_path: PathLink = join(self, subpath)
+ except TypeError:
+ if subpath.__class__ is bytes:
+ subpath: str = subpath.decode()
+ elif subpath.__class__ is str:
+ subpath: bytes = subpath.encode()
+ else:
+ raise ge.NotAPathError(
+ 'right path can only be an instance of '
+ f'"{__package__}.{Path.__name__}" or a path link, '
+ f'not "{subpath.__class__.__name__}".'
+ ) from None
+ joined_path: PathLink = join(self, subpath)
+
+ if self.strict:
+ if isfile(joined_path):
+ pathtype = File
+ elif isdir(joined_path):
+ pathtype = Directory
+ elif self.__class__ is Path:
+ pathtype = Path
+ else:
+ pathtype = SystemPath
+ elif self.__class__ is Path:
+ pathtype = Path
+ else:
+ pathtype = SystemPath
+
+ ins: PathType = pathtype(
+ joined_path,
+ dir_fd =self.dir_fd,
+ follow_symlinks=self.follow_symlinks
+ )
+ ins.strict = self.strict
+ return ins
+
+ def __add__(self, subpath: Union[PathType, PathLink], /) -> PathType:
+ return self / subpath
+
+ def __rtruediv__(self, dirpath: PathLink, /) -> PathType:
+ try:
+ path: PathLink = join(dirpath, self)
+ except TypeError:
+ if dirpath.__class__ is bytes:
+ dirpath: str = dirpath.decode()
+ elif dirpath.__class__ is str:
+ dirpath: bytes = dirpath.encode()
+ else:
+ raise ge.NotAPathError(
+ 'left path type can only be "bytes" or "str", '
+ f'not "{dirpath.__class__.__name__}".'
+ ) from None
+ path: PathLink = join(dirpath, self)
+ return self.__class__(path, follow_symlinks=self.follow_symlinks)
+
+ def __radd__(self, dirpath: PathLink, /) -> PathType:
+ return dirpath / self
+
@property
def basename(self) -> BytesOrStr:
- return basename(self.name)
+ return basename(self)
@property
def dirname(self) -> 'Directory':
return Directory(
- dirname(self.name),
+ dirname(self),
strict =self.strict,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
def dirnamel(self, level: int) -> 'Directory':
- directory: PathLink = self.name
+ directory = self
for _ in range(level):
directory: PathLink = dirname(directory)
return Directory(
@@ -388,58 +459,58 @@ def dirnamel(self, level: int) -> 'Directory':
)
@property
- def abspath(self) -> 'Path':
+ def abspath(self) -> PathType:
return self.__class__(
- abspath(self.name),
+ abspath(self),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def realpath(self, *, strict: bool = False) -> 'Path':
+ def realpath(self, *, strict: bool = False) -> PathType:
return self.__class__(
- realpath(self.name, strict=strict),
+ realpath(self, strict=strict),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def relpath(self, start: Optional[PathLink] = None) -> 'Path':
+ def relpath(self, start: Optional[PathLink] = None) -> PathType:
return self.__class__(
- relpath(self.name, start=start),
+ relpath(self, start=start),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def normpath(self) -> 'Path':
+ def normpath(self) -> PathType:
return self.__class__(
- normpath(self.name),
+ normpath(self),
strict =self.strict,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
- def expanduser(self) -> 'Path':
+ def expanduser(self) -> PathType:
return self.__class__(
- expanduser(self.name),
+ expanduser(self),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
- def expandvars(self) -> 'Path':
+ def expandvars(self) -> PathType:
return self.__class__(
- expandvars(self.name),
+ expandvars(self),
strict =self.strict,
follow_symlinks=self.follow_symlinks
)
def split(self) -> Tuple[PathLink, BytesOrStr]:
- return split(self.name)
+ return split(self)
def splitdrive(self) -> Tuple[BytesOrStr, PathLink]:
- return splitdrive(self.name)
+ return splitdrive(self)
@property
def isabs(self) -> bool:
- return isabs(self.name)
+ return isabs(self)
@property
def exists(self) -> bool:
@@ -475,11 +546,11 @@ def isfile(self) -> bool:
@property
def islink(self) -> bool:
- return islink(self.name)
+ return islink(self)
@property
def ismount(self) -> bool:
- return ismount(self.name)
+ return ismount(self)
@property
def is_block_device(self) -> bool:
@@ -496,20 +567,20 @@ def isfifo(self) -> bool:
@property
def isempty(self) -> bool:
if self.isdir:
- return not bool(listdir(self.name))
+ return not bool(listdir(self))
if self.isfile:
- return not bool(getsize(self.name))
+ return not bool(getsize(self))
if self.exists:
raise ge.NotADirectoryOrFileError(repr(self.name))
raise ge.SystemPathNotFoundError(
- f'system path {repr(self.name)} does not exist.'
+ f'system path {self.name!r} does not exist.'
)
@property
def readable(self) -> bool:
return access(
- self.name, 4,
+ self, 4,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
@@ -517,7 +588,7 @@ def readable(self) -> bool:
@property
def writeable(self) -> bool:
return access(
- self.name, 2,
+ self, 2,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
@@ -525,7 +596,7 @@ def writeable(self) -> bool:
@property
def executable(self) -> bool:
return access(
- self.name, 1,
+ self, 1,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
@@ -537,82 +608,76 @@ def delete(
onerror: Optional[Callable] = None
) -> None:
if self.isdir:
- rmtree(self.name, ignore_errors=ignore_errors, onerror=onerror)
+ rmtree(self, ignore_errors=ignore_errors, onerror=onerror)
else:
try:
- remove(self.name)
+ remove(self)
except FileNotFoundError:
if not ignore_errors:
raise
@dst2abs
def rename(self, dst: PathLink, /) -> None:
- rename(self.name, dst, src_dir_fd=self.dir_fd, dst_dir_fd=self.dir_fd)
+ rename(self, dst, src_dir_fd=self.dir_fd, dst_dir_fd=self.dir_fd)
@dst2abs
def renames(self, dst: PathLink, /) -> None:
- renames(self.name, dst)
+ renames(self, dst)
@dst2abs
def replace(self, dst: PathLink, /) -> None:
- replace(self.name, dst, src_dir_fd=self.dir_fd, dst_dir_fd=self.dir_fd)
+ replace(self, dst, src_dir_fd=self.dir_fd, dst_dir_fd=self.dir_fd)
- @dst2path
def move(
self,
- dst: PathLink,
+ dst: Union[PathType, PathLink],
/, *,
copy_function: Callable[[PathLink, PathLink], None] = copy2
) -> None:
- move(self.name, dst, copy_function=copy_function)
+ move(self, dst, copy_function=copy_function)
- @dst2path
- def copystat(self, dst: PathLink, /) -> None:
- copystat(self.name, dst, follow_symlinks=self.follow_symlinks)
+ def copystat(self, dst: Union[PathType, PathLink], /) -> None:
+ copystat(self, dst, follow_symlinks=self.follow_symlinks)
- @dst2path
- def copymode(self, dst: PathLink, /) -> None:
- copymode(self.name, dst, follow_symlinks=self.follow_symlinks)
+ def copymode(self, dst: Union[PathType, PathLink], /) -> None:
+ copymode(self, dst, follow_symlinks=self.follow_symlinks)
- @dst2path
- def symlink(self, dst: PathLink, /) -> None:
- symlink(self.name, dst, dir_fd=self.dir_fd)
+ def symlink(self, dst: Union[PathType, PathLink], /) -> None:
+ symlink(self, dst, dir_fd=self.dir_fd)
def readlink(self) -> PathLink:
- return readlink(self.name, dir_fd=self.dir_fd)
+ return readlink(self, dir_fd=self.dir_fd)
@property
def stat(self) -> stat_result:
return stat(
- self.name, dir_fd=self.dir_fd, follow_symlinks=self.follow_symlinks
+ self, dir_fd=self.dir_fd, follow_symlinks=self.follow_symlinks
)
@property
def lstat(self) -> stat_result:
- return lstat(self.name, dir_fd=self.dir_fd)
+ return lstat(self, dir_fd=self.dir_fd)
def getsize(self) -> int:
- return getsize(self.name)
+ return getsize(self)
def getctime(self) -> float:
- return getctime(self.name)
+ return getctime(self)
def getmtime(self) -> float:
- return getmtime(self.name)
+ return getmtime(self)
def getatime(self) -> float:
- return getatime(self.name)
+ return getatime(self)
def chmod(self, mode: int, /) -> None:
chmod(
- self.name, mode,
- dir_fd =self.dir_fd,
- follow_symlinks=self.follow_symlinks
+ self, mode, dir_fd=self.dir_fd, follow_symlinks=self.follow_symlinks
)
def access(self, mode: int, /, *, effective_ids: bool = False) -> bool:
return access(
- self.name, mode,
+ self, mode,
dir_fd =self.dir_fd,
effective_ids =effective_ids,
follow_symlinks=self.follow_symlinks
@@ -620,7 +685,7 @@ def access(self, mode: int, /, *, effective_ids: bool = False) -> bool:
if sys.platform != 'win32':
def lchmod(self, mode: int, /) -> None:
- lchmod(self.name, mode)
+ lchmod(self, mode)
@property
def owner(self) -> str:
@@ -632,19 +697,19 @@ def group(self) -> str:
def chown(self, uid: int, gid: int) -> None:
return chown(
- self.name, uid, gid,
+ self, uid, gid,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
def lchown(self, uid: int, gid: int) -> None:
- lchown(self.name, uid, gid)
+ lchown(self, uid, gid)
def chflags(self, flags: int) -> None:
- chflags(self.name, flags, follow_symlinks=self.follow_symlinks)
+ chflags(self, flags, follow_symlinks=self.follow_symlinks)
def lchflags(self, flags: int) -> None:
- lchflags(self.name, flags)
+ lchflags(self, flags)
def chattr(self, operator: Literal['+', '-', '='], attrs: str) -> None:
warnings.warn(
@@ -655,7 +720,9 @@ def chattr(self, operator: Literal['+', '-', '='], attrs: str) -> None:
raise ge.ChattrError(
f'unsupported operation "{operator}", only "+", "-" or "=".'
)
- c: str = f'chattr {operator}{attrs} {self.name}'
+ pathlink: str = self.name if self.name.__class__ is str \
+ else self.name.decode()
+ c: str = f'chattr {operator}{attrs} {pathlink}'
if system(f'sudo {c} &>/dev/null'):
raise ge.ChattrError(c)
@@ -664,7 +731,9 @@ def lsattr(self) -> str:
'implementation of method `lsattr` is to directly call the '
'system command `lsattr`, so this is very unreliable.'
, stacklevel=2)
- c: str = f'lsattr {self.name}'
+ pathlink: str = self.name if self.name.__class__ is str \
+ else self.name.decode()
+ c: str = f'lsattr {pathlink}'
attrs: str = popen(
"sudo %s 2>/dev/null | awk '{print $1}'" % c
).read()[:-1]
@@ -678,8 +747,7 @@ def exattr(self, attr: str, /) -> bool:
if sys.platform == 'linux':
def getxattr(self, attribute: BytesOrStr, /) -> bytes:
return getxattr(
- self.name, attribute,
- follow_symlinks=self.follow_symlinks
+ self, attribute, follow_symlinks=self.follow_symlinks
)
def setxattr(
@@ -690,18 +758,18 @@ def setxattr(
flags: int = 0
) -> None:
setxattr(
- self.name, attribute, value, flags,
+ self, attribute, value, flags,
follow_symlinks=self.follow_symlinks
)
def listxattr(self) -> List[str]:
return listxattr(
- self.name, follow_symlinks=self.follow_symlinks
+ self, follow_symlinks=self.follow_symlinks
)
def removexattr(self, attribute: BytesOrStr, /) -> None:
removexattr(
- self.name, attribute, follow_symlinks=self.follow_symlinks
+ self, attribute, follow_symlinks=self.follow_symlinks
)
def utime(
@@ -710,7 +778,7 @@ def utime(
times: Optional[Tuple[Union[int, float], Union[int, float]]] = None
) -> None:
utime(
- self.name, times,
+ self, times,
dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
)
@@ -725,15 +793,13 @@ def __new__(
if strict and not isdir(name):
raise NotADirectoryError(
- f'system path {repr(name)} is not a directory.'
+ f'system path {name!r} is not a directory.'
)
return instance
@joinpath
- def __getitem__(
- self, name: PathLink
- ) -> Union['SystemPath', 'Directory', 'File', Path]:
+ def __getitem__(self, name: PathLink) -> PathType:
if self.strict:
if isdir(name):
return Directory(name, strict=self.strict)
@@ -742,7 +808,7 @@ def __getitem__(
if exists(name):
return Path(name)
raise ge.SystemPathNotFoundError(
- f'system path {repr(name)} does not exist.'
+ f'system path {name!r} does not exist.'
)
return SystemPath(name)
@@ -751,8 +817,8 @@ def __delitem__(self, path: PathLink) -> None:
Path(path).delete()
def __iter__(self) -> Iterator[Union['Directory', 'File', Path]]:
- for name in listdir(self.name):
- path: PathLink = join(self.name, name)
+ for name in listdir(self):
+ path: PathLink = join(self, name)
yield Directory(path) if isdir(path) else \
File(path) if isfile(path) else Path(path)
@@ -773,10 +839,10 @@ def subpaths(self) -> Iterator[Union['Directory', 'File', Path]]:
@property
def subpath_names(self) -> List[BytesOrStr]:
- return listdir(self.name)
+ return listdir(self)
def scandir(self) -> Iterator:
- return scandir(self.name)
+ return scandir(self)
def tree(
self,
@@ -804,13 +870,12 @@ def walk(
self, *, topdown: bool = True, onerror: Optional[Callable] = None
) -> Iterator[Tuple[PathLink, List[BytesOrStr], List[BytesOrStr]]]:
return walk(
- self.name,
+ self,
topdown =topdown,
onerror =onerror,
followlinks=not self.follow_symlinks
)
- @dst2path
def copytree(
self,
dst: Union['Directory', PathLink],
@@ -822,7 +887,7 @@ def copytree(
dirs_exist_ok: bool = False
) -> None:
copytree(
- self.name, dst,
+ self, dst,
symlinks =symlinks,
ignore =ignore,
copy_function =copy_function,
@@ -836,28 +901,32 @@ def clear(
ignore_errors: bool = False,
onerror: Optional[Callable] = None
) -> None:
- for name in listdir(self.name):
- path: PathLink = join(self.name, name)
+ for name in listdir(self):
+ path: PathLink = join(self, name)
if isdir(path):
rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
else:
- remove(path)
+ try:
+ remove(self)
+ except FileNotFoundError:
+ if not ignore_errors:
+ raise
def mkdir(self, mode: int = 0o777, *, ignore_exists: bool = False) -> None:
try:
- mkdir(self.name, mode)
+ mkdir(self, mode)
except FileExistsError:
if not ignore_exists:
raise
def makedirs(self, mode: int = 0o777, *, exist_ok: bool = False) -> None:
- makedirs(self.name, mode, exist_ok=exist_ok)
+ makedirs(self, mode, exist_ok=exist_ok)
def rmdir(self) -> None:
- rmdir(self.name)
+ rmdir(self)
def removedirs(self) -> None:
- removedirs(self.name)
+ removedirs(self)
def rmtree(
self,
@@ -865,14 +934,14 @@ def rmtree(
ignore_errors: bool = False,
onerror: Optional[Callable] = None
) -> None:
- rmtree(self.name, ignore_errors=ignore_errors, onerror=onerror)
+ rmtree(self, ignore_errors=ignore_errors, onerror=onerror)
@property
def isempty(self) -> bool:
- return not bool(listdir(self.name))
+ return not bool(listdir(self))
def chdir(self) -> None:
- chdir(self.name)
+ chdir(self)
class File(Path):
@@ -883,20 +952,27 @@ def __new__(
instance = Path.__new__(cls, name, strict=strict, **kw)
if strict and not isfile(name):
- raise ge.NotAFileError(f'system path {repr(name)} is not a file.')
+ raise ge.NotAFileError(f'system path {name!r} is not a file.')
return instance
def __bool__(self) -> bool:
return self.isfile
+ def __truediv__(self, other: Any, /) -> NoReturn:
+ x: str = __package__ + '.' + File.__name__
+ y: str = other.__class__.__name__
+ if hasattr(other, '__module__'):
+ y: str = other.__module__ + '.' + y
+ raise TypeError(f'unsupported operand type(s) for /: "{x}" and "{y}".')
+
@property
def open(self) -> 'Open':
return Open(self)
@property
def content(self) -> bytes:
- return FileIO(self.name).read()
+ return FileIO(self).read()
@content.setter
def content(self, content: bytes) -> None:
@@ -907,11 +983,11 @@ def content(self, content: bytes) -> None:
'content type to be written can only be "bytes", '
f'not "{content.__class__.__name__}".'
)
- FileIO(self.name, 'wb').write(content)
+ FileIO(self, 'wb').write(content)
@content.deleter
def content(self) -> None:
- truncate(self.name, 0)
+ truncate(self, 0)
@property
def contents(self) -> 'Content':
@@ -923,15 +999,14 @@ def contents(self, content: ['Content', bytes]) -> None:
pass
def splitext(self) -> Tuple[BytesOrStr, BytesOrStr]:
- return splitext(self.name)
+ return splitext(self)
@property
def extension(self) -> BytesOrStr:
- return splitext(self.name)[1]
+ return splitext(self)[1]
- @dst2path
- def copy(self, dst: PathLink, /) -> None:
- copyfile(self.name, dst, follow_symlinks=self.follow_symlinks)
+ def copy(self, dst: Union[PathType, PathLink], /) -> None:
+ copyfile(self, dst, follow_symlinks=self.follow_symlinks)
def copycontent(
self,
@@ -941,7 +1016,7 @@ def copycontent(
) -> Union['File', FileIO]:
write, read = (
FileIO(other.name, 'wb') if isinstance(other, File) else other
- ).write, FileIO(self.name).read
+ ).write, FileIO(self).read
while True:
content = read(bufsize)
@@ -951,10 +1026,9 @@ def copycontent(
return other
- @dst2path
- def link(self, dst: PathLink, /) -> None:
+ def link(self, dst: Union[PathType, PathLink], /) -> None:
link(
- self.name, dst,
+ self, dst,
src_dir_fd =self.dir_fd,
dst_dir_fd =self.dir_fd,
follow_symlinks=self.follow_symlinks
@@ -962,13 +1036,13 @@ def link(self, dst: PathLink, /) -> None:
@property
def isempty(self) -> bool:
- return not bool(getsize(self.name))
+ return not bool(getsize(self))
def truncate(self, length: int) -> None:
- truncate(self.name, length)
+ truncate(self, length)
def clear(self) -> None:
- truncate(self.name, 0)
+ truncate(self, 0)
if sys.platform == 'win32':
def mknod(
@@ -979,12 +1053,12 @@ def mknod(
**__
) -> None:
try:
- FileIO(self.name, 'xb')
+ FileIO(self, 'xb')
except FileExistsError:
if not ignore_exists:
raise
else:
- chmod(self.name, mode)
+ chmod(self, mode)
else:
def mknod(
self,
@@ -994,7 +1068,7 @@ def mknod(
ignore_exists: bool = False
) -> None:
try:
- mknod(self.name, mode, device, dir_fd=self.dir_fd)
+ mknod(self, mode, device, dir_fd=self.dir_fd)
except FileExistsError:
if not ignore_exists:
raise
@@ -1006,24 +1080,24 @@ def mknods(
device: int = 0,
ignore_exists: bool = False
) -> None:
- parentdir: PathLink = dirname(self.name)
+ parentdir: PathLink = dirname(self)
if not (parentdir in ('', b'') or exists(parentdir)):
makedirs(parentdir, mode, exist_ok=True)
self.mknod(mode, device=device, ignore_exists=ignore_exists)
def remove(self, *, ignore_errors: bool = False) -> None:
try:
- remove(self.name)
+ remove(self)
except FileNotFoundError:
if not ignore_errors:
raise
def unlink(self) -> None:
- unlink(self.name, dir_fd=self.dir_fd)
+ unlink(self, dir_fd=self.dir_fd)
def md5(self, salting: bytes = b'') -> str:
m5 = md5(salting)
- read = FileIO(self.name).read
+ read = FileIO(self).read
while True:
content = read(__read_bufsize__)
@@ -1071,17 +1145,13 @@ def __dir__(self) -> Iterable[str]:
methods = object.__dir__(self)
methods.remove('__modes__')
methods.remove('__pass__')
- methods.remove('__path__')
methods += self.__modes__
return methods
def __repr__(self) -> str:
- return f'<{__package__}.{self.__class__.__name__} ' \
- f'file={repr(self.__path__)}>'
-
- @property
- def __path__(self) -> PathLink:
- return self.file.name if isinstance(self.file, File) else self.file
+ filelink: PathLink = \
+ self.file.name if isinstance(self.file, File) else self.file
+ return f'<{__package__}.{self.__class__.__name__} file={filelink!r}>'
def __pass__(self, buffer: Type[BufferedIOBase], mode: OpenMode) -> Closure:
def init_buffer_instance(
@@ -1096,7 +1166,7 @@ def init_buffer_instance(
) -> Union[BufferedIOBase, TextIOWrapper]:
buf: BufferedIOBase = buffer(
raw=FileIO(
- file =self.__path__,
+ file =self.file,
mode =mode.replace('_plus', '+'),
opener=opener
),
@@ -1127,10 +1197,10 @@ def __bytes__(self) -> bytes:
def __ior__(self, other: Union['Content', bytes], /) -> 'Content':
if isinstance(other, Content):
- if abspath(other.__path__) == abspath(self.__path__):
+ if abspath(other.file) == abspath(self.file):
raise ge.IsSameFileError(
'source and destination cannot be the same, '
- f'path "{abspath(self.__path__)}".'
+ f'path "{abspath(self.file)}".'
)
read, write = other.rb().read, self.wb().write
while True:
@@ -1173,12 +1243,8 @@ def __eq__(self, other: Union['Content', bytes], /) -> bool:
return True
if isinstance(other, Content):
- if isinstance(self.file, File) or isinstance(other.file, File):
- if self.file == other.file:
- return True
- elif abspath(self.file) == abspath(other.file):
+ if abspath(self.file) == abspath(other.file):
return True
-
read1, read2 = self.rb().read, other.rb().read
while True:
content1 = read1(__read_bufsize__)
@@ -1227,10 +1293,10 @@ def __iter__(self) -> Iterator[bytes]:
return (line.rstrip(b'\r\n') for line in self.rb())
def __len__(self) -> int:
- return getsize(self.__path__)
+ return getsize(self.file)
def __bool__(self) -> bool:
- return bool(getsize(self.__path__))
+ return bool(getsize(self.file))
def read(self, size: int = -1, /) -> bytes:
return self.rb().read(size)
@@ -1242,7 +1308,7 @@ def append(self, content: Union['Content', bytes], /) -> None:
self.__iadd__(content)
def contains(self, subcontent: bytes, /) -> bool:
- return self.__contains__(subcontent)
+ return subcontent in self
def copy(
self,
@@ -1260,10 +1326,10 @@ def copy(
write(content)
def truncate(self, length: int, /) -> None:
- truncate(self.__path__, length)
+ truncate(self.file, length)
def clear(self) -> None:
- truncate(self.__path__, 0)
+ truncate(self.file, 0)
def md5(self, salting: bytes = b'') -> str:
m5 = md5(salting)
@@ -1394,6 +1460,7 @@ def __init__(
follow_symlinks=follow_symlinks
)
- __bool__ = Path.__bool__
+ __bool__ = Path.__bool__
+ __truediv__ = Path.__truediv__
isempty = Path.isempty