pathlibutil 0.1.9__tar.gz → 0.2.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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pathlibutil
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Summary: inherits from pathlib.Path with methods for hashing, copying, deleting and more
5
5
  Home-page: https://d-chris.github.io
6
6
  License: MIT
@@ -32,6 +32,7 @@ Description-Content-Type: text/markdown
32
32
  [![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)
33
33
  [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/d-chris/pathlibutil?logo=github&label=github)](https://github.com/d-chris/pathlibutil)
34
34
  [![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)
35
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
35
36
 
36
37
  ---
37
38
 
@@ -55,6 +56,12 @@ Description-Content-Type: text/markdown
55
56
  - `Path.with_suffix()` to change the multiple suffixes of a file
56
57
  - `Path.cwd()` to get the current working directory or executable path when script is bundled, e.g. with `pyinstaller`
57
58
  - `Path.resolve()` to resolve a unc path to a mapped windows drive.
59
+ - `Path.walk()` to walk over a directory tree like `os.walk()`
60
+ - `Path.iterdir()` with `recursive` all files from the directory tree will be yielded and `exclude_dirs` via callable.
61
+
62
+ JSON serialization of `Path` objects is supported in `pathlibutil.json`.
63
+
64
+ - `pathlibutil.json.dumps()` and `pathlibutil.json.dump()` to serialize `Path` objects as posix paths.
58
65
 
59
66
  ## Installation
60
67
 
@@ -176,7 +183,7 @@ deleted directories and the amount of memory freed in MB.
176
183
  > `Path.delete()`, `Path.size()` and `ByteInt`
177
184
 
178
185
  ```python
179
- from pathlibutil import Path, ByteInt
186
+ from pathlibutil import ByteInt, Path
180
187
 
181
188
  mem = ByteInt(0)
182
189
  i = 0
@@ -203,9 +210,10 @@ to register new archive formats.
203
210
  > Path.make_archive(), Path.archive_formats and Path.move()
204
211
 
205
212
  ```python
206
- import pathlibutil
207
213
  import shutil
208
214
 
215
+ import pathlibutil
216
+
209
217
 
210
218
  class RegisterFooBarFormat(pathlibutil.Path, archive="foobar"):
211
219
  @classmethod
@@ -8,6 +8,7 @@
8
8
  [![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)
9
9
  [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/d-chris/pathlibutil?logo=github&label=github)](https://github.com/d-chris/pathlibutil)
10
10
  [![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)
11
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
11
12
 
12
13
  ---
13
14
 
@@ -31,6 +32,12 @@
31
32
  - `Path.with_suffix()` to change the multiple suffixes of a file
32
33
  - `Path.cwd()` to get the current working directory or executable path when script is bundled, e.g. with `pyinstaller`
33
34
  - `Path.resolve()` to resolve a unc path to a mapped windows drive.
35
+ - `Path.walk()` to walk over a directory tree like `os.walk()`
36
+ - `Path.iterdir()` with `recursive` all files from the directory tree will be yielded and `exclude_dirs` via callable.
37
+
38
+ JSON serialization of `Path` objects is supported in `pathlibutil.json`.
39
+
40
+ - `pathlibutil.json.dumps()` and `pathlibutil.json.dump()` to serialize `Path` objects as posix paths.
34
41
 
35
42
  ## Installation
36
43
 
@@ -152,7 +159,7 @@ deleted directories and the amount of memory freed in MB.
152
159
  > `Path.delete()`, `Path.size()` and `ByteInt`
153
160
 
154
161
  ```python
155
- from pathlibutil import Path, ByteInt
162
+ from pathlibutil import ByteInt, Path
156
163
 
157
164
  mem = ByteInt(0)
158
165
  i = 0
@@ -179,9 +186,10 @@ to register new archive formats.
179
186
  > Path.make_archive(), Path.archive_formats and Path.move()
180
187
 
181
188
  ```python
182
- import pathlibutil
183
189
  import shutil
184
190
 
191
+ import pathlibutil
192
+
185
193
 
186
194
  class RegisterFooBarFormat(pathlibutil.Path, archive="foobar"):
187
195
  @classmethod
@@ -3,6 +3,6 @@
3
3
  """
4
4
 
5
5
  from pathlibutil.path import Path, Register7zFormat
6
- from pathlibutil.types import ByteInt, TimeInt, byteint, StatResult
6
+ from pathlibutil.types import ByteInt, StatResult, TimeInt, byteint
7
7
 
8
8
  __all__ = ["Path", "Register7zFormat", "ByteInt", "byteint", "TimeInt", "StatResult"]
@@ -0,0 +1,48 @@
1
+ """
2
+ Wrapper around the `json` module that provides a custom JSON encoder for `pathlib.Path`
3
+ objects. This allows `pathlib.Path` objects to be serialized to JSON without having to
4
+ convert them to strings first.
5
+
6
+ ```python
7
+ from pathlib import Path
8
+ from pathlibutil.json import dump
9
+
10
+ data = {"file": Path(__file__)}
11
+
12
+ with open("data.json", "w") as f:
13
+ dump(data, f)
14
+ ```
15
+ """
16
+
17
+ import functools
18
+ import json
19
+ import pathlib
20
+ from json import load, loads
21
+
22
+
23
+ class PathEncoder(json.JSONEncoder):
24
+ """
25
+ JSON encoder that converts `pathlib.Path` objects to their string representation.
26
+ """
27
+
28
+ def default(self, obj):
29
+ """
30
+ Convert `pathlib.Path` objects to a string using `pathlib.Path.as_posix()`.
31
+ """
32
+ if isinstance(obj, pathlib.Path):
33
+ return obj.as_posix()
34
+
35
+ return super().default(obj)
36
+
37
+
38
+ @functools.wraps(json.dump)
39
+ def dump(obj, fp, *, cls=PathEncoder, **kwargs):
40
+ return json.dump(obj, fp, cls=cls, **kwargs)
41
+
42
+
43
+ @functools.wraps(json.dumps)
44
+ def dumps(obj, *, cls=PathEncoder, **kwargs):
45
+ return json.dumps(obj, cls=cls, **kwargs)
46
+
47
+
48
+ __all__ = ["load", "loads", "dump", "dumps", "PathEncoder"]
@@ -6,7 +6,7 @@ import re
6
6
  import shutil
7
7
  import subprocess
8
8
  import sys
9
- from typing import Callable, Dict, Generator, List, Literal, Set, Union
9
+ from typing import Callable, Dict, Generator, List, Literal, Set, Tuple, Union
10
10
 
11
11
  from pathlibutil.base import BasePath, _Path
12
12
  from pathlibutil.types import ByteInt, StatResult, _stat_result, byteint
@@ -22,8 +22,8 @@ class Path(BasePath):
22
22
 
23
23
  - Contextmanger lets you change the current working directory.
24
24
  ```python
25
- with Path('path/to/directory') as cwd:
26
- print(f'current working directory: {cwd}')
25
+ with Path("path/to/directory") as cwd:
26
+ print(f"current working directory: {cwd}")
27
27
  ```
28
28
  """
29
29
 
@@ -84,15 +84,20 @@ class Path(BasePath):
84
84
  if not self.is_file():
85
85
  raise FileNotFoundError(f"'{self}' is not an existing file")
86
86
 
87
- try:
88
- args = (kwargs.pop("length"),)
89
- except KeyError:
90
- args = ()
91
-
92
- return hashlib.new(
87
+ hash = hashlib.new(
93
88
  name=algorithm or self.default_hash,
94
89
  data=self.read_bytes(),
95
- ).hexdigest(*args)
90
+ )
91
+
92
+ try:
93
+ return hash.hexdigest()
94
+ except TypeError as e:
95
+ try:
96
+ length = kwargs["length"]
97
+ except KeyError:
98
+ raise e
99
+
100
+ return hash.hexdigest(length)
96
101
 
97
102
  def verify(
98
103
  self, digest: str, algorithm: str = None, *, strict: bool = True, **kwargs
@@ -512,6 +517,7 @@ class Path(BasePath):
512
517
  capture_output=True,
513
518
  shell=True,
514
519
  encoding="cp850",
520
+ check=True,
515
521
  )
516
522
 
517
523
  return result.stdout
@@ -523,7 +529,7 @@ class Path(BasePath):
523
529
  re.IGNORECASE | re.MULTILINE,
524
530
  )
525
531
  return {
526
- match.group("unc") + "\\": match.group("drive") + ":\\"
532
+ match.group("unc") + "\\": cls(match.group("drive") + ":\\")
527
533
  for match in mapped_drives
528
534
  }
529
535
  except Exception:
@@ -538,7 +544,7 @@ class Path(BasePath):
538
544
 
539
545
  try:
540
546
  drive = self._netuse[self.anchor]
541
- return self.__class__(drive).joinpath(self.relative_to(self.anchor))
547
+ return drive.joinpath(self.relative_to(self.anchor))
542
548
  except KeyError:
543
549
  return self
544
550
 
@@ -567,6 +573,68 @@ class Path(BasePath):
567
573
 
568
574
  return p._resolve_unc()
569
575
 
576
+ def walk(
577
+ self,
578
+ top_down: bool = True,
579
+ on_error: Callable[[OSError], object] = None,
580
+ follow_symlinks: bool = False,
581
+ ) -> Generator[Tuple[_Path, List[str], List[str]], None, None]:
582
+ """
583
+ Walks the directory tree and yields a 3-tuple of (dirpath, dirnames, filenames).
584
+ """
585
+ try:
586
+ yield from super().walk(
587
+ top_down,
588
+ on_error,
589
+ follow_symlinks,
590
+ )
591
+ except AttributeError:
592
+ for dirpath, dirnames, filenames in os.walk(
593
+ self,
594
+ top_down,
595
+ on_error,
596
+ follow_symlinks,
597
+ ):
598
+ yield self.__class__(dirpath), dirnames, filenames
599
+
600
+ def iterdir(
601
+ self,
602
+ *,
603
+ recursive: Union[bool, int] = False,
604
+ exclude_dirs: Callable[[_Path], bool] = None,
605
+ **kwargs,
606
+ ) -> Generator[_Path, None, None]:
607
+ """
608
+ Iterates over the files in the directory.
609
+
610
+ If `recursive` is `True` all files from the directory tree will
611
+ be yielded if it is an `integer` files are yielded to this max. directory depth
612
+ optional` **kwargs` are passed to `Path.walk()`.
613
+
614
+ When recursing, folders can be excluded by passing a callable for
615
+ `exclude_dirs`, e.g.
616
+
617
+ ```python
618
+ def exclude_version_control(dirpath: _Path) -> bool:
619
+ return dirpath.name in (".git", ".svn", ".hg", ".bzr", "CVS")
620
+ ```
621
+ """
622
+ if recursive is not False:
623
+ if exclude_dirs and not callable(exclude_dirs):
624
+ raise TypeError("exclude_dirs must be a callable")
625
+
626
+ depth = recursive if type(recursive) is int else None
627
+
628
+ for root, dirs, files in self.walk(**kwargs):
629
+ if depth is not None and len(root.relative_to(self).parts) >= depth:
630
+ dirs[:] = []
631
+ elif exclude_dirs:
632
+ dirs[:] = [d for d in dirs if not exclude_dirs(root.joinpath(d))]
633
+
634
+ yield from (root.joinpath(file) for file in files)
635
+ else:
636
+ yield from super().iterdir()
637
+
570
638
 
571
639
  class Register7zFormat(Path, archive="7z"):
572
640
  """
@@ -583,20 +651,18 @@ class Register7zFormat(Path, archive="7z"):
583
651
 
584
652
  Example:
585
653
  ```python
586
- class Register7zArchive(pathlibutil.Path, archive='7z'):
654
+ class Register7zArchive(pathlibutil.Path, archive="7z"):
587
655
  @classmethod
588
656
  def _register_archive_format(cls):
589
657
  try:
590
658
  from py7zr import pack_7zarchive, unpack_7zarchive
591
659
  except ModuleNotFoundError:
592
- raise ModuleNotFoundError('pip install pathlibutil[7z]')
660
+ raise ModuleNotFoundError("pip install pathlibutil[7z]")
593
661
  else:
594
662
  shutil.register_archive_format(
595
- '7z', pack_7zarchive, description='7zip archive'
596
- )
597
- shutil.register_unpack_format(
598
- '7z', ['.7z'], unpack_7zarchive
663
+ "7z", pack_7zarchive, description="7zip archive"
599
664
  )
665
+ shutil.register_unpack_format("7z", [".7z"], unpack_7zarchive)
600
666
  ```
601
667
  """
602
668
 
@@ -2,7 +2,7 @@ import functools
2
2
  import os
3
3
  import re
4
4
  from datetime import datetime, tzinfo
5
- from typing import Set, Tuple, TypeVar, Iterable
5
+ from typing import Iterable, Set, Tuple, TypeVar
6
6
 
7
7
  _ByteInt = TypeVar("_ByteInt", bound="ByteInt")
8
8
  _stat_result = TypeVar("_stat_result", bound="os.stat_result")
@@ -206,6 +206,7 @@ def byteint(func):
206
206
  ```python
207
207
  randbyte = byteint(random.randint)
208
208
 
209
+
209
210
  @byteint
210
211
  def randhexbyte():
211
212
  return hex(random.randint(0, 2**32))
@@ -1,31 +1,38 @@
1
+ [build-system]
2
+ build-backend = "poetry.core.masonry.api"
3
+
4
+ requires = [
5
+ "poetry-core",
6
+ ]
7
+
1
8
  [tool.poetry]
2
9
  name = "pathlibutil"
3
- version = "v0.1.9"
10
+ version = "v0.2.1"
4
11
  description = "inherits from pathlib.Path with methods for hashing, copying, deleting and more"
5
- authors = ["Christoph Dörrer <d-chris@web.de>"]
12
+ authors = [ "Christoph Dörrer <d-chris@web.de>" ]
6
13
  readme = "README.md"
7
14
  license = "MIT"
8
15
  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
- "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3.8",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
16
23
  ]
17
- keywords = ["pathlib", "hashlib", "shutil"]
24
+ keywords = [ "pathlib", "hashlib", "shutil" ]
18
25
  homepage = "https://d-chris.github.io"
19
26
  repository = "https://github.com/d-chris/pathlibutil"
20
27
  documentation = "https://d-chris.github.io/pathlibutil"
21
- include = ["LICENSE"]
28
+ include = [ "LICENSE" ]
22
29
 
23
30
  [tool.poetry.dependencies]
24
31
  python = ">=3.8.1,<3.13"
25
32
  py7zr = { version = "^0.20.2", optional = true }
26
33
 
27
34
  [tool.poetry.extras]
28
- 7z = ["py7zr"]
35
+ 7z = [ "py7zr" ]
29
36
 
30
37
  [tool.poetry.group.dev.dependencies]
31
38
  pytest = "^7.4.3"
@@ -55,19 +62,15 @@ name = "testpypi"
55
62
  url = "https://test.pypi.org/legacy/"
56
63
  priority = "explicit"
57
64
 
58
- [build-system]
59
- requires = ["poetry-core"]
60
- build-backend = "poetry.core.masonry.api"
61
-
62
65
  [tool.pytest.ini_options]
63
66
  minversion = "6.0"
64
67
  testpaths = "tests"
65
68
  addopts = [
66
- "--random-order",
67
- "--color=yes",
68
- "-s",
69
- # "--cov=pathlibutil",
70
- # "--cov-report=term-missing:skip-covered",
71
- # "--cov-append",
72
- # "--cov-report=html",
69
+ "--random-order",
70
+ "--color=yes",
71
+ "-s",
72
+ # "--cov=pathlibutil",
73
+ # "--cov-report=term-missing:skip-covered",
74
+ # "--cov-append",
75
+ # "--cov-report=html",
73
76
  ]
File without changes