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.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pathlibutil
3
- Version: 0.2.1
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,<3.13
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
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pathlibutil)](https://pypi.org/project/pathlibutil/)
28
- [![PyPI](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
32
+ [![PyPI - Version](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
29
33
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
30
34
  [![PyPI - License](https://img.shields.io/pypi/l/pathlibutil)](https://raw.githubusercontent.com/d-chris/pathlibutil/main/LICENSE)
31
- [![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)
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
- [![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
- [![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
+ [![GitHub - Pytest](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)
36
+ [![GitHub - Page](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)
37
+ [![GitHub - Release](https://img.shields.io/github/v/tag/d-chris/pathlibutil?logo=github&label=github)](https://github.com/d-chris/pathlibutil)
38
+ [![codecov](https://codecov.io/gh/d-chris/pathlibutil/graph/badge.svg?token=U7I9FYMRSR)](https://codecov.io/gh/d-chris/pathlibutil)
39
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](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
- f"# MD5 checksums generated with pathlibutil "
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
  [![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/)
8
+ [![PyPI - Version](https://img.shields.io/pypi/v/pathlibutil)](https://pypi.org/project/pathlibutil/)
5
9
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/pathlibutil)](https://pypi.org/project/pathlibutil/)
6
10
  [![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
- [![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
- [![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
- [![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
+ [![GitHub - Pytest](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)
12
+ [![GitHub - Page](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)
13
+ [![GitHub - Release](https://img.shields.io/github/v/tag/d-chris/pathlibutil?logo=github&label=github)](https://github.com/d-chris/pathlibutil)
14
+ [![codecov](https://codecov.io/gh/d-chris/pathlibutil/graph/badge.svg?token=U7I9FYMRSR)](https://codecov.io/gh/d-chris/pathlibutil)
15
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](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
- f"# MD5 checksums generated with pathlibutil "
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.2.1"
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 = ">=3.8.1,<3.13"
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 = "^0.11.0"
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.doc.dependencies]
51
- jinja2-pdoc = "^0.2.0"
45
+ [tool.poetry.group.docs.dependencies]
52
46
  pdoc = "^14.3.0"
53
- click = "^8.1.7"
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
- # "--cov=pathlibutil",
73
- # "--cov-report=term-missing:skip-covered",
74
- # "--cov-append",
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