165 lines
4.3 KiB
Python
165 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import typing
|
|
import urllib.parse
|
|
|
|
|
|
class URL:
|
|
"""
|
|
The URL class.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
url = URL('https://ziomek.dog/').\
|
|
set_path('/index_old.html').\
|
|
set_query({
|
|
'test': 'true',
|
|
}).\
|
|
to_string()
|
|
|
|
The *encoding* argument will be used to decode *initial* if it's
|
|
```bytes``. Also, it'll be used when decoding QS.
|
|
"""
|
|
def __init__(self,
|
|
initial: str | bytes | pathlib.Path | urllib.parse.ParseResult,
|
|
encoding: str = 'utf-8'):
|
|
self.encoding = encoding
|
|
self.parsed_url: urllib.parse.ParseResult
|
|
|
|
if isinstance(initial, urllib.parse.ParseResult):
|
|
self.parsed_url = initial
|
|
elif isinstance(initial, pathlib.Path):
|
|
self.parsed_url = urllib.parse.urlparse(str(initial))
|
|
elif isinstance(initial, bytes):
|
|
self.parsed_url = urllib.parse.urlparse(
|
|
initial.decode(self.encoding),
|
|
)
|
|
else:
|
|
self.parsed_url = urllib.parse.urlparse(initial)
|
|
|
|
def replace_parsed_url_field(self, field: str, value: typing.Any):
|
|
self.parsed_url = self.parsed_url._replace(**{field: value})
|
|
|
|
return self
|
|
|
|
def append_path_component(self, component: str | pathlib.Path) -> URL:
|
|
_ = self.set_path(self.path / component)
|
|
|
|
return self
|
|
|
|
@property
|
|
def scheme(self) -> str:
|
|
return self.parsed_url.scheme
|
|
|
|
def set_scheme(self, new_scheme: str) -> URL:
|
|
self.replace_parsed_url_field('scheme', new_scheme)
|
|
|
|
return self
|
|
|
|
@property
|
|
def netloc(self) -> str:
|
|
return self.parsed_url.netloc
|
|
|
|
def set_netloc(self, new_netloc: str) -> URL:
|
|
self.replace_parsed_url_field('netloc', new_netloc)
|
|
|
|
return self
|
|
|
|
@property
|
|
def hostname(self) -> str | None:
|
|
return self.parsed_url.hostname
|
|
|
|
@property
|
|
def port(self) -> int | None:
|
|
return self.parsed_url.port
|
|
|
|
@property
|
|
def username(self) -> str | None:
|
|
return self.parsed_url.username
|
|
|
|
@property
|
|
def password(self) -> str | None:
|
|
return self.parsed_url.password
|
|
|
|
@property
|
|
def path(self) -> pathlib.PurePosixPath:
|
|
return pathlib.PurePosixPath(self.parsed_url.path)
|
|
|
|
def set_path(self, new_path: str | pathlib.Path | pathlib.PurePath) -> URL:
|
|
self.replace_parsed_url_field(
|
|
'path', str(pathlib.PurePosixPath(new_path)),
|
|
)
|
|
|
|
return self
|
|
|
|
@property
|
|
def params(self) -> str:
|
|
return self.parsed_url.params
|
|
|
|
def set_params(self, new_params: str) -> URL:
|
|
self.replace_parsed_url_field('params', new_params)
|
|
|
|
return self
|
|
|
|
@property
|
|
def query(self) -> dict[typing.AnyStr, list[typing.AnyStr]]:
|
|
if self.parsed_url.query == '':
|
|
return {}
|
|
|
|
return urllib.parse.parse_qs(
|
|
self.parsed_url.query, encoding=self.encoding, # type: ignore[arg-type]
|
|
)
|
|
|
|
def set_query(self,
|
|
new_query: dict[typing.AnyStr, list[typing.AnyStr]] | list,
|
|
) -> URL:
|
|
self.replace_parsed_url_field(
|
|
'query', urllib.parse.urlencode(new_query, doseq=True),
|
|
)
|
|
|
|
return self
|
|
|
|
@property
|
|
def query_string(self) -> str:
|
|
return self.parsed_url.query
|
|
|
|
def set_query_string(self, new_query_string: str) -> URL:
|
|
self.replace_parsed_url_field('query', new_query_string)
|
|
|
|
return self
|
|
|
|
@property
|
|
def fragment(self) -> str:
|
|
return self.parsed_url.fragment
|
|
|
|
def set_fragment(self, new_fragment: str) -> URL:
|
|
self.replace_parsed_url_field('fragment', new_fragment)
|
|
|
|
return self
|
|
|
|
def to_string(self) -> str:
|
|
return urllib.parse.urlunparse(self.parsed_url)
|
|
|
|
def __truediv__(self, component: str | pathlib.Path) -> URL:
|
|
return self.append_path_component(component)
|
|
|
|
def __mod__(self,
|
|
query: dict[typing.AnyStr, list[typing.AnyStr]],
|
|
) -> URL:
|
|
return self.set_query(
|
|
{**self.query, **query},
|
|
)
|
|
|
|
def __str__(self) -> str:
|
|
return self.to_string()
|
|
|
|
def __repr__(self) -> str:
|
|
return f'<URL {self}>'
|
|
|
|
def __bytes__(self) -> bytes:
|
|
return self.__str__().encode(self.encoding)
|