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

stable version #5

Merged
merged 4 commits into from
Jan 14, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install --without dev,doc
poetry install --only-root
- name: Build and publish
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install --without doc
poetry install --only dev
- name: Test with pytest
run: |
poetry run pytest
2 changes: 1 addition & 1 deletion .github/workflows/testbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install --without dev,doc
poetry install --only-root
- name: Build and publish
env:
POETRY_PYPI_TOKEN_TESTPYPI: ${{ secrets.TESTPYPI_TOKEN }}
Expand Down
183 changes: 92 additions & 91 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,94 +54,98 @@ print(f'File size: {readme.size()} Bytes')
## Example 1

Read a file and print its content and some file information to stdout.
> `Path().read_lines()`
> `Path.read_lines()`

```python
from pathlib import Path

readme = Path('README.md')

print(f'File size: {readme.size()} Bytes')
print(f'File sha1: {readme.hexdigest("sha1")}')

print('File content'.center(80, '='))

for line in readme.read_lines(encoding='utf-8'):
print(line, end='')
print(line, end='')

print('EOF'.center(80, '='))
```

## Example 2

Write a file with md5 checksums of all python files in the pathlibutil-directory.
> `Path().hexdigest()`
> `Path.hexdigest()`

```python
from pathlib import Path

file = Path('pathlibutil.md5')

algorithm = file.suffix[1:]

with file.open('w') as f:
f.write(
f'# {algorithm} checksums generated with pathlibutil (https://pypi.org/project/pathlibutil/)\n\n')
f.write(
'# MD5 checksums generated with pathlibutil (https://pypi.org/project/pathlibutil/)\n\n')

i = 0
for i, filename in enumerate(Path('./pathlibutil').glob('*.py'), start=1):
f.write(f'{filename.hexdigest(algorithm)} *{filename}\n')
i = 0
for i, filename in enumerate(Path('./pathlibutil').glob('*.py'), start=1):
f.write(f'{filename.hexdigest()} *{filename}\n')

print(f'\nwritten: {i:>5} {algorithm}-hashes to: {file}')
print(f'\nwritten: {i:>5} {file.default_hash}-hashes to: {file}')
```

## Example 3

Read a file with md5 checksums and verify them.
> `Path().verify()`, `Path.default_hash` and `contextmanager`
> `Path.verify()`, `Path.default_hash` and `contextmanager`

```python
from pathlib import Path

file = Path('pathlibutil.md5')

Path.default_hash = file.suffix[1:]

def no_comment(line: str) -> bool:
return not line.startswith('#')
return not line.startswith('#')

with file.parent as cwd:

for line in filter(no_comment, file.read_lines()):
try:
digest, filename = line.strip().split(' *')
verification = Path(filename).verify(digest)
except ValueError as split_failed:
continue
except FileNotFoundError as verify_failed:
tag = 'missing'
with file.parent as cwd:
miss = 0
ok = 0
fail = 0

for line in filter(no_comment, file.read_lines()):
try:
digest, filename = line.strip().split(' *')
verification = Path(filename).verify(digest, 'md5')
except ValueError as split_failed:
continue
except FileNotFoundError as verify_failed:
tag = 'missing'
miss += 1
else:
if verification:
tag = 'ok'
ok += 1
else:
tag = 'ok' if verification else 'fail
tag = 'fail'
fail += 1

print(f'{tag.ljust(len(digest), ".")} *{filename}')

print(f'{tag.ljust(len(digest), ".")} *{filename}')
print(f'\nok: {ok:<5} fail: {fail:<5} missing: {miss}')
```

## Example 4

Search all pycache directories and free the memory.
> `Path().delete()` and `Path().size()`
> `Path.delete()` and `Path.size()`

