|
| 1 | +import itertools |
1 | 2 | import pathlib |
2 | 3 | import re |
3 | 4 | import urllib.parse as up |
| 5 | +import urllib.request |
4 | 6 | 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 |
7 | 9 |
|
8 | 10 |
|
9 | 11 | @dataclass |
@@ -122,10 +124,10 @@ def urlpath(func): |
122 | 124 | """ |
123 | 125 |
|
124 | 126 | @wraps(func) |
125 | | - def wrapper(self, *args, **kwargs): |
| 127 | + def wrapper(self, *args, **kwargs) -> _UrlPath: |
126 | 128 | result = func(self, *args, **kwargs) |
127 | 129 |
|
128 | | - return UrlPath(result.geturl(), **self._kwargs) |
| 130 | + return self.__class__(result.geturl(), **self._kwargs) |
129 | 131 |
|
130 | 132 | return wrapper |
131 | 133 |
|
@@ -323,6 +325,77 @@ def with_credentials(self, username: str, password: str = None) -> _UrlPath: |
323 | 325 |
|
324 | 326 | return self.with_netloc(netloc) |
325 | 327 |
|
| 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 | + |
326 | 399 |
|
327 | 400 | __all__ = [ |
328 | 401 | "UrlNetloc", |
|
0 commit comments