pathlibutil 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 Christoph Dörrer
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Christoph Dörrer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.1
2
+ Name: pathlibutil
3
+ Version: 0.1.1
4
+ Summary:
5
+ Home-page: https://github.com/d-chris/pathlibutil
6
+ License: MIT
7
+ Keywords: pathlib,hashlib,shutil
8
+ Author: Christoph Dörrer
9
+ Author-email: d-chris@web.de
10
+ Requires-Python: >=3.8,<3.13
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Project-URL: Documentation, https://d-chris.github.io/pathlibutil
19
+ Project-URL: Repository, https://github.com/d-chris/pathlibutil
20
+ Description-Content-Type: text/markdown
21
+
22
+ # pathlibutil
23
+
24
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pathlibutil)](https://pypi.org/project/pathlibutil/)
25
+ [![PyPI](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
26
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
27
+ [![PyPI - License](https://img.shields.io/pypi/l/pathlibutil)](https://raw.githubusercontent.com/d-chris/pathlibutil/main/LICENSE)
28
+ [![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)
29
+
30
+ ---
31
+
32
+ `pathlibutil.Path` inherits from `pathlib.Path` with some useful built-in python functions.
33
+
34
+ - `Path().hexdigest()` to calculate and `Path().verify()` for verification of hexdigest from a file
35
+ - `Path.default_hash` to configurate default hash algorithm for `Path` class (default: *'md5'*)
36
+ - `Path().size()` to get size in bytes of a file or directory
37
+ - `Path().read_lines()` to yield over all lines from a file until EOF
38
+ - `contextmanager` to change current working directory with `with` statement
39
+ - `Path().copy()` copy a file or directory to a new path destination
40
+ - `Path().delete()` delete a file or directory-tree
41
+ - `Path().move()` move a file or directory to a new path destination
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install pathlibutil
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ```python
52
+ from pathlibutil import Path
53
+
54
+ readme = Path('README.md')
55
+
56
+ print(f'File size: {readme.size()} Bytes')
57
+ ```
58
+
59
+ ## Example 1
60
+
61
+ Read a file and print its content and some file information to stdout.
62
+ > `Path().read_lines()`
63
+
64
+ ```python
65
+ readme = Path('README.md')
66
+
67
+ print('File content'.center(80, '='))
68
+
69
+ for line in readme.read_lines(encoding='utf-8'):
70
+ print(line, end='')
71
+
72
+ print('EOF'.center(80, '='))
73
+ ```
74
+
75
+ ## Example 2
76
+
77
+ Write a file with md5 checksums of all python files in the pathlibutil-directory.
78
+ > `Path().hexdigest()`
79
+
80
+ ```python
81
+ file = Path('pathlibutil.md5')
82
+
83
+ algorithm = file.suffix[1:]
84
+
85
+ with file.open('w') as f:
86
+ f.write(
87
+ f'# {algorithm} checksums generated with pathlibutil (https://pypi.org/project/pathlibutil/)\n\n')
88
+
89
+ i = 0
90
+ for i, filename in enumerate(Path('./pathlibutil').glob('*.py'), start=1):
91
+ f.write(f'{filename.hexdigest(algorithm)} *{filename}\n')
92
+
93
+ print(f'\nwritten: {i:>5} {algorithm}-hashes to: {file}')
94
+ ```
95
+
96
+ ## Example 3
97
+
98
+ Read a file with md5 checksums and verify them.
99
+ > `Path().verify()`, `Path.default_hash` and `contextmanager`
100
+
101
+ ```python
102
+ file = Path('pathlibutil.md5')
103
+
104
+ Path.default_hash = file.suffix[1:]
105
+
106
+ def no_comment(line: str) -> bool:
107
+ return not line.startswith('#')
108
+
109
+ with file.parent as cwd:
110
+
111
+ for line in filter(no_comment, file.read_lines()):
112
+ try:
113
+ digest, filename = line.strip().split(' *')
114
+ verification = Path(filename).verify(digest)
115
+ except ValueError as split_failed:
116
+ continue
117
+ except FileNotFoundError as verify_failed:
118
+ tag = 'missing'
119
+ else:
120
+ tag = 'ok' if verification else 'fail
121
+
122
+ print(f'{tag.ljust(len(digest), ".")} *{filename}')
123
+ ```
124
+
@@ -0,0 +1,102 @@
1
+ # pathlibutil
2
+
3
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pathlibutil)](https://pypi.org/project/pathlibutil/)
4
+ [![PyPI](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
5
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
6
+ [![PyPI - License](https://img.shields.io/pypi/l/pathlibutil)](https://raw.githubusercontent.com/d-chris/pathlibutil/main/LICENSE)
7
+ [![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)
8
+
9
+ ---
10
+
11
+ `pathlibutil.Path` inherits from `pathlib.Path` with some useful built-in python functions.
12
+
13
+ - `Path().hexdigest()` to calculate and `Path().verify()` for verification of hexdigest from a file
14
+ - `Path.default_hash` to configurate default hash algorithm for `Path` class (default: *'md5'*)
15
+ - `Path().size()` to get size in bytes of a file or directory
16
+ - `Path().read_lines()` to yield over all lines from a file until EOF
17
+ - `contextmanager` to change current working directory with `with` statement
18
+ - `Path().copy()` copy a file or directory to a new path destination
19
+ - `Path().delete()` delete a file or directory-tree
20
+ - `Path().move()` move a file or directory to a new path destination
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install pathlibutil
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from pathlibutil import Path
32
+
33
+ readme = Path('README.md')
34
+
35
+ print(f'File size: {readme.size()} Bytes')
36
+ ```
37
+
38
+ ## Example 1
39
+
40
+ Read a file and print its content and some file information to stdout.
41
+ > `Path().read_lines()`
42
+
43
+ ```python
44
+ readme = Path('README.md')
45
+
46
+ print('File content'.center(80, '='))
47
+
48
+ for line in readme.read_lines(encoding='utf-8'):
49
+ print(line, end='')
50
+
51
+ print('EOF'.center(80, '='))
52
+ ```
53
+
54
+ ## Example 2
55
+
56
+ Write a file with md5 checksums of all python files in the pathlibutil-directory.
57
+ > `Path().hexdigest()`
58
+
59
+ ```python
60
+ file = Path('pathlibutil.md5')
61
+
62
+ algorithm = file.suffix[1:]
63
+
64
+ with file.open('w') as f:
65
+ f.write(
66
+ f'# {algorithm} checksums generated with pathlibutil (https://pypi.org/project/pathlibutil/)\n\n')
67
+
68
+ i = 0
69
+ for i, filename in enumerate(Path('./pathlibutil').glob('*.py'), start=1):
70
+ f.write(f'{filename.hexdigest(algorithm)} *{filename}\n')
71
+
72
+ print(f'\nwritten: {i:>5} {algorithm}-hashes to: {file}')
73
+ ```
74
+
75
+ ## Example 3
76
+
77
+ Read a file with md5 checksums and verify them.
78
+ > `Path().verify()`, `Path.default_hash` and `contextmanager`
79
+
80
+ ```python
81
+ file = Path('pathlibutil.md5')
82
+
83
+ Path.default_hash = file.suffix[1:]
84
+
85
+ def no_comment(line: str) -> bool:
86
+ return not line.startswith('#')
87
+
88
+ with file.parent as cwd:
89
+
90
+ for line in filter(no_comment, file.read_lines()):
91
+ try:
92
+ digest, filename = line.strip().split(' *')
93
+ verification = Path(filename).verify(digest)
94
+ except ValueError as split_failed:
95
+ continue
96
+ except FileNotFoundError as verify_failed:
97
+ tag = 'missing'
98
+ else:
99
+ tag = 'ok' if verification else 'fail
100
+
101
+ print(f'{tag.ljust(len(digest), ".")} *{filename}')
102
+ ```
@@ -0,0 +1,5 @@
1
+ """
2
+ .. include:: ../README.md
3
+ """
4
+
5
+ from pathlibutil.path import Path
@@ -0,0 +1,155 @@
1
+ import errno
2
+ import hashlib
3
+ import os
4
+ import pathlib
5
+ import shutil
6
+ import sys
7
+ from typing import Generator, Set
8
+
9
+
10
+ class Path(pathlib.Path):
11
+ """Path inherites from `pathlib.Path` and adds some methods to built-in python functions"""
12
+
13
+ default_hash = 'md5'
14
+ """default hash algorithm for the class when no algorithm is specified for `hexdigest()` and `verify()`"""
15
+
16
+ if sys.version_info < (3, 12):
17
+ _flavour = pathlib._windows_flavour if os.name == 'nt' else pathlib._posix_flavour
18
+
19
+ @property
20
+ def algorithms_available(self) -> Set[str]:
21
+ """
22
+ Set of available algorithms that can be passed to `hexdigest()` and `verify()` method.
23
+ """
24
+ return hashlib.algorithms_available
25
+
26
+ def hexdigest(self, algorithm: str = None, /, **kwargs) -> str:
27
+ """
28
+ Returns the hexdigest of the file using the named algorithm (default: `default_hash`).
29
+ """
30
+ try:
31
+ args = (kwargs.pop('length'),)
32
+ except KeyError:
33
+ args = ()
34
+
35
+ return hashlib.new(
36
+ name=algorithm or self.default_hash,
37
+ data=self.read_bytes(),
38
+ ).hexdigest(*args)
39
+
40
+ def verify(self, hexdigest: str, algorithm: str = None, *, strict: bool = True, **kwargs) -> bool:
41
+ """
42
+ Verifies the hash of the file using the named algorithm (default: `default_hash`).
43
+ """
44
+ _hash = self.hexdigest(algorithm, **kwargs)
45
+
46
+ if strict:
47
+ return _hash == hexdigest
48
+
49
+ if len(hexdigest) < 7:
50
+ raise ValueError('hashdigest must be at least 7 characters long')
51
+
52
+ for a, b in zip(_hash, hexdigest):
53
+ if a != b.lower():
54
+ return False
55
+
56
+ return True
57
+
58
+ def __enter__(self) -> 'Path':
59
+ """
60
+ Contextmanager to changes the current working directory.
61
+ """
62
+ cwd = os.getcwd()
63
+
64
+ try:
65
+ os.chdir(self)
66
+ except Exception as e:
67
+ raise e
68
+ else:
69
+ self.__stack = cwd
70
+
71
+ return self
72
+
73
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
74
+ """
75
+ Restore previous working directory.
76
+ """
77
+ try:
78
+ os.chdir(self.__stack)
79
+ finally:
80
+ del self.__stack
81
+
82
+ def read_lines(self, **kwargs) -> Generator[str, None, None]:
83
+ """
84
+ Iterates over all lines of the file until EOF is reached.
85
+ """
86
+ with self.open(**kwargs) as f:
87
+ yield from iter(f.readline, '')
88
+
89
+ def size(self, **kwargs) -> int:
90
+ """
91
+ Returns the size in bytes of a file or directory.
92
+ """
93
+ if self.is_dir():
94
+ return sum([p.size(**kwargs) for p in self.iterdir()])
95
+
96
+ return self.stat(**kwargs).st_size
97
+
98
+ def copy(self, dst: str, exist_ok: bool = True, **kwargs) -> 'Path':
99
+ """
100
+ Copies the file or directory to a destination path.
101
+ """
102
+ try:
103
+ _path = shutil.copytree(
104
+ self,
105
+ dst,
106
+ dirs_exist_ok=exist_ok,
107
+ **kwargs
108
+ )
109
+ except NotADirectoryError:
110
+ dst = Path(dst, self.name)
111
+
112
+ if not exist_ok and dst.exists():
113
+ raise FileExistsError(f'{dst} already exists')
114
+
115
+ dst.parent.mkdir(parents=True, exist_ok=True)
116
+
117
+ _path = shutil.copy2(
118
+ self,
119
+ dst,
120
+ **kwargs
121
+ )
122
+
123
+ return Path(_path)
124
+
125
+ def delete(self, *, recursive: bool = False, missing_ok: bool = False, **kwargs) -> None:
126
+ """
127
+ Deletes the file or directory.
128
+ """
129
+ try:
130
+ self.rmdir()
131
+ except NotADirectoryError:
132
+ self.unlink(missing_ok)
133
+ except FileNotFoundError as e:
134
+ if not missing_ok:
135
+ raise e
136
+ except OSError as e:
137
+ if not recursive or e.errno != errno.ENOTEMPTY:
138
+ raise e
139
+
140
+ shutil.rmtree(self, **kwargs)
141
+
142
+ def move(self, dst: str) -> 'Path':
143
+ """
144
+ Moves the file or directory to a destination path.
145
+ """
146
+ src = self.resolve(strict=True)
147
+ dst = Path(dst).resolve()
148
+ dst.mkdir(parents=True, exist_ok=True)
149
+
150
+ try:
151
+ _path = shutil.move(str(src), str(dst))
152
+ except shutil.Error as e:
153
+ raise OSError(e)
154
+
155
+ return Path(_path)
@@ -1,50 +1,73 @@
1
- [tool.poetry]
2
- name = "pathlibutil"
3
- version = "0.1.0"
4
- description = ""
5
- authors = ["Christoph Dörrer <d-chris@web.de>"]
6
- readme = "README.md"
7
- license = "MIT"
8
- classifiers = [
9
- "Programming Language :: Python :: 3.8",
10
- "Programming Language :: Python :: 3.9",
11
- "Programming Language :: Python :: 3.10",
12
- "Programming Language :: Python :: 3.11",
13
- "Programming Language :: Python :: 3.12",
14
- "License :: OSI Approved :: MIT License",
15
- ]
16
- keywords = ["pathlib", "hashlib"]
17
- repository = "https://github.com/d-chris/pathlibutil"
18
-
19
- [tool.poetry.dependencies]
20
- python = ">=3.8,<3.13"
21
-
22
- [tool.poetry.group.dev.dependencies]
23
- pytest = "^7.4.3"
24
- tox = "^4.11.4"
25
- pytest-random-order = "^1.1.0"
26
- pytest-cov = "^4.1.0"
27
- pytest-mock = "^3.12.0"
28
-
29
- [build-system]
30
- requires = ["poetry-core"]
31
- build-backend = "poetry.core.masonry.api"
32
-
33
- [tool.tox]
34
- legacy_tox_ini = """
35
- [tox]
36
- envlist = py38,py39,py310,py311,py312
37
-
38
- [testenv]
39
- deps =
40
- pytest
41
- pytest-random-order
42
- pytest-mock
43
- pytest-cov
44
- commands = pytest --random-order
45
- """
46
-
47
- [tool.pytest.ini_options]
48
- minversion = "6.0"
49
- testpaths = "tests"
50
- addopts = "--random-order --cov=pathlibutil --cov-report=term-missing:skip-covered --color=yes"
1
+ [tool.poetry]
2
+ name = "pathlibutil"
3
+ version = "v0.1.1"
4
+ description = ""
5
+ authors = ["Christoph Dörrer <d-chris@web.de>"]
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ classifiers = [
9
+ "Programming Language :: Python :: 3.8",
10
+ "Programming Language :: Python :: 3.9",
11
+ "Programming Language :: Python :: 3.10",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
+ "License :: OSI Approved :: MIT License",
15
+ ]
16
+ keywords = ["pathlib", "hashlib", "shutil"]
17
+ repository = "https://github.com/d-chris/pathlibutil"
18
+ documentation = "https://d-chris.github.io/pathlibutil"
19
+ include = ["LICENSE"]
20
+
21
+ [tool.poetry.dependencies]
22
+ python = ">=3.8,<3.13"
23
+
24
+ [tool.poetry.group.dev.dependencies]
25
+ pytest = "^7.4.3"
26
+ tox = "^4.11.4"
27
+ pytest-random-order = "^1.1.0"
28
+ pytest-cov = "^4.1.0"
29
+ pytest-mock = "^3.12.0"
30
+
31
+
32
+ [tool.poetry.group.doc.dependencies]
33
+ jinja2 = "^3.1.2"
34
+ pdoc = "^14.3.0"
35
+
36
+ [[tool.poetry.source]]
37
+ name = "PyPI"
38
+ priority = "primary"
39
+
40
+ [[tool.poetry.source]]
41
+ name = "testpypi"
42
+ url = "https://test.pypi.org/legacy/"
43
+ priority = "explicit"
44
+
45
+ [build-system]
46
+ requires = ["poetry-core"]
47
+ build-backend = "poetry.core.masonry.api"
48
+
49
+ [tool.tox]
50
+ legacy_tox_ini = """
51
+ [tox]
52
+ envlist = py38,py39,py310,py311,py312
53
+
54
+ [testenv]
55
+ deps =
56
+ pytest
57
+ pytest-random-order
58
+ pytest-mock
59
+ pytest-cov
60
+ commands = pytest --random-order
61
+ """
62
+
63
+ [tool.pytest.ini_options]
64
+ minversion = "6.0"
65
+ testpaths = "tests"
66
+ addopts = [
67
+ "--random-order",
68
+ "--color=yes",
69
+ "--cov=pathlibutil",
70
+ "--cov-report=term-missing:skip-covered",
71
+ "--cov-append",
72
+ # "--cov-report=html",
73
+ ]
@@ -1,77 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pathlibutil
3
- Version: 0.1.0
4
- Summary:
5
- Home-page: https://github.com/d-chris/pathlibutil
6
- License: MIT
7
- Keywords: pathlib,hashlib
8
- Author: Christoph Dörrer
9
- Author-email: d-chris@web.de
10
- Requires-Python: >=3.8,<3.13
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.8
14
- Classifier: Programming Language :: Python :: 3.9
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Project-URL: Repository, https://github.com/d-chris/pathlibutil
19
- Description-Content-Type: text/markdown
20
-
21
- # pathlibutil
22
-
23
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pathlibutil)](https://pypi.org/project/pathlibutil/)
24
- [![PyPI](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
25
- [![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
26
- [![PyPI - License](https://img.shields.io/pypi/l/pathlibutil)](./LICENSE)
27
- [![GitHub Workflow Test)](https://img.shields.io/github/actions/workflow/status/d-chris/pathlibutil/pytest.yml?logo=github&label=test)](https://github.com/d-chris/pathlibutil)
28
-
29
- ---
30
-
31
- `pathlibutil.Path` inherits from `pathlib.Path` with some useful built-in python functions.
32
-
33
- - `Path().hexdigest()` to calculate and `Path().verify()` for verification of hexdigest from a file
34
- - `Path.default_hash` to configurate default hash algorithm for `Path` class (default: *'md5'*)
35
- - `Path().size()` to get size in bytes of a file or directory
36
- - `Path().read_lines()` to yield over all lines from a file until EOF
37
- - `contextmanager` to change current working directory with `with` statement
38
-
39
- ## Installation
40
-
41
- ```bash
42
- pip install pathlibutil
43
- ```
44
-
45
- ## Usage
46
-
47
- ```python
48
- from pathlibutil import Path
49
-
50
- readme = Path('README.md')
51
-
52
- print(f'File size: {readme.size()} Bytes')
53
- print(f'File sha1: {readme.hexdigest("sha1")}')
54
-
55
- print('-- File content --')
56
- for line in readme.read_lines(encoding='utf-8'):
57
- print(line, end='')
58
- print('-- EOF --')
59
-
60
- with readme.parent as cwd:
61
- print(f'Current working directory: {cwd}')
62
-
63
- # Change default hash algorithm from md5 to sha1
64
- Path.default_hash = 'sha1'
65
-
66
- print(f'File verification: {readme.verify("add3f48fded5e0829a8e3e025e44c2891542c58e")}')
67
- ```
68
-
69
- ## Examples
70
-
71
- 1. [Read file line by line to stdout](./examples/example1.py)
72
- > `Path().read_lines()`
73
- 2. [Write calculated hash to file](./examples/example2.py)
74
- > `Path().hexdigest()`
75
- 3. [Read hashes from file for verification](./examples/example3.py)
76
- > `Path().verify()` and `contextmanager`
77
-
@@ -1,56 +0,0 @@
1
- # pathlibutil
2
-
3
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pathlibutil)](https://pypi.org/project/pathlibutil/)
4
- [![PyPI](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
5
- [![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
6
- [![PyPI - License](https://img.shields.io/pypi/l/pathlibutil)](./LICENSE)
7
- [![GitHub Workflow Test)](https://img.shields.io/github/actions/workflow/status/d-chris/pathlibutil/pytest.yml?logo=github&label=test)](https://github.com/d-chris/pathlibutil)
8
-
9
- ---
10
-
11
- `pathlibutil.Path` inherits from `pathlib.Path` with some useful built-in python functions.
12
-
13
- - `Path().hexdigest()` to calculate and `Path().verify()` for verification of hexdigest from a file
14
- - `Path.default_hash` to configurate default hash algorithm for `Path` class (default: *'md5'*)
15
- - `Path().size()` to get size in bytes of a file or directory
16
- - `Path().read_lines()` to yield over all lines from a file until EOF
17
- - `contextmanager` to change current working directory with `with` statement
18
-
19
- ## Installation
20
-
21
- ```bash
22
- pip install pathlibutil
23
- ```
24
-
25
- ## Usage
26
-
27
- ```python
28
- from pathlibutil import Path
29
-
30
- readme = Path('README.md')
31
-
32
- print(f'File size: {readme.size()} Bytes')
33
- print(f'File sha1: {readme.hexdigest("sha1")}')
34
-
35
- print('-- File content --')
36
- for line in readme.read_lines(encoding='utf-8'):
37
- print(line, end='')
38
- print('-- EOF --')
39
-
40
- with readme.parent as cwd:
41
- print(f'Current working directory: {cwd}')
42
-
43
- # Change default hash algorithm from md5 to sha1
44
- Path.default_hash = 'sha1'
45
-
46
- print(f'File verification: {readme.verify("add3f48fded5e0829a8e3e025e44c2891542c58e")}')
47
- ```
48
-
49
- ## Examples
50
-
51
- 1. [Read file line by line to stdout](./examples/example1.py)
52
- > `Path().read_lines()`
53
- 2. [Write calculated hash to file](./examples/example2.py)
54
- > `Path().hexdigest()`
55
- 3. [Read hashes from file for verification](./examples/example3.py)
56
- > `Path().verify()` and `contextmanager`
@@ -1 +0,0 @@
1
- from pathlibutil.path import Path
@@ -1,91 +0,0 @@
1
- import hashlib
2
- import os
3
- import pathlib
4
- import sys
5
- from typing import Generator, Set
6
-
7
-
8
- class Path(pathlib.Path):
9
- default_hash = 'md5'
10
-
11
- if sys.version_info < (3, 12):
12
- _flavour = pathlib._windows_flavour if os.name == 'nt' else pathlib._posix_flavour
13
-
14
- @property
15
- def algorithms_available(self) -> Set[str]:
16
- """
17
- Set of available algorithms that can be passed to hexdigest() method.
18
- """
19
- return hashlib.algorithms_available
20
-
21
- def hexdigest(self, algorithm: str = None, /, **kwargs) -> str:
22
- """
23
- Returns the hex digest of the file using the named algorithm (default: md5).
24
- """
25
- try:
26
- args = (kwargs.pop('length'),)
27
- except KeyError:
28
- args = ()
29
-
30
- return hashlib.new(
31
- name=algorithm or self.default_hash,
32
- data=self.read_bytes(),
33
- ).hexdigest(*args)
34
-
35
- def verify(self, hashdigest: str, algorithm: str = None, *, strict: bool = True, **kwargs) -> bool:
36
- """
37
- Verifies the hash of the file using the named algorithm (default: md5).
38
- """
39
- _hash = self.hexdigest(algorithm, **kwargs)
40
-
41
- if strict:
42
- return _hash == hashdigest
43
-
44
- if len(hashdigest) < 7:
45
- raise ValueError('hashdigest must be at least 7 characters long')
46
-
47
- for a, b in zip(_hash, hashdigest):
48
- if a != b.lower():
49
- return False
50
-
51
- return True
52
-
53
- def __enter__(self) -> 'Path':
54
- """
55
- Contextmanager to changes the current working directory.
56
- """
57
- cwd = os.getcwd()
58
-
59
- try:
60
- os.chdir(self)
61
- except Exception as e:
62
- raise e
63
- else:
64
- self.__stack = cwd
65
-
66
- return self
67
-
68
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
69
- """
70
- Restore previous working directory.
71
- """
72
- try:
73
- os.chdir(self.__stack)
74
- finally:
75
- del self.__stack
76
-
77
- def read_lines(self, **kwargs) -> Generator[str, None, None]:
78
- """
79
- Iterates over all lines of the file until EOF is reached.
80
- """
81
- with self.open(**kwargs) as f:
82
- yield from iter(f.readline, '')
83
-
84
- def size(self, **kwargs) -> int:
85
- """
86
- Returns the size in bytes of a file or directory.
87
- """
88
- if self.is_dir():
89
- return sum([p.size(**kwargs) for p in self.iterdir()])
90
-
91
- return self.stat(**kwargs).st_size