Skip to content

Commit d46814a

Browse files
authored
Merge pull request #19 from d-chris/develop
Update for Python 3.13
2 parents 06a4566 + f3fb9a0 commit d46814a

7 files changed

Lines changed: 403 additions & 312 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
os: [ubuntu-latest, windows-latest]
17-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
17+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
1818
steps:
1919
- uses: actions/checkout@v4
2020
- name: Install poetry

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.6.0
3+
rev: v5.0.0
44
hooks:
55
- id: check-yaml
66
- id: end-of-file-fixer
77
exclude: \.md$
88
- id: trailing-whitespace
99
- id: check-toml
1010
- repo: https://github.com/tox-dev/pyproject-fmt
11-
rev: 2.2.1
11+
rev: 2.4.3
1212
hooks:
1313
- id: pyproject-fmt
1414
- repo: https://github.com/tox-dev/tox-ini-fmt
15-
rev: 1.3.1
15+
rev: 1.4.1
1616
hooks:
1717
- id: tox-ini-fmt
1818
- repo: https://github.com/rhysd/actionlint
19-
rev: v1.7.1
19+
rev: v1.7.3
2020
hooks:
2121
- id: actionlint
2222
- repo: https://github.com/psf/black
23-
rev: 24.8.0
23+
rev: 24.10.0
2424
hooks:
2525
- id: black
2626
- repo: https://github.com/adamchainz/blacken-docs
27-
rev: "1.18.0"
27+
rev: "1.19.0"
2828
hooks:
2929
- id: blacken-docs
3030
files: pathlibutil/

docs/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def main() -> int:
6565
"pathlibutil.urlpath",
6666
"pathlib",
6767
"urllib.parse",
68+
"urllib.request",
6869
"shutil",
6970
"hashlib",
7071
"builtins",
@@ -103,4 +104,4 @@ def main() -> int:
103104

104105

105106
if __name__ == "__main__":
106-
SystemExit(main())
107+
raise SystemExit(main())

pathlibutil/urlpath.py

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import itertools
12
import pathlib
23
import re
34
import urllib.parse as up
5+
import urllib.request
46
from dataclasses import asdict, dataclass, field
5-
from functools import wraps
6-
from typing import Any, Dict, Optional, TypeVar, Union
7+
from functools import cached_property, wraps
8+
from typing import Any, Dict, Optional, Tuple, TypeVar, Union
79

810

911
@dataclass
@@ -122,10 +124,10 @@ def urlpath(func):
122124
"""
123125

124126
@wraps(func)
125-
def wrapper(self, *args, **kwargs):
127+
def wrapper(self, *args, **kwargs) -> _UrlPath:
126128
result = func(self, *args, **kwargs)
127129

128-
return UrlPath(result.geturl(), **self._kwargs)
130+
return self.__class__(result.geturl(), **self._kwargs)
129131

130132
return wrapper
131133

@@ -323,6 +325,77 @@ def with_credentials(self, username: str, password: str = None) -> _UrlPath:
323325

324326
return self.with_netloc(netloc)
325327

328+
@cached_property
329+
def parts(self) -> Tuple[str, ...]:
330+
"""
331+
return the parts of the path without any '/'.
332+
"""
333+
return tuple(part for part in self._path.parts if not part.startswith("/"))
334+
335+
@property
336+
def anchor(self) -> str:
337+
"""
338+
The concatenation of the netloc and root of the path.
339+
340+
>>> UrlPath("//server/root/path/file.txt").anchor
341+
'//server/root'
342+
"""
343+
try:
344+
root = self.parts[0]
345+
except IndexError:
346+
root = ""
347+
348+
return f"//{self.netloc}/{root}"
349+
350+
def with_anchor(self, anchor: str, root: bool = False, **kwargs) -> _UrlPath:
351+
"""
352+
Change the anchor of the URL.
353+
354+
If `root` is `True`, the root of the path will not be removed.
355+
356+
>>> url = UrlPath("//server/root/path/file.txt")
357+
>>> url.with_anchor("https://www.server.com").geturl()
358+
'https://www.server.com/path/file.txt'
359+
"""
360+
anchor = self.__class__(anchor, **kwargs)
361+
362+
url = self.with_netloc(anchor.netloc)
363+
364+
if anchor.scheme != url.scheme:
365+
url = url.with_scheme(anchor.scheme)
366+
367+
if root is False:
368+
parts = url.parts[1:]
369+
else:
370+
parts = url.parts
371+
372+
# if anchor has a path, anchor and url path are concatenated
373+
if any(anchor.parts):
374+
return url.with_path("/".join(itertools.chain(anchor.parts, parts)))
375+
376+
# if root is False, the root of the path is removed
377+
if root is False:
378+
return url.with_path("/".join(parts))
379+
380+
return url
381+
382+
def exists(self, errors: bool = False, **kwargs) -> bool:
383+
"""
384+
Check if the URL returns a 200 status code.
385+
386+
If `errors` is `False`, exceptions are suppressed and `False` is returned.
387+
388+
For `kwargs` see `urllib.request.urlopen`.
389+
"""
390+
try:
391+
with urllib.request.urlopen(self.normalize(False), **kwargs) as response:
392+
return response.status == 200
393+
except Exception as e:
394+
if errors is not False:
395+
raise e
396+
397+
return False
398+
326399

327400
__all__ = [
328401
"UrlNetloc",

0 commit comments

Comments
 (0)