Skip to content

Commit

Permalink
SPath: Implement more helper methods (#10)
Browse files Browse the repository at this point in the history
* SPath: Implement more helper methods

* SPath: Fix import error

* SPath: Implement fglob

```bash
$ python benchmark.py
Total time for 10000 iterations: 0.4242 seconds
Average time per fglob call: 0.000042 seconds
Total time for 10000 simple glob iterations: 0.5244 seconds
Average time per simple glob call: 0.000052 seconds
```

* Address comments

* Rename new exception

* return of the if (-el)

* Reorder methods

* SPath.get_size: check if self exists

* Remove unnecessary shutil import

* find_newest_file: Get folder of self
  • Loading branch information
LightArrowsEXE authored Oct 21, 2024
1 parent 7b332ea commit eba8a9a
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 5 deletions.
8 changes: 7 additions & 1 deletion stgpytools/exceptions/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
'FileWasNotFoundError',
'FilePermissionError',
'FileTypeMismatchError',
'FileIsADirectoryError'
'FileIsADirectoryError',

'PathIsNotADirectoryError',
]


Expand All @@ -30,3 +32,7 @@ class FileTypeMismatchError(CustomError, OSError):

class FileIsADirectoryError(CustomError, IsADirectoryError):
"""Raised when you try to access a file but it's a directory instead"""


class PathIsNotADirectoryError(CustomError, NotADirectoryError):
"""Raised when you try to access a directory but it's not a directory"""
64 changes: 60 additions & 4 deletions stgpytools/types/file.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from __future__ import annotations

from os import PathLike, listdir, path
import fnmatch
import shutil
from os import PathLike, listdir, path, walk
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, TypeAlias, Union

from ..exceptions import FileNotExistsError, PathIsNotADirectoryError

__all__ = [
'FilePathType', 'FileDescriptor',
'FileOpener',
Expand Down Expand Up @@ -89,10 +93,8 @@ def mkdirp(self, mode: int = 0o777) -> None:
def rmdirs(self, missing_ok: bool = False, ignore_errors: bool = True) -> None:
"""Remove the dir path with its contents."""

from shutil import rmtree

try:
return rmtree(str(self.get_folder()), ignore_errors)
return shutil.rmtree(str(self.get_folder()), ignore_errors)
except FileNotFoundError:
if not missing_ok:
raise
Expand All @@ -119,7 +121,14 @@ def append_to_stem(self, suffixes: str | Iterable[str], sep: str = '_') -> SPath

return self.with_stem(sep.join([self.stem, *to_arr(suffixes)])) # type:ignore[list-item]

def is_empty_dir(self) -> bool:
"""Check if the directory is empty."""

return self.is_dir() and not any(self.iterdir())

def move_dir(self, dst: SPath, *, mode: int = 0o777) -> None:
"""Move the directory to the specified destination."""

dst.mkdir(mode, True, True)

for file in listdir(self):
Expand All @@ -133,5 +142,52 @@ def move_dir(self, dst: SPath, *, mode: int = 0o777) -> None:

self.rmdir()

def copy_dir(self, dst: SPath) -> SPath:
"""Copy the directory to the specified destination."""

if not self.is_dir():
raise PathIsNotADirectoryError('The given path, \"{self}\" is not a directory!', self.copy_dir)

dst.mkdirp()
shutil.copytree(self, dst, dirs_exist_ok=True)

return SPath(dst)

def lglob(self, pattern: str = '*') -> list[SPath]:
"""Glob the path and return the list of paths."""

return list(map(SPath, self.glob(pattern)))

def fglob(self, pattern: str = '*') -> SPath | None:
"""Glob the path and return the first match."""

for root, dirs, files in walk(self):
for name in dirs + files:
if fnmatch.fnmatch(name, pattern):
return SPath(path.join(root, name))

return None

def find_newest_file(self, pattern: str = '*') -> SPath | None:
"""Find the most recently modified file matching the given pattern in the directory."""

matching_files = self.get_folder().glob(pattern)

if not matching_files:
return None

return max(matching_files, key=lambda p: p.stat().st_mtime, default=None) # type:ignore

def get_size(self) -> int:
"""Get the size of the file or directory in bytes."""

if not self.exists():
raise FileNotExistsError('The given path, \"{self}\" is not a file or directory!', self.get_size)

if self.is_file():
return self.stat().st_size

return sum(f.stat().st_size for f in self.rglob('*') if f.is_file())


SPathLike = Union[str, Path, SPath]

0 comments on commit eba8a9a

Please sign in to comment.