From a18c066f976c907bae2fc5189b06dcb37038460e Mon Sep 17 00:00:00 2001 From: Klemek Date: Mon, 15 Jun 2026 13:17:11 +0200 Subject: [PATCH] feat: just did the stuff --- README.md | 21 +++++ cool_domain/__main__.py | 18 ++++ cool_domain/cache.py | 48 +++++++++++ cool_domain/constants.py | 6 ++ cool_domain/factory.py | 76 +++++++++++++++++ cool_domain/params.py | 111 +++++++++++++++++++++++- cool_domain/tld_list.py | 59 +++++++++++++ cool_domain/word_list.py | 35 ++++++++ pyproject.toml | 7 +- tests/__init__.py | 176 --------------------------------------- tests/test_sample.py | 6 -- uv.lock | 159 ++++++++++++++++------------------- 12 files changed, 445 insertions(+), 277 deletions(-) create mode 100644 cool_domain/cache.py create mode 100644 cool_domain/constants.py create mode 100644 cool_domain/factory.py create mode 100644 cool_domain/tld_list.py create mode 100644 cool_domain/word_list.py delete mode 100644 tests/__init__.py delete mode 100644 tests/test_sample.py diff --git a/README.md b/README.md index 8515e52..34d99d7 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,24 @@ pipx run --spec git+https://git.klemek.fr/klemek/cool-domain cool-domain # run with uvx uvx --from git+https://git.klemek.fr/klemek/cool-domain cool-domain ``` + +## Usage + +```txt +usage: cool-domain [-h] [--count DOMAIN_COUNT] [--lang {english,french}] [--dig | --no-dig] [--debug | --no-debug] [--quiet | --no-quiet] + [--cache | --no-cache] [--cache-file CACHE_FILE] [--tld-list-uri TLD_LIST_URI] + +options: + -h, --help show this help message and exit + --count DOMAIN_COUNT number of domains to generate (default: 1) + --lang {english,french} + word list lang (default: english) + --dig, --no-dig check availability with dig (default: true) + --debug, --no-debug + --quiet, --no-quiet + --cache, --no-cache use cache (default: true) + --cache-file CACHE_FILE + cache file location (default: /tmp/.cool-domain-cache) + --tld-list-uri TLD_LIST_URI + Top Level Domains list (default: https://publicsuffix.org/list/public_suffix_list.dat) +``` diff --git a/cool_domain/__main__.py b/cool_domain/__main__.py index 18b2411..e8727df 100644 --- a/cool_domain/__main__.py +++ b/cool_domain/__main__.py @@ -2,12 +2,30 @@ import logging import sys from . import PKG_NAME, PKG_VERSION +from .cache import Cache +from .constants import LANGS +from .factory import Factory from .logs import setup_logs from .params import parse_parameters +from .tld_list import TLDList def main() -> int: params = parse_parameters(sys.argv[1:]) setup_logs(params) logging.getLogger().info("%s %s", PKG_NAME, PKG_VERSION) + with ( + Cache(filepath=params.cache_file, no_load=params.cache) as cache, + TLDList(cache=cache, remote_uri=params.tld_list_uri) as tld_list, + LANGS[params.lang] as word_list, + ): + factory = Factory(tld_list=tld_list, word_list=word_list) + logging.getLogger().info( + "Searching %s domain%s", params.count, "s" if params.count > 1 else "" + ) + for _ in range(params.count): + if params.dig: + logging.getLogger().info(factory.random_available_domain()) + else: + logging.getLogger().info(factory.random_domain()) return 0 diff --git a/cool_domain/cache.py b/cool_domain/cache.py new file mode 100644 index 0000000..44eda45 --- /dev/null +++ b/cool_domain/cache.py @@ -0,0 +1,48 @@ +import json +import pathlib +import typing + +T = typing.TypeVar("T") + + +class Cache: + __slots__ = ["__data", "__filepath", "__no_load"] + + def __init__(self, filepath: str, no_load: bool) -> None: # noqa: FBT001 + self.__filepath = pathlib.Path(filepath) + self.__no_load = no_load + self.__data: dict[str, typing.Any] | None = None + + def load(self) -> None: + if not self.__no_load and self.__filepath.exists(): + self.__data = json.loads(self.__filepath.read_text()) + else: + self.__data = {} + + def write(self) -> None: + self.__filepath.write_text(json.dumps(self.__data)) + + def __enter__(self) -> typing.Self: + self.load() + return self + + def __exit__(self, *_: object) -> None: + self.write() + + def has(self, key: str) -> bool: + return self.__data is not None and key in self.__data + + def get(self, key: str) -> typing.Any: + if self.__data is not None and key in self.__data: + return self.__data[key] + return None + + def set(self, key: str, value: typing.Any) -> None: + if self.__data is not None: + self.__data[key] = value + self.write() + + def get_or_set(self, key: str, callback: typing.Callable[[], T]) -> T: + if not self.has(key): + self.set(key, callback()) + return self.get(key) diff --git a/cool_domain/constants.py b/cool_domain/constants.py new file mode 100644 index 0000000..4ededee --- /dev/null +++ b/cool_domain/constants.py @@ -0,0 +1,6 @@ +from .word_list import WordList + +LANGS: dict[str, WordList] = { + "english": WordList("english", "english", "words_alpha.txt"), + "french": WordList("french", "french", "francais.txt"), +} diff --git a/cool_domain/factory.py b/cool_domain/factory.py new file mode 100644 index 0000000..47bb0cc --- /dev/null +++ b/cool_domain/factory.py @@ -0,0 +1,76 @@ +import logging +import random +import shutil +import subprocess +import typing + +if typing.TYPE_CHECKING: + from .tld_list import TLDList + from .word_list import WordList + + +class FactoryError(Exception): + pass + + +class Factory: + __slots__ = ["__found", "__tld_list", "__word_list", "logger", "logger"] + + def __init__(self, tld_list: TLDList, word_list: WordList) -> None: + self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) + self.__tld_list = tld_list + self.__word_list = word_list + self.__found: set[str] = set() + + def __iter__(self) -> typing.Self: + return self + + def __next__(self) -> str: + return self.random_domain() + + @property + def __tld_list_shuffled(self) -> typing.Iterable[str]: + if self.__tld_list.tlds is None: + msg = "TLD list is not loaded" + raise FactoryError(msg) + return random.sample(self.__tld_list.tlds, len(self.__tld_list.tlds)) + + def __is_domain_available(self, domain: str) -> bool: + dig_bin = shutil.which("dig") + if dig_bin is None: + msg = "dig is not installed" + raise FactoryError(msg) + try: + output = subprocess.check_output([dig_bin, domain, "+short"]) # noqa: S603 + return len(output) == 0 + except subprocess.CalledProcessError: + return True + + def random_domain(self) -> str: + k = 10000 + while (k := k - 1) > 0 and (domain := self.__random_domain()) is None: + pass + if domain is None: + raise StopIteration + return domain + + def random_available_domain(self) -> str: + k = 10000 + while (k := k - 1) > 0 and not self.__is_domain_available( + domain := self.random_domain() + ): + pass + if k <= 0: + raise StopIteration + return domain + + def __random_domain(self) -> str | None: + word = random.choice(self.__word_list.words) # noqa: S311 + for tld in self.__tld_list_shuffled: + tld_escaped = tld.replace(".", "") + if word.endswith(tld_escaped) and len(word) > len(tld_escaped): + domain = word[: -len(tld_escaped)] + "." + tld + if domain not in self.__found: + self.__found.add(domain) + return domain + return None diff --git a/cool_domain/params.py b/cool_domain/params.py index dd0d026..72e3f91 100644 --- a/cool_domain/params.py +++ b/cool_domain/params.py @@ -1,31 +1,140 @@ import argparse import dataclasses +import os +import pathlib +import tempfile import typing from . import PKG_NAME +from .constants import LANGS @dataclasses.dataclass(frozen=True, slots=True) class Parameters: debug: bool = False quiet: bool = False + cache: bool = True + count: int = 1 + dig: bool = True + lang: str = "english" + cache_file: str = str(pathlib.Path(tempfile.gettempdir()) / ".cool-domain-cache") + tld_list_uri: str = "https://publicsuffix.org/list/public_suffix_list.dat" @classmethod def from_namespace(cls, args: argparse.Namespace) -> Parameters: return Parameters(**vars(args)) +def __get_env_str(var: str, default: str) -> str: + if (result := os.getenv(var)) is None: + return default + return result + + +def __get_env_int(var: str, default: int) -> int: + value = __get_env_str(var, str(default)) + if value.isdecimal(): + return int(value) + return default + + +def __add_arg_str( + parser: argparse.ArgumentParser, + *flags: str, + env_var: str, + default: str, + help_txt: str, +) -> None: + parser.add_argument( + *flags, + metavar=env_var, + default=__get_env_str(env_var, default), + help=f"{help_txt} (default: {default})" if len(default) else help_txt, + ) + + +def __add_arg_int( + parser: argparse.ArgumentParser, + *flags: str, + env_var: str, + default: int, + help_txt: str, +) -> None: + parser.add_argument( + *flags, + type=int, + metavar=env_var, + default=__get_env_int(env_var, default), + help=f"{help_txt} (default: {default})", + ) + + +def __add_arg_bool( + parser: argparse.ArgumentParser, + *flags: str, + default: bool, + help_txt: str, + dest: str | None = None, +) -> None: + parser.add_argument( + *flags, + action=argparse.BooleanOptionalAction, + help=f"{help_txt} (default: {str(default).lower()})", + default=default, + dest=dest, + ) + + def parse_parameters(args: typing.Sequence[str]) -> Parameters: default_values = Parameters() parser = argparse.ArgumentParser( - PKG_NAME, + PKG_NAME.replace("_", "-"), suggest_on_error=True, ) + __add_arg_int( + parser, + "--count", + env_var="DOMAIN_COUNT", + default=default_values.count, + help_txt="number of domains to generate", + ) + parser.add_argument( + "--lang", + default=default_values.lang, + help="word list lang (default: english)", + choices=LANGS.keys(), + ) + __add_arg_bool( + parser, + "--dig", + default=default_values.dig, + help_txt="check availability with dig", + ) parser.add_argument( "--debug", action=argparse.BooleanOptionalAction, default=default_values.debug ) parser.add_argument( "--quiet", action=argparse.BooleanOptionalAction, default=default_values.quiet ) + __add_arg_bool( + parser, + "--cache", + default=default_values.cache, + help_txt="use cache", + ) + __add_arg_str( + parser, + "--cache-file", + env_var="CACHE_FILE", + default=default_values.cache_file, + help_txt="cache file location", + ) + __add_arg_str( + parser, + "--tld-list-uri", + env_var="TLD_LIST_URI", + default=default_values.tld_list_uri, + help_txt="Top Level Domains list", + ) parsed_args = parser.parse_args(args) return Parameters.from_namespace(parsed_args) diff --git a/cool_domain/tld_list.py b/cool_domain/tld_list.py new file mode 100644 index 0000000..574e25a --- /dev/null +++ b/cool_domain/tld_list.py @@ -0,0 +1,59 @@ +import http +import logging +import typing + +import requests +import unidecode + +if typing.TYPE_CHECKING: + from .cache import Cache + + +class TLDError(Exception): + pass + + +class TLDList: + __slots__ = ["__cache", "__remote_uri", "logger", "tlds"] + + def __init__(self, cache: Cache, remote_uri: str) -> None: + self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) + self.tlds: list[str] | None = None + self.__cache = cache + self.__remote_uri = remote_uri + + def __load_remote(self) -> list[str]: + response = requests.get(self.__remote_uri, timeout=5) + if response.status_code != http.HTTPStatus.OK: + msg = f"Failed to read {self.__remote_uri}." + raise TLDError(msg) + raw_lines = response.content.decode().split("\n") + out_lines = [] + for line in raw_lines: + stripped_line = line.strip() + if ( + len(stripped_line) + and not stripped_line.startswith("//") + and not stripped_line.startswith("*") + and unidecode.unidecode(stripped_line) == stripped_line + ): + if "." in stripped_line: + for other in out_lines: + if stripped_line.endswith(other): + break + else: + out_lines += [stripped_line] + else: + out_lines += [stripped_line] + return out_lines + + def load(self) -> None: + self.tlds = self.__cache.get_or_set("tld_list", self.__load_remote) + self.logger.info("Loaded %d TLD entries", len(self.tlds)) + + def __enter__(self) -> typing.Self: + self.load() + return self + + def __exit__(self, *_: object) -> None: + pass diff --git a/cool_domain/word_list.py b/cool_domain/word_list.py new file mode 100644 index 0000000..249bf5f --- /dev/null +++ b/cool_domain/word_list.py @@ -0,0 +1,35 @@ +import logging +import pathlib +import typing + +import unidecode + + +class WordList: + __slots__ = ["__filepath", "__name", "logger", "words"] + + def __init__(self, name: str, path: str, filename: str) -> None: + self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) + self.__name = name + self.__filepath = pathlib.Path(__file__).parent / ".." / path / filename + self.words: list[str] = [] + + def open(self) -> None: + with self.__filepath.open(encoding="utf-8") as file: + self.words = list( + filter(lambda v: len(v) > 0, map(self.__escape_word, file.readlines())) + ) + self.logger.info("[%s] Loaded %d words", self.__name, len(self.words)) + + def __escape_word(self, word: str) -> str: + return unidecode.unidecode(word.strip()).lower() + + def close(self) -> None: + pass + + def __enter__(self) -> typing.Self: + self.open() + return self + + def __exit__(self, *_: object) -> None: + self.close() diff --git a/pyproject.toml b/pyproject.toml index aa5149d..fd03bf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.14" dependencies = [ + "requests>=2.34.2,<3.0.0", "unidecode>=1.4.0,<2.0.0", ] @@ -17,8 +18,6 @@ build-backend = "uv_build" [dependency-groups] dev = [ - "coverage>=7.13.5,<8.0.0", - "pytest>=9.0.3,<10.0.0", "ruff>=0.15.12,<0.16.0", "ty>=0.0.34,<0.0.35", ] @@ -47,7 +46,3 @@ include = ["cool_domain", "tests"] [tool.ty.environment] python = "./.venv/bin/python3" - -[tool.coverage.run] -source = ["cool_domain"] -omit = ["cool_domain/logs.py", "cool_domain/__main__.py"] diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 37789d2..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,176 +0,0 @@ -import contextlib -import pathlib -import tempfile -import typing -import unittest -import unittest.mock - -__import__("sys").modules["unittest.util"]._MAX_LENGTH = 999999999 # ty:ignore[unresolved-attribute] # noqa: SLF001 - - -class BaseTestCase(unittest.TestCase): - @typing.override - def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: - self.mocks: list[unittest.mock.Mock] = [] - self.tmp_dir: tempfile.TemporaryDirectory | None = None - self.tmp_path: pathlib.Path = pathlib.Path() - super().__init__(*args, **kwargs) - - @typing.override - def tearDown(self) -> None: - if self.tmp_dir is not None: - self.tmp_dir.cleanup() - self.tmp_dir = None - super().tearDown() - - def get_tmp_dir(self) -> str: - self.tmp_dir = tempfile.TemporaryDirectory(delete=False) - self.tmp_path = pathlib.Path(self.tmp_dir.name) - return self.tmp_dir.name - - def new_mock(self, spec: type | None = None) -> unittest.mock.Mock: - mock = unittest.mock.Mock(spec) - self.mocks += [mock] - return mock - - @contextlib.contextmanager - def patch( - self, target: str, return_value: typing.Any = None, count: int = 1 - ) -> typing.Iterator[unittest.mock.Mock]: - with unittest.mock.patch( - target, return_value=return_value, create=True - ) as mock: - yield mock - self.assertEqual(mock.call_count, count, mock) - - @contextlib.contextmanager - def patch_calls( - self, - target: str, - args: list[typing.Iterable[typing.Any]] | None = None, - return_values: list[typing.Any] | None = None, - kwargs: list[dict[str, typing.Any]] | None = None, - ) -> typing.Iterator[unittest.mock.Mock]: - if args is None: - args = [[]] - if return_values is None: - return_values = [None] * len(args) - if kwargs is None: - kwargs = [{}] * len(args) - with unittest.mock.patch( - target, side_effect=return_values, create=True - ) as mock: - yield mock - self.__check_calls(mock, args, kwargs) - - @contextlib.contextmanager - def patch_call( - self, - target: str, - args: typing.Iterable[typing.Any] | None = None, - return_value: typing.Any = None, - kwargs: dict[str, typing.Any] | None = None, - ) -> typing.Iterator[unittest.mock.Mock]: - if args is None: - args = [] - if kwargs is None: - kwargs = {} - with self.patch_calls(target, [args], [return_value], [kwargs]) as mock: - yield mock - - @contextlib.contextmanager - def seal_mocks(self, *extra_mocks: unittest.mock.Mock) -> typing.Iterator[None]: - for mock in self.mocks: - unittest.mock.seal(mock) - for mock in extra_mocks: - unittest.mock.seal(mock) - yield - - @contextlib.contextmanager - def mock_calls( - self, - mock: unittest.mock.Mock, - args: list[typing.Iterable[typing.Any]] | None = None, - return_values: list[typing.Any] | None = None, - kwargs: list[dict[str, typing.Any]] | None = None, - ) -> typing.Iterator[None]: - if args is None: - args = [[]] - if return_values is None: - return_values = [None] * len(args) - if kwargs is None: - kwargs = [{}] * len(args) - mock.side_effect = return_values - mock.reset_mock() - yield - self.__check_calls(mock, args, kwargs) - - @contextlib.contextmanager - def mock_call( - self, - mock: unittest.mock.Mock, - args: typing.Iterable[typing.Any] | None = None, - return_value: typing.Any = None, - kwargs: dict[str, typing.Any] | None = None, - ) -> typing.Iterator[None]: - if args is None: - args = [] - if kwargs is None: - kwargs = {} - with self.mock_calls(mock, [args], [return_value], [kwargs]): - yield - - @contextlib.contextmanager - def mock_calls_unchecked( - self, - mock: unittest.mock.Mock, - count: int = 1, - return_values: list[typing.Any] | None = None, - ) -> typing.Iterator[None]: - if return_values is None: - return_values = [None] * count - mock.side_effect = return_values - mock.reset_mock() - yield - self.assertEqual(mock.call_count, count, mock) - - @contextlib.contextmanager - def mock_call_unchecked( - self, - mock: unittest.mock.Mock, - return_value: typing.Any = None, - ) -> typing.Iterator[None]: - with self.mock_calls_unchecked(mock, 1, [return_value]): - yield - - def assert_file_content(self, file: pathlib.Path, *expected_content: str) -> None: - assert file.parent.is_dir(), file - assert file.exists(), file - assert file.is_file(), file - with file.open() as file_content: - self.assertEqual(file_content.read(), "\n".join(expected_content)) - - def __check_calls( - self, - mock: unittest.mock.Mock, - args: list[typing.Iterable[typing.Any]], - kwargs: list[dict[str, typing.Any]], - ) -> None: - total_rows = max(len(args), len(mock.method_calls), len(kwargs)) - missing_calls = max(0, total_rows - len(mock.mock_calls)) - missing_args = max(0, total_rows - len(args)) - missing_kwargs = max(0, total_rows - len(kwargs)) - for i, values in enumerate( - zip( - mock.mock_calls + [None] * missing_calls, - args + [[]] * missing_args, - kwargs + [{}] * missing_kwargs, - strict=False, - ) - ): - real_call, expected_args, expected_kwargs = values - self.assertEqual( - real_call, - unittest.mock.call(*expected_args, **expected_kwargs), - f"{i + 1}: {mock}", - ) diff --git a/tests/test_sample.py b/tests/test_sample.py deleted file mode 100644 index 5e39f89..0000000 --- a/tests/test_sample.py +++ /dev/null @@ -1,6 +0,0 @@ -from . import BaseTestCase - - -class TestSample(BaseTestCase): - def test_sample(self) -> None: - self.skipTest("TODO") diff --git a/uv.lock b/uv.lock index b211f4b..64c0f0a 100644 --- a/uv.lock +++ b/uv.lock @@ -3,12 +3,53 @@ revision = 3 requires-python = ">=3.14" [[package]] -name = "colorama" -version = "0.4.6" +name = "certifi" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] @@ -16,117 +57,50 @@ name = "cool-domain" version = "0.0.0" source = { editable = "." } dependencies = [ + { name = "requests" }, { name = "unidecode" }, ] [package.dev-dependencies] dev = [ - { name = "coverage" }, - { name = "pytest" }, { name = "ruff" }, { name = "ty" }, ] [package.metadata] -requires-dist = [{ name = "unidecode", specifier = ">=1.4.0,<2.0.0" }] +requires-dist = [ + { name = "requests", specifier = ">=2.34.2,<3.0.0" }, + { name = "unidecode", specifier = ">=1.4.0,<2.0.0" }, +] [package.metadata.requires-dev] dev = [ - { name = "coverage", specifier = ">=7.13.5,<8.0.0" }, - { name = "pytest", specifier = ">=9.0.3,<10.0.0" }, { name = "ruff", specifier = ">=0.15.12,<0.16.0" }, { name = "ty", specifier = ">=0.0.34,<0.0.35" }, ] [[package]] -name = "coverage" -version = "7.13.5" +name = "idna" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "packaging" -version = "26.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.3" +name = "requests" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -186,3 +160,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/94/7d/a8a765761bbc0c836 wheels = [ { url = "https://files.pythonhosted.org/packages/8f/b7/559f59d57d18b44c6d1250d2eeaa676e028b9c527431f5d0736478a73ba1/Unidecode-1.4.0-py3-none-any.whl", hash = "sha256:c3c7606c27503ad8d501270406e345ddb480a7b5f38827eafe4fa82a137f0021", size = 235837, upload-time = "2025-04-24T08:45:01.609Z" }, ] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +]