Skip to content

Commit

Permalink
Complete rework for Path using decorators and mutable contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
matfax committed Oct 14, 2019
1 parent a09ef77 commit e26a218
Show file tree
Hide file tree
Showing 8 changed files with 677 additions and 112 deletions.
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
This library is for you if you are also annoyed that there is no mutable pathlib wrapper for use cases where paths are often changed.
mutapath solves this by wrapping the extended pathlib library path.py and updating the encapsulated object every time the path might be changed.

mutapath also adds the possibility to delimit file and path modifications to a safe fallback context.

## MutaPath Class

The MutaPath Class allows direct manipulation of its attributes at any time, just as any mutable object.
Once a file operation is called that is intended to modify its path, the underlying path is also mutated.

```python

Expand All @@ -21,7 +27,7 @@ mutapath solves this by wrapping the extended pathlib library path.py and updati
>>> folder
Path('/home/joe/doe/folder/sub')

>>> folder.name = top
>>> folder.name = "top"
>>> folder
Path('/home/joe/doe/folder/top')

Expand All @@ -32,5 +38,53 @@ Path('/home/joe/doe/folder/next')
>>> next.rename(folder)
>>> next
Path('/home/joe/doe/folder/top')
>>> next.exists()
True
>>> Path('/home/joe/doe/folder/sub').exists()
False

```

## Path Class

This class is immutable by default, just as the `pathlib.Path`. However, it allows to open a editing context via `mutate()`.
Moreover, there are additional contexts for file operations. They update the file and its path while closing the context.
If the file operations don't succeed, they throw an exception and fall back to the original path value.

```python

>>> from mutapath import Path

>>> folder = Path("/home/joe/doe/folder/sub")
>>> folder
Path('/home/joe/doe/folder/sub')

>>> folder.name = "top"
AttributeError: mutapath.Path is an immutable class, unless mutate() context is used.
>>> folder
Path('/home/joe/doe/folder/sub')

>>> with folder.mutate() as m:
... m.name = "top"
>>> folder
Path('/home/joe/doe/folder/top')

>>> next = Path("/home/joe/doe/folder/next")
>>> next.copy(folder)
>>> next
Path('/home/joe/doe/folder/next')
>>> folder.exists()
True
>>> folder.remove()

>>> with next.renaming() as m:
... m.stem = folder.stem
... m.suffix = ".txt"
>>> next
Path("/home/joe/doe/folder/sub.txt")
>>> next.exists()
True
>>> next.with_name("next").exists()
False

```
69 changes: 69 additions & 0 deletions mutapath/decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import functools
import inspect

import path

import mutapath

__EXCLUDE_FROM_WRAPPING = ["__dir__", "__eq__", "__format__", "__repr__", "__str__", "__sizeof__", "__init__",
"__post_init__", "__getattribute__", "__delattr__", "__setattr__", "__getattr__",
"__fspath__", "_norm"]

__MUTABLE_FUNCTIONS = {"rename", "renames", "copy", "copy2", "copyfile", "copymode", "copystat", "copytree", "move",
"basename", "abspath", "join", "joinpath", "normpath", "relpath", "realpath", "relpathto"}


def __is_def(member):
while isinstance(member, functools.partial):
member = member.func
if inspect.isbuiltin(member):
return False
return inspect.isroutine(member)


def __path_func(orig_func):
def wrap_decorator(*args, **kwargs):
result = orig_func(*args, **kwargs)
if isinstance(result, path.Path):
return mutapath.Path(result)
else:
return result

return wrap_decorator


def path_wrap(cls):
for name, method in inspect.getmembers(path.Path, __is_def):
if not name.startswith("__"):
setattr(path.Path, name, __path_func(method))
for name, method in inspect.getmembers(cls, __is_def):
if name not in __EXCLUDE_FROM_WRAPPING:
setattr(cls, name, __path_func(method))
return cls


def __mutate_func(cls, method_name):
from mutapath import Path

def mutation_decorator(self, *args, **kwargs):
orig_func = getattr(path.Path, method_name)
if isinstance(self, Path):
result = orig_func(self._contained, *args, **kwargs)
if isinstance(result, mutapath.Path):
self._contained = result._contained
return self
else:
return result
else:
result = orig_func(self, *args, **kwargs)
return cls(result)

return mutation_decorator


def path_mutable(cls):
names, _ = zip(*inspect.getmembers(path.Path, __is_def))
names = __MUTABLE_FUNCTIONS.intersection(names)
for method_name in names:
setattr(cls, method_name, __mutate_func(cls, method_name))
return cls
5 changes: 5 additions & 0 deletions mutapath/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class PathException(BaseException):
"""
Exception about inconsistencies between the virtual path and the real file system.
"""
pass

0 comments on commit e26a218

Please sign in to comment.