hotpocket/services/packages/common/hotpocket_common/url.py
Tomek Wójcik b4338e2769
Some checks failed
CI / Checks (push) Failing after 13m2s
Release v1.0.0
2025-08-20 21:00:50 +02:00

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)