```python
from pathlib import Path

mem = 0
i = 0

for i, cache in enumerate(Path('.').rglob('*/__pycache__/'), start=1):
cache_size = cache.size()
try:
cache.delete(recursive=True)
except OSError:
print(f'Failed to delete {cache}')
else:
mem += cache_size
cache_size = cache.size()
try:
cache.delete(recursive=True)
except OSError:
print(f'Failed to delete {cache}')
else:
mem += cache_size

print(f'{i} cache directories deleted, {mem / 2**20:.2f} MB freed.')
```
Expand All @@ -151,63 +155,60 @@ print(f'{i} cache directories deleted, {mem / 2**20:.2f} MB freed.')
Inherit from `pathlibutil.Path` to register new a archive format.
Specify a `archive` as keyword argument in the new subclass, which has to be the suffix without `.` of the archives.
Implement a classmethod `_register_archive_format()` to register new archive formats.
> `Path().make_archive()`, `Path.archive_formats` and `Path().move()`
> `Path.make_archive()`, `Path.archive_formats` and `Path.move()`

```python
import shutil
import pathlibutil


class RegisterFooBarFormat(pathlibutil.Path, archive='foobar'):
@classmethod
def _register_archive_format(cls):
"""
implement new register functions for given `archive`
"""
try:
import <required_package_name>
except ModuleNotFoundError:
raise ModuleNotFoundError(
'pip install <required_package_name>'
)

def pack_foobar(base_name, base_dir, owner=None, group=None, dry_run=None, logger=None) -> str:
"""callable that will be used to unpack archives.

Args:
base_name (`str`): name of the file to create
base_dir (`str`): directory to start archiving from, defaults to `os.curdir`
owner (`Any`, optional): as passed in `make_archive(*args, owner=None, **kwargs)`. Defaults to None.
group (`Any`, optional): as passed in `make_archive(*args, group=None, **kwargs)`. Defaults to None.
dry_run (`Any`, optional): as passed in `make_archive(*args, dry_run=None, **kwargs)`.
Defaults to None.
logger (`logging.Logger`, optional): as passed in `make_archive(*args, logger=None, **kwargs)`.
Defaults to None.

Returns:
str: path of the new created archive
"""
raise NotImplementedError('implement your own pack function')

def unpack_foobar(archive, path, filter=None, extra_args=None) -> None:
"""callable that will be used to unpack archives.

Args:
archive (`str`): path of the archive
path (`str`): directory the archive must be extracted to
filter (`Any`, optional): as passed in `unpack_archive(*args, filter=None, **kwargs)`.
Defaults to None.
extra_args (`Sequence[Tuple[name, value]]`, optional): additional keyword arguments.
as passd in `register_unpack_format(*args, extra_args=None, **kwargs)`. Defaults to None.
"""
raise NotImplementedError('implement your own unpack function')

shutil.register_archive_format(
'foobar', pack_foobar, description='foobar archives'
)
shutil.register_unpack_format(
'foobar', ['.foo.bar'], unpack_foobar
@classmethod
def _register_archive_format(cls):
"""
implement new register functions for given `archive`
"""
try:
import required_package_name
except ModuleNotFoundError:
raise ModuleNotFoundError(
'pip install <required_package_name>'
)

def pack_foobar(base_name, base_dir, owner=None, group=None, dry_run=None, logger=None) -> str:
"""callable that will be used to unpack archives.

Args:
base_name (`str`): name of the file to create
base_dir (`str`): directory to start archiving from, defaults to `os.curdir`
owner (`Any`, optional): as passed in `make_archive(*args, owner=None, **kwargs)`. Defaults to None.
group (`Any`, optional): as passed in `make_archive(*args, group=None, **kwargs)`. Defaults to None.
dry_run (`Any`, optional): as passed in `make_archive(*args, dry_run=None, **kwargs)`. Defaults to None.
logger (`logging.Logger`, optional): as passed in `make_archive(*args, logger=None, **kwargs)`. Defaults to None.

Returns:
str: path of the new created archive
"""
raise NotImplementedError('implement your own pack function')

def unpack_foobar(archive, path, filter=None, extra_args=None) -> None:
"""callable that will be used to unpack archives.

Args:
archive (`str`): path of the archive
path (`str`): directory the archive must be extracted to
filter (`Any`, optional): as passed in `unpack_archive(*args, filter=None, **kwargs)`. Defaults to None.
extra_args (`Sequence[Tuple[name, value]]`, optional): additional keyword arguments, specified by `register_unpack_format(*args, extra_args=None, **kwargs)`. Defaults to None.
"""
raise NotImplementedError('implement your own unpack function')

shutil.register_archive_format(
'foobar', pack_foobar, description='foobar archives'
)
shutil.register_unpack_format(
'foobar', ['.foo.bar'], unpack_foobar
)


file = pathlibutil.Path('README.md')

print(f"available archive formats: {file.archive_formats}")
Expand Down
99 changes: 99 additions & 0 deletions docs/README.md.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# pathlibutil

[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pathlibutil)](https://pypi.org/project/pathlibutil/)
[![PyPI](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
[![PyPI - License](https://img.shields.io/pypi/l/pathlibutil)](https://raw.githubusercontent.com/d-chris/pathlibutil/main/LICENSE)
[![GitHub Workflow Test)](https://img.shields.io/github/actions/workflow/status/d-chris/pathlibutil/pytest.yml?logo=github&label=pytest)](https://github.com/d-chris/pathlibutil/actions/workflows/pytest.yml)
[![Website](https://img.shields.io/website?url=https%3A%2F%2Fd-chris.github.io%2Fpathlibutil&up_message=pdoc&logo=github&label=documentation)](https://d-chris.github.io/pathlibutil)
[![GitHub tag (with filter)](https://img.shields.io/github/v/tag/d-chris/pathlibutil?logo=github&label=github)](https://github.com/d-chris/pathlibutil)
[![Coverage](https://img.shields.io/website?url=https%3A%2F%2Fd-chris.github.io%2Fpathlibutil%2Fhtmlcov&up_message=available&down_message=missing&logo=codecov&label=coverage)](https://d-chris.github.io/pathlibutil/htmlcov)

---

`pathlibutil.Path` inherits from `pathlib.Path` with some useful built-in python functions from `shutil` and `hashlib`

- `Path.hexdigest()` to calculate and `Path.verify()` for verification of hexdigest from a file
- `Path.default_hash` to configurate default hash algorithm for `Path` class (default: *'md5'*)
- `Path.size()` to get size in bytes of a file or directory
- `Path.read_lines()` to yield over all lines from a file until EOF
- `contextmanager` to change current working directory with `with` statement
- `Path.copy()` copy a file or directory to a new path destination
- `Path.delete()` delete a file or directory-tree
- `Path.move()` move a file or directory to a new path destination
- `Path.make_archive()` creates and `Path.unpack_archive()` uncompresses an archive from a file or directory
- `Path.archive_formats` to get all available archive formats

## Installation

```bash
pip install pathlibutil
```

### 7zip support

to handle 7zip archives, an extra package `py7zr>=0.20.2` is required!

[![PyPI - Version](https://img.shields.io/pypi/v/py7zr?logo=python&logoColor=white&label=py7zr&color=FFFF33)](https://pypi.org/project/py7zr/)

```bash
# install as extra dependency
pip install pathlibutil[7z]
```

## Usage

```python
from pathlibutil import Path

readme = Path('README.md')

print(f'File size: {readme.size()} Bytes')
```

## Example 1

Read a file and print its content and some file information to stdout.
> `Path.read_lines()`

```python
{% pdoc examples/example1.py:main:code.indent -%}
```

## Example 2

Write a file with md5 checksums of all python files in the pathlibutil-directory.
> `Path.hexdigest()`

```python
{% pdoc examples/example2.py:main:code.indent -%}
```

## Example 3

Read a file with md5 checksums and verify them.
> `Path.verify()`, `Path.default_hash` and `contextmanager`

```python
{% pdoc examples/example3.py:main:code.indent -%}
```

## Example 4

Search all pycache directories and free the memory.
> `Path.delete()` and `Path.size()`

```python
{% pdoc examples/example4.py:main:code.indent -%}
```

## Example 5

Inherit from `pathlibutil.Path` to register new a archive format.
Specify a `archive` as keyword argument in the new subclass, which has to be the suffix without `.` of the archives.
Implement a classmethod `_register_archive_format()` to register new archive formats.
> `Path.make_archive()`, `Path.archive_formats` and `Path.move()`

```python
{% pdoc examples/example5.py:main:code.indent -%}
```
Loading