pathlibutil 0.2.1__tar.gz → 0.3.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.
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/PKG-INFO +23 -11
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/README.md +21 -8
- pathlibutil-0.3.1/pathlibutil/base.py +44 -0
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/pathlibutil/path.py +48 -1
- pathlibutil-0.3.1/pathlibutil/urlpath.py +331 -0
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/pyproject.toml +17 -22
- pathlibutil-0.2.1/pathlibutil/base.py +0 -19
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/LICENSE +0 -0
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/pathlibutil/__init__.py +0 -0
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/pathlibutil/json.py +0 -0
- {pathlibutil-0.2.1 → pathlibutil-0.3.1}/pathlibutil/types.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pathlibutil
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
7
|
-
Keywords: pathlib,hashlib,shutil
|
|
7
|
+
Keywords: pathlib,hashlib,shutil,urllib.parse
|
|
8
8
|
Author: Christoph Dörrer
|
|
9
9
|
Author-email: d-chris@web.de
|
|
10
|
-
Requires-Python: >=3.8.1,<
|
|
10
|
+
Requires-Python: >=3.8.1,<4.0.0
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -22,17 +22,21 @@ Project-URL: Documentation, https://d-chris.github.io/pathlibutil
|
|
|
22
22
|
Project-URL: Repository, https://github.com/d-chris/pathlibutil
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
25
|
+
<!--
|
|
26
|
+
filename: ./README.md
|
|
27
|
+
-->
|
|
28
|
+
|
|
25
29
|
# pathlibutil
|
|
26
30
|
|
|
27
31
|
[](https://pypi.org/project/pathlibutil/)
|
|
28
|
-
[](https://pypi.org/project/pathlibutil/)
|
|
32
|
+
[](https://pypi.org/project/pathlibutil/)
|
|
29
33
|
[](https://pypi.org/project/pathlibutil/)
|
|
30
34
|
[](https://raw.githubusercontent.com/d-chris/pathlibutil/main/LICENSE)
|
|
31
|
-
[](https://
|
|
35
|
+
[](https://github.com/d-chris/pathlibutil/actions/workflows/pytest.yml)
|
|
36
|
+
[](https://d-chris.github.io/pathlibutil)
|
|
37
|
+
[](https://github.com/d-chris/pathlibutil)
|
|
38
|
+
[](https://codecov.io/gh/d-chris/pathlibutil)
|
|
39
|
+
[](https://raw.githubusercontent.com/d-chris/pathlibutil/main/.pre-commit-config.yaml)
|
|
36
40
|
|
|
37
41
|
---
|
|
38
42
|
|
|
@@ -58,11 +62,20 @@ Description-Content-Type: text/markdown
|
|
|
58
62
|
- `Path.resolve()` to resolve a unc path to a mapped windows drive.
|
|
59
63
|
- `Path.walk()` to walk over a directory tree like `os.walk()`
|
|
60
64
|
- `Path.iterdir()` with `recursive` all files from the directory tree will be yielded and `exclude_dirs` via callable.
|
|
65
|
+
- `Path.is_expired()` to check if a file is expired by a given `datetime.timedelta`
|
|
66
|
+
- `Path.expand()` yields file paths for multiple file patterns if they exsits.
|
|
61
67
|
|
|
62
68
|
JSON serialization of `Path` objects is supported in `pathlibutil.json`.
|
|
63
69
|
|
|
64
70
|
- `pathlibutil.json.dumps()` and `pathlibutil.json.dump()` to serialize `Path` objects as posix paths.
|
|
65
71
|
|
|
72
|
+
Parse and modify URLs with `pathlibutil.urlpath`.
|
|
73
|
+
|
|
74
|
+
- `pathlibutil.urlpath.UrlPath()` modify URL and easy access the `path` of the url like a `pathlib.PurePosixPath` object.
|
|
75
|
+
- `pathlibutil.urlpath.UrlNetloc()` to parse and modify the `netloc` part of a URL.
|
|
76
|
+
- `pathlibutil.urlpath.normalize_url()` to normalize a URL string.
|
|
77
|
+
|
|
78
|
+
|
|
66
79
|
## Installation
|
|
67
80
|
|
|
68
81
|
```bash
|
|
@@ -123,7 +136,7 @@ file = Path("pathlibutil.md5")
|
|
|
123
136
|
|
|
124
137
|
with file.open("w") as f:
|
|
125
138
|
f.write(
|
|
126
|
-
|
|
139
|
+
"# MD5 checksums generated with pathlibutil "
|
|
127
140
|
"(https://pypi.org/project/pathlibutil/)\n\n"
|
|
128
141
|
)
|
|
129
142
|
|
|
@@ -308,4 +321,3 @@ Path.cwd(frozen=True) is K:/pathlibutil/examples
|
|
|
308
321
|
Path.cwd(frozen=False) is K:/pathlibutil
|
|
309
322
|
Path.cwd(frozen=_MEIPASS) is C:/Users/CHRIST~1.DOE/AppData/Local/Temp/_MEI106042
|
|
310
323
|
```
|
|
311
|
-
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
filename: ./README.md
|
|
3
|
+
-->
|
|
4
|
+
|
|
1
5
|
# pathlibutil
|
|
2
6
|
|
|
3
7
|
[](https://pypi.org/project/pathlibutil/)
|
|
4
|
-
[](https://pypi.org/project/pathlibutil/)
|
|
8
|
+
[](https://pypi.org/project/pathlibutil/)
|
|
5
9
|
[](https://pypi.org/project/pathlibutil/)
|
|
6
10
|
[](https://raw.githubusercontent.com/d-chris/pathlibutil/main/LICENSE)
|
|
7
|
-
[](https://
|
|
11
|
+
[](https://github.com/d-chris/pathlibutil/actions/workflows/pytest.yml)
|
|
12
|
+
[](https://d-chris.github.io/pathlibutil)
|
|
13
|
+
[](https://github.com/d-chris/pathlibutil)
|
|
14
|
+
[](https://codecov.io/gh/d-chris/pathlibutil)
|
|
15
|
+
[](https://raw.githubusercontent.com/d-chris/pathlibutil/main/.pre-commit-config.yaml)
|
|
12
16
|
|
|
13
17
|
---
|
|
14
18
|
|
|
@@ -34,11 +38,20 @@
|
|
|
34
38
|
- `Path.resolve()` to resolve a unc path to a mapped windows drive.
|
|
35
39
|
- `Path.walk()` to walk over a directory tree like `os.walk()`
|
|
36
40
|
- `Path.iterdir()` with `recursive` all files from the directory tree will be yielded and `exclude_dirs` via callable.
|
|
41
|
+
- `Path.is_expired()` to check if a file is expired by a given `datetime.timedelta`
|
|
42
|
+
- `Path.expand()` yields file paths for multiple file patterns if they exsits.
|
|
37
43
|
|
|
38
44
|
JSON serialization of `Path` objects is supported in `pathlibutil.json`.
|
|
39
45
|
|
|
40
46
|
- `pathlibutil.json.dumps()` and `pathlibutil.json.dump()` to serialize `Path` objects as posix paths.
|
|
41
47
|
|
|
48
|
+
Parse and modify URLs with `pathlibutil.urlpath`.
|
|
49
|
+
|
|
50
|
+
- `pathlibutil.urlpath.UrlPath()` modify URL and easy access the `path` of the url like a `pathlib.PurePosixPath` object.
|
|
51
|
+
- `pathlibutil.urlpath.UrlNetloc()` to parse and modify the `netloc` part of a URL.
|
|
52
|
+
- `pathlibutil.urlpath.normalize_url()` to normalize a URL string.
|
|
53
|
+
|
|
54
|
+
|
|
42
55
|
## Installation
|
|
43
56
|
|
|
44
57
|
```bash
|
|
@@ -99,7 +112,7 @@ file = Path("pathlibutil.md5")
|
|
|
99
112
|
|
|
100
113
|
with file.open("w") as f:
|
|
101
114
|
f.write(
|
|
102
|
-
|
|
115
|
+
"# MD5 checksums generated with pathlibutil "
|
|
103
116
|
"(https://pypi.org/project/pathlibutil/)\n\n"
|
|
104
117
|
)
|
|
105
118
|
|
|
@@ -283,4 +296,4 @@ os.getcwd is K:/pathlibutil
|
|
|
283
296
|
Path.cwd(frozen=True) is K:/pathlibutil/examples
|
|
284
297
|
Path.cwd(frozen=False) is K:/pathlibutil
|
|
285
298
|
Path.cwd(frozen=_MEIPASS) is C:/Users/CHRIST~1.DOE/AppData/Local/Temp/_MEI106042
|
|
286
|
-
```
|
|
299
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Generator, TypeVar
|
|
5
|
+
|
|
6
|
+
_Path = TypeVar("_Path", bound=pathlib.Path)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasePath(pathlib.Path):
|
|
10
|
+
"""
|
|
11
|
+
Baseclass to inherit from `pathlib.Path`.
|
|
12
|
+
|
|
13
|
+
This class is only needed for python versions < 3.12.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
if sys.version_info < (3, 12):
|
|
17
|
+
_flavour = (
|
|
18
|
+
pathlib._windows_flavour if os.name == "nt" else pathlib._posix_flavour
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def expand(cls, file: str) -> Generator[_Path, None, None]:
|
|
23
|
+
"""
|
|
24
|
+
yields only Path object of file names that exists. Supports glob patterns in
|
|
25
|
+
filename as wildcards.
|
|
26
|
+
|
|
27
|
+
>>> list(Path.expand(__file__))
|
|
28
|
+
[BasePath('pathlibutil/base.py')]
|
|
29
|
+
|
|
30
|
+
>>> list(Path.expand("pathlibutil/*.py")
|
|
31
|
+
[BasePath('pathlibutil/base.py'), BasePath('pathlibutil/json.py'),
|
|
32
|
+
BasePath('pathlibutil/path.py'), BasePath('pathlibutil/types.py'),
|
|
33
|
+
BasePath('pathlibutil/__init__.py')]
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
file = cls(file)
|
|
37
|
+
try:
|
|
38
|
+
file.resolve(True)
|
|
39
|
+
except (OSError, FileNotFoundError):
|
|
40
|
+
parent, pattern = file.parent, file.name
|
|
41
|
+
|
|
42
|
+
yield from parent.glob(pattern)
|
|
43
|
+
else:
|
|
44
|
+
yield file
|
|
@@ -6,10 +6,11 @@ import re
|
|
|
6
6
|
import shutil
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
|
+
from datetime import datetime, timedelta
|
|
9
10
|
from typing import Callable, Dict, Generator, List, Literal, Set, Tuple, Union
|
|
10
11
|
|
|
11
12
|
from pathlibutil.base import BasePath, _Path
|
|
12
|
-
from pathlibutil.types import ByteInt, StatResult, _stat_result, byteint
|
|
13
|
+
from pathlibutil.types import ByteInt, StatResult, TimeInt, _stat_result, byteint
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class Path(BasePath):
|
|
@@ -635,6 +636,52 @@ class Path(BasePath):
|
|
|
635
636
|
else:
|
|
636
637
|
yield from super().iterdir()
|
|
637
638
|
|
|
639
|
+
def is_expired(self, *, stat="st_mtime", **kwargs) -> bool:
|
|
640
|
+
"""
|
|
641
|
+
Returns `True` if the time of the file is greater than a given threshold.
|
|
642
|
+
|
|
643
|
+
For `**kwargs` see `datetime.timedelta`.
|
|
644
|
+
|
|
645
|
+
>>> Path("README.md").is_expired(weeks=9999)
|
|
646
|
+
False
|
|
647
|
+
"""
|
|
648
|
+
try:
|
|
649
|
+
attr: TimeInt = getattr(self.stat(), stat)
|
|
650
|
+
diff = datetime.now() - attr.datetime
|
|
651
|
+
except AttributeError as e:
|
|
652
|
+
stats = [attr for attr in dir(os.stat_result) if attr.endswith("time")]
|
|
653
|
+
|
|
654
|
+
raise ValueError(f"{stat=} is not from {stats}") from e
|
|
655
|
+
|
|
656
|
+
return diff > timedelta(**kwargs)
|
|
657
|
+
|
|
658
|
+
@classmethod
|
|
659
|
+
def expand(
|
|
660
|
+
cls,
|
|
661
|
+
*files: str,
|
|
662
|
+
duplicates: bool = True,
|
|
663
|
+
) -> Generator[_Path, None, None]:
|
|
664
|
+
"""
|
|
665
|
+
Yields only Path object of file names that exists. Supports glob patterns in
|
|
666
|
+
filename as wildcards.
|
|
667
|
+
|
|
668
|
+
If `duplicates` is `False` only one instance of each file is yielded.
|
|
669
|
+
|
|
670
|
+
>>> list(Path.expand("README.md", "*.md", duplicates=False))
|
|
671
|
+
[Path('README.md')]
|
|
672
|
+
"""
|
|
673
|
+
if duplicates:
|
|
674
|
+
for file in files:
|
|
675
|
+
yield from super().expand(file)
|
|
676
|
+
else:
|
|
677
|
+
seen = set()
|
|
678
|
+
|
|
679
|
+
for file in files:
|
|
680
|
+
for item in super().expand(file):
|
|
681
|
+
if item not in seen:
|
|
682
|
+
seen.add(item)
|
|
683
|
+
yield item
|
|
684
|
+
|
|
638
685
|
|
|
639
686
|
class Register7zFormat(Path, archive="7z"):
|
|
640
687
|
"""
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import re
|
|
3
|
+
import urllib.parse as up
|
|
4
|
+
from dataclasses import asdict, dataclass, field
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from typing import Any, Dict, Optional, TypeVar, Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class UrlNetloc:
|
|
11
|
+
"""
|
|
12
|
+
A dataclass to represent the netloc part of a URL.
|
|
13
|
+
|
|
14
|
+
>>> url = UrlNetloc.from_netloc("www.example.com:443")
|
|
15
|
+
>>> url.port = None
|
|
16
|
+
>>> str(url)
|
|
17
|
+
'www.example.com'
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
hostname: str
|
|
21
|
+
port: Optional[int] = field(default=None)
|
|
22
|
+
username: Optional[str] = field(default=None)
|
|
23
|
+
password: Optional[str] = field(default=None)
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return self.netloc
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def netloc(self) -> str:
|
|
30
|
+
"""netloc string representation of the `dataclass`"""
|
|
31
|
+
|
|
32
|
+
netloc = ""
|
|
33
|
+
|
|
34
|
+
if self.username:
|
|
35
|
+
netloc += self.username
|
|
36
|
+
|
|
37
|
+
if self.password:
|
|
38
|
+
netloc += f":{self.password}"
|
|
39
|
+
|
|
40
|
+
netloc += "@"
|
|
41
|
+
|
|
42
|
+
if ":" in self.hostname:
|
|
43
|
+
netloc += f"[{self.hostname}]"
|
|
44
|
+
else:
|
|
45
|
+
netloc += self.hostname
|
|
46
|
+
|
|
47
|
+
if self.port:
|
|
48
|
+
netloc += f":{self.port:d}"
|
|
49
|
+
|
|
50
|
+
return netloc
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_netloc(cls, netloc: str, normalize: bool = False) -> "UrlNetloc":
|
|
54
|
+
"""Parse a netloc string into a `UrlNetloc` object"""
|
|
55
|
+
|
|
56
|
+
if not netloc.startswith("//"):
|
|
57
|
+
netloc = f"//{netloc}"
|
|
58
|
+
|
|
59
|
+
url = up.urlparse(netloc)
|
|
60
|
+
|
|
61
|
+
hostname = url.hostname
|
|
62
|
+
|
|
63
|
+
if normalize is False:
|
|
64
|
+
try:
|
|
65
|
+
pattern = re.escape(url.hostname)
|
|
66
|
+
hostname = re.search(pattern, netloc, re.IGNORECASE).group()
|
|
67
|
+
except AttributeError:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
return cls(
|
|
71
|
+
hostname=hostname,
|
|
72
|
+
port=url.port,
|
|
73
|
+
username=url.username,
|
|
74
|
+
password=url.password,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def to_dict(self, prune: bool = False) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Convert the `UrlNetloc` object to a dictionary
|
|
80
|
+
|
|
81
|
+
If `prune` is `True`, remove all key-value pairs from the dict where the value
|
|
82
|
+
is `None`.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
data = asdict(self)
|
|
86
|
+
|
|
87
|
+
if not prune:
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
return {k: v for k, v in data.items() if v is not None}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
_UrlPath = TypeVar("_UrlPath", bound="UrlPath")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def normalize_url(
|
|
97
|
+
url: str,
|
|
98
|
+
port: bool = False,
|
|
99
|
+
sort: bool = True,
|
|
100
|
+
) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Function to normalize a URL by converting the scheme and host to lowercase, removing
|
|
103
|
+
port if present, and sorting the query parameters.
|
|
104
|
+
|
|
105
|
+
>>> normalize_url("https://www.ExamplE.com:443/Path?b=2&a=1")
|
|
106
|
+
'https://www.example.com/Path?a=1&b=2'
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
url = UrlPath(url)
|
|
110
|
+
|
|
111
|
+
if port is False:
|
|
112
|
+
ports = {url.scheme.lower(): url.port}
|
|
113
|
+
else:
|
|
114
|
+
ports = {}
|
|
115
|
+
|
|
116
|
+
return url.normalize(sort=sort, ports=ports)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def urlpath(func):
|
|
120
|
+
"""
|
|
121
|
+
decorator to return a `UrlPath` object from a `urllib.parse.ParseResult` object.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
@wraps(func)
|
|
125
|
+
def wrapper(self, *args, **kwargs):
|
|
126
|
+
result = func(self, *args, **kwargs)
|
|
127
|
+
|
|
128
|
+
return UrlPath(result.geturl(), **self._kwargs)
|
|
129
|
+
|
|
130
|
+
return wrapper
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class UrlPath(up.ParseResult):
|
|
134
|
+
"""
|
|
135
|
+
Class to manipulate URLs to change the scheme, netloc, path, query, and fragment.
|
|
136
|
+
|
|
137
|
+
Wrap the `pathlib.PurePosixPath` methods to return a new `UrlPath` object
|
|
138
|
+
|
|
139
|
+
>>> url = UrlPath("https://www.example.com/path/to/file").with_suffix(".txt")
|
|
140
|
+
>>> str(url)
|
|
141
|
+
'https://www.example.com/path/to/file.txt'
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
_default_ports = {
|
|
146
|
+
"http": 80,
|
|
147
|
+
"https": 443,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
def __new__(cls, url, **kwargs) -> _UrlPath:
|
|
151
|
+
parsed_url = up.urlparse(url, **kwargs)
|
|
152
|
+
return super().__new__(cls, *parsed_url)
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
url: str,
|
|
157
|
+
scheme: str = "",
|
|
158
|
+
allow_fragments: bool = True,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Initialize the `UrlPath` object with a URL string.
|
|
162
|
+
|
|
163
|
+
A `ValueError` is raised if the URL is not valid.
|
|
164
|
+
"""
|
|
165
|
+
self._url = url
|
|
166
|
+
self._kwargs = {
|
|
167
|
+
"scheme": scheme,
|
|
168
|
+
"allow_fragments": allow_fragments,
|
|
169
|
+
}
|
|
170
|
+
self._path = pathlib.PurePosixPath(up.unquote(self.path))
|
|
171
|
+
|
|
172
|
+
def __str__(self) -> str:
|
|
173
|
+
return self.normalize()
|
|
174
|
+
|
|
175
|
+
def geturl(self, normalize: bool = False) -> str:
|
|
176
|
+
"""
|
|
177
|
+
Return a re-combined version of the URL.
|
|
178
|
+
|
|
179
|
+
If `normalize` is `True` scheme and netloc is converted to lowercase,
|
|
180
|
+
default ports are removed and query parameters are sorted.
|
|
181
|
+
"""
|
|
182
|
+
if normalize:
|
|
183
|
+
return self.normalize()
|
|
184
|
+
|
|
185
|
+
return super().geturl()
|
|
186
|
+
|
|
187
|
+
def normalize(self, sort: bool = True, **kwargs) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Normalize the URL by converting the scheme and host to lowercase, removing the
|
|
190
|
+
default port if present, and sorting the query parameters.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
ports = kwargs.get("ports", self._default_ports)
|
|
194
|
+
|
|
195
|
+
scheme = self.scheme.lower()
|
|
196
|
+
netloc = UrlNetloc.from_netloc(self.netloc, normalize=True)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
if ports[scheme] == netloc.port:
|
|
200
|
+
netloc.port = None
|
|
201
|
+
except KeyError:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
path = up.quote(up.unquote(self.path))
|
|
205
|
+
query = up.urlencode(sorted(up.parse_qsl(self.query))) if sort else self.query
|
|
206
|
+
|
|
207
|
+
return up.urlunparse(
|
|
208
|
+
(
|
|
209
|
+
scheme,
|
|
210
|
+
str(netloc),
|
|
211
|
+
path,
|
|
212
|
+
self.params,
|
|
213
|
+
query,
|
|
214
|
+
self.fragment,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def __getattr__(self, attr: str) -> Any:
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
attr = getattr(self._path, attr)
|
|
222
|
+
except AttributeError as e:
|
|
223
|
+
raise AttributeError(
|
|
224
|
+
f"'{self.__class__.__name__}' object has no attribute '{attr}'"
|
|
225
|
+
) from e
|
|
226
|
+
|
|
227
|
+
if not callable(attr):
|
|
228
|
+
return attr
|
|
229
|
+
|
|
230
|
+
@wraps(attr)
|
|
231
|
+
def wrapper(*args, **kwargs) -> _UrlPath:
|
|
232
|
+
result = attr(*args, **kwargs)
|
|
233
|
+
|
|
234
|
+
return self.with_path(result)
|
|
235
|
+
|
|
236
|
+
return wrapper
|
|
237
|
+
|
|
238
|
+
@urlpath
|
|
239
|
+
def with_scheme(self, scheme: str) -> _UrlPath:
|
|
240
|
+
"""
|
|
241
|
+
Change the scheme of the URL.
|
|
242
|
+
"""
|
|
243
|
+
return self._replace(scheme=scheme)
|
|
244
|
+
|
|
245
|
+
@urlpath
|
|
246
|
+
def with_netloc(self, netloc: Union[str, UrlNetloc]) -> _UrlPath:
|
|
247
|
+
"""
|
|
248
|
+
Change the netloc of the URL.
|
|
249
|
+
"""
|
|
250
|
+
return self._replace(netloc=str(netloc))
|
|
251
|
+
|
|
252
|
+
@urlpath
|
|
253
|
+
def with_path(self, path: Union[str, pathlib.PurePosixPath]) -> _UrlPath:
|
|
254
|
+
"""
|
|
255
|
+
Change the path of the URL.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
path = path.as_posix()
|
|
260
|
+
except AttributeError as e:
|
|
261
|
+
if not isinstance(path, str):
|
|
262
|
+
raise TypeError(
|
|
263
|
+
f"Expected str or PurePosixPath, got {type(path)}"
|
|
264
|
+
) from e
|
|
265
|
+
|
|
266
|
+
return self._replace(path=path)
|
|
267
|
+
|
|
268
|
+
@urlpath
|
|
269
|
+
def with_params(self, params: str) -> _UrlPath:
|
|
270
|
+
"""
|
|
271
|
+
Change the parameters of the URL.
|
|
272
|
+
"""
|
|
273
|
+
return self._replace(params=params)
|
|
274
|
+
|
|
275
|
+
@urlpath
|
|
276
|
+
def with_query(self, query: str) -> _UrlPath:
|
|
277
|
+
"""
|
|
278
|
+
Change the query of the URL.
|
|
279
|
+
"""
|
|
280
|
+
return self._replace(query=query)
|
|
281
|
+
|
|
282
|
+
@urlpath
|
|
283
|
+
def with_fragment(self, fragment: str) -> _UrlPath:
|
|
284
|
+
"""
|
|
285
|
+
Change the fragment of the URL.
|
|
286
|
+
"""
|
|
287
|
+
return self._replace(fragment=fragment)
|
|
288
|
+
|
|
289
|
+
def with_port(self, port: int) -> _UrlPath:
|
|
290
|
+
"""
|
|
291
|
+
change the port in the netloc of the URL.
|
|
292
|
+
|
|
293
|
+
If `port` is `None`, the port is removed.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
netloc = UrlNetloc.from_netloc(self.netloc)
|
|
297
|
+
netloc.port = port
|
|
298
|
+
|
|
299
|
+
return self.with_netloc(netloc)
|
|
300
|
+
|
|
301
|
+
def with_hostname(self, hostname: str) -> _UrlPath:
|
|
302
|
+
"""
|
|
303
|
+
change the hostname in the netloc of the URL
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
netloc = UrlNetloc.from_netloc(self.netloc)
|
|
307
|
+
netloc.hostname = hostname
|
|
308
|
+
|
|
309
|
+
return self.with_netloc(netloc)
|
|
310
|
+
|
|
311
|
+
def with_credentials(self, username: str, password: str = None) -> _UrlPath:
|
|
312
|
+
"""
|
|
313
|
+
change the username and password in the netloc of the URL
|
|
314
|
+
|
|
315
|
+
to change only `username` the `password` must also be provided.
|
|
316
|
+
|
|
317
|
+
If `username` is `None`, the credentials are removed.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
netloc = UrlNetloc.from_netloc(self.netloc)
|
|
321
|
+
netloc.username = username
|
|
322
|
+
netloc.password = password
|
|
323
|
+
|
|
324
|
+
return self.with_netloc(netloc)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
__all__ = [
|
|
328
|
+
"UrlNetloc",
|
|
329
|
+
"UrlPath",
|
|
330
|
+
"normalize_url",
|
|
331
|
+
]
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
build-backend = "poetry.core.masonry.api"
|
|
3
3
|
|
|
4
|
-
requires = [
|
|
5
|
-
"poetry-core",
|
|
6
|
-
]
|
|
4
|
+
requires = [ "poetry-core" ]
|
|
7
5
|
|
|
8
6
|
[tool.poetry]
|
|
9
7
|
name = "pathlibutil"
|
|
10
|
-
version = "v0.
|
|
8
|
+
version = "v0.3.1"
|
|
11
9
|
description = "inherits from pathlib.Path with methods for hashing, copying, deleting and more"
|
|
12
10
|
authors = [ "Christoph Dörrer <d-chris@web.de>" ]
|
|
13
11
|
readme = "README.md"
|
|
@@ -21,37 +19,32 @@ classifiers = [
|
|
|
21
19
|
"License :: OSI Approved :: MIT License",
|
|
22
20
|
"Operating System :: OS Independent",
|
|
23
21
|
]
|
|
24
|
-
keywords = [ "pathlib", "hashlib", "shutil" ]
|
|
22
|
+
keywords = [ "pathlib", "hashlib", "shutil", "urllib.parse" ]
|
|
25
23
|
homepage = "https://d-chris.github.io"
|
|
26
24
|
repository = "https://github.com/d-chris/pathlibutil"
|
|
27
25
|
documentation = "https://d-chris.github.io/pathlibutil"
|
|
28
|
-
include = [ "LICENSE" ]
|
|
29
26
|
|
|
30
27
|
[tool.poetry.dependencies]
|
|
31
|
-
python = "
|
|
28
|
+
python = "^3.8.1"
|
|
32
29
|
py7zr = { version = "^0.20.2", optional = true }
|
|
33
30
|
|
|
34
31
|
[tool.poetry.extras]
|
|
35
32
|
7z = [ "py7zr" ]
|
|
36
33
|
|
|
37
34
|
[tool.poetry.group.dev.dependencies]
|
|
38
|
-
pytest = "^7.4.3"
|
|
39
35
|
tox = "^4.11.4"
|
|
36
|
+
pyinstaller = { version = "^6.10.0", python = "<3.14" }
|
|
37
|
+
|
|
38
|
+
[tool.poetry.group.test.dependencies]
|
|
39
|
+
pytest = "^7.4.3"
|
|
40
40
|
pytest-random-order = "^1.1.0"
|
|
41
41
|
pytest-cov = "^4.1.0"
|
|
42
42
|
pytest-mock = "^3.12.0"
|
|
43
|
-
exrex = "
|
|
44
|
-
|
|
45
|
-
[tool.poetry.group.code.dependencies]
|
|
46
|
-
flake8 = "^7.0.0"
|
|
47
|
-
black = "^23.12.1"
|
|
48
|
-
docformatter = "^1.7.5"
|
|
43
|
+
exrex = { git = "https://github.com/asciimoo/exrex", rev = "1c22c70" }
|
|
49
44
|
|
|
50
|
-
[tool.poetry.group.
|
|
51
|
-
jinja2-pdoc = "^0.2.0"
|
|
45
|
+
[tool.poetry.group.docs.dependencies]
|
|
52
46
|
pdoc = "^14.3.0"
|
|
53
|
-
|
|
54
|
-
pyinstaller = "^6.5.0"
|
|
47
|
+
jinja2-pdoc = "^1.1.0"
|
|
55
48
|
|
|
56
49
|
[[tool.poetry.source]]
|
|
57
50
|
name = "PyPI"
|
|
@@ -62,6 +55,9 @@ name = "testpypi"
|
|
|
62
55
|
url = "https://test.pypi.org/legacy/"
|
|
63
56
|
priority = "explicit"
|
|
64
57
|
|
|
58
|
+
[tool.isort]
|
|
59
|
+
profile = "black"
|
|
60
|
+
|
|
65
61
|
[tool.pytest.ini_options]
|
|
66
62
|
minversion = "6.0"
|
|
67
63
|
testpaths = "tests"
|
|
@@ -69,8 +65,7 @@ addopts = [
|
|
|
69
65
|
"--random-order",
|
|
70
66
|
"--color=yes",
|
|
71
67
|
"-s",
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# "--cov-report=html",
|
|
68
|
+
"--cov=pathlibutil",
|
|
69
|
+
"--cov-report=term-missing:skip-covered",
|
|
70
|
+
"--cov-report=xml",
|
|
76
71
|
]
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import pathlib
|
|
3
|
-
import sys
|
|
4
|
-
from typing import TypeVar
|
|
5
|
-
|
|
6
|
-
_Path = TypeVar("_Path", bound=pathlib.Path)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class BasePath(pathlib.Path):
|
|
10
|
-
"""
|
|
11
|
-
Baseclass to inherit from `pathlib.Path`.
|
|
12
|
-
|
|
13
|
-
This class is only needed for python versions < 3.12.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
if sys.version_info < (3, 12):
|
|
17
|
-
_flavour = (
|
|
18
|
-
pathlib._windows_flavour if os.name == "nt" else pathlib._posix_flavour
|
|
19
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|