From 07f3e45cee6e80a3bf9adeefcda72abece1d38ac Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Sun, 29 Dec 2024 18:59:51 +0700 Subject: [PATCH 01/60] refactor use pydantic and drop python 3.8 3.9 --- .github/workflows/lint.yaml | 29 + .gitignore | 152 +---- .python-version | 1 + MANIFEST.in | 4 +- Makefile | 31 + examples/basic.py | 20 +- examples/basic2.py | 55 +- examples/http_client.py | 2 +- pyproject.toml | 189 ++++-- requirements-dev.txt | 4 - requirements.txt | 1 - setup.py | 9 - tests/conftest.py | 53 +- tests/test_client.py | 409 ++++++++++++- tests/test_models.py | 574 ------------------ uv.lock | 832 +++++++++++++++++++++++++++ valorant/__init__.py | 115 ++-- valorant/asset.py | 204 ------- valorant/cache.py | 822 -------------------------- valorant/client.py | 595 +++++++++---------- valorant/enums.py | 264 +++------ valorant/errors.py | 28 +- valorant/file.py | 123 ---- valorant/http.py | 214 +++---- valorant/localization.py | 226 -------- valorant/models/__init__.py | 71 +-- valorant/models/agents.py | 504 ++-------------- valorant/models/base.py | 161 ++---- valorant/models/buddies.py | 113 +--- valorant/models/bundles.py | 184 +----- valorant/models/ceremonies.py | 43 +- valorant/models/competitive_tiers.py | 144 +---- valorant/models/content_tiers.py | 68 +-- valorant/models/contracts.py | 225 ++------ valorant/models/currencies.py | 76 +-- valorant/models/events.py | 73 +-- valorant/models/gamemodes.py | 184 ++---- valorant/models/gear.py | 76 +-- valorant/models/level_borders.py | 58 +- valorant/models/maps.py | 149 +---- valorant/models/missions.py | 123 +--- valorant/models/player_cards.py | 95 +-- valorant/models/player_titles.py | 61 +- valorant/models/seasons.py | 171 +----- valorant/models/sprays.py | 144 +---- valorant/models/themes.py | 62 +- valorant/models/version.py | 68 +-- valorant/models/weapons.py | 562 +++--------------- valorant/types/__init__.py | 10 - valorant/types/agents.py | 81 --- valorant/types/buddies.py | 51 -- valorant/types/bundles.py | 47 -- valorant/types/ceremonies.py | 37 -- valorant/types/competitive_tiers.py | 51 -- valorant/types/content_tiers.py | 43 -- valorant/types/contracts.py | 70 --- valorant/types/currencies.py | 40 -- valorant/types/events.py | 40 -- valorant/types/gamemodes.py | 69 --- valorant/types/gear.py | 40 -- valorant/types/level_borders.py | 39 -- valorant/types/maps.py | 58 -- valorant/types/missions.py | 50 -- valorant/types/object.py | 46 -- valorant/types/player_cards.py | 43 -- valorant/types/player_titles.py | 39 -- valorant/types/response.py | 32 -- valorant/types/seasons.py | 62 -- valorant/types/sprays.py | 56 -- valorant/types/themes.py | 39 -- valorant/types/version.py | 41 -- valorant/types/weapons.py | 127 ---- valorant/utils.py | 91 --- 73 files changed, 2479 insertions(+), 7194 deletions(-) create mode 100644 .github/workflows/lint.yaml create mode 100644 .python-version create mode 100644 Makefile delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt delete mode 100644 setup.py delete mode 100644 tests/test_models.py create mode 100644 uv.lock delete mode 100644 valorant/asset.py delete mode 100644 valorant/cache.py delete mode 100644 valorant/file.py delete mode 100644 valorant/localization.py delete mode 100644 valorant/types/__init__.py delete mode 100644 valorant/types/agents.py delete mode 100644 valorant/types/buddies.py delete mode 100644 valorant/types/bundles.py delete mode 100644 valorant/types/ceremonies.py delete mode 100644 valorant/types/competitive_tiers.py delete mode 100644 valorant/types/content_tiers.py delete mode 100644 valorant/types/contracts.py delete mode 100644 valorant/types/currencies.py delete mode 100644 valorant/types/events.py delete mode 100644 valorant/types/gamemodes.py delete mode 100644 valorant/types/gear.py delete mode 100644 valorant/types/level_borders.py delete mode 100644 valorant/types/maps.py delete mode 100644 valorant/types/missions.py delete mode 100644 valorant/types/object.py delete mode 100644 valorant/types/player_cards.py delete mode 100644 valorant/types/player_titles.py delete mode 100644 valorant/types/response.py delete mode 100644 valorant/types/seasons.py delete mode 100644 valorant/types/sprays.py delete mode 100644 valorant/types/themes.py delete mode 100644 valorant/types/version.py delete mode 100644 valorant/types/weapons.py delete mode 100644 valorant/utils.py diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..f0fbe9e --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,29 @@ +name: lint + +on: + push: + pull_request: + branches: + - master + types: + - opened + - reopened + - synchronize + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "0.5.x" + enable-cache: true + - run: uv run make lint + \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6f1e8a..e3c4550 100644 --- a/.gitignore +++ b/.gitignore @@ -1,164 +1,16 @@ # Byte-compiled / optimized / DLL files __pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so # Distribution / packaging -.Python build/ -develop-eggs/ dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ *.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt # Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ .coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ # MacOS .DS_Store -# vscode -.vscode/ - -# pypi configuration file -.pypirc - -# test files -test.py \ No newline at end of file +# VSCode +.vscode/ \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..7c7a975 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 6a37972..74215c3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,2 @@ include README.md -include LICENSE -include requirements.txt -include requirements-dev.txt \ No newline at end of file +include LICENSE \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d930520 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +venv=.venv +python=$(venv)/bin/python + +default: help + +.PHONY: help +help: + @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done + +.PHONY: report +.SILENT: report +report: # See the coverage report + $(python) -m coverage report + +.PHONY: lint +.SILENT: lint +lint: # Run the linter + mypy valorant + ruff check valorant + ruff format valorant --check + +.PHONY: format +.SILENT: format +format: # Format the code + ruff check valorant --fix + ruff format valorant + +.PHONY: test +.SILENT: test +test: # Run the tests + $(python) -m pytest diff --git a/examples/basic.py b/examples/basic.py index a6b8de0..198546d 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -3,10 +3,10 @@ import valorant -async def main(): - client = valorant.Client(valorant.Locale.thai) # set default locale to thai +async def main() -> None: + client = valorant.Client() async with client: - weapon = client.get_weapon('9c82e19d-4575-0200-1a81-3eacf00cf872') # Vandal + weapon = await client.fetch_weapon('9c82e19d-4575-0200-1a81-3eacf00cf872') # Vandal assert weapon is not None for skin in weapon.skins: @@ -16,15 +16,15 @@ async def main(): # specify locale print(skin.display_name.ja_JP) print(skin.display_name.japanese) - print(skin.display_name.from_locale(valorant.Locale.japanese)) + # print(skin.display_name.from_locale(valorant.Locale.japanese)) - if skin.theme is not None: - print(skin.theme.display_name) - print(skin.theme.display_icon) + # if skin.theme is not None: + # print(skin.theme.display_name) + # print(skin.theme.display_icon) - if skin.content_tier is not None: - print(skin.content_tier.display_name) - print(skin.content_tier.display_icon) + # if skin.content_tier is not None: + # print(skin.content_tier.display_name) + # print(skin.content_tier.display_icon) for level in skin.levels: print(level.display_name) diff --git a/examples/basic2.py b/examples/basic2.py index de94987..4b2ceb0 100644 --- a/examples/basic2.py +++ b/examples/basic2.py @@ -3,32 +3,35 @@ import valorant -async def main(): - client = valorant.Client(valorant.Locale.american_english) # set default locale to thai +async def main() -> None: + client = valorant.Client() async with client: # agent - for agent in client.agents: - print(agent.display_name) # default locale - role = agent.role - print(role.display_name) - print(role.display_icon) + agents = await client.fetch_agents() + for agent in agents: + print(agent.display_name) + # role = agent.role + # print(role.display_name) + # print(role.display_icon) print(agent.display_icon) print(agent.display_icon_small) print(agent.full_portrait_v2) - grenade = agent.get_ability(valorant.AbilitySlot.grenade) - if grenade is not None: - print(grenade.display_name) + # grenade = agent.get_ability(valorant.AbilitySlot.grenade) + # if grenade is not None: + # print(grenade.display_name) # buddy - for buddy in client.buddies: + buddies = await client.fetch_buddies() + for buddy in buddies: print(buddy.display_name) print(buddy.display_icon) print(buddy.levels) # player card - for player_card in client.player_cards: + player_cards = await client.fetch_player_cards() + for player_card in player_cards: print(player_card.display_name) print(player_card.display_icon) print(player_card.large_art) @@ -36,8 +39,8 @@ async def main(): print(player_card.small_art) # weapon - - for weapon in client.weapons: + weapons = await client.fetch_weapons() + for weapon in weapons: print(weapon.display_name) for skin in weapon.skins: print(skin.display_name) @@ -48,24 +51,26 @@ async def main(): # skin child of weapon - for skin in client.skins: - weapon = skin.parent + skins = await client.fetch_weapon_skins() + for skin in skins: + # weapon = skin.parent print(weapon.display_name, skin.display_name) # skin level child of skin - for level in client.skin_levels: - skin = level.parent - weapon = skin.parent - print(skin.display_name, str(level.display_name).strip()) + skin_levels = await client.fetch_weapon_skin_levels() + for level in skin_levels: + # skin = level.parent + # weapon = skin.parent + # print(skin.display_name, str(level.display_name).strip()) print(level.display_icon) # skin chroma child of skin - - for chroma in client.skin_chromas: - skin = chroma.parent - weapon = skin.parent - print(weapon.display_name, skin.display_name, str(chroma.display_name).strip()) + skin_chromas = await client.fetch_weapon_skin_chromas() + for chroma in skin_chromas: + # skin = chroma.parent + # weapon = skin.parent + # print(weapon.display_name, skin.display_name, str(chroma.display_name).strip()) print(chroma.display_icon) diff --git a/examples/http_client.py b/examples/http_client.py index 571d111..2878738 100644 --- a/examples/http_client.py +++ b/examples/http_client.py @@ -3,7 +3,7 @@ from valorant.http import HTTPClient -async def main(): +async def main() -> None: http = HTTPClient() await http.init() diff --git a/pyproject.toml b/pyproject.toml index c131699..f076118 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,77 +1,132 @@ -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" - [project] name = "valorant.py" description = "A modern, easy to use, feature-rich, and async ready API wrapper for Valorant API written in Python." -readme = { file = "README.md", content-type = "text/markdown" } -requires-python = ">=3.8" -license = "MIT" +readme = "README.md" +requires-python = ">=3.10" authors = [{ name = "STACiA", email = "stacia.dev@gmail.com" }] classifiers = [ - 'License :: OSI Approved :: MIT License', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python", + "Development Status :: 4 - Beta", + "Typing :: Typed", + "Framework :: AsyncIO", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Internet :: WWW/HTTP", +] +dependencies = ["aiohttp>=3.11.11,<4.0", "pydantic>2.0,<3.0"] +dynamic = ["version"] + +[dependency-groups] +dev = [ + "mypy>=1.14.0,<1.15", + "ruff>=0.8.4,<0.9", + "pytest>=8.3.4,<9.0", + "pytest-cov>=6.0.0,<7.0", + "coverage>=7.6.10,<8.0", + "anyio>=4.7.0,<5.0", ] -dynamic = ["version", "dependencies"] + +[tool.setuptools.dynamic] +version = { attr = "valorant.__version__" } [project.urls] homepage = "https://github.com/staciax/valorant" repository = "https://github.com/staciax/valorant" issues = "https://github.com/staciax/valorant/issues" -[tool.setuptools.dynamic] -dependencies = { file = "requirements.txt" } - -[tool.pyright] -include = ["valorant", "valorant/models", "valorant/types"] -exclude = ["**/__pycache__", "build", "dist"] -pythonVersion = "3.8" -typeCheckingMode = "basic" -reportUnnecessaryTypeIgnoreComment = "warning" -reportMissingParameterType = "error" -reportMissingTypeArgument = "error" -reportUnusedVariable = "warning" +[tool.mypy] +strict = true +python_version = "3.10" +exclude = [".venv", "build"] [tool.ruff] line-length = 120 -target-version = "py38" -exclude = [".git", ".ruff_cache", ".venv"] +target-version = "py310" +exclude = [ + ".git", + ".ruff_cache", + ".mypy_cache", + ".pytest_cache", + ".venv", + "dist", + "build", +] + +[tool.ruff.format] +preview = true +quote-style = "single" +skip-magic-trailing-comma = false [tool.ruff.lint] preview = true select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "UP", # pyupgrade, - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "TCH", # flake8-type-checking - "LOG", # flake8-logging + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade + "D", # pydocstyle + + "YTT", # flake8-2020 + "ANN", # flake8-annotations + "ASYNC", # flake8-async + "S", # flake8-bandit + "BLE", # flake8-blind-except + "A", # flake8-builtins + "COM", # flake8-commas + # "DTZ", # flake8-datetimez + "FA", # flake8-future-annotations + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "G", # flake8-logging-format + "PIE", # flake8-pie + "PYI", # flake8-pyi + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RSE", # flake8-raise + "RET", # flake8-return + "SIM", # flake8-simplify + "TC", # flake8-type-checking + "ARG", # flake8-unused-arguments + + "PGH", # pygrep-hooks + "PL", # pylint + "TRY", # tryceratops + "PERF", # perflint + "FURB", # refurb + + "RUF", # ruff-specific rules ] ignore = [ - "E501", # line-too-long + "ANN401", # any-type + "COM812", # trailing-comma-missing, conflicts with black + "COM819", # prohibited-trailing-comma + "E501", # line-too-long, handled by black + "ISC001", # single-line-implicit-string-concatenation, conflicts with black + "S101", # assert, handled by pytest + "SIM117", # multiple-with-statements + + "D100", # undocumented-public-module + "D101", # undocumented-public-class + "D102", # undocumented-public-method + "D103", # undocumented-public-function + "D104", # undocumented-public-package + "D105", # undocumented-magic-method + "D106", # undocumented-public-nested-class + "D107", # undocumented-public-init ] -[tool.ruff.lint.per-file-ignores] -"valorant/models/maps.py" = ["UP031"] -"valorant/models/seasons.py" = ["UP031"] -"valorant/models/weapons.py" = ["UP031"] -"valorant/models/version.py" = ["UP031"] - -[tool.ruff.format] -quote-style = "single" -skip-magic-trailing-comma = false [tool.ruff.lint.isort] combine-as-imports = true @@ -83,12 +138,32 @@ convention = "numpy" # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true +[tool.ruff.lint.flake8-quotes] +inline-quotes = "single" + +[tool.ruff.lint.flake8-type-checking] +quote-annotations = true +runtime-evaluated-base-classes = [ + "pydantic.BaseModel", + "valorant.models.base.BaseModel", +] + +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true + +[tool.ruff.lint.per-file-ignores] +"valorant/*" = [ + "PLR0904", # too-many-public-methods +] +"valorant/http.py" = [ + "A005", # builtin-module-shadowing + "PLR2004", # magic-value-comparison +] + [tool.pytest.ini_options] -asyncio_mode = "strict" -addopts = "-v --showlocals --tb=short --strict -p no:warnings --cov=valorant --cov-report term-missing" +addopts = "-vv --showlocals --tb=short --strict -p no:warnings --cov=valorant" [tool.coverage.report] -exclude_also = [ - "if TYPE_CHECKING:", - "def __repr__", -] \ No newline at end of file +show_missing = true +precision = 2 +exclude_also = ["if TYPE_CHECKING:", "def __repr__"] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index cb5e2ba..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -pytest==8.1.1 -pytest-asyncio==0.23.6 -pytest-cov==5.0.0 -ruff==0.3.7 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 35515c2..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -aiohttp>=3.8.5,<4 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index af3861b..0000000 --- a/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -import re - -from setuptools import setup - -version = '' -with open('valorant/__init__.py') as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) # type: ignore - -setup(version=version) diff --git a/tests/conftest.py b/tests/conftest.py index eb1be60..0263074 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,52 +1,21 @@ from __future__ import annotations -import asyncio +from typing import TYPE_CHECKING, Any import pytest -import pytest_asyncio -import valorant from valorant import Client -try: - import uvloop # type: ignore -except ImportError: - pass -else: - uvloop.install() +if TYPE_CHECKING: + from collections.abc import AsyncGenerator @pytest.fixture(scope='session') -def event_loop(): - try: - loop = asyncio.get_running_loop() - except RuntimeError: - # policy = asyncio.get_event_loop_policy() - # loop = policy.new_event_loop() - loop = asyncio.new_event_loop() - yield loop - loop.close() - - -@pytest_asyncio.fixture(scope='class') -def client_class(request) -> None: - client = Client(locale=valorant.Locale.american_english) - request.cls.client = client - - -@pytest.mark.usefixtures('client_class') -class BaseTest: - client: Client - - def test_client(self) -> None: - assert self.client is not None - assert self.client.is_ready() is False - - -class BaseAuthTest(BaseTest): - @pytest.mark.asyncio - async def test_authorize(self) -> None: - assert self.client is not None - assert self.client.is_ready() is False - await self.client.wait_until_ready() - assert self.client.is_ready() is True +def anyio_backend() -> Any: + return 'asyncio' + + +@pytest.fixture(scope='session') +async def client() -> AsyncGenerator[Client]: + async with Client() as client: + yield client diff --git a/tests/test_client.py b/tests/test_client.py index d44c5c0..9c196c4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,35 +1,394 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import pytest -from .conftest import BaseTest +from valorant.errors import NotFound + +if TYPE_CHECKING: + from valorant import Client + + +@pytest.mark.anyio +async def test_agents(client: Client) -> None: + agents = await client.fetch_agents() + assert len(agents) > 0 + + agent_id = agents[0].uuid + agent = await client.fetch_agent(agent_id) + assert agent is not None + assert agent_id == agent.uuid + + with pytest.raises(NotFound): + await client.fetch_agent('fake-agent-id') + + +@pytest.mark.anyio +async def test_buddies(client: Client) -> None: + buddies = await client.fetch_buddies() + assert len(buddies) > 0 + + buddy_id = buddies[0].uuid + buddy = await client.fetch_buddy(buddy_id) + assert buddy is not None + assert buddy_id == buddy.uuid + + with pytest.raises(NotFound): + await client.fetch_buddy('fake-buddy-id') + + +@pytest.mark.anyio +async def test_buddy_levels(client: Client) -> None: + buddy_levels = await client.fetch_buddy_levels() + assert len(buddy_levels) > 0 + + buddy_level_id = buddy_levels[0].uuid + buddy_level = await client.fetch_buddy_level(buddy_level_id) + assert buddy_level is not None + assert buddy_level_id == buddy_level.uuid + + with pytest.raises(NotFound): + await client.fetch_buddy_level('fake-buddy-level-id') + + +@pytest.mark.anyio +async def test_bundles(client: Client) -> None: + bundles = await client.fetch_bundles() + assert len(bundles) > 0 + + bundle_id = bundles[0].uuid + bundle = await client.fetch_bundle(bundle_id) + assert bundle is not None + assert bundle_id == bundle.uuid + + with pytest.raises(NotFound): + await client.fetch_bundle('fake-bundle-id') + + +@pytest.mark.anyio +async def test_ceremonies(client: Client) -> None: + ceremonies = await client.fetch_ceremonies() + assert len(ceremonies) > 0 + + ceremony_id = ceremonies[0].uuid + ceremony = await client.fetch_ceremony(ceremony_id) + assert ceremony is not None + assert ceremony_id == ceremony.uuid + + with pytest.raises(NotFound): + await client.fetch_ceremony('fake-ceremony-id') + + +@pytest.mark.anyio +async def test_competitive_tiers(client: Client) -> None: + competitive_tiers = await client.fetch_competitive_tiers() + assert len(competitive_tiers) > 0 + + competitive_tier_id = competitive_tiers[0].uuid + competitive_tier = await client.fetch_competitive_tier(competitive_tier_id) + assert competitive_tier is not None + assert competitive_tier_id == competitive_tier.uuid + + with pytest.raises(NotFound): + await client.fetch_competitive_tier('fake-competitive-tier-id') + + +@pytest.mark.anyio +async def test_content_tiers(client: Client) -> None: + content_tiers = await client.fetch_content_tiers() + assert len(content_tiers) > 0 + + content_tier_id = content_tiers[0].uuid + content_tier = await client.fetch_content_tier(content_tier_id) + assert content_tier is not None + assert content_tier_id == content_tier.uuid + + with pytest.raises(NotFound): + await client.fetch_content_tier('fake-content-tier-id') + + +@pytest.mark.anyio +async def test_contracts(client: Client) -> None: + contracts = await client.fetch_contracts() + assert len(contracts) > 0 + + contract_id = contracts[0].uuid + contract = await client.fetch_contract(contract_id) + assert contract is not None + assert contract_id == contract.uuid + + with pytest.raises(NotFound): + await client.fetch_contract('fake-contract-id') + + +@pytest.mark.anyio +async def test_currencies(client: Client) -> None: + currencies = await client.fetch_currencies() + assert len(currencies) > 0 + + currency_id = currencies[0].uuid + currency = await client.fetch_currency(currency_id) + assert currency is not None + assert currency_id == currency.uuid + + with pytest.raises(NotFound): + await client.fetch_currency('fake-currency-id') + + +@pytest.mark.anyio +async def test_events(client: Client) -> None: + events = await client.fetch_events() + assert len(events) > 0 + + event_id = events[0].uuid + event = await client.fetch_event(event_id) + assert event is not None + assert event_id == event.uuid + + with pytest.raises(NotFound): + await client.fetch_event('fake-event-id') + + +@pytest.mark.anyio +async def test_game_modes(client: Client) -> None: + game_modes = await client.fetch_game_modes() + assert len(game_modes) > 0 + + game_mode_id = game_modes[0].uuid + game_mode = await client.fetch_game_mode(game_mode_id) + assert game_mode is not None + assert game_mode_id == game_mode.uuid + + with pytest.raises(NotFound): + await client.fetch_game_mode('fake-game-mode-id') + + +@pytest.mark.anyio +async def test_game_mode_equippables(client: Client) -> None: + game_mode_equippables = await client.fetch_game_mode_equippables() + assert len(game_mode_equippables) > 0 + + game_mode_equippable_id = game_mode_equippables[0].uuid + game_mode_equippable = await client.fetch_game_mode_equippable(game_mode_equippable_id) + assert game_mode_equippable is not None + assert game_mode_equippable_id == game_mode_equippable.uuid + + with pytest.raises(NotFound): + await client.fetch_game_mode_equippable('fake-game-mode-equippable-id') + + +@pytest.mark.anyio +async def test_gears(client: Client) -> None: + gears = await client.fetch_gears() + assert len(gears) > 0 + + gear_id = gears[0].uuid + gear = await client.fetch_gear(gear_id) + assert gear is not None + assert gear_id == gear.uuid + + with pytest.raises(NotFound): + await client.fetch_gear('fake-gear-id') + + +@pytest.mark.anyio +async def test_level_borders(client: Client) -> None: + level_borders = await client.fetch_level_borders() + assert len(level_borders) > 0 + + level_border_id = level_borders[0].uuid + level_border = await client.fetch_level_border(level_border_id) + assert level_border is not None + assert level_border_id == level_border.uuid + + with pytest.raises(NotFound): + await client.fetch_level_border('fake-level-border-id') + + +@pytest.mark.anyio +async def test_maps(client: Client) -> None: + maps = await client.fetch_maps() + assert len(maps) > 0 + + map_id = maps[0].uuid + map_ = await client.fetch_map(map_id) + assert map_ is not None + assert map_id == map_.uuid + + with pytest.raises(NotFound): + await client.fetch_map('fake-map-id') + + +@pytest.mark.anyio +async def test_missions(client: Client) -> None: + missions = await client.fetch_missions() + assert len(missions) > 0 + + mission_id = missions[0].uuid + mission = await client.fetch_mission(mission_id) + assert mission is not None + assert mission_id == mission.uuid + + with pytest.raises(NotFound): + await client.fetch_mission('fake-mission-id') + + +@pytest.mark.anyio +async def test_player_cards(client: Client) -> None: + player_cards = await client.fetch_player_cards() + assert len(player_cards) > 0 + + player_card_id = player_cards[0].uuid + player_card = await client.fetch_player_card(player_card_id) + assert player_card is not None + assert player_card_id == player_card.uuid + + with pytest.raises(NotFound): + await client.fetch_player_card('fake-player-card-id') + + +@pytest.mark.anyio +async def test_player_titles(client: Client) -> None: + player_titles = await client.fetch_player_titles() + assert len(player_titles) > 0 + + player_title_id = player_titles[0].uuid + player_title = await client.fetch_player_title(player_title_id) + assert player_title is not None + assert player_title_id == player_title.uuid + + with pytest.raises(NotFound): + await client.fetch_player_title('fake-player-title-id') + + +@pytest.mark.anyio +async def test_seasons(client: Client) -> None: + seasons = await client.fetch_seasons() + assert len(seasons) > 0 + + season_id = seasons[0].uuid + season = await client.fetch_season(season_id) + assert season is not None + assert season_id == season.uuid + + with pytest.raises(NotFound): + await client.fetch_season('fake-season-id') + + +@pytest.mark.anyio +async def test_competitive_seasons(client: Client) -> None: + competitive_seasons = await client.competitive_seasons() + assert len(competitive_seasons) > 0 + + competitive_season_id = competitive_seasons[0].uuid + competitive_season = await client.competitive_season(competitive_season_id) + assert competitive_season is not None + assert competitive_season_id == competitive_season.uuid + + with pytest.raises(NotFound): + await client.competitive_season('fake-competitive-season-id') + + +@pytest.mark.anyio +async def test_sprays(client: Client) -> None: + sprays = await client.fetch_sprays() + assert len(sprays) > 0 + + spray_id = sprays[0].uuid + spray = await client.fetch_spray(spray_id) + assert spray is not None + assert spray_id == spray.uuid + + with pytest.raises(NotFound): + await client.fetch_spray('fake-spray-id') + + +@pytest.mark.anyio +async def test_spray_levels(client: Client) -> None: + spray_levels = await client.fetch_spray_levels() + assert len(spray_levels) > 0 + + spray_level_id = spray_levels[0].uuid + spray_level = await client.fetch_spray_level(spray_level_id) + assert spray_level is not None + assert spray_level_id == spray_level.uuid + + with pytest.raises(NotFound): + await client.fetch_spray_level('fake-spray-level-id') + + +@pytest.mark.anyio +async def test_themes(client: Client) -> None: + themes = await client.fetch_themes() + assert len(themes) > 0 + + theme_id = themes[0].uuid + theme = await client.fetch_theme(theme_id) + assert theme is not None + assert theme_id == theme.uuid + + with pytest.raises(NotFound): + await client.fetch_theme('fake-theme-id') + + +@pytest.mark.anyio +async def test_weapons(client: Client) -> None: + weapons = await client.fetch_weapons() + assert len(weapons) > 0 + + weapon_id = weapons[0].uuid + weapon = await client.fetch_weapon(weapon_id) + assert weapon is not None + assert weapon_id == weapon.uuid + + with pytest.raises(NotFound): + await client.fetch_weapon('fake-weapon-id') + + +@pytest.mark.anyio +async def test_weapon_skins(client: Client) -> None: + weapon_skins = await client.fetch_weapon_skins() + assert len(weapon_skins) > 0 + + weapon_skin_id = weapon_skins[0].uuid + weapon_skin = await client.fetch_weapon_skin(weapon_skin_id) + assert weapon_skin is not None + assert weapon_skin_id == weapon_skin.uuid + + with pytest.raises(NotFound): + await client.fetch_weapon_skin('fake-weapon-skin-id') + + +@pytest.mark.anyio +async def test_weapon_skin_chromas(client: Client) -> None: + weapon_skin_chromas = await client.fetch_weapon_skin_chromas() + assert len(weapon_skin_chromas) > 0 + weapon_skin_chroma_id = weapon_skin_chromas[0].uuid + weapon_skin_chroma = await client.fetch_weapon_skin_chroma(weapon_skin_chroma_id) + assert weapon_skin_chroma is not None + assert weapon_skin_chroma_id == weapon_skin_chroma.uuid -class TestInGameAPI(BaseTest): - @pytest.mark.asyncio - async def test_client_init(self) -> None: - assert self.client is not None - await self.client.init() + with pytest.raises(NotFound): + await client.fetch_weapon_skin_chroma('fake-weapon-skin-chroma-id') - @pytest.mark.asyncio - async def test_client(self) -> None: - assert self.client is not None - assert self.client.is_ready() is True - assert self.client.is_closed() is False - for key in self.client.cache.__dict__.keys(): - if key.startswith('_') and isinstance(self.client.cache.__dict__[key], dict): - assert len(self.client.cache.__dict__[key]) > 0 +@pytest.mark.anyio +async def test_weapon_skin_levels(client: Client) -> None: + weapon_skin_levels = await client.fetch_weapon_skin_levels() + assert len(weapon_skin_levels) > 0 - await self.client.close() - assert self.client.is_closed() is True - self.client.clear() + weapon_skin_level_id = weapon_skin_levels[0].uuid + weapon_skin_level = await client.fetch_weapon_skin_level(weapon_skin_level_id) + assert weapon_skin_level is not None + assert weapon_skin_level_id == weapon_skin_level.uuid - for key in self.client.cache.__dict__.keys(): - if key.startswith('_') and isinstance(self.client.cache.__dict__[key], dict): - assert len(self.client.cache.__dict__[key]) == 0 + with pytest.raises(NotFound): + await client.fetch_weapon_skin_level('fake-weapon-skin-level-id') - assert self.client.is_ready() is False - assert self.client.is_closed() is False - @pytest.mark.asyncio - async def test_close(self) -> None: - await self.client.close() +@pytest.mark.anyio +async def test_version(client: Client) -> None: + version = await client.fetch_version() + assert version is not None diff --git a/tests/test_models.py b/tests/test_models.py deleted file mode 100644 index 0859e9f..0000000 --- a/tests/test_models.py +++ /dev/null @@ -1,574 +0,0 @@ -import datetime - -import pytest - -from valorant import AbilitySlot, Agent, Event, RelationType, RewardType, Season -from valorant.utils import MISSING - -from .conftest import BaseTest - - -class TestValorantAPI(BaseTest): - @pytest.mark.asyncio - async def test_fetch_assets(self) -> None: - await self.client.init() - - @pytest.mark.asyncio - async def test_agents(self) -> None: - assert len(self.client.agents) > 0 - for agent in self.client.agents: - # fagent = await self.client.fetch_agent(agent.uuid) - fagent = self.client.get_agent(str(agent.uuid)) - assert fagent is not None - assert fagent == agent - - assert agent.display_name is not None - assert agent.description is not None - assert agent.display_icon is not None - assert agent.bust_portrait is not None - assert agent.full_portrait is not None - assert agent.full_portrait_v2 is not None - assert agent.killfeed_portrait is not None - assert agent.background is not None - - assert agent.role is not None - assert agent.role.display_name is not None - assert agent.role.description is not None - assert agent.role.display_icon is not None - - assert agent.abilities is not None - assert isinstance(agent.abilities, list) - assert len(agent.abilities) >= 4 or len(agent.abilities) <= 5 - for ability in agent.abilities: - assert ability is not None - assert ability.slot is not None - assert isinstance(ability.slot, AbilitySlot) - assert ability.display_name is not None - assert ability.description is not None - if ability.display_icon: - assert ability.display_icon is not None - - if agent.voice_line_localization is not None: - voice_line = agent.voice_line - if voice_line is not None: - assert agent.voice_line_localization.voice_locale == voice_line - for voice in agent.voice_line_localization.all(): - assert voice is not None - assert voice.min_duration is not None - assert voice.max_duration is not None - assert voice.media_list is not None - for media in voice.media_list: - assert media is not None - assert media.id is not None - assert media.wwise is not None - assert media.wave is not None - - @pytest.mark.asyncio - async def test_buddies(self) -> None: - assert len(self.client.buddies) > 0 - for buddy in self.client.buddies: - assert buddy is not None - # fbuddy = await self.client.fetch_buddy(buddy.uuid) - fbuddy = self.client.get_buddy(str(buddy.uuid)) - assert fbuddy is not None - assert fbuddy == buddy - assert buddy is not None - assert buddy.display_name is not None - assert isinstance(buddy.is_hidden_if_not_owned(), bool) - if buddy.theme_uuid is not None: - assert buddy.theme is not None - assert buddy.display_icon is not None - assert buddy.asset_path is not None - assert len(buddy.levels) > 0 - assert buddy.get_buddy_level(1) is not None - - for level in buddy.levels: - assert level is not None - assert level.parent is not None - assert level.charm_level is not None - assert level.display_name is not None - assert level.display_icon is not None - assert level.asset_path is not None - - @pytest.mark.asyncio - async def test_bundles(self) -> None: - assert len(self.client.bundles) > 0 - for bundle in self.client.bundles: - # fbundle = await self.client.fetch_bundle(bundle.uuid) - fbundle = self.client.get_bundle(str(bundle.uuid)) - assert fbundle is not None - assert fbundle == bundle - - assert bundle is not None - assert bundle.display_name is not None - assert bundle.description is not None - if bundle.display_name_sub_text: - assert bundle.display_name_sub_text is not None - if bundle.extra_description: - assert bundle.extra_description is not None - if bundle.promo_description: - assert bundle.promo_description is not None - assert isinstance(bundle.use_additional_context, bool) - assert bundle.display_icon is not None - assert bundle.display_icon_2 is not None - if bundle.vertical_promo_image: - assert bundle.vertical_promo_image is not None - assert bundle.asset_path is not None - - @pytest.mark.asyncio - async def test_ceremonies(self) -> None: - assert len(self.client.ceremonies) > 0 - for ceremony in self.client.ceremonies: - assert ceremony is not None - assert ceremony.display_name is not None - assert ceremony.asset_path is not None - - @pytest.mark.asyncio - async def test_competitive_tiers(self) -> None: - assert len(self.client.competitive_tiers) > 0 - for c_tier in self.client.competitive_tiers: - assert c_tier is not None - assert c_tier.asset_path is not None - assert len(c_tier.tiers) > 0 - for tier in c_tier.tiers: - assert tier is not None - assert tier.tier is not None - assert tier.name is not None - assert tier.division is not None - assert tier.division_name is not None - assert tier.color is not None - assert tier.background_color is not None - if tier.small_icon: - assert tier.small_icon is not None - if tier.large_icon: - assert tier.large_icon is not None - if tier.rank_triangle_down_icon: - assert tier.rank_triangle_down_icon is not None - if tier.rank_triangle_up_icon: - assert tier.rank_triangle_up_icon is not None - - @pytest.mark.asyncio - async def test_content_tiers(self) -> None: - assert len(self.client.content_tiers) > 0 - for content_tier in self.client.content_tiers: - assert content_tier is not None - assert content_tier.display_name is not None - assert content_tier.dev_name is not None - assert content_tier.rank is not None - assert content_tier.juice_value is not None - assert content_tier.juice_cost is not None - assert content_tier.highlight_color is not None - assert content_tier.display_icon is not None - assert content_tier.asset_path is not None - - @pytest.mark.asyncio - async def test_contracts(self) -> None: - assert len(self.client.contracts) > 0 - for contract in self.client.contracts: - assert contract is not None - assert contract.display_name is not None - if contract.display_icon: - assert contract.display_icon is not None - assert isinstance(contract.ship_it, bool) - assert contract.free_reward_schedule_uuid is not None - assert contract.asset_path is not None - content = contract.content - assert content is not None - assert isinstance(content.relation_type, RelationType) - assert content.premium_vp_cost is not None - # assert content.premium_reward_schedule_uuid is not None - if content._relation_uuid is None: - assert content.relationship is None - if content.relationship is not None: - assert isinstance(content.relationship, (Agent, Event, Season)) - assert len(content.chapters) > 0 - for chapter in content.chapters: - assert chapter is not None - assert isinstance(chapter.is_epilogue(), bool) - assert len(chapter.levels) > 0 - for level in chapter.levels: - assert level is not None - assert level.reward is not None - reward = level.reward - assert reward.type is not None - assert isinstance(reward.type, RewardType) - assert reward.amount is not None - assert isinstance(reward.amount, int) - item = reward.get_item() - assert item is not None # if item is None, maybe game is updated new reward type - assert isinstance(level.xp, int) - assert isinstance(level.vp_cost, int) - assert isinstance(level.dough_cost, int) - assert isinstance(level.is_purchasable_with_vp(), bool) - assert isinstance(level.is_purchasable_with_dough(), bool) - - if chapter.free_rewards is not None: - assert len(chapter.free_rewards) > 0 - for reward in chapter.free_rewards: - assert reward is not None - assert reward.type is not None - assert isinstance(reward.type, RewardType) - assert reward.amount is not None - assert isinstance(reward.amount, int) - item_free = reward.get_item() - assert item_free is not None # if item is None, maybe game is updated new reward type - - @pytest.mark.asyncio - async def test_currencies(self) -> None: - assert len(self.client.currencies) > 0 - for currency in self.client.currencies: - assert currency is not None - assert currency.display_name is not None - assert currency.display_name_singular is not None - assert currency.display_icon is not None - assert currency.large_icon is not None - assert currency.asset_path is not None - - @pytest.mark.asyncio - async def test_events(self) -> None: - assert len(self.client.events) > 0 - for event in self.client.events: - assert event is not None - assert event.display_name is not None - assert event.short_display_name is not None - assert event.start_time is not None - assert isinstance(event.start_time, datetime.datetime) - assert event.end_time is not None - assert isinstance(event.end_time, datetime.datetime) - assert event.asset_path is not None - - @pytest.mark.asyncio - async def test_gamemodes(self) -> None: - assert len(self.client.game_modes) > 0 - for gm in self.client.game_modes: - assert gm is not None - assert gm.display_name is not None - assert isinstance(gm.allows_match_timeouts, bool) - assert isinstance(gm.is_team_voice_allowed(), bool) - assert isinstance(gm.is_minimap_hidden(), bool) - assert gm.orb_count is not None - assert isinstance(gm.orb_count, int) - assert gm.rounds_per_half is not None - assert isinstance(gm.rounds_per_half, int) - team_roles = gm.team_roles - if team_roles is not None: - assert isinstance(team_roles, list) - assert len(team_roles) > 0 - for tr in team_roles: - assert tr is not None - assert isinstance(tr, str) - feature_overrides = gm.game_feature_overrides - if feature_overrides is not None: - assert len(feature_overrides) > 0 - for fo in feature_overrides: - assert fo is not None - assert fo.feature_name is not None - assert isinstance(fo.state, bool) - game_rule_bool_overrides = gm.game_rule_bool_overrides - if game_rule_bool_overrides is not None: - assert len(game_rule_bool_overrides) > 0 - for gro in game_rule_bool_overrides: - assert gro is not None - assert gro.rule_name is not None - assert isinstance(gro.state, bool) - if gm.display_icon: - assert gm.display_icon is not None - assert gm.asset_path is not None - - @pytest.mark.asyncio - async def test_gamemode_equippables(self) -> None: - assert len(self.client.game_mode_equippables) > 0 - for gme in self.client.game_mode_equippables: - assert gme is not None - assert gme.display_name is not None - assert gme.display_icon is not None - assert gme.kill_stream_icon is not None - assert gme.asset_path is not None - # weapon = gme.get_weapon() - - @pytest.mark.asyncio - async def test_gear(self) -> None: - assert len(self.client.gear) > 0 - for gear in self.client.gear: - assert gear is not None - assert gear.display_name is not None - assert gear.display_icon is not None - shop_data = gear.shop_data - assert shop_data is not None - assert shop_data.cost is not None - assert shop_data.category is not None - assert shop_data.category_text is not None - grid_position = shop_data.grid_position - if grid_position is not None: - assert grid_position.row is not None - assert grid_position.column is not None - assert shop_data.can_be_trashed is not None - assert isinstance(shop_data.can_be_trashed, bool) - if shop_data.new_image: - assert shop_data.new_image is not None - assert shop_data.new_image is not None - if shop_data.new_image_2: - assert shop_data.new_image_2 is not None - assert shop_data.asset_path is not None - assert gear.asset_path is not None - - @pytest.mark.asyncio - async def test_level_borders(self) -> None: - assert len(self.client.level_borders) > 0 - for border in self.client.level_borders: - assert border is not None - assert border.starting_level is not None - assert border.level_number_appearance is not None - assert border.small_player_card_appearance is not None - assert border.asset_path is not None - - @pytest.mark.asyncio - async def test_maps(self) -> None: - assert len(self.client.maps) > 0 - for mp in self.client.maps: - assert mp is not None - assert mp.display_name is not None - assert mp.coordinates is not None - assert mp.display_name is not None - assert mp.list_view_icon is not None - assert mp.asset_path is not None - assert mp.url is not None - assert mp.x_multiplier is not None - assert mp.y_multiplier is not None - assert mp.x_scalar_to_add is not None - assert mp.y_scalar_to_add is not None - callouts = mp.callouts - if callouts is not None: - for callout in callouts: - assert callout.region_name is not None - assert callout.super_region_name is not None - assert callout.location is not None - assert callout.location.x is not None - assert callout.location.y is not None - - @pytest.mark.asyncio - async def test_missions(self) -> None: - assert len(self.client.missions) > 0 - for mission in self.client.missions: - assert mission is not None - if mission.display_name: - assert mission.display_name is not None - if mission.title: - assert mission.title is not None - if mission.type: - assert mission.type is not None - assert mission.xp_grant is not None - assert mission.progress_to_complete is not None - assert mission.activation_date is not None - assert mission.expiration_date is not None - if mission.tags is not None: - assert len(mission.tags) > 0 - if mission.objectives: - assert len(mission.objectives) > 0 - for objective in mission.objectives: - assert objective is not None - assert objective.value is not None - assert isinstance(objective.value, int) - assert mission.asset_path is not None - - @pytest.mark.asyncio - async def test_player_cards(self) -> None: - assert len(self.client.player_cards) > 0 - for card in self.client.player_cards: - assert card is not None - assert card.display_name is not None - assert card.display_icon is not None - assert card.asset_path is not None - assert isinstance(card.is_hidden_if_not_owned(), bool) - assert card.small_art is not None - assert card.wide_art is not None - assert card.large_art is not None - if card.theme_uuid is not None: - assert card.theme is not None - - @pytest.mark.asyncio - async def test_player_titles(self) -> None: - assert len(self.client.player_titles) > 0 - for title in self.client.player_titles: - assert title is not None - assert title.display_name is not None - assert title.text is not None - assert isinstance(title.is_hidden_if_not_owned(), bool) - assert title.asset_path is not None - - @pytest.mark.asyncio - async def test_seasons(self) -> None: - assert len(self.client.seasons) > 0 - for season in self.client.seasons: - assert season is not None - assert season.display_name is not None - if season.type is not None: - assert season.type.lower() == 'earesseasontype::act' - assert season.start_time is not None - assert season.end_time is not None - if season.parent_uuid is not None: - assert season.parent is not None - assert season.asset_path is not None - - @pytest.mark.asyncio - async def test_competitive_seasons(self) -> None: - assert len(self.client.competitive_seasons) > 0 - for cs in self.client.competitive_seasons: - assert cs is not None - assert cs.start_time is not None - assert cs.end_time is not None - assert cs.season_uuid is not None - assert cs.competitive_tiers_uuid is not None - if cs.borders is not None: - for border in cs.borders: - assert border is not None - assert border.level is not None - assert isinstance(border.level, int) - assert border.wins_required is not None - assert isinstance(border.wins_required, int) - assert border.display_icon is not None - if border.small_icon: - assert border.small_icon is not None - assert border.asset_path is not None - assert cs.asset_path is not None - - @pytest.mark.asyncio - async def test_sprays(self) -> None: - assert len(self.client.sprays) > 0 - for spray in self.client.sprays: - assert spray is not None - assert spray.display_name is not None - if spray.category: - assert spray.category is not None - if spray.theme_uuid is not None: - assert spray.theme is not None - assert isinstance(spray.is_null_spray(), bool) - assert spray.display_icon is not None - if spray.full_icon: - assert spray.full_icon is not None - if spray.full_transparent_icon: - assert spray.full_transparent_icon is not None - if spray.animation_png: - assert spray.animation_png is not None - if spray.animation_gif: - # TODO: Asset support gif - assert spray.animation_gif is not None - assert spray.asset_path is not None - for level in spray.levels: - assert level is not None - assert level.parent is not None - assert level.spray_level is not None - assert level.display_name is not None - assert level.asset_path is not None - if level.display_icon: - assert level.display_icon is not None - - @pytest.mark.asyncio - async def test_themes(self) -> None: - assert len(self.client.themes) > 0 - for theme in self.client.themes: - assert theme is not None - assert theme.display_name is not None - if theme.display_icon: - assert theme.display_icon is not None - if theme.store_featured_image: - assert theme.store_featured_image is not None - assert theme.asset_path is not None - - @pytest.mark.asyncio - async def test_version(self) -> None: - version = self.client.version - assert version.manifest_id is not None - assert version.branch is not None - assert version.version is not None - assert version.build_version is not None - assert version.engine_version is not None - assert version.riot_client_version is not None - assert version.riot_client_build is not None - assert version.build_date is not None - - @pytest.mark.asyncio - async def test_weapons(self) -> None: - assert len(self.client.weapons) > 0 - for weapon in self.client.weapons: - assert weapon is not None - assert weapon.display_name is not None - assert weapon._default_skin_uuid is not None - assert weapon.display_icon is not None - assert weapon.kill_stream_icon is not None - assert weapon.asset_path is not None - stats = weapon.weapon_stats - if stats: - assert stats is not None - assert stats.fire_rate is not None - assert stats.magazine_size is not None - assert stats.run_speed_multiplier is not None - assert stats.equip_time_seconds is not None - assert stats.reload_time_seconds is not None - assert stats.first_bullet_accuracy is not None - assert stats.wall_penetration is not None - if stats.feature is not None: - assert stats.feature is not None - if stats.fire_mode is not None: - assert stats.fire_mode is not None - if stats.alt_fire_type is not None: - assert stats.alt_fire_type is not None - - # TODO: test all attributes of weapon stats - if weapon.shop_data: - assert weapon.shop_data is not None - # TODO: test all attributes of shop data - for skin in weapon.skins: - assert skin is not None - if skin.theme_uuid is not None: - assert skin.theme is not None - for chroma in skin.chromas: - assert chroma is not None - assert chroma.parent is not None - assert chroma.display_name is not None - if chroma.display_icon: - assert chroma.display_icon is not None - assert chroma.full_render is not None - if chroma.swatch: - assert chroma.swatch is not None - if chroma.streamed_video: - assert chroma.streamed_video is not None - assert chroma.asset_path is not None - - # helper method - if chroma.display_icon_fix: - if skin.theme is not None: - assert chroma.theme is not None - assert chroma.display_icon_fix is not None - - for index, level in enumerate(skin.levels): - assert level is not None - assert level.parent is not None - assert level.display_name is not None - if level.level_item: - assert level.level_item is not None - if level.display_icon: - assert level.display_icon is not None - if level.streamed_video: - assert level.streamed_video is not None - assert level.asset_path is not None - - # helper method - assert level.display_icon_fix is not None - if skin.theme is not None: - assert level.theme is not None - assert level.level_number is not None - assert isinstance(level.is_level_one(), bool) - if index == 0: - assert level.is_level_one() is True - - def test_clear(self) -> None: - self.client.clear() - assert self.client.version is MISSING - for key in self.client.cache.__dict__.keys(): - if key.startswith('_') and isinstance(self.client.cache.__dict__[key], dict): - assert len(self.client.cache.__dict__[key]) == 0 - - @pytest.mark.asyncio - async def test_close(self) -> None: - await self.client.close() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..f5a8cfa --- /dev/null +++ b/uv.lock @@ -0,0 +1,832 @@ +version = 1 +requires-python = ">=3.10" + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/55/e4373e888fdacb15563ef6fa9fa8c8252476ea071e96fb46defac9f18bf2/aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745", size = 21977 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/74/fbb6559de3607b3300b9be3cc64e97548d55678e44623db17820dbd20002/aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8", size = 14756 }, +] + +[[package]] +name = "aiohttp" +version = "3.11.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/ed/f26db39d29cd3cb2f5a3374304c713fe5ab5a0e4c8ee25a0c45cc6adf844/aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e", size = 7669618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/7d/ff2e314b8f9e0b1df833e2d4778eaf23eae6b8cc8f922495d110ddcbf9e1/aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8", size = 708550 }, + { url = "https://files.pythonhosted.org/packages/09/b8/aeb4975d5bba233d6f246941f5957a5ad4e3def8b0855a72742e391925f2/aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5", size = 468430 }, + { url = "https://files.pythonhosted.org/packages/9c/5b/5b620279b3df46e597008b09fa1e10027a39467387c2332657288e25811a/aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2", size = 455593 }, + { url = "https://files.pythonhosted.org/packages/d8/75/0cdf014b816867d86c0bc26f3d3e3f194198dbf33037890beed629cd4f8f/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43", size = 1584635 }, + { url = "https://files.pythonhosted.org/packages/df/2f/95b8f4e4dfeb57c1d9ad9fa911ede35a0249d75aa339edd2c2270dc539da/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f", size = 1632363 }, + { url = "https://files.pythonhosted.org/packages/39/cb/70cf69ea7c50f5b0021a84f4c59c3622b2b3b81695f48a2f0e42ef7eba6e/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d", size = 1668315 }, + { url = "https://files.pythonhosted.org/packages/2f/cc/3a3fc7a290eabc59839a7e15289cd48f33dd9337d06e301064e1e7fb26c5/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef", size = 1589546 }, + { url = "https://files.pythonhosted.org/packages/15/b4/0f7b0ed41ac6000e283e7332f0f608d734b675a8509763ca78e93714cfb0/aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438", size = 1544581 }, + { url = "https://files.pythonhosted.org/packages/58/b9/4d06470fd85c687b6b0e31935ef73dde6e31767c9576d617309a2206556f/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3", size = 1529256 }, + { url = "https://files.pythonhosted.org/packages/61/a2/6958b1b880fc017fd35f5dfb2c26a9a50c755b75fd9ae001dc2236a4fb79/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55", size = 1536592 }, + { url = "https://files.pythonhosted.org/packages/0f/dd/b974012a9551fd654f5bb95a6dd3f03d6e6472a17e1a8216dd42e9638d6c/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e", size = 1607446 }, + { url = "https://files.pythonhosted.org/packages/e0/d3/6c98fd87e638e51f074a3f2061e81fcb92123bcaf1439ac1b4a896446e40/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33", size = 1628809 }, + { url = "https://files.pythonhosted.org/packages/a8/2e/86e6f85cbca02be042c268c3d93e7f35977a0e127de56e319bdd1569eaa8/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c", size = 1564291 }, + { url = "https://files.pythonhosted.org/packages/0b/8d/1f4ef3503b767717f65e1f5178b0173ab03cba1a19997ebf7b052161189f/aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745", size = 416601 }, + { url = "https://files.pythonhosted.org/packages/ad/86/81cb83691b5ace3d9aa148dc42bacc3450d749fc88c5ec1973573c1c1779/aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9", size = 442007 }, + { url = "https://files.pythonhosted.org/packages/34/ae/e8806a9f054e15f1d18b04db75c23ec38ec954a10c0a68d3bd275d7e8be3/aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76", size = 708624 }, + { url = "https://files.pythonhosted.org/packages/c7/e0/313ef1a333fb4d58d0c55a6acb3cd772f5d7756604b455181049e222c020/aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538", size = 468507 }, + { url = "https://files.pythonhosted.org/packages/a9/60/03455476bf1f467e5b4a32a465c450548b2ce724eec39d69f737191f936a/aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/be/f9/469588603bd75bf02c8ffb8c8a0d4b217eed446b49d4a767684685aa33fd/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9", size = 1685694 }, + { url = "https://files.pythonhosted.org/packages/88/b9/1b7fa43faf6c8616fa94c568dc1309ffee2b6b68b04ac268e5d64b738688/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03", size = 1743660 }, + { url = "https://files.pythonhosted.org/packages/2a/8b/0248d19dbb16b67222e75f6aecedd014656225733157e5afaf6a6a07e2e8/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287", size = 1785421 }, + { url = "https://files.pythonhosted.org/packages/c4/11/f478e071815a46ca0a5ae974651ff0c7a35898c55063305a896e58aa1247/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e", size = 1675145 }, + { url = "https://files.pythonhosted.org/packages/26/5d/284d182fecbb5075ae10153ff7374f57314c93a8681666600e3a9e09c505/aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665", size = 1619804 }, + { url = "https://files.pythonhosted.org/packages/1b/78/980064c2ad685c64ce0e8aeeb7ef1e53f43c5b005edcd7d32e60809c4992/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b", size = 1654007 }, + { url = "https://files.pythonhosted.org/packages/21/8d/9e658d63b1438ad42b96f94da227f2e2c1d5c6001c9e8ffcc0bfb22e9105/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34", size = 1650022 }, + { url = "https://files.pythonhosted.org/packages/85/fd/a032bf7f2755c2df4f87f9effa34ccc1ef5cea465377dbaeef93bb56bbd6/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d", size = 1732899 }, + { url = "https://files.pythonhosted.org/packages/c5/0c/c2b85fde167dd440c7ba50af2aac20b5a5666392b174df54c00f888c5a75/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2", size = 1755142 }, + { url = "https://files.pythonhosted.org/packages/bc/78/91ae1a3b3b3bed8b893c5d69c07023e151b1c95d79544ad04cf68f596c2f/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773", size = 1692736 }, + { url = "https://files.pythonhosted.org/packages/77/89/a7ef9c4b4cdb546fcc650ca7f7395aaffbd267f0e1f648a436bec33c9b95/aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62", size = 416418 }, + { url = "https://files.pythonhosted.org/packages/fc/db/2192489a8a51b52e06627506f8ac8df69ee221de88ab9bdea77aa793aa6a/aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac", size = 442509 }, + { url = "https://files.pythonhosted.org/packages/69/cf/4bda538c502f9738d6b95ada11603c05ec260807246e15e869fc3ec5de97/aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886", size = 704666 }, + { url = "https://files.pythonhosted.org/packages/46/7b/87fcef2cad2fad420ca77bef981e815df6904047d0a1bd6aeded1b0d1d66/aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2", size = 464057 }, + { url = "https://files.pythonhosted.org/packages/5a/a6/789e1f17a1b6f4a38939fbc39d29e1d960d5f89f73d0629a939410171bc0/aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c", size = 455996 }, + { url = "https://files.pythonhosted.org/packages/b7/dd/485061fbfef33165ce7320db36e530cd7116ee1098e9c3774d15a732b3fd/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a", size = 1682367 }, + { url = "https://files.pythonhosted.org/packages/e9/d7/9ec5b3ea9ae215c311d88b2093e8da17e67b8856673e4166c994e117ee3e/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231", size = 1736989 }, + { url = "https://files.pythonhosted.org/packages/d6/fb/ea94927f7bfe1d86178c9d3e0a8c54f651a0a655214cce930b3c679b8f64/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e", size = 1793265 }, + { url = "https://files.pythonhosted.org/packages/40/7f/6de218084f9b653026bd7063cd8045123a7ba90c25176465f266976d8c82/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8", size = 1691841 }, + { url = "https://files.pythonhosted.org/packages/77/e2/992f43d87831cbddb6b09c57ab55499332f60ad6fdbf438ff4419c2925fc/aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8", size = 1619317 }, + { url = "https://files.pythonhosted.org/packages/96/74/879b23cdd816db4133325a201287c95bef4ce669acde37f8f1b8669e1755/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c", size = 1641416 }, + { url = "https://files.pythonhosted.org/packages/30/98/b123f6b15d87c54e58fd7ae3558ff594f898d7f30a90899718f3215ad328/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab", size = 1646514 }, + { url = "https://files.pythonhosted.org/packages/d7/38/257fda3dc99d6978ab943141d5165ec74fd4b4164baa15e9c66fa21da86b/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da", size = 1702095 }, + { url = "https://files.pythonhosted.org/packages/0c/f4/ddab089053f9fb96654df5505c0a69bde093214b3c3454f6bfdb1845f558/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853", size = 1734611 }, + { url = "https://files.pythonhosted.org/packages/c3/d6/f30b2bc520c38c8aa4657ed953186e535ae84abe55c08d0f70acd72ff577/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e", size = 1694576 }, + { url = "https://files.pythonhosted.org/packages/bc/97/b0a88c3f4c6d0020b34045ee6d954058abc870814f6e310c4c9b74254116/aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600", size = 411363 }, + { url = "https://files.pythonhosted.org/packages/7f/23/cc36d9c398980acaeeb443100f0216f50a7cfe20c67a9fd0a2f1a5a846de/aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d", size = 437666 }, + { url = "https://files.pythonhosted.org/packages/49/d1/d8af164f400bad432b63e1ac857d74a09311a8334b0481f2f64b158b50eb/aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9", size = 697982 }, + { url = "https://files.pythonhosted.org/packages/92/d1/faad3bf9fa4bfd26b95c69fc2e98937d52b1ff44f7e28131855a98d23a17/aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194", size = 460662 }, + { url = "https://files.pythonhosted.org/packages/db/61/0d71cc66d63909dabc4590f74eba71f91873a77ea52424401c2498d47536/aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f", size = 452950 }, + { url = "https://files.pythonhosted.org/packages/07/db/6d04bc7fd92784900704e16b745484ef45b77bd04e25f58f6febaadf7983/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104", size = 1665178 }, + { url = "https://files.pythonhosted.org/packages/54/5c/e95ade9ae29f375411884d9fd98e50535bf9fe316c9feb0f30cd2ac8f508/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff", size = 1717939 }, + { url = "https://files.pythonhosted.org/packages/6f/1c/1e7d5c5daea9e409ed70f7986001b8c9e3a49a50b28404498d30860edab6/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3", size = 1775125 }, + { url = "https://files.pythonhosted.org/packages/5d/66/890987e44f7d2f33a130e37e01a164168e6aff06fce15217b6eaf14df4f6/aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1", size = 1677176 }, + { url = "https://files.pythonhosted.org/packages/8f/dc/e2ba57d7a52df6cdf1072fd5fa9c6301a68e1cd67415f189805d3eeb031d/aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4", size = 1603192 }, + { url = "https://files.pythonhosted.org/packages/6c/9e/8d08a57de79ca3a358da449405555e668f2c8871a7777ecd2f0e3912c272/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d", size = 1618296 }, + { url = "https://files.pythonhosted.org/packages/56/51/89822e3ec72db352c32e7fc1c690370e24e231837d9abd056490f3a49886/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87", size = 1616524 }, + { url = "https://files.pythonhosted.org/packages/2c/fa/e2e6d9398f462ffaa095e84717c1732916a57f1814502929ed67dd7568ef/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2", size = 1685471 }, + { url = "https://files.pythonhosted.org/packages/ae/5f/6bb976e619ca28a052e2c0ca7b0251ccd893f93d7c24a96abea38e332bf6/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12", size = 1715312 }, + { url = "https://files.pythonhosted.org/packages/79/c1/756a7e65aa087c7fac724d6c4c038f2faaa2a42fe56dbc1dd62a33ca7213/aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5", size = 1672783 }, + { url = "https://files.pythonhosted.org/packages/73/ba/a6190ebb02176c7f75e6308da31f5d49f6477b651a3dcfaaaca865a298e2/aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d", size = 410229 }, + { url = "https://files.pythonhosted.org/packages/b8/62/c9fa5bafe03186a0e4699150a7fed9b1e73240996d0d2f0e5f70f3fdf471/aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99", size = 436081 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, +] + +[[package]] +name = "attrs" +version = "24.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.6.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/12/2a2a923edf4ddabdffed7ad6da50d96a5c126dae7b80a33df7310e329a1e/coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78", size = 207982 }, + { url = "https://files.pythonhosted.org/packages/ca/49/6985dbca9c7be3f3cb62a2e6e492a0c88b65bf40579e16c71ae9c33c6b23/coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c", size = 208414 }, + { url = "https://files.pythonhosted.org/packages/35/93/287e8f1d1ed2646f4e0b2605d14616c9a8a2697d0d1b453815eb5c6cebdb/coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a", size = 236860 }, + { url = "https://files.pythonhosted.org/packages/de/e1/cfdb5627a03567a10031acc629b75d45a4ca1616e54f7133ca1fa366050a/coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165", size = 234758 }, + { url = "https://files.pythonhosted.org/packages/6d/85/fc0de2bcda3f97c2ee9fe8568f7d48f7279e91068958e5b2cc19e0e5f600/coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988", size = 235920 }, + { url = "https://files.pythonhosted.org/packages/79/73/ef4ea0105531506a6f4cf4ba571a214b14a884630b567ed65b3d9c1975e1/coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5", size = 234986 }, + { url = "https://files.pythonhosted.org/packages/c6/4d/75afcfe4432e2ad0405c6f27adeb109ff8976c5e636af8604f94f29fa3fc/coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3", size = 233446 }, + { url = "https://files.pythonhosted.org/packages/86/5b/efee56a89c16171288cafff022e8af44f8f94075c2d8da563c3935212871/coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5", size = 234566 }, + { url = "https://files.pythonhosted.org/packages/f2/db/67770cceb4a64d3198bf2aa49946f411b85ec6b0a9b489e61c8467a4253b/coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244", size = 210675 }, + { url = "https://files.pythonhosted.org/packages/8d/27/e8bfc43f5345ec2c27bc8a1fa77cdc5ce9dcf954445e11f14bb70b889d14/coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e", size = 211518 }, + { url = "https://files.pythonhosted.org/packages/85/d2/5e175fcf6766cf7501a8541d81778fd2f52f4870100e791f5327fd23270b/coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3", size = 208088 }, + { url = "https://files.pythonhosted.org/packages/4b/6f/06db4dc8fca33c13b673986e20e466fd936235a6ec1f0045c3853ac1b593/coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43", size = 208536 }, + { url = "https://files.pythonhosted.org/packages/0d/62/c6a0cf80318c1c1af376d52df444da3608eafc913b82c84a4600d8349472/coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132", size = 240474 }, + { url = "https://files.pythonhosted.org/packages/a3/59/750adafc2e57786d2e8739a46b680d4fb0fbc2d57fbcb161290a9f1ecf23/coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f", size = 237880 }, + { url = "https://files.pythonhosted.org/packages/2c/f8/ef009b3b98e9f7033c19deb40d629354aab1d8b2d7f9cfec284dbedf5096/coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994", size = 239750 }, + { url = "https://files.pythonhosted.org/packages/a6/e2/6622f3b70f5f5b59f705e680dae6db64421af05a5d1e389afd24dae62e5b/coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99", size = 238642 }, + { url = "https://files.pythonhosted.org/packages/2d/10/57ac3f191a3c95c67844099514ff44e6e19b2915cd1c22269fb27f9b17b6/coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd", size = 237266 }, + { url = "https://files.pythonhosted.org/packages/ee/2d/7016f4ad9d553cabcb7333ed78ff9d27248ec4eba8dd21fa488254dff894/coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377", size = 238045 }, + { url = "https://files.pythonhosted.org/packages/a7/fe/45af5c82389a71e0cae4546413266d2195c3744849669b0bab4b5f2c75da/coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8", size = 210647 }, + { url = "https://files.pythonhosted.org/packages/db/11/3f8e803a43b79bc534c6a506674da9d614e990e37118b4506faf70d46ed6/coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609", size = 211508 }, + { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, + { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, + { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, + { url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 }, + { url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 }, + { url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 }, + { url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 }, + { url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 }, + { url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 }, + { url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 }, + { url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 }, + { url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 }, + { url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 }, + { url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 }, + { url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 }, + { url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 }, + { url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 }, + { url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 }, + { url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 }, + { url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 }, + { url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 }, + { url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 }, + { url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 }, + { url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 }, + { url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 }, + { url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 }, + { url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 }, + { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, + { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, + { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, + { url = "https://files.pythonhosted.org/packages/a1/70/de81bfec9ed38a64fc44a77c7665e20ca507fc3265597c28b0d989e4082e/coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f", size = 200223 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/79/29d44c4af36b2b240725dce566b20f63f9b36ef267aaaa64ee7466f4f2f8/frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", size = 94451 }, + { url = "https://files.pythonhosted.org/packages/47/47/0c999aeace6ead8a44441b4f4173e2261b18219e4ad1fe9a479871ca02fc/frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", size = 54301 }, + { url = "https://files.pythonhosted.org/packages/8d/60/107a38c1e54176d12e06e9d4b5d755b677d71d1219217cee063911b1384f/frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", size = 52213 }, + { url = "https://files.pythonhosted.org/packages/17/62/594a6829ac5679c25755362a9dc93486a8a45241394564309641425d3ff6/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", size = 240946 }, + { url = "https://files.pythonhosted.org/packages/7e/75/6c8419d8f92c80dd0ee3f63bdde2702ce6398b0ac8410ff459f9b6f2f9cb/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", size = 264608 }, + { url = "https://files.pythonhosted.org/packages/88/3e/82a6f0b84bc6fb7e0be240e52863c6d4ab6098cd62e4f5b972cd31e002e8/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", size = 261361 }, + { url = "https://files.pythonhosted.org/packages/fd/85/14e5f9ccac1b64ff2f10c927b3ffdf88772aea875882406f9ba0cec8ad84/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", size = 231649 }, + { url = "https://files.pythonhosted.org/packages/ee/59/928322800306f6529d1852323014ee9008551e9bb027cc38d276cbc0b0e7/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", size = 241853 }, + { url = "https://files.pythonhosted.org/packages/7d/bd/e01fa4f146a6f6c18c5d34cab8abdc4013774a26c4ff851128cd1bd3008e/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", size = 243652 }, + { url = "https://files.pythonhosted.org/packages/a5/bd/e4771fd18a8ec6757033f0fa903e447aecc3fbba54e3630397b61596acf0/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", size = 241734 }, + { url = "https://files.pythonhosted.org/packages/21/13/c83821fa5544af4f60c5d3a65d054af3213c26b14d3f5f48e43e5fb48556/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", size = 260959 }, + { url = "https://files.pythonhosted.org/packages/71/f3/1f91c9a9bf7ed0e8edcf52698d23f3c211d8d00291a53c9f115ceb977ab1/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", size = 262706 }, + { url = "https://files.pythonhosted.org/packages/4c/22/4a256fdf5d9bcb3ae32622c796ee5ff9451b3a13a68cfe3f68e2c95588ce/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", size = 250401 }, + { url = "https://files.pythonhosted.org/packages/af/89/c48ebe1f7991bd2be6d5f4ed202d94960c01b3017a03d6954dd5fa9ea1e8/frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", size = 45498 }, + { url = "https://files.pythonhosted.org/packages/28/2f/cc27d5f43e023d21fe5c19538e08894db3d7e081cbf582ad5ed366c24446/frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", size = 51622 }, + { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, + { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, + { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, + { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, + { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, + { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, + { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, + { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, + { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, + { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, + { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, + { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, + { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, + { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, + { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, + { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, + { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, + { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, + { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, + { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, + { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, + { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, + { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, + { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, + { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, + { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, + { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, + { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "multidict" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/68/259dee7fd14cf56a17c554125e534f6274c2860159692a414d0b402b9a6d/multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", size = 48628 }, + { url = "https://files.pythonhosted.org/packages/50/79/53ba256069fe5386a4a9e80d4e12857ced9de295baf3e20c68cdda746e04/multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", size = 29327 }, + { url = "https://files.pythonhosted.org/packages/ff/10/71f1379b05b196dae749b5ac062e87273e3f11634f447ebac12a571d90ae/multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", size = 29689 }, + { url = "https://files.pythonhosted.org/packages/71/45/70bac4f87438ded36ad4793793c0095de6572d433d98575a5752629ef549/multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", size = 126639 }, + { url = "https://files.pythonhosted.org/packages/80/cf/17f35b3b9509b4959303c05379c4bfb0d7dd05c3306039fc79cf035bbac0/multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", size = 134315 }, + { url = "https://files.pythonhosted.org/packages/ef/1f/652d70ab5effb33c031510a3503d4d6efc5ec93153562f1ee0acdc895a57/multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", size = 129471 }, + { url = "https://files.pythonhosted.org/packages/a6/64/2dd6c4c681688c0165dea3975a6a4eab4944ea30f35000f8b8af1df3148c/multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", size = 124585 }, + { url = "https://files.pythonhosted.org/packages/87/56/e6ee5459894c7e554b57ba88f7257dc3c3d2d379cb15baaa1e265b8c6165/multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", size = 116957 }, + { url = "https://files.pythonhosted.org/packages/36/9e/616ce5e8d375c24b84f14fc263c7ef1d8d5e8ef529dbc0f1df8ce71bb5b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db", size = 128609 }, + { url = "https://files.pythonhosted.org/packages/8c/4f/4783e48a38495d000f2124020dc96bacc806a4340345211b1ab6175a6cb4/multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", size = 123016 }, + { url = "https://files.pythonhosted.org/packages/3e/b3/4950551ab8fc39862ba5e9907dc821f896aa829b4524b4deefd3e12945ab/multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", size = 133542 }, + { url = "https://files.pythonhosted.org/packages/96/4d/f0ce6ac9914168a2a71df117935bb1f1781916acdecbb43285e225b484b8/multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", size = 130163 }, + { url = "https://files.pythonhosted.org/packages/be/72/17c9f67e7542a49dd252c5ae50248607dfb780bcc03035907dafefb067e3/multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", size = 126832 }, + { url = "https://files.pythonhosted.org/packages/71/9f/72d719e248cbd755c8736c6d14780533a1606ffb3fbb0fbd77da9f0372da/multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", size = 26402 }, + { url = "https://files.pythonhosted.org/packages/04/5a/d88cd5d00a184e1ddffc82aa2e6e915164a6d2641ed3606e766b5d2f275a/multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", size = 28800 }, + { url = "https://files.pythonhosted.org/packages/93/13/df3505a46d0cd08428e4c8169a196131d1b0c4b515c3649829258843dde6/multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", size = 48570 }, + { url = "https://files.pythonhosted.org/packages/f0/e1/a215908bfae1343cdb72f805366592bdd60487b4232d039c437fe8f5013d/multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", size = 29316 }, + { url = "https://files.pythonhosted.org/packages/70/0f/6dc70ddf5d442702ed74f298d69977f904960b82368532c88e854b79f72b/multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", size = 29640 }, + { url = "https://files.pythonhosted.org/packages/d8/6d/9c87b73a13d1cdea30b321ef4b3824449866bd7f7127eceed066ccb9b9ff/multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", size = 131067 }, + { url = "https://files.pythonhosted.org/packages/cc/1e/1b34154fef373371fd6c65125b3d42ff5f56c7ccc6bfff91b9b3c60ae9e0/multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", size = 138507 }, + { url = "https://files.pythonhosted.org/packages/fb/e0/0bc6b2bac6e461822b5f575eae85da6aae76d0e2a79b6665d6206b8e2e48/multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", size = 133905 }, + { url = "https://files.pythonhosted.org/packages/ba/af/73d13b918071ff9b2205fcf773d316e0f8fefb4ec65354bbcf0b10908cc6/multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", size = 129004 }, + { url = "https://files.pythonhosted.org/packages/74/21/23960627b00ed39643302d81bcda44c9444ebcdc04ee5bedd0757513f259/multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", size = 121308 }, + { url = "https://files.pythonhosted.org/packages/8b/5c/cf282263ffce4a596ed0bb2aa1a1dddfe1996d6a62d08842a8d4b33dca13/multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", size = 132608 }, + { url = "https://files.pythonhosted.org/packages/d7/3e/97e778c041c72063f42b290888daff008d3ab1427f5b09b714f5a8eff294/multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", size = 127029 }, + { url = "https://files.pythonhosted.org/packages/47/ac/3efb7bfe2f3aefcf8d103e9a7162572f01936155ab2f7ebcc7c255a23212/multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", size = 137594 }, + { url = "https://files.pythonhosted.org/packages/42/9b/6c6e9e8dc4f915fc90a9b7798c44a30773dea2995fdcb619870e705afe2b/multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", size = 134556 }, + { url = "https://files.pythonhosted.org/packages/1d/10/8e881743b26aaf718379a14ac58572a240e8293a1c9d68e1418fb11c0f90/multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", size = 130993 }, + { url = "https://files.pythonhosted.org/packages/45/84/3eb91b4b557442802d058a7579e864b329968c8d0ea57d907e7023c677f2/multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", size = 26405 }, + { url = "https://files.pythonhosted.org/packages/9f/0b/ad879847ecbf6d27e90a6eabb7eff6b62c129eefe617ea45eae7c1f0aead/multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", size = 28795 }, + { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, + { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, + { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, + { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, + { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, + { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, + { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, + { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, + { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, + { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, + { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, + { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, + { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, + { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, + { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, + { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, + { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, + { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, + { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, + { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, + { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, + { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, + { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, + { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, + { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, + { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, + { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, + { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, + { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, + { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, +] + +[[package]] +name = "mypy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/7b/08046ef9330735f536a09a2e31b00f42bccdb2795dcd979636ba43bb2d63/mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6", size = 3215684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/97/f00ded038482230e0beaaa08f9c5483a54530b362ad1b0d752d5d2b2f211/mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87", size = 11207956 }, + { url = "https://files.pythonhosted.org/packages/68/67/8b4db0da19c9e3fa6264e948f1c135ab4dd45bede1809f4fdb613dc119f6/mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179", size = 10363681 }, + { url = "https://files.pythonhosted.org/packages/f5/00/56b1619ff1f3fcad2d411eccda60d74d20e73bda39c218d5ad2769980682/mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e", size = 12832976 }, + { url = "https://files.pythonhosted.org/packages/e7/8b/9247838774b0bd865f190cc221822212091317f16310305ef924d9772532/mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3", size = 13013704 }, + { url = "https://files.pythonhosted.org/packages/b2/69/0c0868a6f3d9761d2f704d1fb6ef84d75998c27d342738a8b20f109a411f/mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44", size = 9782230 }, + { url = "https://files.pythonhosted.org/packages/34/c1/b9dd3e955953aec1c728992545b7877c9f6fa742a623ce4c200da0f62540/mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a", size = 11121032 }, + { url = "https://files.pythonhosted.org/packages/ee/96/c52d5d516819ab95bf41f4a1ada828a3decc302f8c152ff4fc5feb0e4529/mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc", size = 10286294 }, + { url = "https://files.pythonhosted.org/packages/69/2c/3dbe51877a24daa467f8d8631f9ffd1aabbf0f6d9367a01c44a59df81fe0/mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015", size = 12746528 }, + { url = "https://files.pythonhosted.org/packages/a1/a8/eb20cde4ba9c4c3e20d958918a7c5d92210f4d1a0200c27de9a641f70996/mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb", size = 12883489 }, + { url = "https://files.pythonhosted.org/packages/91/17/a1fc6c70f31d52c99299320cf81c3cb2c6b91ec7269414e0718a6d138e34/mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc", size = 9780113 }, + { url = "https://files.pythonhosted.org/packages/fe/d8/0e72175ee0253217f5c44524f5e95251c02e95ba9749fb87b0e2074d203a/mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd", size = 11269011 }, + { url = "https://files.pythonhosted.org/packages/e9/6d/4ea13839dabe5db588dc6a1b766da16f420d33cf118a7b7172cdf6c7fcb2/mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1", size = 10253076 }, + { url = "https://files.pythonhosted.org/packages/3e/38/7db2c5d0f4d290e998f7a52b2e2616c7bbad96b8e04278ab09d11978a29e/mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63", size = 12862786 }, + { url = "https://files.pythonhosted.org/packages/bf/4b/62d59c801b34141040989949c2b5c157d0408b45357335d3ec5b2845b0f6/mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d", size = 12971568 }, + { url = "https://files.pythonhosted.org/packages/f1/9c/e0f281b32d70c87b9e4d2939e302b1ff77ada4d7b0f2fb32890c144bc1d6/mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba", size = 9879477 }, + { url = "https://files.pythonhosted.org/packages/13/33/8380efd0ebdfdfac7fc0bf065f03a049800ca1e6c296ec1afc634340d992/mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741", size = 11251509 }, + { url = "https://files.pythonhosted.org/packages/15/6d/4e1c21c60fee11af7d8e4f2902a29886d1387d6a836be16229eb3982a963/mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7", size = 10244282 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/7a8ae5c0161edae15d25c2c67c68ce8b150cbdc45aefc13a8be271ee80b2/mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8", size = 12867676 }, + { url = "https://files.pythonhosted.org/packages/9c/d0/71f7bbdcc7cfd0f2892db5b13b1e8857673f2cc9e0c30e3e4340523dc186/mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc", size = 12964189 }, + { url = "https://files.pythonhosted.org/packages/a7/40/fb4ad65d6d5f8c51396ecf6305ec0269b66013a5bf02d0e9528053640b4a/mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f", size = 9888247 }, + { url = "https://files.pythonhosted.org/packages/39/32/0214608af400cdf8f5102144bb8af10d880675c65ed0b58f7e0e77175d50/mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab", size = 2752803 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "propcache" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a5/0ea64c9426959ef145a938e38c832fc551843481d356713ececa9a8a64e8/propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6", size = 79296 }, + { url = "https://files.pythonhosted.org/packages/76/5a/916db1aba735f55e5eca4733eea4d1973845cf77dfe67c2381a2ca3ce52d/propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2", size = 45622 }, + { url = "https://files.pythonhosted.org/packages/2d/62/685d3cf268b8401ec12b250b925b21d152b9d193b7bffa5fdc4815c392c2/propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea", size = 45133 }, + { url = "https://files.pythonhosted.org/packages/4d/3d/31c9c29ee7192defc05aa4d01624fd85a41cf98e5922aaed206017329944/propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212", size = 204809 }, + { url = "https://files.pythonhosted.org/packages/10/a1/e4050776f4797fc86140ac9a480d5dc069fbfa9d499fe5c5d2fa1ae71f07/propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3", size = 219109 }, + { url = "https://files.pythonhosted.org/packages/c9/c0/e7ae0df76343d5e107d81e59acc085cea5fd36a48aa53ef09add7503e888/propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d", size = 217368 }, + { url = "https://files.pythonhosted.org/packages/fc/e1/e0a2ed6394b5772508868a977d3238f4afb2eebaf9976f0b44a8d347ad63/propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634", size = 205124 }, + { url = "https://files.pythonhosted.org/packages/50/c1/e388c232d15ca10f233c778bbdc1034ba53ede14c207a72008de45b2db2e/propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2", size = 195463 }, + { url = "https://files.pythonhosted.org/packages/0a/fd/71b349b9def426cc73813dbd0f33e266de77305e337c8c12bfb0a2a82bfb/propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958", size = 198358 }, + { url = "https://files.pythonhosted.org/packages/02/f2/d7c497cd148ebfc5b0ae32808e6c1af5922215fe38c7a06e4e722fe937c8/propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c", size = 195560 }, + { url = "https://files.pythonhosted.org/packages/bb/57/f37041bbe5e0dfed80a3f6be2612a3a75b9cfe2652abf2c99bef3455bbad/propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583", size = 196895 }, + { url = "https://files.pythonhosted.org/packages/83/36/ae3cc3e4f310bff2f064e3d2ed5558935cc7778d6f827dce74dcfa125304/propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf", size = 207124 }, + { url = "https://files.pythonhosted.org/packages/8c/c4/811b9f311f10ce9d31a32ff14ce58500458443627e4df4ae9c264defba7f/propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034", size = 210442 }, + { url = "https://files.pythonhosted.org/packages/18/dd/a1670d483a61ecac0d7fc4305d91caaac7a8fc1b200ea3965a01cf03bced/propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b", size = 203219 }, + { url = "https://files.pythonhosted.org/packages/f9/2d/30ced5afde41b099b2dc0c6573b66b45d16d73090e85655f1a30c5a24e07/propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4", size = 40313 }, + { url = "https://files.pythonhosted.org/packages/23/84/bd9b207ac80da237af77aa6e153b08ffa83264b1c7882495984fcbfcf85c/propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba", size = 44428 }, + { url = "https://files.pythonhosted.org/packages/bc/0f/2913b6791ebefb2b25b4efd4bb2299c985e09786b9f5b19184a88e5778dd/propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16", size = 79297 }, + { url = "https://files.pythonhosted.org/packages/cf/73/af2053aeccd40b05d6e19058419ac77674daecdd32478088b79375b9ab54/propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717", size = 45611 }, + { url = "https://files.pythonhosted.org/packages/3c/09/8386115ba7775ea3b9537730e8cf718d83bbf95bffe30757ccf37ec4e5da/propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3", size = 45146 }, + { url = "https://files.pythonhosted.org/packages/03/7a/793aa12f0537b2e520bf09f4c6833706b63170a211ad042ca71cbf79d9cb/propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9", size = 232136 }, + { url = "https://files.pythonhosted.org/packages/f1/38/b921b3168d72111769f648314100558c2ea1d52eb3d1ba7ea5c4aa6f9848/propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787", size = 239706 }, + { url = "https://files.pythonhosted.org/packages/14/29/4636f500c69b5edea7786db3c34eb6166f3384b905665ce312a6e42c720c/propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465", size = 238531 }, + { url = "https://files.pythonhosted.org/packages/85/14/01fe53580a8e1734ebb704a3482b7829a0ef4ea68d356141cf0994d9659b/propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af", size = 231063 }, + { url = "https://files.pythonhosted.org/packages/33/5c/1d961299f3c3b8438301ccfbff0143b69afcc30c05fa28673cface692305/propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7", size = 220134 }, + { url = "https://files.pythonhosted.org/packages/00/d0/ed735e76db279ba67a7d3b45ba4c654e7b02bc2f8050671ec365d8665e21/propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f", size = 220009 }, + { url = "https://files.pythonhosted.org/packages/75/90/ee8fab7304ad6533872fee982cfff5a53b63d095d78140827d93de22e2d4/propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54", size = 212199 }, + { url = "https://files.pythonhosted.org/packages/eb/ec/977ffaf1664f82e90737275873461695d4c9407d52abc2f3c3e24716da13/propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505", size = 214827 }, + { url = "https://files.pythonhosted.org/packages/57/48/031fb87ab6081764054821a71b71942161619549396224cbb242922525e8/propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82", size = 228009 }, + { url = "https://files.pythonhosted.org/packages/1a/06/ef1390f2524850838f2390421b23a8b298f6ce3396a7cc6d39dedd4047b0/propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca", size = 231638 }, + { url = "https://files.pythonhosted.org/packages/38/2a/101e6386d5a93358395da1d41642b79c1ee0f3b12e31727932b069282b1d/propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e", size = 222788 }, + { url = "https://files.pythonhosted.org/packages/db/81/786f687951d0979007e05ad9346cd357e50e3d0b0f1a1d6074df334b1bbb/propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034", size = 40170 }, + { url = "https://files.pythonhosted.org/packages/cf/59/7cc7037b295d5772eceb426358bb1b86e6cab4616d971bd74275395d100d/propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3", size = 44404 }, + { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, + { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, + { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, + { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, + { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, + { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, + { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, + { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, + { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, + { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, + { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, + { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, + { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, + { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, + { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, + { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, + { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, + { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, + { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, + { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, + { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, + { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, + { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, + { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, + { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, + { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, + { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, + { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, + { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, + { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, + { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, + { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, + { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, +] + +[[package]] +name = "pydantic" +version = "2.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "ruff" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, + { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, + { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, + { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, + { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, + { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, + { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, + { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, + { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, + { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, + { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, + { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, + { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, + { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, + { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, + { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, + { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "valorant-py" +version = "2.0.0a0" +source = { virtual = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "pydantic" }, +] + +[package.dev-dependencies] +dev = [ + { name = "anyio" }, + { name = "coverage" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.11.11,<4.0" }, + { name = "pydantic", specifier = ">2.0,<3.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "anyio", specifier = ">=4.7.0,<5.0" }, + { name = "coverage", specifier = ">=7.6.10,<8.0" }, + { name = "mypy", specifier = ">=1.14.0,<1.15.0" }, + { name = "pytest", specifier = ">=8.3.4,<9.0" }, + { name = "pytest-cov", specifier = ">=6.0.0,<7.0" }, + { name = "ruff", specifier = ">=0.8.4,<0.9.0" }, +] + +[[package]] +name = "yarl" +version = "1.18.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/98/e005bc608765a8a5569f58e650961314873c8469c333616eb40bff19ae97/yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34", size = 141458 }, + { url = "https://files.pythonhosted.org/packages/df/5d/f8106b263b8ae8a866b46d9be869ac01f9b3fb7f2325f3ecb3df8003f796/yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7", size = 94365 }, + { url = "https://files.pythonhosted.org/packages/56/3e/d8637ddb9ba69bf851f765a3ee288676f7cf64fb3be13760c18cbc9d10bd/yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed", size = 92181 }, + { url = "https://files.pythonhosted.org/packages/76/f9/d616a5c2daae281171de10fba41e1c0e2d8207166fc3547252f7d469b4e1/yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde", size = 315349 }, + { url = "https://files.pythonhosted.org/packages/bb/b4/3ea5e7b6f08f698b3769a06054783e434f6d59857181b5c4e145de83f59b/yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b", size = 330494 }, + { url = "https://files.pythonhosted.org/packages/55/f1/e0fc810554877b1b67420568afff51b967baed5b53bcc983ab164eebf9c9/yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5", size = 326927 }, + { url = "https://files.pythonhosted.org/packages/a9/42/b1753949b327b36f210899f2dd0a0947c0c74e42a32de3f8eb5c7d93edca/yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc", size = 319703 }, + { url = "https://files.pythonhosted.org/packages/f0/6d/e87c62dc9635daefb064b56f5c97df55a2e9cc947a2b3afd4fd2f3b841c7/yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd", size = 310246 }, + { url = "https://files.pythonhosted.org/packages/e3/ef/e2e8d1785cdcbd986f7622d7f0098205f3644546da7919c24b95790ec65a/yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990", size = 319730 }, + { url = "https://files.pythonhosted.org/packages/fc/15/8723e22345bc160dfde68c4b3ae8b236e868f9963c74015f1bc8a614101c/yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db", size = 321681 }, + { url = "https://files.pythonhosted.org/packages/86/09/bf764e974f1516efa0ae2801494a5951e959f1610dd41edbfc07e5e0f978/yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62", size = 324812 }, + { url = "https://files.pythonhosted.org/packages/f6/4c/20a0187e3b903c97d857cf0272d687c1b08b03438968ae8ffc50fe78b0d6/yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760", size = 337011 }, + { url = "https://files.pythonhosted.org/packages/c9/71/6244599a6e1cc4c9f73254a627234e0dad3883ece40cc33dce6265977461/yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b", size = 338132 }, + { url = "https://files.pythonhosted.org/packages/af/f5/e0c3efaf74566c4b4a41cb76d27097df424052a064216beccae8d303c90f/yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690", size = 331849 }, + { url = "https://files.pythonhosted.org/packages/8a/b8/3d16209c2014c2f98a8f658850a57b716efb97930aebf1ca0d9325933731/yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6", size = 84309 }, + { url = "https://files.pythonhosted.org/packages/fd/b7/2e9a5b18eb0fe24c3a0e8bae994e812ed9852ab4fd067c0107fadde0d5f0/yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8", size = 90484 }, + { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, + { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, + { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, + { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, + { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, + { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, + { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, + { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, + { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, + { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, + { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, + { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, + { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, + { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, + { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, + { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, + { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, + { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, + { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, + { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, + { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, + { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, + { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, + { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, + { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, + { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, + { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, + { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, + { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, + { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, + { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, + { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, + { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, + { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, + { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, + { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, + { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, + { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, + { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, + { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, + { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, + { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, + { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, + { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, + { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, + { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, + { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, + { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, + { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, +] diff --git a/valorant/__init__.py b/valorant/__init__.py index 51c0cde..dae1613 100644 --- a/valorant/__init__.py +++ b/valorant/__init__.py @@ -1,6 +1,6 @@ """ -Valorant API Wrapper -~~~~~~~~~~~~~~~~~~~ +Valorant API Wrapper. + A basic wrapper for the Valorant API. :copyright: (c) 2023-present STACiA :license: MIT, see LICENSE for more details. @@ -10,86 +10,39 @@ __author__ = 'STACiA' __license__ = 'MIT' __copyright__ = 'Copyright 2023-present STACiA' -__version__ = '1.0.8' - -from typing import Literal, NamedTuple +__version__ = '2.0.0a' -from . import models as models, utils as utils -from .asset import Asset as Asset -from .client import Client as Client +from . import models +from .client import Client from .enums import ( - MELEE_WEAPON_ID as MELEE_WEAPON_ID, - AbilitySlot as AbilitySlot, - Locale as Locale, - MissionType as MissionType, - RelationType as RelationType, - RewardType as RewardType, - try_enum as try_enum, + AbilitySlot, + DivisionTier, + GameRule, + Locale, + MissionTag, + MissionType, + RelationType, + RewardType, + WeaponCategory, ) -from .localization import Localization as Localization -from .models import ( - Ability as Ability, - AdsStats as AdsStats, - Agent as Agent, - AirBurstStats as AirBurstStats, - AltShotgunStats as AltShotgunStats, - BaseModel as BaseModel, - Border as Border, - Buddy as Buddy, - BuddyLevel as BuddyLevel, - Bundle as Bundle, - Callout as Callout, - Ceremony as Ceremony, - Chapter as Chapter, - ChapterLevel as ChapterLevel, - CompetitiveSeason as CompetitiveSeason, - CompetitiveTier as CompetitiveTier, - Content as Content, - ContentTier as ContentTier, - Contract as Contract, - Currency as Currency, - DamageRange as DamageRange, - Event as Event, - GameFeatureOverride as GameFeatureOverride, - GameMode as GameMode, - GameModeEquippable as GameModeEquippable, - GameRuleBoolOverride as GameRuleBoolOverride, - Gear as Gear, - GridPosition as GridPosition, - Level as Level, - LevelBorder as LevelBorder, - Location as Location, - Map as Map, - Media as Media, - Mission as Mission, - PlayerCard as PlayerCard, - PlayerTitle as PlayerTitle, - Reward as Reward, - Role as Role, - Season as Season, - ShopData as ShopData, - Skin as Skin, - SkinChroma as SkinChroma, - SkinLevel as SkinLevel, - Spray as Spray, - SprayLevel as SprayLevel, - Theme as Theme, - Tier as Tier, - Version as Version, - VoiceLine as VoiceLine, - VoiceLineLocalization as VoiceLineLocalization, - Weapon as Weapon, - WeaponStats as WeaponStats, +from .errors import BadRequest, Forbidden, HTTPException, InternalServerError, RateLimited, ValorantError + +__all__ = ( + 'AbilitySlot', + 'BadRequest', + 'Client', + 'DivisionTier', + 'Forbidden', + 'GameRule', + 'HTTPException', + 'InternalServerError', + 'Locale', + 'MissionTag', + 'MissionType', + 'RateLimited', + 'RelationType', + 'RewardType', + 'ValorantError', + 'WeaponCategory', + 'models', ) - - -class VersionInfo(NamedTuple): - major: int - minor: int - micro: int - release: Literal['alpha', 'beta', 'final'] - - -version_info: VersionInfo = VersionInfo(major=1, minor=0, micro=8, release='final') - -del NamedTuple, Literal, VersionInfo diff --git a/valorant/asset.py b/valorant/asset.py deleted file mode 100644 index 93147ef..0000000 --- a/valorant/asset.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2015-present Rapptz - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -# source : https://github.com/Rapptz/discord.py/blob/master/discord/asset.py -from __future__ import annotations - -import io -from typing import TYPE_CHECKING, Any, Optional, Tuple, Union - -import yarl - -from . import utils -from .file import File - -# fmt: off -__all__ = ( - 'Asset', -) -# fmt: on - -if TYPE_CHECKING: - import os - - from typing_extensions import Self - - from .cache import CacheState - -MISSING = utils.MISSING - - -class AssetMixin: - __slots__ = () - url: str - _state: CacheState - - async def read(self): - """|coro| - Retrieves the content of this asset as a :class:`bytes` object. - Raises - ------ - Exception - There was no internal connection state. - HTTPException - Downloading the asset failed. - NotFound - The asset was deleted. - Returns - ------- - :class:`bytes` - The content of the asset. - """ - if self._state is None: - raise ValueError('Asset has no client') - - return await self._state.http.read_from_url(self.url) - - async def save(self, fp: Union[str, bytes, os.PathLike[Any], io.BufferedIOBase], *, seek_begin: bool = True) -> int: - """|coro| - Saves this asset into a file-like object. - Parameters - ---------- - fp: Union[:class:`io.BufferedIOBase`, :class:`os.PathLike`] - The file-like object to save this asset to or the filename - to use. If a filename is passed then a file is created with that - filename and used instead. - seek_begin: :class:`bool` - Whether to seek to the beginning of the file after saving is - successfully done. - Raises - ------ - Exception - There was no internal connection state. - HTTPException - Downloading the asset failed. - NotFound - The asset was deleted. - Returns - -------- - :class:`int` - The number of bytes written. - """ - data = await self.read() - if isinstance(fp, io.BufferedIOBase): - written = fp.write(data) - if seek_begin: - fp.seek(0) - return written - else: - with open(fp, 'wb') as f: - return f.write(data) - - async def to_file(self, *, filename: Optional[str] = MISSING) -> File: - """|coro| - Converts the asset into a :class:`File` - Parameters - ----------- - filename: Optional[:class:`str`] - The filename of the file. If not provided, then the filename from - the asset's URL is used. - Raises - ------ - Exception - The asset does not have an associated state. - ValueError - The asset is a unicode emoji. - TypeError - The asset is a sticker with lottie type. - HTTPException - Downloading the asset failed. - NotFound - The asset was deleted. - Returns - ------- - :class:`File` - The asset as a file suitable for sending. - """ - data = await self.read() - file_filename = filename if filename is not MISSING else yarl.URL(self.url).name - return File(io.BytesIO(data), filename=file_filename) - - -class Asset(AssetMixin): - """Represents a CDN asset on Valorant. - .. container:: operations - .. describe:: str(x) - Returns the URL of the CDN asset. - .. describe:: len(x) - Returns the length of the CDN asset's URL. - .. describe:: x == y - Checks if the asset is equal to another asset. - .. describe:: x != y - Checks if the asset is not equal to another asset. - .. describe:: hash(x) - Returns the hash of the asset. - """ - - __slot__: Tuple[str, ...] = ('_client', '_url', '_video', '_animated') - - def __init__(self, state: CacheState, url: str, *, animated: bool = False, video: bool = False) -> None: - self._state = state - self._url = url - self._video = video - self._animated = animated - - @classmethod - def _from_url(cls, state: CacheState, url: Optional[str] = None, *, animated: bool = False) -> Self: - if url is None: - raise TypeError('Expected URL, not NoneType') - - video = True if url.endswith('.mp4') else False - if not animated and url.endswith('.gif'): - animated = True - return cls(state=state, url=url, animated=animated, video=video) - - def __str__(self) -> str: - return str(self._url) - - def __len__(self) -> int: - return len(self._url) - - def __eq__(self, other: object) -> bool: - return isinstance(other, Asset) and self._url == other._url - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash(self._url) - - @property - def url(self) -> str: - """:class:`str`: Returns the underlying URL of the asset.""" - return self._url - - @property - def animated(self) -> bool: - """:class:`bool`: Returns whether the asset is animated.""" - return self._animated - - @property - def video(self) -> bool: - """:class:`bool`: Returns whether the asset is a video.""" - return self._video diff --git a/valorant/cache.py b/valorant/cache.py deleted file mode 100644 index 789bea3..0000000 --- a/valorant/cache.py +++ /dev/null @@ -1,822 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from __future__ import annotations - -import asyncio -import logging -from typing import TYPE_CHECKING, Dict, List, Optional - -from .models import ( - Agent, - Buddy, - BuddyLevel, - Bundle, - Ceremony, - CompetitiveSeason, - CompetitiveTier, - ContentTier, - Contract, - Currency, - Event, - GameMode, - GameModeEquippable, - Gear, - LevelBorder, - Map, - Mission, - PlayerCard, - PlayerTitle, - Season, - Skin, - SkinChroma, - SkinLevel, - Spray, - SprayLevel, - Theme, - Version, - Weapon, -) -from .utils import MISSING - -if TYPE_CHECKING: - from .enums import Locale - from .http import HTTPClient - from .types import ( - agents, - buddies, - bundles, - ceremonies, - competitive_tiers, - content_tiers, - contracts, - currencies, - events, - gamemodes, - gear, - level_borders, - maps, - missions, - player_cards, - player_titles, - seasons, - sprays, - themes, - version, - weapons, - ) - -# class BaseCache(ABC): - -# @abstractmethod -# def get(self): -# raise NotImplementedError - -# @abstractmethod -# def find(self): -# raise NotImplementedError - -_log = logging.getLogger(__name__) - - -class CacheState: - def __init__(self, *, locale: Locale, http: HTTPClient) -> None: - self.locale = locale - self.http = http - # - - self._agents: Dict[str, Agent] = {} - self._buddies: Dict[str, Buddy] = {} - self._bundles: Dict[str, Bundle] = {} - self._ceremonies: Dict[str, Ceremony] = {} - self._competitive_tiers: Dict[str, CompetitiveTier] = {} - self._content_tiers: Dict[str, ContentTier] = {} - self._contracts: Dict[str, Contract] = {} - self._currencies: Dict[str, Currency] = {} - self._events: Dict[str, Event] = {} - self._game_modes: Dict[str, GameMode] = {} - self._game_mode_equippables: Dict[str, GameModeEquippable] = {} - self._gear: Dict[str, Gear] = {} - self._level_borders: Dict[str, LevelBorder] = {} - self._maps: Dict[str, Map] = {} - self._missions: Dict[str, Mission] = {} - self._player_cards: Dict[str, PlayerCard] = {} - self._player_titles: Dict[str, PlayerTitle] = {} - self._seasons: Dict[str, Season] = {} - self._competitive_seasons: Dict[str, CompetitiveSeason] = {} - self._sprays: Dict[str, Spray] = {} - self._themes: Dict[str, Theme] = {} - self._version: Version = MISSING - self._weapons: Dict[str, Weapon] = {} - - async def init(self) -> None: - tasks = ( - self.http.get_agents, - self.http.get_buddies, - self.http.get_bundles, - self.http.get_ceremonies, - self.http.get_competitive_tiers, - self.http.get_content_tiers, - self.http.get_contracts, - self.http.get_currencies, - self.http.get_events, - self.http.get_game_modes, - self.http.get_game_mode_equippables, - self.http.get_gear, - self.http.get_level_borders, - self.http.get_maps, - self.http.get_missions, - self.http.get_player_cards, - self.http.get_player_titles, - self.http.get_seasons, - self.http.get_competitive_seasons, - self.http.get_sprays, - self.http.get_themes, - self.http.get_weapons, - self.http.get_version, - ) - results = await asyncio.gather(*(task() for task in tasks)) - for func, result in zip(tasks, results): - assert result is not None - funcname = func.__name__.split('_')[1:] - funcname = '_'.join(funcname) - parse_func = getattr(self, f'_add_{funcname}') - parse_func(result) - _log.info('cache initialized') - - def clear(self) -> None: - for key in self.__dict__.keys(): - if key.startswith('_') and isinstance(self.__dict__[key], dict): - self.__dict__[key].clear() - self._version = MISSING - _log.info('cache cleared') - - # agents - - @property - def agents(self) -> List[Agent]: - return list(self._agents.values()) - - def get_agent(self, uuid: Optional[str], /) -> Optional[Agent]: - return self._agents.get(uuid) # type: ignore - - def store_agent(self, data: agents.Agent) -> Agent: - agent_id = data['uuid'] - self._agents[agent_id] = agent = Agent(state=self, data=data) - return agent - - def _add_agents(self, data: agents.Agents) -> None: - agent_data = data['data'] - for agent in agent_data: - existing = self.get_agent(agent['uuid']) - if existing is None: - self.store_agent(agent) - - async def fetch_agents(self) -> List[Agent]: - data = await self.http.get_agents() - self._add_agents(data) - return self.agents - - # buddies - - @property - def buddies(self) -> List[Buddy]: - return list(self._buddies.values()) - - @property - def buddy_levels(self) -> List[BuddyLevel]: - levels = [] - for buddy in self.buddies: - levels.extend(buddy.levels) - return levels - - def get_buddy(self, uuid: Optional[str], /) -> Optional[Buddy]: - return self._buddies.get(uuid) # type: ignore - - def get_buddy_level(self, uuid: Optional[str], /) -> Optional[BuddyLevel]: - for level in self.buddy_levels: - if level._uuid == uuid: - return level - return None - - def store_buddy(self, data: buddies.Buddy) -> Buddy: - buddy_id = data['uuid'] - self._buddies[buddy_id] = buddy = Buddy(state=self, data=data) - return buddy - - def _add_buddies(self, data: buddies.Buddies) -> None: - buddy_data = data['data'] - for buddy in buddy_data: - buddy_existing = self.get_buddy(buddy['uuid']) - if buddy_existing is None: - self.store_buddy(buddy) - - async def fetch_buddies(self) -> List[Buddy]: - data = await self.http.get_buddies() - self._add_buddies(data) - return self.buddies - - # bundles - - @property - def bundles(self) -> List[Bundle]: - return list(self._bundles.values()) - - def get_bundle(self, uuid: Optional[str], /) -> Optional[Bundle]: - return self._bundles.get(uuid) # type: ignore - - def store_bundle(self, data: bundles.Bundle) -> Bundle: - bundle_id = data['uuid'] - self._bundles[bundle_id] = bundle = Bundle(state=self, data=data) - return bundle - - def _add_bundles(self, data: bundles.Bundles) -> None: - bundle_data = data['data'] - for bundle in bundle_data: - bundle_existing = self.get_bundle(bundle['uuid']) - if bundle_existing is None: - self.store_bundle(bundle) - - async def fetch_bundles(self) -> List[Bundle]: - data = await self.http.get_bundles() - self._add_bundles(data) - return self.bundles - - # ceremonies - - @property - def ceremonies(self) -> List[Ceremony]: - return list(self._ceremonies.values()) - - def get_ceremony(self, uuid: Optional[str], /) -> Optional[Ceremony]: - return self._ceremonies.get(uuid) # type: ignore - - def store_ceremony(self, data: ceremonies.Ceremony) -> Ceremony: - ceremony_id = data['uuid'] - self._ceremonies[ceremony_id] = ceremony = Ceremony(state=self, data=data) - return ceremony - - def _add_ceremonies(self, data: ceremonies.Ceremonies) -> None: - ceremony_data = data['data'] - for ceremony in ceremony_data: - ceremony_existing = self.get_ceremony(ceremony['uuid']) - if ceremony_existing is None: - self.store_ceremony(ceremony) - - async def fetch_ceremonies(self) -> List[Ceremony]: - data = await self.http.get_ceremonies() - self._add_ceremonies(data) - return self.ceremonies - - # competitive tiers - - @property - def competitive_tiers(self) -> List[CompetitiveTier]: - return list(self._competitive_tiers.values()) - - def get_competitive_tier(self, uuid: Optional[str], /) -> Optional[CompetitiveTier]: - return self._competitive_tiers.get(uuid) # type: ignore - - def store_competitive_tier(self, data: competitive_tiers.CompetitiveTier) -> CompetitiveTier: - competitive_tier_id = data['uuid'] - self._competitive_tiers[competitive_tier_id] = competitive_tier = CompetitiveTier(state=self, data=data) - return competitive_tier - - def _add_competitive_tiers(self, data: competitive_tiers.CompetitiveTiers) -> None: - competitive_tier_data = data['data'] - for competitive_tier in competitive_tier_data: - competitive_tier_existing = self.get_competitive_tier(competitive_tier['uuid']) - if competitive_tier_existing is None: - self.store_competitive_tier(competitive_tier) - - async def fetch_competitive_tiers(self) -> List[CompetitiveTier]: - data = await self.http.get_competitive_tiers() - self._add_competitive_tiers(data) - return self.competitive_tiers - - # content tiers - - @property - def content_tiers(self) -> List[ContentTier]: - return list(self._content_tiers.values()) - - def get_content_tier(self, uuid: Optional[str], /) -> Optional[ContentTier]: - return self._content_tiers.get(uuid) # type: ignore - - def store_content_tier(self, data: content_tiers.ContentTier) -> ContentTier: - content_tier_id = data['uuid'] - self._content_tiers[content_tier_id] = content_tier = ContentTier(state=self, data=data) - return content_tier - - def _add_content_tiers(self, data: content_tiers.ContentTiers) -> None: - content_tier_data = data['data'] - for content_tier in content_tier_data: - content_tier_existing = self.get_content_tier(content_tier['uuid']) - if content_tier_existing is None: - self.store_content_tier(content_tier) - - async def fetch_content_tiers(self) -> List[ContentTier]: - data = await self.http.get_content_tiers() - self._add_content_tiers(data) - return self.content_tiers - - # contracts - - @property - def contracts(self) -> List[Contract]: - return list(self._contracts.values()) - - def get_contract(self, uuid: Optional[str], /) -> Optional[Contract]: - return self._contracts.get(uuid) # type: ignore - - def store_contract(self, data: contracts.Contract) -> Contract: - contract_id = data['uuid'] - self._contracts[contract_id] = contract = Contract(state=self, data=data) - return contract - - def _add_contracts(self, data: contracts.Contracts) -> None: - contract_data = data['data'] - for contract in contract_data: - contract_existing = self.get_contract(contract['uuid']) - if contract_existing is None: - self.store_contract(contract) - - async def fetch_contracts(self) -> List[Contract]: - data = await self.http.get_contracts() - self._add_contracts(data) - return self.contracts - - # currencies - - @property - def currencies(self) -> List[Currency]: - return list(self._currencies.values()) - - def get_currency(self, uuid: Optional[str], /) -> Optional[Currency]: - return self._currencies.get(uuid) # type: ignore - - def store_currency(self, data: currencies.Currency) -> Currency: - currency_id = data['uuid'] - self._currencies[currency_id] = currency = Currency(state=self, data=data) - return currency - - def _add_currencies(self, data: currencies.Currencies) -> None: - currency_data = data['data'] - for currency in currency_data: - currency_existing = self.get_currency(currency['uuid']) - if currency_existing is None: - self.store_currency(currency) - - async def fetch_currencies(self) -> List[Currency]: - data = await self.http.get_currencies() - self._add_currencies(data) - return self.currencies - - # events - - @property - def events(self) -> List[Event]: - return list(self._events.values()) - - def get_event(self, uuid: Optional[str], /) -> Optional[Event]: - return self._events.get(uuid) # type: ignore - - def store_event(self, data: events.Event) -> Event: - event_id = data['uuid'] - self._events[event_id] = event = Event(state=self, data=data) - return event - - def _add_events(self, data: events.Events) -> None: - event_data = data['data'] - for event in event_data: - event_existing = self.get_event(event['uuid']) - if event_existing is None: - self.store_event(event) - - async def fetch_events(self) -> List[Event]: - data = await self.http.get_events() - self._add_events(data) - return self.events - - # game modes - - @property - def game_modes(self) -> List[GameMode]: - return list(self._game_modes.values()) - - @property - def game_mode_equippables(self) -> List[GameModeEquippable]: - return list(self._game_mode_equippables.values()) - - def get_game_mode(self, uuid: Optional[str], /) -> Optional[GameMode]: - return self._game_modes.get(uuid) # type: ignore - - def get_game_mode_equippable(self, uuid: Optional[str], /) -> Optional[GameModeEquippable]: - return self._game_mode_equippables.get(uuid) # type: ignore - - def store_game_mode(self, data: gamemodes.GameMode) -> GameMode: - game_mode_id = data['uuid'] - self._game_modes[game_mode_id] = game_mode = GameMode(state=self, data=data) - return game_mode - - def store_game_mode_equippable(self, data: gamemodes.GameModeEquippable) -> GameModeEquippable: - game_mode_equippable_id = data['uuid'] - self._game_mode_equippables[game_mode_equippable_id] = game_mode_equippable = GameModeEquippable( - state=self, - data=data, - ) - return game_mode_equippable - - def _add_game_modes(self, data: gamemodes.GameModes) -> None: - game_mode_data = data['data'] - for game_mode in game_mode_data: - game_mode_existing = self.get_game_mode(game_mode['uuid']) - if game_mode_existing is None: - self.store_game_mode(game_mode) - - async def fetch_game_modes(self) -> List[GameMode]: - data = await self.http.get_game_modes() - self._add_game_modes(data) - return self.game_modes - - def _add_game_mode_equippables(self, data: gamemodes.GameModeEquippables) -> None: - game_mode_equippable_data = data['data'] - for game_mode_equippable in game_mode_equippable_data: - game_mode_equippable_existing = self.get_game_mode_equippable(game_mode_equippable['uuid']) - if game_mode_equippable_existing is None: - self.store_game_mode_equippable(game_mode_equippable) - - async def fetch_game_mode_equippables(self) -> List[GameModeEquippable]: - data = await self.http.get_game_mode_equippables() - self._add_game_mode_equippables(data) - return self.game_mode_equippables - - # gear - - @property - def gear(self) -> List[Gear]: - return list(self._gear.values()) - - def get_gear(self, uuid: Optional[str], /) -> Optional[Gear]: - return self._gear.get(uuid) # type: ignore - - def store_gear(self, data: gear.Gear_) -> Gear: - gear_id = data['uuid'] - self._gear[gear_id] = gear = Gear(state=self, data=data) - return gear - - def _add_gear(self, data: gear.Gear) -> None: - gear_data = data['data'] - for gear_ in gear_data: - gear_existing = self.get_gear(gear_['uuid']) - if gear_existing is None: - self.store_gear(gear_) - - async def fetch_gear(self) -> List[Gear]: - data = await self.http.get_gear() - self._add_gear(data) - return self.gear - - # level borders - - @property - def level_borders(self) -> List[LevelBorder]: - return list(self._level_borders.values()) - - def get_level_border(self, uuid: Optional[str], /) -> Optional[LevelBorder]: - return self._level_borders.get(uuid) # type: ignore - - def store_level_border(self, data: level_borders.LevelBorder) -> LevelBorder: - level_border_id = data['uuid'] - self._level_borders[level_border_id] = level_border = LevelBorder(state=self, data=data) - return level_border - - def _add_level_borders(self, data: level_borders.LevelBorders) -> None: - level_border_data = data['data'] - for level_border in level_border_data: - level_border_existing = self.get_level_border(level_border['uuid']) - if level_border_existing is None: - self.store_level_border(level_border) - - async def fetch_level_borders(self) -> List[LevelBorder]: - data = await self.http.get_level_borders() - self._add_level_borders(data) - return self.level_borders - - # maps - - @property - def maps(self) -> List[Map]: - return list(self._maps.values()) - - def get_map(self, uuid: Optional[str], /) -> Optional[Map]: - return self._maps.get(uuid) # type: ignore - - def store_map(self, data: maps.Map) -> Map: - map_id = data['uuid'] - self._maps[map_id] = map_ = Map(state=self, data=data) - return map_ - - def _add_maps(self, data: maps.Maps) -> None: - map_data = data['data'] - for map_ in map_data: - map_existing = self.get_map(map_['uuid']) - if map_existing is None: - self.store_map(map_) - - async def fetch_maps(self) -> List[Map]: - data = await self.http.get_maps() - self._add_maps(data) - return self.maps - - # missions - - @property - def missions(self) -> List[Mission]: - return list(self._missions.values()) - - def get_mission(self, uuid: Optional[str], /) -> Optional[Mission]: - return self._missions.get(uuid) # type: ignore - - def store_mission(self, data: missions.Mission) -> Mission: - mission_id = data['uuid'] - self._missions[mission_id] = mission = Mission(state=self, data=data) - return mission - - def _add_missions(self, data: missions.Missions) -> None: - mission_data = data['data'] - for mission in mission_data: - mission_existing = self.get_mission(mission['uuid']) - if mission_existing is None: - self.store_mission(mission) - - async def fetch_missions(self) -> List[Mission]: - data = await self.http.get_missions() - self._add_missions(data) - return self.missions - - # player_cards - - @property - def player_cards(self) -> List[PlayerCard]: - return list(self._player_cards.values()) - - def get_player_card(self, uuid: Optional[str], /) -> Optional[PlayerCard]: - return self._player_cards.get(uuid) # type: ignore - - def store_player_card(self, data: player_cards.PlayerCard) -> PlayerCard: - player_card_id = data['uuid'] - self._player_cards[player_card_id] = player_card = PlayerCard(state=self, data=data) - return player_card - - def _add_player_cards(self, data: player_cards.PlayerCards) -> None: - player_card_data = data['data'] - for player_card in player_card_data: - player_card_existing = self.get_player_card(player_card['uuid']) - if player_card_existing is None: - self.store_player_card(player_card) - - async def fetch_player_cards(self) -> List[PlayerCard]: - data = await self.http.get_player_cards() - self._add_player_cards(data) - return self.player_cards - - # player_titles - - @property - def player_titles(self) -> List[PlayerTitle]: - return list(self._player_titles.values()) - - def get_player_title(self, uuid: Optional[str], /) -> Optional[PlayerTitle]: - return self._player_titles.get(uuid) # type: ignore - - def store_player_title(self, data: player_titles.PlayerTitle) -> PlayerTitle: - player_title_id = data['uuid'] - self._player_titles[player_title_id] = player_title = PlayerTitle(state=self, data=data) - return player_title - - def _add_player_titles(self, data: player_titles.PlayerTitles) -> None: - player_title_data = data['data'] - for player_title in player_title_data: - player_title_existing = self.get_player_title(player_title['uuid']) - if player_title_existing is None: - self.store_player_title(player_title) - - async def fetch_player_titles(self) -> List[PlayerTitle]: - data = await self.http.get_player_titles() - self._add_player_titles(data) - return self.player_titles - - # seasons - - @property - def seasons(self) -> List[Season]: - return list(self._seasons.values()) - - def get_season(self, uuid: Optional[str], /) -> Optional[Season]: - return self._seasons.get(uuid) # type: ignore - - def store_season(self, data: seasons.Season) -> Season: - season_id = data['uuid'] - self._seasons[season_id] = season = Season(state=self, data=data) - return season - - def _add_seasons(self, data: seasons.Seasons) -> None: - season_data = data['data'] - for season in season_data: - season_existing = self.get_season(season['uuid']) - if season_existing is None: - self.store_season(season) - - async def fetch_seasons(self) -> List[Season]: - data = await self.http.get_seasons() - self._add_seasons(data) - return self.seasons - - @property - def competitive_seasons(self) -> List[CompetitiveSeason]: - return list(self._competitive_seasons.values()) - - def get_competitive_season(self, uuid: Optional[str], /) -> Optional[CompetitiveSeason]: - return self._competitive_seasons.get(uuid) # type: ignore - - def store_competitive_season(self, data: seasons.CompetitiveSeason) -> CompetitiveSeason: - season_id = data['uuid'] - self._competitive_seasons[season_id] = season = CompetitiveSeason(state=self, data=data) - return season - - def _add_competitive_seasons(self, data: seasons.CompetitiveSeasons) -> None: - season_data = data['data'] - for season in season_data: - season_existing = self.get_competitive_season(season['uuid']) - if season_existing is None: - self.store_competitive_season(season) - - async def fetch_competitive_seasons(self) -> List[CompetitiveSeason]: - data = await self.http.get_competitive_seasons() - self._add_competitive_seasons(data) - return self.competitive_seasons - - # sprays - - @property - def sprays(self) -> List[Spray]: - return list(self._sprays.values()) - - def get_spray(self, uuid: Optional[str], /) -> Optional[Spray]: - return self._sprays.get(uuid) # type: ignore - - @property - def spray_levels(self) -> List[SprayLevel]: - levels = [] - for spray in self.sprays: - levels.extend(spray.levels) - return levels - - def get_spray_level(self, uuid: Optional[str], /) -> Optional[SprayLevel]: - for level in self.spray_levels: - if level._uuid == uuid: - return level - return - - def store_spray(self, data: sprays.Spray) -> Spray: - spray_id = data['uuid'] - self._sprays[spray_id] = spray = Spray(state=self, data=data) - return spray - - def _add_sprays(self, data: sprays.Sprays) -> None: - spray_data = data['data'] - for spray in spray_data: - spray_existing = self.get_spray(spray['uuid']) - if spray_existing is None: - self.store_spray(spray) - - async def fetch_sprays(self) -> List[Spray]: - data = await self.http.get_sprays() - self._add_sprays(data) - return self.sprays - - # themes - - @property - def themes(self) -> List[Theme]: - return list(self._themes.values()) - - def get_theme(self, uuid: Optional[str], /) -> Optional[Theme]: - return self._themes.get(uuid) # type: ignore - - def store_theme(self, data: themes.Theme) -> Theme: - theme_id = data['uuid'] - self._themes[theme_id] = theme = Theme(state=self, data=data) - return theme - - def _add_themes(self, data: themes.Themes) -> None: - theme_data = data['data'] - for theme in theme_data: - theme_existing = self.get_theme(theme['uuid']) - if theme_existing is None: - self.store_theme(theme) - - async def fetch_themes(self) -> List[Theme]: - data = await self.http.get_themes() - self._add_themes(data) - return self.themes - - # version - - @property - def version(self) -> Version: - return self._version - - def _add_version(self, data: version.Version) -> None: - self._version = Version(state=self, data=data['data']) - - async def fetch_version(self) -> Version: - data = await self.http.get_version() - self._add_version(data) - return self.version - - # weapons - - @property - def weapons(self) -> List[Weapon]: - return list(self._weapons.values()) - - def get_weapon(self, uuid: Optional[str], /) -> Optional[Weapon]: - return self._weapons.get(uuid) # type: ignore - - @property - def skins(self) -> List[Skin]: - skins = [] - for weapon in self.weapons: - skins.extend(weapon.skins) - return skins - - def get_skin(self, uuid: Optional[str], /) -> Optional[Skin]: - for skin in self.skins: - if skin._uuid == uuid: - return skin - return None - - @property - def skin_chromas(self) -> List[SkinChroma]: - chromas = [] - for skin in self.skins: - chromas.extend(skin.chromas) - return chromas - - def get_skin_chroma(self, uuid: Optional[str], /) -> Optional[SkinChroma]: - for chroma in self.skin_chromas: - if chroma._uuid == uuid: - return chroma - return None - - @property - def skin_levels(self) -> List[SkinLevel]: - levels = [] - for skin in self.skins: - levels.extend(skin.levels) - return levels - - def get_skin_level(self, uuid: Optional[str], /) -> Optional[SkinLevel]: - for level in self.skin_levels: - if level._uuid == uuid: - return level - return None - - def store_weapon(self, data: weapons.Weapon) -> Weapon: - weapon_id = data['uuid'] - self._weapons[weapon_id] = weapon = Weapon(state=self, data=data) - return weapon - - def _add_weapons(self, data: weapons.Weapons) -> None: - weapon_data = data['data'] - for weapon in weapon_data: - weapon_existing = self.get_weapon(weapon['uuid']) - if weapon_existing is None: - self.store_weapon(weapon) - - async def fetch_weapons(self) -> List[Weapon]: - data = await self.http.get_weapons() - self._add_weapons(data) - return self.weapons diff --git a/valorant/client.py b/valorant/client.py index fe8772c..83f9bfe 100644 --- a/valorant/client.py +++ b/valorant/client.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -24,41 +24,39 @@ from __future__ import annotations -import asyncio import logging -from typing import TYPE_CHECKING, List, Optional, Type - -from .cache import CacheState -from .enums import Locale -from .http import HTTPClient -from .utils import MISSING +from typing import TYPE_CHECKING if TYPE_CHECKING: from types import TracebackType + from aiohttp import ClientSession from typing_extensions import Self - from .models.agents import Agent - from .models.buddies import Buddy, BuddyLevel - from .models.bundles import Bundle - from .models.ceremonies import Ceremony - from .models.competitive_tiers import CompetitiveTier - from .models.content_tiers import ContentTier - from .models.contracts import Contract - from .models.currencies import Currency - from .models.events import Event - from .models.gamemodes import GameMode, GameModeEquippable - from .models.gear import Gear - from .models.level_borders import LevelBorder - from .models.maps import Map - from .models.missions import Mission - from .models.player_cards import PlayerCard - from .models.player_titles import PlayerTitle - from .models.seasons import CompetitiveSeason, Season - from .models.sprays import Spray, SprayLevel - from .models.themes import Theme - from .models.version import Version - from .models.weapons import Skin, SkinChroma, SkinLevel, Weapon + +from .http import HTTPClient +from .models.agents import Agent +from .models.base import Response +from .models.buddies import Buddy, Level as BuddyLevel +from .models.bundles import Bundle +from .models.ceremonies import Ceremony +from .models.competitive_tiers import CompetitiveTier +from .models.content_tiers import ContentTier +from .models.contracts import Contract +from .models.currencies import Currency +from .models.events import Event +from .models.gamemodes import Equippable as GameModeEquippable, GameMode +from .models.gear import Gear +from .models.level_borders import LevelBorder +from .models.maps import Map +from .models.missions import Mission +from .models.player_cards import PlayerCard +from .models.player_titles import PlayerTitle +from .models.seasons import Competitive as CompetitiveSeason, Season +from .models.sprays import Level as SprayLevel, Spray +from .models.themes import Theme +from .models.version import Version +from .models.weapons import Chroma as SkinChroma, Level as SkinLevel, Skin, Weapon _log = logging.getLogger(__name__) @@ -68,19 +66,16 @@ ) # fmt: on +# TODO: support default locale + class Client: def __init__( self, - locale: Locale = Locale.american_english, - # *, - # enable_cache: bool = True, + *, + session: ClientSession | None = None, ) -> None: - self.locale: Locale = locale - # self.enable_cache: bool = enable_cache - self.http: HTTPClient = HTTPClient() - self.cache: CacheState = CacheState(locale=locale, http=self.http) - self._ready: asyncio.Event = MISSING + self.http: HTTPClient = HTTPClient(session) self._closed: bool = False async def __aenter__(self) -> Self: @@ -89,404 +84,344 @@ async def __aenter__(self) -> Self: async def __aexit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: if not self.is_closed(): await self.close() async def init(self) -> None: - self._ready = asyncio.Event() await self.http.init() - await self.cache.init() - self._ready.set() _log.info('client initialized') - async def reload(self) -> None: - self.cache.clear() - await self.cache.init() - _log.info('client reloaded') - def is_closed(self) -> bool: return self._closed - def is_ready(self) -> bool: - return self._ready is not MISSING and self._ready.is_set() - async def close(self) -> None: if self._closed: return self._closed = True - if self._ready is not MISSING: - self._ready.clear() - - self.cache.clear() await self.http.close() _log.info('client closed') def clear(self) -> None: self._closed = False - self._ready.clear() - self._ready = MISSING - self.cache.clear() self.http.clear() - async def wait_until_ready(self) -> None: - if self._ready is not MISSING: - await self._ready.wait() - else: - raise RuntimeError('Client not initialized yet.') - # agents - @property - def agents(self) -> List[Agent]: - return self.cache.agents - - def get_agent(self, uuid: str, /) -> Optional[Agent]: - return self.cache.get_agent(uuid) + async def fetch_agent(self, uuid: str, /) -> Agent | None: + data = await self.http.get_agent(uuid) + agent = Response[Agent].model_validate(data) + return agent.data - async def fetch_agent(self, uuid: str, /) -> Optional[Agent]: - await self.cache.fetch_agents() - return self.get_agent(uuid) + async def fetch_agents(self) -> list[Agent]: + data = await self.http.get_agents() + agents = Response[list[Agent]].model_validate(data) + return agents.data # buddies - @property - def buddies(self) -> List[Buddy]: - return self.cache.buddies + async def fetch_buddy(self, uuid: str, /) -> Buddy | None: + data = await self.http.get_buddy(uuid) + buddy = Response[Buddy].model_validate(data) + return buddy.data - @property - def buddy_levels(self) -> List[BuddyLevel]: - return self.cache.buddy_levels + async def fetch_buddies(self) -> list[Buddy]: + data = await self.http.get_buddies() + buddies = Response[list[Buddy]].model_validate(data) + return buddies.data - def get_buddy(self, uuid: str, /) -> Optional[Buddy]: - return self.cache.get_buddy(uuid) + async def fetch_buddy_level(self, uuid: str, /) -> BuddyLevel | None: + data = await self.http.get_buddy_level(uuid) + buddy_level = Response[BuddyLevel].model_validate(data) + return buddy_level.data - def get_buddy_level(self, uuid: str, /) -> Optional[BuddyLevel]: - return self.cache.get_buddy_level(uuid) - - async def fetch_buddy(self, uuid: str, /) -> Optional[Buddy]: - await self.cache.fetch_buddies() - return self.get_buddy(uuid) - - async def fetch_buddy_level(self, uuid: str, /) -> Optional[BuddyLevel]: - await self.cache.fetch_buddies() - return self.get_buddy_level(uuid) + async def fetch_buddy_levels(self) -> list[BuddyLevel]: + data = await self.http.get_buddy_levels() + buddy_levels = Response[list[BuddyLevel]].model_validate(data) + return buddy_levels.data # bundles - @property - def bundles(self) -> List[Bundle]: - return self.cache.bundles - - def get_bundle(self, uuid: str, /) -> Optional[Bundle]: - return self.cache.get_bundle(uuid) + async def fetch_bundle(self, uuid: str, /) -> Bundle | None: + data = await self.http.get_bundle(uuid) + bundle = Response[Bundle].model_validate(data) + return bundle.data - async def fetch_bundle(self, uuid: str, /) -> Optional[Bundle]: - await self.cache.fetch_bundles() - return self.get_bundle(uuid) + async def fetch_bundles(self) -> list[Bundle]: + data = await self.http.get_bundles() + bundles = Response[list[Bundle]].model_validate(data) + return bundles.data # ceremonies - @property - def ceremonies(self) -> List[Ceremony]: - return self.cache.ceremonies + async def fetch_ceremony(self, uuid: str, /) -> Ceremony | None: + data = await self.http.get_ceremony(uuid) + ceremony = Response[Ceremony].model_validate(data) + return ceremony.data - def get_ceremony(self, uuid: str, /) -> Optional[Ceremony]: - return self.cache.get_ceremony(uuid) - - async def fetch_ceremony(self, uuid: str, /) -> Optional[Ceremony]: - await self.cache.fetch_ceremonies() - return self.get_ceremony(uuid) + async def fetch_ceremonies(self) -> list[Ceremony]: + data = await self.http.get_ceremonies() + ceremonies = Response[list[Ceremony]].model_validate(data) + return ceremonies.data # competitive_tiers - @property - def competitive_tiers(self) -> List[CompetitiveTier]: - return self.cache.competitive_tiers - - def get_competitive_tier(self, uuid: str, /) -> Optional[CompetitiveTier]: - return self.cache.get_competitive_tier(uuid) + async def fetch_competitive_tier(self, uuid: str, /) -> CompetitiveTier | None: + data = await self.http.get_competitive_tier(uuid) + competitive_tier = Response[CompetitiveTier].model_validate(data) + return competitive_tier.data - async def fetch_competitive_tier(self, uuid: str, /) -> Optional[CompetitiveTier]: - await self.cache.fetch_competitive_tiers() - return self.get_competitive_tier(uuid) + async def fetch_competitive_tiers(self) -> list[CompetitiveTier]: + data = await self.http.get_competitive_tiers() + competitive_tiers = Response[list[CompetitiveTier]].model_validate(data) + return competitive_tiers.data # content_tiers - @property - def content_tiers(self) -> List[ContentTier]: - return self.cache.content_tiers - - def get_content_tier(self, uuid: str, /) -> Optional[ContentTier]: - return self.cache.get_content_tier(uuid) + async def fetch_content_tier(self, uuid: str, /) -> ContentTier | None: + data = await self.http.get_content_tier(uuid) + content_tier = Response[ContentTier].model_validate(data) + return content_tier.data - async def fetch_content_tier(self, uuid: str, /) -> Optional[ContentTier]: - await self.cache.fetch_content_tiers() - return self.get_content_tier(uuid) + async def fetch_content_tiers(self) -> list[ContentTier]: + data = await self.http.get_content_tiers() + content_tiers = Response[list[ContentTier]].model_validate(data) + return content_tiers.data # contracts - @property - def contracts(self) -> List[Contract]: - return self.cache.contracts + async def fetch_contract(self, uuid: str, /) -> Contract | None: + data = await self.http.get_contract(uuid) + contract = Response[Contract].model_validate(data) + return contract.data - def get_contract(self, uuid: str, /) -> Optional[Contract]: - return self.cache.get_contract(uuid) - - async def fetch_contract(self, uuid: str, /) -> Optional[Contract]: - await self.cache.fetch_contracts() - return self.get_contract(uuid) + async def fetch_contracts(self) -> list[Contract]: + data = await self.http.get_contracts() + contracts = Response[list[Contract]].model_validate(data) + return contracts.data # currencies - @property - def currencies(self) -> List[Currency]: - return self.cache.currencies - - def get_currency(self, uuid: str, /) -> Optional[Currency]: - return self.cache.get_currency(uuid) + async def fetch_currency(self, uuid: str, /) -> Currency | None: + data = await self.http.get_currency(uuid) + currency = Response[Currency].model_validate(data) + return currency.data - async def fetch_currency(self, uuid: str, /) -> Optional[Currency]: - await self.cache.fetch_currencies() - return self.get_currency(uuid) + async def fetch_currencies(self) -> list[Currency]: + data = await self.http.get_currencies() + currencies = Response[list[Currency]].model_validate(data) + return currencies.data # events - @property - def events(self) -> List[Event]: - return self.cache.events - - def get_event(self, uuid: str, /) -> Optional[Event]: - return self.cache.get_event(uuid) + async def fetch_event(self, uuid: str, /) -> Event | None: + data = await self.http.get_event(uuid) + event = Response[Event].model_validate(data) + return event.data - async def fetch_event(self, uuid: str, /) -> Optional[Event]: - await self.cache.fetch_events() - return self.get_event(uuid) + async def fetch_events(self) -> list[Event]: + data = await self.http.get_events() + events = Response[list[Event]].model_validate(data) + return events.data # game_modes - @property - def game_modes(self) -> List[GameMode]: - return self.cache.game_modes + async def fetch_game_mode(self, uuid: str, /) -> GameMode | None: + data = await self.http.get_game_mode(uuid) + game_mode = Response[GameMode].model_validate(data) + return game_mode.data - @property - def game_mode_equippables(self) -> List[GameModeEquippable]: - return self.cache.game_mode_equippables + async def fetch_game_modes(self) -> list[GameMode]: + data = await self.http.get_game_modes() + game_modes = Response[list[GameMode]].model_validate(data) + return game_modes.data - def get_game_mode(self, uuid: str, /) -> Optional[GameMode]: - return self.cache.get_game_mode(uuid) + async def fetch_game_mode_equippable(self, uuid: str, /) -> GameModeEquippable | None: + data = await self.http.get_game_mode_equippable(uuid) + game_mode_equippable = Response[GameModeEquippable].model_validate(data) + return game_mode_equippable.data - def get_game_mode_equippable(self, uuid: str, /) -> Optional[GameModeEquippable]: - return self.cache.get_game_mode_equippable(uuid) - - async def fetch_game_mode(self, uuid: str, /) -> Optional[GameMode]: - await self.cache.fetch_game_modes() - return self.get_game_mode(uuid) - - async def fetch_game_mode_equippable(self, uuid: str, /) -> Optional[GameModeEquippable]: - await self.cache.fetch_game_modes() - return self.get_game_mode_equippable(uuid) + async def fetch_game_mode_equippables(self) -> list[GameModeEquippable]: + data = await self.http.get_game_mode_equippables() + game_mode_equippables = Response[list[GameModeEquippable]].model_validate(data) + return game_mode_equippables.data # gear - @property - def gear(self) -> List[Gear]: - return self.cache.gear - - def get_gear(self, uuid: str, /) -> Optional[Gear]: - return self.cache.get_gear(uuid) + async def fetch_gear(self, uuid: str, /) -> Gear | None: + data = await self.http.get_gear(uuid) + gear = Response[Gear].model_validate(data) + return gear.data - async def fetch_gear(self, uuid: str, /) -> Optional[Gear]: - await self.cache.fetch_gear() - return self.get_gear(uuid) + async def fetch_gears(self) -> list[Gear]: + data = await self.http.get_all_gear() + gears = Response[list[Gear]].model_validate(data) + return gears.data # level_borders - @property - def level_borders(self) -> List[LevelBorder]: - return self.cache.level_borders + async def fetch_level_border(self, uuid: str, /) -> LevelBorder | None: + data = await self.http.get_level_border(uuid) + level_border = Response[LevelBorder].model_validate(data) + return level_border.data - def get_level_border(self, uuid: str, /) -> Optional[LevelBorder]: - return self.cache.get_level_border(uuid) - - async def fetch_level_border(self, uuid: str, /) -> Optional[LevelBorder]: - await self.cache.fetch_level_borders() - return self.get_level_border(uuid) + async def fetch_level_borders(self) -> list[LevelBorder]: + data = await self.http.get_level_borders() + level_borders = Response[list[LevelBorder]].model_validate(data) + return level_borders.data # maps - @property - def maps(self) -> List[Map]: - return self.cache.maps - - def get_map(self, uuid: str, /) -> Optional[Map]: - return self.cache.get_map(uuid) + async def fetch_map(self, uuid: str, /) -> Map | None: + data = await self.http.get_map(uuid) + map_ = Response[Map].model_validate(data) + return map_.data - async def fetch_map(self, uuid: str, /) -> Optional[Map]: - await self.cache.fetch_maps() - return self.get_map(uuid) + async def fetch_maps(self) -> list[Map]: + data = await self.http.get_maps() + maps = Response[list[Map]].model_validate(data) + return maps.data # missions - @property - def missions(self) -> List[Mission]: - return self.cache.missions - - def get_mission(self, uuid: str, /) -> Optional[Mission]: - return self.cache.get_mission(uuid) + async def fetch_mission(self, uuid: str, /) -> Mission | None: + data = await self.http.get_mission(uuid) + mission = Response[Mission].model_validate(data) + return mission.data - async def fetch_mission(self, uuid: str, /) -> Optional[Mission]: - await self.cache.fetch_missions() - return self.get_mission(uuid) + async def fetch_missions(self) -> list[Mission]: + data = await self.http.get_missions() + missions = Response[list[Mission]].model_validate(data) + return missions.data - # player_cards + # player cards - @property - def player_cards(self) -> List[PlayerCard]: - return self.cache.player_cards + async def fetch_player_card(self, uuid: str, /) -> PlayerCard | None: + data = await self.http.get_player_card(uuid) + player_card = Response[PlayerCard].model_validate(data) + return player_card.data - def get_player_card(self, uuid: str) -> Optional[PlayerCard]: - return self.cache.get_player_card(uuid) + async def fetch_player_cards(self) -> list[PlayerCard]: + data = await self.http.get_player_cards() + player_cards = Response[list[PlayerCard]].model_validate(data) + return player_cards.data - async def fetch_player_card(self, uuid: str) -> Optional[PlayerCard]: - await self.cache.fetch_player_cards() - return self.get_player_card(uuid) + # player titles - # player_titles + async def fetch_player_title(self, uuid: str, /) -> PlayerTitle | None: + data = await self.http.get_player_title(uuid) + player_title = Response[PlayerTitle].model_validate(data) + return player_title.data - @property - def player_titles(self) -> List[PlayerTitle]: - return self.cache.player_titles - - def get_player_title(self, uuid: str, /) -> Optional[PlayerTitle]: - return self.cache.get_player_title(uuid) - - async def fetch_player_title(self, uuid: str, /) -> Optional[PlayerTitle]: - await self.cache.fetch_player_titles() - return self.get_player_title(uuid) + async def fetch_player_titles(self) -> list[PlayerTitle]: + data = await self.http.get_player_titles() + player_titles = Response[list[PlayerTitle]].model_validate(data) + return player_titles.data # seasons - @property - def seasons(self) -> List[Season]: - return self.cache.seasons - - def get_season(self, uuid: str, /) -> Optional[Season]: - return self.cache.get_season(uuid) + async def fetch_season(self, uuid: str, /) -> Season | None: + data = await self.http.get_season(uuid) + season = Response[Season].model_validate(data) + return season.data - async def fetch_season(self, uuid: str, /) -> Optional[Season]: - await self.cache.fetch_seasons() - return self.get_season(uuid) + async def fetch_seasons(self) -> list[Season]: + data = await self.http.get_seasons() + seasons = Response[list[Season]].model_validate(data) + return seasons.data - @property - def competitive_seasons(self) -> List[CompetitiveSeason]: - return list(self.cache.competitive_seasons) + async def competitive_season(self, uuid: str, /) -> CompetitiveSeason | None: + data = await self.http.get_competitive_season(uuid) + competitive_season = Response[CompetitiveSeason].model_validate(data) + return competitive_season.data - def get_competitive_season(self, uuid: str, /) -> Optional[CompetitiveSeason]: - return self.cache.get_competitive_season(uuid) - - def get_competitive_season_season_id(self, season_id: str, /) -> Optional[CompetitiveSeason]: - for season in self.competitive_seasons: - if season.season_uuid == season_id: - return season - return None - - async def fetch_competitive_season(self, uuid: str, /) -> Optional[CompetitiveSeason]: - await self.cache.fetch_competitive_seasons() - return self.get_competitive_season(uuid) + async def competitive_seasons(self) -> list[CompetitiveSeason]: + data = await self.http.get_competitive_seasons() + competitive_seasons = Response[list[CompetitiveSeason]].model_validate(data) + return competitive_seasons.data # sprays - @property - def sprays(self) -> List[Spray]: - return self.cache.sprays - - def get_spray(self, uuid: str, /) -> Optional[Spray]: - return self.cache.get_spray(uuid) + async def fetch_spray(self, uuid: str, /) -> Spray | None: + data = await self.http.get_spray(uuid) + spray = Response[Spray].model_validate(data) + return spray.data - async def fetch_spray(self, uuid: str, /) -> Optional[Spray]: - await self.cache.fetch_sprays() - return self.get_spray(uuid) + async def fetch_sprays(self) -> list[Spray]: + data = await self.http.get_sprays() + sprays = Response[list[Spray]].model_validate(data) + return sprays.data - @property - def spray_levels(self) -> List[SprayLevel]: - return self.cache.spray_levels + async def fetch_spray_level(self, uuid: str, /) -> SprayLevel | None: + data = await self.http.get_spray_level(uuid) + spray_level = Response[SprayLevel].model_validate(data) + return spray_level.data - def get_spray_level(self, uuid: str, /) -> Optional[SprayLevel]: - return self.cache.get_spray_level(uuid) - - async def fetch_spray_level(self, uuid: str, /) -> Optional[SprayLevel]: - await self.cache.fetch_sprays() - return self.get_spray_level(uuid) + async def fetch_spray_levels(self) -> list[SprayLevel]: + data = await self.http.get_spray_levels() + spray_levels = Response[list[SprayLevel]].model_validate(data) + return spray_levels.data # themes - @property - def themes(self) -> List[Theme]: - return self.cache.themes - - def get_theme(self, uuid: str) -> Optional[Theme]: - return self.cache.get_theme(uuid) - - async def fetch_theme(self, uuid: str) -> Optional[Theme]: - await self.cache.fetch_themes() - return self.get_theme(uuid) - - # version - - @property - def version(self) -> Version: - return self.cache.version + async def fetch_theme(self, uuid: str, /) -> Theme | None: + data = await self.http.get_theme(uuid) + theme = Response[Theme].model_validate(data) + return theme.data - async def fetch_version(self) -> Version: - await self.cache.fetch_version() - return self.version + async def fetch_themes(self) -> list[Theme]: + data = await self.http.get_themes() + themes = Response[list[Theme]].model_validate(data) + return themes.data # weapons - @property - def weapons(self) -> List[Weapon]: - return self.cache.weapons - - def get_weapon(self, uuid: str, /) -> Optional[Weapon]: - return self.cache.get_weapon(uuid) - - async def fetch_weapon(self, uuid: str, /) -> Optional[Weapon]: - await self.cache.fetch_weapons() - return self.get_weapon(uuid) - - @property - def skins(self) -> List[Skin]: - return self.cache.skins - - def get_skin(self, uuid: str, /) -> Optional[Skin]: - return self.cache.get_skin(uuid) - - async def fetch_skin(self, uuid: str, /) -> Optional[Skin]: - await self.cache.fetch_weapons() - return self.get_skin(uuid) + async def fetch_weapon(self, uuid: str, /) -> Weapon | None: + data = await self.http.get_weapon(uuid) + weapon = Response[Weapon].model_validate(data) + return weapon.data + + async def fetch_weapons(self) -> list[Weapon]: + data = await self.http.get_weapons() + weapons = Response[list[Weapon]].model_validate(data) + return weapons.data + + async def fetch_weapon_skin(self, uuid: str, /) -> Skin | None: + data = await self.http.get_weapon_skin(uuid) + skin = Response[Skin].model_validate(data) + return skin.data + + async def fetch_weapon_skins(self) -> list[Skin]: + data = await self.http.get_weapon_skins() + skins = Response[list[Skin]].model_validate(data) + return skins.data + + async def fetch_weapon_skin_chroma(self, uuid: str, /) -> SkinChroma | None: + data = await self.http.get_weapon_skin_chroma(uuid) + skin_chroma = Response[SkinChroma].model_validate(data) + return skin_chroma.data + + async def fetch_weapon_skin_chromas(self) -> list[SkinChroma]: + data = await self.http.get_weapon_skin_chromas() + skin_chromas = Response[list[SkinChroma]].model_validate(data) + return skin_chromas.data + + async def fetch_weapon_skin_level(self, uuid: str, /) -> SkinLevel | None: + data = await self.http.get_weapon_skin_level(uuid) + skin_level = Response[SkinLevel].model_validate(data) + return skin_level.data + + async def fetch_weapon_skin_levels(self) -> list[SkinLevel]: + data = await self.http.get_weapon_skin_levels() + skin_levels = Response[list[SkinLevel]].model_validate(data) + return skin_levels.data - @property - def skin_chromas(self) -> List[SkinChroma]: - return self.cache.skin_chromas - - def get_skin_chroma(self, uuid: str, /) -> Optional[SkinChroma]: - return self.cache.get_skin_chroma(uuid) - - async def fetch_skin_chroma(self, uuid: str, /) -> Optional[SkinChroma]: - await self.cache.fetch_weapons() - return self.get_skin_chroma(uuid) - - @property - def skin_levels(self) -> List[SkinLevel]: - return self.cache.skin_levels - - def get_skin_level(self, uuid: str, /) -> Optional[SkinLevel]: - return self.cache.get_skin_level(uuid) + # version - async def fetch_skin_level(self, uuid: str, /) -> Optional[SkinLevel]: - await self.cache.fetch_weapons() - return self.get_skin_level(uuid) + async def fetch_version(self) -> Version: + data = await self.http.get_version() + version = Response[Version].model_validate(data) + return version.data diff --git a/valorant/enums.py b/valorant/enums.py index 4f6f2fe..73c773e 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -1,7 +1,6 @@ """ -The MIT License (MIT) +The MIT License (MIT). -Copyright (c) 2015-present Rapptz Copyright (c) 2023-present STACiA Permission is hereby granted, free of charge, to any person obtaining a @@ -23,157 +22,22 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations - -import types -from collections import namedtuple -from typing import TYPE_CHECKING, Any, ClassVar, Dict, Iterator, List, Mapping, Optional, Tuple, Type, TypeVar - -# enums class for discord.py: https://github.com/Rapptz/discord.py/blob/master/discord/asset.py - - -def _create_value_cls(name: str, comparable: bool): - # All the type ignores here are due to the type checker being unable to recognise - # Runtime type creation without exploding. - cls = namedtuple('_EnumValue_' + name, 'name value') - cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' # type: ignore - cls.__str__ = lambda self: f'{name}.{self.name}' # type: ignore - if comparable: - cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value # type: ignore - cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value # type: ignore - cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value # type: ignore - cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value # type: ignore - return cls - - -def _is_descriptor(obj): - return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__') - - -class EnumMeta(type): - if TYPE_CHECKING: - __name__: ClassVar[str] - _enum_member_names_: ClassVar[List[str]] - _enum_member_map_: ClassVar[Dict[str, Any]] - _enum_value_map_: ClassVar[Dict[Any, Any]] - _enum_string_key_map_: ClassVar[Dict[str, Any]] - - def __new__(cls, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any], *, comparable: bool = False) -> Self: - value_mapping = {} - string_key_mapping = {} - member_mapping = {} - member_names = [] - - value_cls = _create_value_cls(name, comparable) - for key, value in list(attrs.items()): - is_descriptor = _is_descriptor(value) - if key[0] == '_' and not is_descriptor: - continue - - # Special case classmethod to just pass through - if isinstance(value, classmethod): - continue - - if is_descriptor: - setattr(value_cls, key, value) - del attrs[key] - continue - - try: - new_value = value_mapping[value] - except KeyError: - new_value = value_cls(name=key, value=value) - value_mapping[value] = new_value - member_names.append(key) - - try: - string_key_mapping[value] - except KeyError: - string_key_mapping[str(key)] = value_cls(name=key, value=value) - - member_mapping[key] = new_value - attrs[key] = new_value - - attrs['_enum_value_map_'] = value_mapping - attrs['_enum_string_key_map_'] = string_key_mapping - attrs['_enum_member_map_'] = member_mapping - attrs['_enum_member_names_'] = member_names - attrs['_enum_value_cls_'] = value_cls - actual_cls = super().__new__(cls, name, bases, attrs) - value_cls._actual_enum_cls_ = actual_cls # type: ignore # Runtime attribute isn't understood - return actual_cls - - def __iter__(cls) -> Iterator[Any]: - return (cls._enum_member_map_[name] for name in cls._enum_member_names_) - - def __reversed__(cls) -> Iterator[Any]: - return (cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_)) - - def __len__(cls) -> int: - return len(cls._enum_member_names_) - - def __repr__(cls) -> str: - return f'' - - @property - def __members__(cls) -> Mapping[str, Any]: - return types.MappingProxyType(cls._enum_member_map_) - - def __call__(cls, value: str) -> Any: - try: - return cls._enum_value_map_[value] - except (KeyError, TypeError) as e: - raise ValueError(f'{value!r} is not a valid {cls.__name__}') from e - - def __getitem__(cls, key: str) -> Any: - return cls._enum_member_map_[key] - - def __setattr__(cls, name: str, value: Any) -> None: - raise TypeError('Enums are immutable.') - - def __delattr__(cls, attr: str) -> None: - raise TypeError('Enums are immutable') - - def __instancecheck__(self, instance: Any) -> bool: - # isinstance(x, Y) - # -> __instancecheck__(Y, x) - try: - return instance._actual_enum_cls_ is self - except AttributeError: - return False - - -if TYPE_CHECKING: - from enum import Enum - - from typing_extensions import Self -else: - - class Enum(metaclass=EnumMeta): - @classmethod - def try_value(cls, value): - try: - return cls._enum_value_map_[value] - except (KeyError, TypeError): - return value - - -# -- +from enum import Enum __all__ = ( - 'MELEE_WEAPON_ID', 'AbilitySlot', + 'DivisionTier', + 'GameRule', 'Locale', + 'MissionTag', 'MissionType', 'RelationType', 'RewardType', - 'try_enum', + 'WeaponCategory', ) -MELEE_WEAPON_ID: str = '2f59173c-4bed-b6c3-2191-dea9b58be9c7' - -class AbilitySlot(Enum): +class AbilitySlot(str, Enum): passive = 'Passive' grenade = 'Grenade' ability_1 = 'Ability1' @@ -181,45 +45,83 @@ class AbilitySlot(Enum): ultimate = 'Ultimate' -class RelationType(Enum): +class DivisionTier(str, Enum): + unranked = 'ECompetitiveDivision::UNRANKED' + invalid = 'ECompetitiveDivision::INVALID' + iron = 'ECompetitiveDivision::IRON' + bronze = 'ECompetitiveDivision::BRONZE' + silver = 'ECompetitiveDivision::SILVER' + gold = 'ECompetitiveDivision::GOLD' + platinum = 'ECompetitiveDivision::PLATINUM' + diamond = 'ECompetitiveDivision::DIAMOND' + ascendant = 'ECompetitiveDivision::ASCENDANT' + immortal = 'ECompetitiveDivision::IMMORTAL' + radiant = 'ECompetitiveDivision::RADIANT' + + +class GameRule(str, Enum): + accolades_enabled = 'EGameRuleBoolName::AccoladesEnabled' + allow_drop_out = 'EGameRuleBoolName::AllowDropOut' + assign_random_agents = 'EGameRuleBoolName::AssignRandomAgents' + assign_random_agent = 'EGameRuleBoolName::AssignRandomAgent' + combat_report_only_show_last_life = 'EGameRuleBoolName::CombatReportOnlyShowLastLife' + context_aware_module_enabled = 'EGameRuleBoolName::ContextAwareModuleEnabled' + destroy_abilities_on_death = 'EGameRuleBoolName::DestroyAbilitiesOnDeath' + disable_shop_selling = 'EGameRuleBoolName::DisableShopSelling' + downed_characters_can_give_up = 'EGameRuleBoolName::DownedCharactersCanGiveUp' + fill_with_bots = 'EGameRuleBoolName::FillWithBots' + game_rule_bool_name = 'EGameRuleBoolName::EGameRuleBoolName' + is_overtime_win_by_two = 'EGameRuleBoolName::IsOvertimeWinByTwo' + majority_vote_agents = 'EGameRuleBoolName::MajorityVoteAgents' + pip_ability_casting = 'EGameRuleBoolName::PipAbilityCasting' + prevent_ability_recharge = 'EGameRuleBoolName::PreventAbilityRecharge' + skip_pregame = 'EGameRuleBoolName::SkipPregame' + use_all_ability_cooldowns = 'EGameRuleBoolName::UseAllAbilityCooldowns' + use_in_dev_weapons = 'EGameRuleBoolName::UseInDevWeapons' + + +class MissionType(str, Enum): + weekly = 'EAresMissionType::Weekly' + daily = 'EAresMissionType::Daily' + tutorial = 'EAresMissionType::Tutorial' + npe = 'EAresMissionType::NPE' + bte = 'EAresMissionType::BTE' + + +class MissionTag(str, Enum): + econ = 'EAresMissionTag::Econ' + combat = 'EAresMissionTag::Combat' + + +class RelationType(str, Enum): agent = 'Agent' event = 'Event' season = 'Season' - none = 'None' - - def __str__(self) -> str: - return str(self.value) -class RewardType(Enum): +class RewardType(str, Enum): skin_level = 'EquippableSkinLevel' buddy_level = 'EquippableCharmLevel' currency = 'Currency' player_card = 'PlayerCard' player_title = 'Title' spray = 'Spray' - agent = 'Character' - def __str__(self) -> str: - return str(self.value) - - -class MissionType(Enum): - none = 'None' - weekly = 'EAresMissionType::Weekly' - daily = 'EAresMissionType::Daily' - tutorial = 'EAresMissionType::Tutorial' - npe = 'EAresMissionType::NPE' - def __str__(self) -> str: - return str(self.value) +class WeaponCategory(str, Enum): + melee = 'EEquippableCategory::Melee' + sidearm = 'EEquippableCategory::Sidearm' + smg = 'EEquippableCategory::SMG' + shotgun = 'EEquippableCategory::Shotgun' + heavy = 'EEquippableCategory::Heavy' + rifle = 'EEquippableCategory::Rifle' + sniper = 'EEquippableCategory::Sniper' -class Locale(Enum): +class Locale(str, Enum): arabic = 'ar-AE' german = 'de-DE' american_english = 'en-US' - british_english = 'en-US' spain_spanish = 'es-ES' spanish_mexican = 'es-MX' french = 'fr-FR' @@ -235,37 +137,3 @@ class Locale(Enum): vietnamese = 'vi-VN' chinese = 'zh-CN' taiwan_chinese = 'zh-TW' - - # aliases - english = 'en-US' - - def __str__(self) -> str: - return str(self.value) - - -# from discord.py -# https://github.com/Rapptz/discord.py/blob/master/discord/enums.py - -E = TypeVar('E', bound='Enum') - - -def create_unknown_value(cls: Type[E], val: Any) -> E: - value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below - name = f'unknown_{val}' - return value_cls(name=name, value=val) - - -def try_enum(cls: Type[E], val: Any, default: Optional[Any] = None) -> E: - """A function that tries to turn the value into enum ``cls``. - - If it fails it returns a proxy invalid value instead. - """ - try: - return cls._enum_value_map_[val] # type: ignore # All errors are caught below - except (KeyError, TypeError, AttributeError): - if default is not None: - return default - return create_unknown_value(cls, val) - - -# --- diff --git a/valorant/errors.py b/valorant/errors.py index a876e59..14eff23 100644 --- a/valorant/errors.py +++ b/valorant/errors.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -24,7 +24,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from aiohttp import ClientResponse @@ -43,13 +43,12 @@ class ValorantError(Exception): """Exception that's raised when a Valorant API request operation fails.""" - pass - class HTTPException(ValorantError): """Exception that's raised when an HTTP request operation fails. + Attributes - ------------ + ---------- response: :class:`aiohttp.ClientResponse` The response of the failed HTTP request. This is an instance of :class:`aiohttp.ClientResponse`. In some cases @@ -60,7 +59,7 @@ class HTTPException(ValorantError): The status code of the HTTP request. """ - def __init__(self, response: ClientResponse, message: Optional[Union[str, Dict[str, Any]]]): + def __init__(self, response: ClientResponse, message: str | dict[str, Any] | None) -> None: self.response: ClientResponse = response self.status: int = response.status self.text: str @@ -69,7 +68,7 @@ def __init__(self, response: ClientResponse, message: Optional[Union[str, Dict[s else: self.text = message or '' - fmt = '{0.status} {0.reason}' + fmt = '{0.status}' if len(self.text): fmt += ': {1}' @@ -78,39 +77,34 @@ def __init__(self, response: ClientResponse, message: Optional[Union[str, Dict[s class BadRequest(HTTPException): """Exception that's raised for when status code 400 occurs. + Subclass of :exc:`HTTPException` """ - pass - class Forbidden(HTTPException): """Exception that's raised for when status code 403 occurs. + Subclass of :exc:`HTTPException` """ - pass - class NotFound(HTTPException): """Exception that's raised for when status code 404 occurs. + Subclass of :exc:`HTTPException` """ - pass - class InternalServerError(HTTPException): """Exception that's raised for when status code 500 occurs. + Subclass of :exc:`HTTPException` """ - pass - class RateLimited(HTTPException): """Exception that's raised for when a 429 status code occurs. + Subclass of :exc:`HTTPException`. """ - - pass diff --git a/valorant/file.py b/valorant/file.py deleted file mode 100644 index d6f3664..0000000 --- a/valorant/file.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2015-present Rapptz - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from __future__ import annotations - -import io -import os -from typing import Any, Dict, Optional, Union - -# fmt: off -__all__ = ( - 'File', -) -# fmt: on - -# source: https://github.com/Rapptz/discord.py/blob/master/discord/file.py - - -class File: - r"""A class that represents a file to be sent. - - Attributes - ---------- - fp: Union[:class:`os.PathLike`, :class:`io.BufferedIOBase`] - A file-like object opened in binary mode and read mode - or a filename representing a file in the hard drive to - open. - - .. note:: - - If the file-like object passed is opened via ``open`` then the - modes 'rb' should be used. - - To pass binary data, consider usage of ``io.BytesIO``. - - """ - - __slots__ = ('fp', '_filename', '_original_pos', '_owner', '_closer') - - def __init__(self, fp: Union[str, bytes, os.PathLike[Any], io.BufferedIOBase], filename: Optional[str] = None): - if isinstance(fp, io.IOBase): - if not (fp.seekable() and fp.readable()): - raise ValueError(f'File buffer {fp!r} must be seekable and readable') - self.fp: io.IOBase = fp - self._original_pos = fp.tell() - self._owner = False - else: - self.fp = open(fp, 'rb') - self._original_pos = 0 - self._owner = True - - # aiohttp only uses two methods from IOBase - # read and close, since I want to control when the files - # close, I need to stub it, so it doesn't close unless - # I tell it to - self._closer = self.fp.close - self.fp.close = lambda: None - - if filename is None: - if isinstance(fp, str): - _, filename = os.path.split(fp) - else: - filename = getattr(fp, 'name', 'untitled') - - self._filename = filename - - @property - def filename(self) -> str: - """:class:`str`: The filename. - If this is not given then it defaults to ``fp.name`` or if ``fp`` is - a string then the ``filename`` will default to the string given. - """ - return self._filename # type: ignore - - @filename.setter - def filename(self, value: str) -> None: - self._filename = value - - def reset(self, *, seek: Union[int, bool] = True) -> None: - # The `seek` parameter is needed because - # the retry-loop is iterated over multiple times - # starting from 0, as an implementation quirk - # the resetting must be done at the beginning - # before a request is done, since the first index - # is 0, and thus false, then this prevents an - # unnecessary seek since it's the first request - # done. - if seek: - self.fp.seek(self._original_pos) - - def close(self) -> None: - self.fp.close = self._closer - if self._owner: - self._closer() - - def to_dict(self, index: int) -> Dict[str, Any]: - payload = { - 'id': index, - 'filename': self.filename, - } - - return payload diff --git a/valorant/http.py b/valorant/http.py index 4fdf498..a44967f 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2015-present Rapptz Copyright (c) 2023-present STACiA @@ -27,52 +27,24 @@ import logging import sys -from typing import TYPE_CHECKING, Any, ClassVar, Coroutine, Dict, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar from urllib.parse import quote as _uriquote import aiohttp -from . import __version__, utils -from .errors import BadRequest, Forbidden, HTTPException, InternalServerError, NotFound, RateLimited - -MISSING = utils.MISSING +from . import __version__ +from .errors import BadRequest, HTTPException, InternalServerError, NotFound, RateLimited if TYPE_CHECKING: - from .types import ( - agents, - buddies, - bundles, - ceremonies, - competitive_tiers, - content_tiers, - contracts, - currencies, - events, - gamemodes, - gear, - level_borders, - maps, - missions, - player_cards, - player_titles, - seasons, - sprays, - themes, - version, - weapons, - ) + from collections.abc import Coroutine T = TypeVar('T') Response = Coroutine[Any, Any, T] _log = logging.getLogger(__name__) -# http-client inspired by https://github.com/Rapptz/discord.py/blob/master/discord/http.pyS - -async def to_json(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]: - text = await response.text(encoding='utf-8') - return utils._from_json(text) +# http-client inspired by https://github.com/Rapptz/discord.py/blob/master/discord/http.py class Route: @@ -97,26 +69,30 @@ def __init__( class HTTPClient: - def __init__(self, session: aiohttp.ClientSession = MISSING) -> None: - self.__session: aiohttp.ClientSession = session + def __init__(self, session: aiohttp.ClientSession | None = None) -> None: + self.__session: aiohttp.ClientSession | None = session user_agent = 'valorantx (https://github.com/staciax/valorant {0}) Python/{1[0]}.{1[1]} aiohttp/{2}' self.user_agent: str = user_agent.format(__version__, sys.version_info, aiohttp.__version__) async def init(self) -> None: - if self.__session is MISSING: + if self.__session is None: self.__session = aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=0)) async def request(self, route: Route, **kwargs: Any) -> Any: + assert self.__session is not None, 'Session is not initialized' + method = route.method url = route.url kwargs['headers'] = {'User-Agent': self.user_agent} - response: Optional[aiohttp.ClientResponse] = None - data: Optional[Union[Dict[str, Any], str]] = None + response: aiohttp.ClientResponse | None = None + data: dict[str, Any] | str | None = None - async with self.__session.request(method, url, **kwargs) as response: + async with self.__session.request(method, url, **kwargs) as response: # noqa: F811 _log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), response.status) - data = await to_json(response) + + data = await response.json() + if 300 > response.status >= 200: _log.debug('%s %s has received %s', method, url, data) return data @@ -126,250 +102,228 @@ async def request(self, route: Route, **kwargs: Any) -> Any: if response.status == 429: raise RateLimited(response, data) - elif response.status == 404: + + if response.status == 404: raise NotFound(response, data) - elif response.status >= 500: + + if response.status >= 500: raise InternalServerError(response, data) - else: - raise HTTPException(response, data) + + raise HTTPException(response, data) async def close(self) -> None: - if self.__session is not MISSING: + if self.__session is not None: await self.__session.close() def clear(self) -> None: if self.__session and self.__session.closed: - self.__session = MISSING - - async def read_from_url(self, url: str) -> bytes: - async with self.__session.get(url) as resp: - if resp.status == 200: - return await resp.read() - elif resp.status == 403: - raise Forbidden(resp, 'cannot retrieve asset') - elif resp.status == 404: - raise NotFound(resp, 'asset not found') - else: - raise HTTPException(resp, 'failed to get asset') + self.__session = None # valorant-api.com - def get_agents( - self, *, language: Optional[str] = 'all', is_playable_character: bool = True - ) -> Response[agents.Agents]: + def get_agents(self, *, language: str | None = 'all', is_playable_character: bool = True) -> Response[Any]: params = {'isPlayableCharacter': str(is_playable_character), 'language': language} return self.request(Route('GET', '/agents'), params=params) - def get_agent(self, uuid: str, *, language: Optional[str] = 'all') -> Response[agents.AgentUUID]: + def get_agent(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: params = {'language': language} return self.request(Route('GET', '/agents/{uuid}', uuid=uuid), params=params) - # - + # # - - def get_buddies(self, *, language: Optional[str] = 'all') -> Response[buddies.Buddies]: + def get_buddies(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/buddies'), params={'language': language}) - def get_buddy(self, uuid: str, *, language: Optional[str] = 'all') -> Response[buddies.BuddyUUID]: + def get_buddy(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/buddies/{uuid}', uuid=uuid), params={'language': language}) - def get_buddy_levels(self, *, language: Optional[str] = 'all') -> Response[buddies.BuddyLevels]: + def get_buddy_levels(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/buddies/levels'), params={'language': language}) - def get_buddy_level(self, uuid: str, *, language: Optional[str] = 'all') -> Response[buddies.BuddyLevelUUID]: + def get_buddy_level(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/buddies/levels/{uuid}', uuid=uuid), params={'language': language}) # - - def get_bundles(self, *, language: Optional[str] = 'all') -> Response[bundles.Bundles]: + def get_bundles(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/bundles'), params={'language': language}) - def get_bundle(self, uuid: str, *, language: Optional[str] = 'all') -> Response[bundles.BundleUUID]: + def get_bundle(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/bundles/{uuid}', uuid=uuid), params={'language': language}) # - - def get_ceremonies(self, *, language: Optional[str] = 'all') -> Response[ceremonies.Ceremonies]: + def get_ceremonies(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/ceremonies'), params={'language': language}) - def get_ceremony(self, uuid: str, *, language: Optional[str] = 'all') -> Response[ceremonies.CeremonyUUID]: + def get_ceremony(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/ceremonies/{uuid}', uuid=uuid), params={'language': language}) # - - def get_competitive_tiers(self, *, language: Optional[str] = 'all') -> Response[competitive_tiers.CompetitiveTiers]: + def get_competitive_tiers(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/competitivetiers'), params={'language': language}) - def get_competitive_tier( - self, uuid: str, *, language: Optional[str] = 'all' - ) -> Response[competitive_tiers.CompetitiveTierUUID]: + def get_competitive_tier(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/competitivetiers/{uuid}', uuid=uuid), params={'language': language}) # - - def get_content_tiers(self, *, language: Optional[str] = 'all') -> Response[content_tiers.ContentTiers]: + def get_content_tiers(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/contenttiers'), params={'language': language}) - def get_content_tier( - self, uuid: str, *, language: Optional[str] = 'all' - ) -> Response[content_tiers.ContentTierUUID]: + def get_content_tier(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/contenttiers/{uuid}', uuid=uuid), params={'language': language}) # - - def get_contracts(self, *, language: Optional[str] = 'all') -> Response[contracts.Contracts]: + def get_contracts(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/contracts'), params={'language': language}) - def get_contract(self, uuid: str, *, language: Optional[str] = 'all') -> Response[contracts.ContractUUID]: + def get_contract(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/contracts/{uuid}', uuid=uuid), params={'language': language}) # - - def get_currencies(self, *, language: Optional[str] = 'all') -> Response[currencies.Currencies]: + def get_currencies(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/currencies'), params={'language': language}) - def get_currency(self, uuid: str, *, language: Optional[str] = 'all') -> Response[currencies.CurrencyUUID]: + def get_currency(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/currencies/{uuid}', uuid=uuid), params={'language': language}) # - - def get_events(self, *, language: Optional[str] = 'all') -> Response[events.Events]: + def get_events(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/events'), params={'language': language}) - def get_event(self, uuid: str, *, language: Optional[str] = 'all') -> Response[events.EventUUID]: + def get_event(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/events/{uuid}', uuid=uuid), params={'language': language}) # - - def get_game_modes(self, *, language: Optional[str] = 'all') -> Response[gamemodes.GameModes]: + def get_game_modes(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/gamemodes'), params={'language': language}) - def get_game_mode(self, uuid: str, *, language: Optional[str] = 'all') -> Response[gamemodes.GameModeUUID]: + def get_game_mode(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/gamemodes/{uuid}', uuid=uuid), params={'language': language}) - def get_game_mode_equippables(self, *, language: Optional[str] = 'all') -> Response[gamemodes.GameModeEquippables]: + def get_game_mode_equippables(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/gamemodes/equippables'), params={'language': language}) - def get_game_mode_equippable( - self, uuid: str, *, language: Optional[str] = 'all' - ) -> Response[gamemodes.GameModeEquippableUUID]: - return self.request( - Route('GET', '/gamemodes/equippables/{uuid}', uuid=uuid), - params={'language': language}, - ) + def get_game_mode_equippable(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: + return self.request(Route('GET', '/gamemodes/equippables/{uuid}', uuid=uuid), params={'language': language}) # - - def get_gear(self, *, language: Optional[str] = 'all') -> Response[gear.Gear]: + def get_all_gear(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/gear'), params={'language': language}) - def get_gear_(self, uuid: str, *, language: Optional[str] = 'all') -> Response[gear.GearUUID]: + def get_gear(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/gear/{uuid}', uuid=uuid), params={'language': language}) # - - def get_level_borders(self) -> Response[level_borders.LevelBorders]: - return self.request(Route('GET', '/levelborders')) + def get_level_borders(self) -> Response[Any]: + return self.request(Route('GET', '/levelborders'), params={'language': 'all'}) - def get_level_border(self, uuid: str) -> Response[level_borders.LevelBorderUUID]: - return self.request(Route('GET', '/levelborders/{uuid}', uuid=uuid)) + def get_level_border(self, uuid: str) -> Response[Any]: + return self.request(Route('GET', '/levelborders/{uuid}', uuid=uuid), params={'language': 'all'}) # - - def get_maps(self, *, language: Optional[str] = 'all') -> Response[maps.Maps]: + def get_maps(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/maps'), params={'language': language}) - def get_map(self, uuid: str, *, language: Optional[str] = 'all') -> Response[maps.MapUUID]: + def get_map(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/maps/{uuid}', uuid=uuid), params={'language': language}) # - - def get_missions(self, *, language: Optional[str] = 'all') -> Response[missions.Missions]: + def get_missions(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/missions'), params={'language': language}) - def get_mission(self, uuid: str, *, language: Optional[str] = 'all') -> Response[missions.MissionUUID]: + def get_mission(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/missions/{uuid}', uuid=uuid), params={'language': language}) # - - def get_player_cards(self, *, language: Optional[str] = 'all') -> Response[player_cards.PlayerCards]: + def get_player_cards(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/playercards'), params={'language': language}) - def get_player_card(self, uuid: str, *, language: Optional[str] = 'all') -> Response[player_cards.PlayerCardUUID]: + def get_player_card(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/playercards/{uuid}', uuid=uuid), params={'language': language}) # - - def get_player_titles(self, *, language: Optional[str] = 'all') -> Response[player_titles.PlayerTitles]: + def get_player_titles(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/playertitles'), params={'language': language}) - def get_player_title( - self, uuid: str, *, language: Optional[str] = 'all' - ) -> Response[player_titles.PlayerTitleUUID]: + def get_player_title(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/playertitles/{uuid}', uuid=uuid), params={'language': language}) # - - def get_seasons(self, *, language: Optional[str] = 'all') -> Response[seasons.Seasons]: + def get_seasons(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/seasons'), params={'language': language}) - def get_season(self, uuid: str, *, language: Optional[str] = 'all') -> Response[seasons.SeasonUUID]: + def get_season(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/seasons/{uuid}', uuid=uuid), params={'language': language}) - def get_competitive_seasons(self) -> Response[seasons.CompetitiveSeasons]: + def get_competitive_seasons(self) -> Response[Any]: return self.request(Route('GET', '/seasons/competitive')) - def get_competitive_season(self, uuid: str) -> Response[seasons.CompetitiveSeasonUUID]: + def get_competitive_season(self, uuid: str) -> Response[Any]: return self.request(Route('GET', '/seasons/competitive/{uuid}', uuid=uuid)) # - - def get_sprays(self, *, language: Optional[str] = 'all') -> Response[sprays.Sprays]: + def get_sprays(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/sprays'), params={'language': language}) - def get_spray(self, uuid: str, *, language: Optional[str] = 'all') -> Response[sprays.SprayUUID]: + def get_spray(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/sprays/{uuid}', uuid=uuid), params={'language': language}) - def get_spray_levels(self, *, language: Optional[str] = 'all') -> Response[sprays.SprayLevels]: + def get_spray_levels(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/sprays/levels'), params={'language': language}) - def get_spray_level(self, uuid: str, *, language: Optional[str] = 'all') -> Response[sprays.SprayLevelUUID]: + def get_spray_level(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/sprays/levels/{uuid}', uuid=uuid), params={'language': language}) # - - def get_themes(self, *, language: Optional[str] = 'all') -> Response[themes.Themes]: + def get_themes(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/themes'), params={'language': language}) - def get_theme(self, uuid: str, *, language: Optional[str] = 'all') -> Response[themes.ThemeUUID]: + def get_theme(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/themes/{uuid}', uuid=uuid), params={'language': language}) # - - def get_weapons(self, *, language: Optional[str] = 'all') -> Response[weapons.Weapons]: + def get_weapons(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons'), params={'language': language}) - def get_weapon(self, uuid: str, *, language: Optional[str] = 'all') -> Response[weapons.WeaponUUID]: + def get_weapon(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/{uuid}', uuid=uuid), params={'language': language}) - def get_weapon_skins(self, *, language: Optional[str] = 'all') -> Response[weapons.Skins]: + def get_weapon_skins(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/skins'), params={'language': language}) - def get_weapon_skin(self, uuid: str, *, language: Optional[str] = 'all') -> Response[weapons.SkinUUID]: + def get_weapon_skin(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/skins/{uuid}', uuid=uuid), params={'language': language}) - def get_weapon_skin_chromas(self, *, language: Optional[str] = 'all') -> Response[weapons.SkinChromas]: + def get_weapon_skin_chromas(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/skinchromas'), params={'language': language}) - def get_weapon_skin_chroma(self, uuid: str, *, language: Optional[str] = 'all') -> Response[weapons.SkinChromaUUID]: + def get_weapon_skin_chroma(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/skinchromas/{uuid}', uuid=uuid), params={'language': language}) - def get_weapon_skin_levels(self, *, language: Optional[str] = 'all') -> Response[weapons.SkinLevels]: + def get_weapon_skin_levels(self, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/skinlevels'), params={'language': language}) - def get_weapon_skin_level(self, uuid: str, *, language: Optional[str] = 'all') -> Response[weapons.SkinLevelUUID]: + def get_weapon_skin_level(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: return self.request(Route('GET', '/weapons/skinlevels/{uuid}', uuid=uuid), params={'language': language}) # - - def get_version(self) -> Response[version.Version]: + def get_version(self) -> Response[Any]: return self.request(Route('GET', '/version')) diff --git a/valorant/localization.py b/valorant/localization.py deleted file mode 100644 index 4285052..0000000 --- a/valorant/localization.py +++ /dev/null @@ -1,226 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from __future__ import annotations - -from typing import Dict, Optional, Union - -from . import utils -from .enums import Locale - -# fmt: off -__all__ = ( - 'Localization', -) -# fmt: on - - -class Localization: - def __init__( - self, - untranslated: Optional[Union[str, Dict[str, str]]], - locale: Union[str, Locale] = Locale.american_english, - ) -> None: - self.untranslated = untranslated - self._locale = locale - if self.untranslated is None: - self.untranslated = {} - if isinstance(self.untranslated, str): - self.untranslated = {str(self._locale): self.untranslated} - default = self.untranslated.get(str(self._locale), '') - self.ar_AE: str = self.untranslated.get('ar-AE', default) - self.de_DE: str = self.untranslated.get('de-DE', default) - self.en_US: str = self.untranslated.get('en-US', default) - self.es_ES: str = self.untranslated.get('es-ES', default) - self.es_MX: str = self.untranslated.get('es-MX', default) - self.fr_FR: str = self.untranslated.get('fr-FR', default) - self.id_ID: str = self.untranslated.get('id-ID', default) - self.it_IT: str = self.untranslated.get('it-IT', default) - self.ja_JP: str = self.untranslated.get('ja-JP', default) - self.ko_KR: str = self.untranslated.get('ko-KR', default) - self.pl_PL: str = self.untranslated.get('pl-PL', default) - self.pt_BR: str = self.untranslated.get('pt-BR', default) - self.ru_RU: str = self.untranslated.get('ru-RU', default) - self.th_TH: str = self.untranslated.get('th-TH', default) - self.tr_TR: str = self.untranslated.get('tr-TR', default) - self.vi_VN: str = self.untranslated.get('vi-VN', default) - self.zh_CN: str = self.untranslated.get('zh-CN', default) - self.zh_TW: str = self.untranslated.get('zh-TW', default) - - def __str__(self) -> str: - """Return the default locale.""" - return self.locale - - def __repr__(self) -> str: - return self.__str__() - - def __eq__(self, other: object) -> bool: - return isinstance(other, Localization) and self.untranslated == other.untranslated - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash(self.untranslated) - - def __lt__(self, other: Localization) -> bool: - return self.locale < other.locale - - def __le__(self, other: Localization) -> bool: - return self.locale <= other.locale - - def __gt__(self, other: Localization) -> bool: - return self.locale > other.locale - - def __ge__(self, other: Localization) -> bool: - return self.locale >= other.locale - - @property - def arabic(self) -> str: - """:class:`str`: Returns the Arabic locale.""" - return utils.string_escape(self.ar_AE) - - @property - def german(self) -> str: - """:class:`str`: Returns the German locale.""" - return utils.string_escape(self.de_DE) - - @property - def english(self) -> str: - """:class:`str`: Returns the English locale.""" - return utils.string_escape(self.en_US) - - @property - def american_english(self) -> str: - """:class:`str`: Returns the American English locale.""" - return utils.string_escape(self.en_US) - - @property - def british_english(self) -> str: - """:class:`str`: Returns the British English locale.""" - return utils.string_escape(self.en_US) - - @property - def spanish(self) -> str: - """:class:`str`: Returns the Spanish locale.""" - return utils.string_escape(self.es_ES) - - @property - def spanish_mexican(self) -> str: - """:class:`str`: Returns the Spanish Mexican locale.""" - return utils.string_escape(self.es_MX) - - @property - def french(self) -> str: - """:class:`str`: Returns the French locale.""" - return utils.string_escape(self.fr_FR) - - @property - def indonesian(self) -> str: - """:class:`str`: Returns the Indonesian locale.""" - return utils.string_escape(self.id_ID) - - @property - def italian(self) -> str: - """:class:`str`: Returns the Italian locale.""" - return utils.string_escape(self.it_IT) - - @property - def japanese(self) -> str: - """:class:`str`: Returns the Japanese locale.""" - return utils.string_escape(self.ja_JP) - - @property - def korean(self) -> str: - """:class:`str`: Returns the Korean locale.""" - return utils.string_escape(self.ko_KR) - - @property - def polish(self) -> str: - """:class:`str`: Returns the Polish locale.""" - return utils.string_escape(self.pl_PL) - - @property - def portuguese_brazil(self) -> str: - """:class:`str`: Returns the Portuguese Brazil locale.""" - return utils.string_escape(self.pt_BR) - - @property - def russian(self) -> str: - """:class:`str`: Returns the Russian locale.""" - return utils.string_escape(self.ru_RU) - - @property - def thai(self) -> str: - """:class:`str`: Returns the Thai locale.""" - return utils.string_escape(self.th_TH) - - @property - def turkish(self) -> str: - """:class:`str`: Returns the Turkish locale.""" - return utils.string_escape(self.tr_TR) - - @property - def vietnamese(self) -> str: - """:class:`str`: Returns the Vietnamese locale.""" - return utils.string_escape(self.vi_VN) - - @property - def chinese_simplified(self) -> str: - """:class:`str`: Returns the Chinese Simplified locale.""" - return utils.string_escape(self.zh_CN) - - @property - def chinese_traditional(self) -> str: - """:class:`str`: Returns the Chinese Traditional locale.""" - return utils.string_escape(self.zh_TW) - - @property - def default(self) -> str: - """:class:`str`: Returns the english locale is default.""" - if isinstance(self.untranslated, str): - return self.untranslated - elif isinstance(self.untranslated, dict): - return self.untranslated.get('en-US', '') - return '' - - @property - def locale(self) -> str: - """:class:`str`: Returns from your current locale.""" - if isinstance(self.untranslated, dict): - return self.untranslated.get(str(self._locale), self.default) - return self.default - - def from_locale(self, locale: Optional[Union[Locale, str]] = None) -> str: - """:class:`str`: Returns the locale from the locale code.""" - if locale is None: - return self.locale - if isinstance(locale, Locale): - locale = str(locale) - - if hasattr(self, locale.replace('-', '_')): - return getattr(self, locale.replace('-', '_')) - elif isinstance(self.untranslated, dict): - return self.untranslated.get(locale, self.default) - return self.default diff --git a/valorant/models/__init__.py b/valorant/models/__init__.py index 34a6f98..5e0aa77 100644 --- a/valorant/models/__init__.py +++ b/valorant/models/__init__.py @@ -1,76 +1,7 @@ """ -valorant.models -~~~~~~~~~~~~~~ +valorant models. Typings for the Valorant API - :copyright: (c) 2023-present STACiA :license: MIT, see LICENSE for more details. - """ - -from .agents import ( - Ability as Ability, - Agent as Agent, - Media as Media, - Role as Role, - VoiceLine as VoiceLine, - VoiceLineLocalization as VoiceLineLocalization, -) -from .base import BaseModel as BaseModel, GridPosition as GridPosition, ShopData as ShopData -from .buddies import ( - Buddy as Buddy, - BuddyLevel as BuddyLevel, -) -from .bundles import Bundle as Bundle -from .ceremonies import Ceremony as Ceremony -from .competitive_tiers import CompetitiveTier as CompetitiveTier, Tier as Tier -from .content_tiers import ContentTier as ContentTier -from .contracts import ( - Chapter as Chapter, - ChapterLevel as ChapterLevel, - Content as Content, - Contract as Contract, - Level as Level, - Reward as Reward, -) -from .currencies import Currency as Currency -from .events import Event as Event -from .gamemodes import ( - GameFeatureOverride as GameFeatureOverride, - GameMode as GameMode, - GameModeEquippable as GameModeEquippable, - GameRuleBoolOverride as GameRuleBoolOverride, -) -from .gear import Gear as Gear -from .level_borders import LevelBorder as LevelBorder -from .maps import ( - Callout as Callout, - Location as Location, - Map as Map, -) -from .missions import Mission as Mission -from .player_cards import PlayerCard as PlayerCard -from .player_titles import PlayerTitle as PlayerTitle -from .seasons import ( - Border as Border, - CompetitiveSeason as CompetitiveSeason, - Season as Season, -) -from .sprays import ( - Spray as Spray, - SprayLevel as SprayLevel, -) -from .themes import Theme as Theme -from .version import Version as Version -from .weapons import ( - AdsStats as AdsStats, - AirBurstStats as AirBurstStats, - AltShotgunStats as AltShotgunStats, - DamageRange as DamageRange, - Skin as Skin, - SkinChroma as SkinChroma, - SkinLevel as SkinLevel, - Weapon as Weapon, - WeaponStats as WeaponStats, -) diff --git a/valorant/models/agents.py b/valorant/models/agents.py index abb8be6..3e217a0 100644 --- a/valorant/models/agents.py +++ b/valorant/models/agents.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,486 +22,76 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from datetime import datetime +from typing import Any -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union - -from ..asset import Asset -from ..enums import AbilitySlot, Locale, try_enum -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..types.agents import ( - Ability as AbilityPayload, - Agent as AgentPayload, - Media as MediaPayload, - Role as RolePayload, - VoiceLine as VoiceLinePayload, - ) +from pydantic import Field +from ..enums import AbilitySlot +from .base import BaseModel, LocalizedField __all__ = ( 'Ability', 'Agent', - 'Media', + 'Recruitment', 'Role', - 'VoiceLine', - 'VoiceLineLocalization', ) class Role(BaseModel): - def __init__(self, state: CacheState, data: RolePayload) -> None: - super().__init__(data['uuid']) - self._state = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._description: Union[str, Dict[str, str]] = data['description'] - self._display_icon: str = data['displayIcon'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._description, locale=self._state.locale) - self._description_localized: Localization = Localization(self._display_name, locale=self._state.locale) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + description: LocalizedField + display_icon: str = Field(alias='displayIcon') + asset_path: str = Field(alias='assetPath') def __repr__(self) -> str: return f'' - def __str__(self) -> str: - return self.display_name.locale - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def description_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._description_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the agent role's name.""" - return self._display_name_localized - @property - def description(self) -> Localization: - """:class: `str` Returns the agent role's description.""" - return self._description_localized +class Recruitment(BaseModel): + counter_id: str = Field(alias='counterId') + milestone_id: str = Field(alias='milestoneId') + milestone_threshold: int = Field(alias='milestoneThreshold') + use_level_vp_cost_override: bool = Field(alias='useLevelVpCostOverride') + level_vp_cost_override: int = Field(alias='levelVpCostOverride') + start_date: datetime = Field(alias='startDate') + end_date: datetime = Field(alias='endDate') - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the agent role's display icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) - -class Ability: - def __init__(self, state: CacheState, data: AbilityPayload) -> None: - self._state = state - self.slot: AbilitySlot = try_enum(AbilitySlot, data['slot']) - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._description: Union[str, Dict[str, str]] = data['description'] - self._display_icon: Optional[str] = data['displayIcon'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._description_localized: Localization = Localization(self._description, locale=self._state.locale) +class Ability(BaseModel): + slot: AbilitySlot + display_name: LocalizedField = Field(alias='displayName') + description: LocalizedField + display_icon: str | None = Field(alias='displayIcon') def __repr__(self) -> str: return f'' - def __str__(self) -> str: - return self.display_name.locale - - def __eq__(self, other: object) -> bool: - return isinstance(other, Ability) and other.slot == self.slot - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def description_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._description_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the agent role's name.""" - return self._display_name_localized - - @property - def description(self) -> Localization: - """:class: `str` Returns the agent role's description.""" - return self._description_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the agent role's display icon.""" - if self._display_icon is None: - return None - return Asset._from_url(state=self._state, url=self._display_icon) - - -class Media: - def __init__(self, data: MediaPayload) -> None: - self.id: int = data['id'] - self.wwise: str = data['wwise'] - self.wave: str = data['wave'] - - def __repr__(self) -> str: - return f'' - - def __eq__(self, other: object) -> bool: - return isinstance(other, Media) and other.id == self.id - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash(self.id) - - -class VoiceLine: - def __init__(self, data: VoiceLinePayload) -> None: - self.min_duration: float = data['minDuration'] - self.max_duration: float = data['maxDuration'] - self.media_list: List[Media] = [Media(media) for media in data['mediaList']] - - def __repr__(self) -> str: - return f'' - - def __eq__(self, other: object) -> bool: - return ( - isinstance(other, VoiceLine) - and other.min_duration == self.min_duration - and other.max_duration == self.max_duration - ) - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash((self.min_duration, self.max_duration)) - - -class VoiceLineLocalization: - def __init__( - self, - untranslated: Any, - locale: Locale = Locale.american_english, - ) -> None: - self.ut = untranslated - self._locale = locale - - # locale code - self.ar_AE: Optional[VoiceLine] = VoiceLine(self.ut['ar-AE']) if self.ut.get('ar-AE') else None - self.de_DE: Optional[VoiceLine] = VoiceLine(self.ut['de-DE']) if self.ut.get('de-DE') else None - self.en_US: Optional[VoiceLine] = VoiceLine(self.ut['en-US']) if self.ut.get('en-US') else None - self.es_ES: Optional[VoiceLine] = VoiceLine(self.ut['es-ES']) if self.ut.get('es-ES') else None - self.es_MX: Optional[VoiceLine] = VoiceLine(self.ut['es-MX']) if self.ut.get('es-MX') else None - self.fr_FR: Optional[VoiceLine] = VoiceLine(self.ut['fr-FR']) if self.ut.get('fr-FR') else None - self.id_ID: Optional[VoiceLine] = VoiceLine(self.ut['id-ID']) if self.ut.get('id-ID') else None - self.it_IT: Optional[VoiceLine] = VoiceLine(self.ut['it-IT']) if self.ut.get('it-IT') else None - self.ja_JP: Optional[VoiceLine] = VoiceLine(self.ut['ja-JP']) if self.ut.get('ja-JP') else None - self.ko_KR: Optional[VoiceLine] = VoiceLine(self.ut['ko-KR']) if self.ut.get('ko-KR') else None - self.pl_PL: Optional[VoiceLine] = VoiceLine(self.ut['pl-PL']) if self.ut.get('pl-PL') else None - self.pt_BR: Optional[VoiceLine] = VoiceLine(self.ut['pt-BR']) if self.ut.get('pt-BR') else None - self.ru_RU: Optional[VoiceLine] = VoiceLine(self.ut['ru-RU']) if self.ut.get('ru-RU') else None - self.th_TH: Optional[VoiceLine] = VoiceLine(self.ut['th-TH']) if self.ut.get('th-TH') else None - self.tr_TR: Optional[VoiceLine] = VoiceLine(self.ut['tr-TR']) if self.ut.get('tr-TR') else None - self.vi_VN: Optional[VoiceLine] = VoiceLine(self.ut['vi-VN']) if self.ut.get('vi-VN') else None - self.zh_CN: Optional[VoiceLine] = VoiceLine(self.ut['zh-CN']) if self.ut.get('zh-CN') else None - self.zh_TW: Optional[VoiceLine] = VoiceLine(self.ut['zh-TW']) if self.ut.get('zh-TW') else None - - def __repr__(self) -> str: - attrs = [ - ('ar_AE', self.ar_AE), - ('de_DE', self.de_DE), - ('en_US', self.en_US), - ('es_ES', self.es_ES), - ('es_MX', self.es_MX), - ('fr_FR', self.fr_FR), - ('id_ID', self.id_ID), - ('it_IT', self.it_IT), - ('ja_JP', self.ja_JP), - ('ko_KR', self.ko_KR), - ('pl_PL', self.pl_PL), - ('pt_BR', self.pt_BR), - ('ru_RU', self.ru_RU), - ('th_TH', self.th_TH), - ('tr_TR', self.tr_TR), - ('vi_VN', self.vi_VN), - ('zh_CN', self.zh_CN), - ('zh_TW', self.zh_TW), - ] - joined = ' '.join('%s=%r' % t for t in attrs) # noqa: UP031 - return f'<{self.__class__.__name__} {joined}>' - - def __eq__(self, other: object) -> bool: - return isinstance(other, Localization) and self.ut == other.untranslated - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash(self.ut) - - def _dict(self) -> Dict[Locale, Optional[VoiceLine]]: - """:class:`Dict[Locale, VoiceLine]`: Returns all locales as a dictionary.""" - voices = { - Locale.arabic: self.ar_AE, - Locale.german: self.de_DE, - Locale.american_english: self.en_US, - Locale.british_english: self.en_US, - Locale.english: self.en_US, - Locale.spain_spanish: self.es_ES, - Locale.spanish_mexican: self.es_MX, - Locale.french: self.fr_FR, - Locale.indonesian: self.id_ID, - Locale.italian: self.it_IT, - Locale.japanese: self.ja_JP, - Locale.korean: self.ko_KR, - Locale.polish: self.pl_PL, - Locale.brazil_portuguese: self.pt_BR, - Locale.russian: self.ru_RU, - Locale.thai: self.th_TH, - Locale.turkish: self.tr_TR, - Locale.vietnamese: self.vi_VN, - Locale.chinese: self.zh_CN, - Locale.taiwan_chinese: self.zh_TW, - } - return voices - - def all(self) -> List[VoiceLine]: - """:class:`List[VoiceLine]`: Returns all locales as a list.""" - return [v for v in self._dict().values() if v is not None] - - @property - def voice_locale(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the voice locale of the current locale.""" - return self._dict().get(self._locale) - - @property - def arabic(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Arabic locale.""" - return self.ar_AE - - @property - def german(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the German locale.""" - return self.de_DE - - @property - def english(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the English locale.""" - return self.en_US - - @property - def american_english(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the American English locale.""" - return self.en_US - - @property - def british_english(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the British English locale.""" - return self.en_US - - @property - def spanish(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Spanish locale.""" - return self.es_ES - - @property - def spanish_mexican(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Spanish Mexican locale.""" - return self.es_MX - - @property - def french(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the French locale.""" - return self.fr_FR - - @property - def indonesian(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Indonesian locale.""" - return self.id_ID - - @property - def italian(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Italian locale.""" - return self.it_IT - - @property - def japanese(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Japanese locale.""" - return self.ja_JP - - @property - def korean(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Korean locale.""" - return self.ko_KR - - @property - def polish(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Polish locale.""" - return self.pl_PL - - @property - def portuguese_brazil(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Portuguese Brazil locale.""" - return self.pt_BR - - @property - def russian(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Russian locale.""" - return self.ru_RU - - @property - def thai(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Thai locale.""" - return self.th_TH - - @property - def turkish(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Turkish locale.""" - return self.tr_TR - - @property - def vietnamese(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Vietnamese locale.""" - return self.vi_VN - - @property - def chinese_simplified(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Chinese Simplified locale.""" - return self.zh_CN - - @property - def chinese_traditional(self) -> Optional[VoiceLine]: - """:class:`Optional[VoiceLine]`: Returns the Chinese Traditional locale.""" - return self.zh_TW - class Agent(BaseModel): - def __init__(self, *, state: CacheState, data: AgentPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._description: Union[str, Dict[str, str]] = data['description'] - self.developer_name: str = data['developerName'] - if data['characterTags'] is not None: - self.character_tags = [Localization(tag, locale=self._state.locale) for tag in data['characterTags']] - self._display_icon: str = data['displayIcon'] - self._display_icon_small: str = data['displayIconSmall'] - self._bust_portrait: str = data['bustPortrait'] - self._full_portrait: str = data['fullPortrait'] - self._full_portrait_v2: str = data['fullPortraitV2'] - self._killfeed_portrait: str = data['killfeedPortrait'] - self._background: str = data['background'] - self.background_gradient_colors: List[str] = data['backgroundGradientColors'] - self.asset_path: str = data['assetPath'] - self._is_full_portrait_right_facing: bool = data['isFullPortraitRightFacing'] - self._is_playable_character: bool = data['isPlayableCharacter'] - self._is_available_for_test: bool = data['isAvailableForTest'] - self._is_base_content: bool = data['isBaseContent'] - self.role: Role = Role(state=self._state, data=data['role']) - self._abilities: Dict[AbilitySlot, Ability] = { - try_enum(AbilitySlot, ability['slot']): Ability(state=self._state, data=ability) - for ability in data['abilities'] - } - self._voice_line: Optional[Union[VoiceLinePayload, Dict[str, Optional[VoiceLinePayload]]]] = data['voiceLine'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._description_localized: Localization = Localization(self._description, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale + uuid: str + display_name: LocalizedField = Field(alias='displayName') + description: LocalizedField + developer_name: str = Field(alias='developerName') + character_tags: list[LocalizedField] | None = Field(alias='characterTags') + display_icon: str = Field(alias='displayIcon') + display_icon_small: str = Field(alias='displayIconSmall') + bust_portrait: str | None = Field(alias='bustPortrait') + full_portrait: str | None = Field(alias='fullPortrait') + full_portrait_v2: str | None = Field(alias='fullPortraitV2') + killfeed_portrait: str = Field(alias='killfeedPortrait') + background: str | None + background_gradient_colors: list[str] = Field(alias='backgroundGradientColors') + asset_path: str = Field(alias='assetPath') + is_full_portrait_right_facing: bool = Field(alias='isFullPortraitRightFacing') + is_playable_character: bool = Field(alias='isPlayableCharacter') + is_available_for_test: bool = Field(alias='isAvailableForTest') + is_base_content: bool = Field(alias='isBaseContent') + role: Role | None + recruitment_data: Recruitment | None = Field(alias='recruitmentData') + abilities: list[Ability] + voice_line: Any = Field(alias='voiceLine') def __repr__(self) -> str: return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def description_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._description_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the agent's name.""" - return self._display_name_localized - - @property - def description(self) -> Localization: - """:class: `str` Returns the agent's description.""" - return self._description_localized - - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the agent's display icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) - - @property - def display_icon_small(self) -> Asset: - """:class: `Asset` Returns the agent's display icon small.""" - return Asset._from_url(state=self._state, url=self._display_icon_small) - - @property - def bust_portrait(self) -> Asset: - """:class: `Asset` Returns the agent's bust portrait.""" - return Asset._from_url(state=self._state, url=self._bust_portrait) - - @property - def full_portrait(self) -> Asset: - """:class: `Asset` Returns the agent's full portrait.""" - return Asset._from_url(state=self._state, url=self._full_portrait) - - @property - def full_portrait_v2(self) -> Asset: - """:class: `Asset` Returns the agent's full portrait v2.""" - return Asset._from_url(state=self._state, url=self._full_portrait_v2) - - @property - def killfeed_portrait(self) -> Asset: - """:class: `Asset` Returns the agent's killfeed portrait.""" - return Asset._from_url(state=self._state, url=self._killfeed_portrait) - - @property - def background(self) -> Asset: - """:class: `Asset` Returns the agent's background.""" - return Asset._from_url(state=self._state, url=self._background) - - @property - def abilities(self) -> List[Ability]: - """:class: `List[AgentAbility]` Returns the agent's abilities.""" - return list(self._abilities.values()) - - @property - def voice_line(self) -> Optional[VoiceLine]: - """:class: `AgentVoiceLineLocalization` Returns the agent's voice line.""" - if self.voice_line_localization is None: - return None - return self.voice_line_localization.voice_locale - - @property - def voice_line_localization(self) -> Optional[VoiceLineLocalization]: - """:class: `AgentVoiceLineLocalization` Returns the agent's voice line.""" - if self._voice_line is None: - return None - return VoiceLineLocalization(self._voice_line) - - def is_full_portrait_right_facing(self) -> bool: - """:class: `bool` Returns whether the agent's full portrait is right facing.""" - return self._is_full_portrait_right_facing - - def is_playable_character(self) -> bool: - """:class: `bool` Returns whether the agent is a playable character.""" - return self._is_playable_character - - def is_available_for_test(self) -> bool: - """:class: `bool` Returns whether the agent is available for test.""" - return self._is_available_for_test - - def is_base_content(self) -> bool: - """:class: `bool` Returns whether the agent is base content.""" - return self._is_base_content - - # helpers - - def get_ability(self, slot: AbilitySlot) -> Optional[Ability]: - """:class: `AgentAbility` Returns the agent's ability from the slot.""" - return self._abilities.get(slot) diff --git a/valorant/models/base.py b/valorant/models/base.py index e1b4d5d..324b623 100644 --- a/valorant/models/base.py +++ b/valorant/models/base.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,119 +22,74 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from typing import Generic, TypeVar -import uuid -from typing import TYPE_CHECKING, Dict, Optional, Union - -from ..asset import Asset -from ..localization import Localization - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.object import GridPosition as GridPositionPayload, ShopData as ShopDataPayload - from .gear import Gear - from .weapons import Weapon +from pydantic import BaseModel as PydanticBaseModel, Field +T = TypeVar('T') __all__ = ( 'BaseModel', - 'GridPosition', - 'ShopData', + 'LocalizedField', + 'Response', ) -class BaseModel: - - def __init__(self, uuid: str) -> None: - self._uuid = uuid - - @property - def uuid(self) -> uuid.UUID: - return uuid.UUID(self._uuid) - - def __repr__(self) -> str: - return f'<{self.__class__.__name__} uuid={self.uuid!r}>' - - def __eq__(self, other: object) -> bool: - return isinstance(other, self.__class__) and self.uuid == other.uuid - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash(self.uuid) - - -class GridPosition: - def __init__(self, data: GridPositionPayload) -> None: - self.row: float = data['row'] - self.column: float = data['column'] +class BaseModel(PydanticBaseModel): + """Base class for all models.""" def __repr__(self) -> str: - return f'' - - def __eq__(self, other: object) -> bool: - return isinstance(other, GridPosition) and self.row == other.row and self.column == other.column - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - -class ShopData: - def __init__(self, *, state: CacheState, item: Union[Weapon, Gear], data: ShopDataPayload) -> None: - self._item: Union[Weapon, Gear] = item - self._state: CacheState = state - self.cost: int = data['cost'] - self.category: Optional[str] = data['category'] - self._category_text: Union[str, Dict[str, str]] = data['categoryText'] - self.grid_position: Optional[GridPosition] = None - if data['gridPosition'] is not None: - self.grid_position = GridPosition(data['gridPosition']) - self.can_be_trashed: bool = data['canBeTrashed'] - self._image: Optional[str] = data['image'] - self._new_image: Optional[str] = data['newImage'] - self._new_image_2: Optional[str] = data['newImage2'] - self.asset_path: str = data['assetPath'] - self._category_text_localized: Localization = Localization(self._category_text, locale=self._state.locale) + return f'<{self.__class__.__name__}>' + + +class LocalizedField(BaseModel): + de_DE: str = Field(alias='de-DE') + es_ES: str = Field(alias='es-ES') + ar_AE: str = Field(alias='ar-AE') + id_ID: str = Field(alias='id-ID') + es_MX: str = Field(alias='es-MX') + fr_FR: str = Field(alias='fr-FR') + en_US: str = Field(alias='en-US') + it_IT: str = Field(alias='it-IT') + ja_JP: str = Field(alias='ja-JP') + ko_KR: str = Field(alias='ko-KR') + th_TH: str = Field(alias='th-TH') + pl_PL: str = Field(alias='pl-PL') + pt_BR: str = Field(alias='pt-BR') + ru_RU: str = Field(alias='ru-RU') + tr_TR: str = Field(alias='tr-TR') + vi_VN: str = Field(alias='vi-VN') + zh_TW: str = Field(alias='zh-TW') + zh_CN: str = Field(alias='zh-CN') + + # aliases + + arabic: str = ar_AE + german: str = de_DE + american_english: str = en_US + spain_spanish: str = es_ES + spanish_mexican: str = es_MX + french: str = fr_FR + indonesian: str = id_ID + italian: str = it_IT + japanese: str = ja_JP + korean: str = ko_KR + polish: str = pl_PL + brazil_portuguese: str = pt_BR + russian: str = ru_RU + thai: str = th_TH + turkish: str = tr_TR + vietnamese: str = vi_VN + chinese: str = zh_CN + taiwan_chinese: str = zh_TW + + def __str__(self) -> str: + return self.en_US def __repr__(self) -> str: - return f'' - - def __int__(self) -> int: - return self.cost - - def __eq__(self, other: object) -> bool: - return isinstance(other, ShopData) and self.item == other.item and self.cost == other.cost - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def category_text_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._category_text_localized.from_locale(locale) - - @property - def item(self) -> Union[Weapon, Gear]: - """:class: `Weapon` or :class: `Gear` Returns the item.""" - return self._item - - @property - def category_text(self) -> Localization: - """:class: `str` Returns the weapon's shop category text.""" - return self._category_text_localized - - @property - def image(self) -> Optional[Asset]: - """:class: `Asset` Returns the weapon's image.""" - return Asset._from_url(self._state, url=self._image) if self._image else None + return f'' - @property - def new_image(self) -> Optional[Asset]: - """:class: `Asset` Returns the weapon's new image.""" - return Asset._from_url(self._state, url=self._new_image) if self._new_image else None - @property - def new_image_2(self) -> Optional[Asset]: - """:class: `Asset` Returns the weapon's new image 2.""" - return Asset._from_url(self._state, url=self._new_image_2) if self._new_image_2 else None +class Response(BaseModel, Generic[T]): + status: int + data: T diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index e5a645c..b38042a 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,107 +22,38 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from typing import Any -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from pydantic import Field -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.buddies import Buddy as BuddyPayload, BuddyLevel as BuddyLevelPayload - from .themes import Theme +from .base import BaseModel, LocalizedField __all__ = ( 'Buddy', - 'BuddyLevel', + 'Level', ) -class Buddy(BaseModel): - def __init__(self, *, state: CacheState, data: BuddyPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._is_hidden_if_not_owned: bool = data['isHiddenIfNotOwned'] - self.theme_uuid: Optional[str] = data['themeUuid'] - self._display_icon: Optional[str] = data['displayIcon'] - self.asset_path: str = data['assetPath'] - self.levels: List[Level] = [Level(state=self._state, data=level, parent=self) for level in data['levels']] - self._name_localized = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the buddy's name.""" - return self._name_localized - - @property - def theme(self) -> Optional[Theme]: - """:class: `Theme` Returns the buddy's theme.""" - return self._state.get_theme(self.theme_uuid) - - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the buddy's icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) - - def is_hidden_if_not_owned(self) -> bool: - """:class: `bool` Returns whether the buddy is hidden if not owned.""" - return self._is_hidden_if_not_owned - - # helper methods - - def get_buddy_level(self, level: int = 1) -> Optional[Level]: - """Returns the buddy level for the given level number.""" - return next((b for b in self.levels if b.charm_level == level), None) - - class Level(BaseModel): - def __init__(self, *, state: CacheState, data: BuddyLevelPayload, parent: Buddy) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self.charm_level: int = data['charmLevel'] - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_icon: Optional[str] = data['displayIcon'] - self.asset_path: str = data['assetPath'] - self.parent: Buddy = parent - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale + uuid: str + charm_level: int = Field(alias='charmLevel') + hide_if_not_owned: bool = Field(alias='hideIfNotOwned') + display_name: LocalizedField = Field(alias='displayName') + display_icon: str = Field(alias='displayIcon') + asset_path: str = Field(alias='assetPath') def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) + return f'' - @property - def level(self) -> int: - """:class: `int` alias for :attr: `BuddyLevel.charm_level`""" - return self.charm_level - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the buddy's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Asset: - """:class: `str` Returns the buddy's display icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) +class Buddy(BaseModel): + uuid: str + display_name: LocalizedField = Field(alias='displayName') + is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') + theme_uuid: Any = Field(alias='themeUuid') + display_icon: str = Field(alias='displayIcon') + asset_path: str = Field(alias='assetPath') + levels: list[Level] -BuddyLevel = Level + def __repr__(self) -> str: + return f'' diff --git a/valorant/models/bundles.py b/valorant/models/bundles.py index 862a19e..a51116b 100644 --- a/valorant/models/bundles.py +++ b/valorant/models/bundles.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,175 +22,25 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from typing import Any -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from pydantic import Field -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel +from .base import BaseModel, LocalizedField -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.bundles import Bundle as BundlePayload - from .buddies import Buddy - from .player_cards import PlayerCard - from .player_titles import PlayerTitle - from .sprays import Spray - from .weapons import Skin - - BundleItem = Union[Skin, Buddy, Spray, PlayerCard, PlayerTitle] - -# fmt: off -__all__ = ( - 'Bundle', -) -# fmt: on +__all__ = ('Bundle',) class Bundle(BaseModel): - def __init__(self, *, state: CacheState, data: BundlePayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_name_sub_text: Optional[Union[str, Dict[str, str]]] = data['displayNameSubText'] - self._description: Union[str, Dict[str, str]] = data['description'] - self._extra_description: Optional[Union[str, Dict[str, str]]] = data['extraDescription'] - self._promo_description: Optional[Union[str, Dict[str, str]]] = data['promoDescription'] - self.use_additional_context: bool = data['useAdditionalContext'] - self._display_icon: str = data['displayIcon'] - self._display_icon_2: str = data['displayIcon2'] - self._vertical_promo_image: str = data['verticalPromoImage'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._display_name_sub_text_localized: Localization = Localization( - self._display_name_sub_text, locale=self._state.locale - ) - self._description_localized: Localization = Localization(self._description, locale=self._state.locale) - self._extra_description_localized: Localization = Localization( - self._extra_description, locale=self._state.locale - ) - self._promo_description_localized: Localization = Localization( - self._promo_description, locale=self._state.locale - ) - self._items: List[BundleItem] = [] - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def display_name_sub_text_localized(self, locale: Optional[Union[Locale, str]] = None) -> Optional[str]: - if self._description is None: - return None - return self._display_name_sub_text_localized.from_locale(locale) - - def description_localized(self, locale: Optional[Union[Locale, str]] = None) -> Optional[str]: - if self._description is None: - return None - return self._description_localized.from_locale(locale) - - def extra_description_localized(self, locale: Optional[Union[Locale, str]] = None) -> Optional[str]: - if self._extra_description is None: - return None - return self._extra_description_localized.from_locale(locale) - - def promo_description_localized(self, locale: Optional[Union[Locale, str]] = None) -> Optional[str]: - if self._promo_description is None: - return None - return self._promo_description_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the bundle's name.""" - return self._display_name_localized - - @property - def display_name_sub_text(self) -> Optional[Localization]: - """:class: `str` Returns the bundle's sub name.""" - if self._display_name_sub_text is None: - return None - return self._display_name_sub_text_localized - - @property - def description(self) -> Optional[Localization]: - """:class: `str` Returns the bundle's description.""" - if self._description is None: - return None - return self._description_localized - - @property - def extra_description(self) -> Optional[Localization]: - """:class: `str` Returns the bundle's description extra localizations.""" - if self._extra_description is None: - return None - return self._extra_description_localized - - @property - def promo_description(self) -> Optional[Localization]: - """:class: `str` Returns the bundle's description promo.""" - if self._promo_description is None: - return None - return self._promo_description_localized - - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the bundle's icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) - - @property - def display_icon_2(self) -> Asset: - """:class: `Asset` Returns the bundle's icon 2.""" - return Asset._from_url(state=self._state, url=self._display_icon_2) - - @property - def vertical_promo_image(self) -> Optional[Asset]: - """:class: `Asset` Returns the bundle's vertical promo image.""" - if self._vertical_promo_image is None: - return None - return Asset._from_url(state=self._state, url=self._vertical_promo_image) - - def _add_item(self, item: Union[Skin, Buddy, Spray, PlayerCard]) -> None: - self._items.append(item) - - @property - def items(self) -> List[BundleItem]: - """:class: `List[Union[Buddy, Spray, PlayerCard]]` Returns the bundle's items.""" - return self._items - - @property - def buddies(self) -> List[Buddy]: - """:class: `List[Buddy]` Returns the bundle's buddies.""" - # avoid circular imports - from .buddies import Buddy - - return [item for item in self._items if isinstance(item, Buddy)] - - @property - def sprays(self) -> List[Spray]: - """:class: `List[Spray]` Returns the bundle's sprays.""" - # avoid circular imports - from .sprays import Spray - - return [item for item in self._items if isinstance(item, Spray)] - - @property - def player_cards(self) -> List[PlayerCard]: - """:class: `List[PlayerCard]` Returns the bundle's player cards.""" - # avoid circular imports - from .player_cards import PlayerCard - - return [item for item in self._items if isinstance(item, PlayerCard)] - - @property - def skins(self) -> List[Skin]: - """:class: `List[Skin]` Returns the bundle's skins.""" - # avoid circular imports - from .weapons import Skin - - return [item for item in self._items if isinstance(item, Skin)] + uuid: str + display_name: LocalizedField = Field(alias='displayName') + display_name_sub_text: Any = Field(alias='displayNameSubText') + description: LocalizedField + extra_description: Any = Field(alias='extraDescription') + promo_description: Any = Field(alias='promoDescription') + use_additional_context: bool = Field(alias='useAdditionalContext') + display_icon: str = Field(alias='displayIcon') + display_icon2: str = Field(alias='displayIcon2') + logo_icon: Any = Field(alias='logoIcon') + vertical_promo_image: str | None = Field(alias='verticalPromoImage') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/ceremonies.py b/valorant/models/ceremonies.py index fb736d2..3351068 100644 --- a/valorant/models/ceremonies.py +++ b/valorant/models/ceremonies.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,43 +22,14 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, Optional, Union +from .base import BaseModel, LocalizedField -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.ceremonies import Ceremony as CeremonyPayload - -# fmt: off -__all__ = ( - 'Ceremony', -) -# fmt: on +__all__ = ('Ceremony',) class Ceremony(BaseModel): - def __init__(self, state: CacheState, data: CeremonyPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the ceremony's name.""" - return self._display_name_localized + uuid: str + display_name: LocalizedField = Field(alias='displayName') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/competitive_tiers.py b/valorant/models/competitive_tiers.py index b7f09aa..acf68b8 100644 --- a/valorant/models/competitive_tiers.py +++ b/valorant/models/competitive_tiers.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,19 +22,10 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations - -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.competitive_tiers import CompetitiveTier as CompetitiveTierPayload, Tier as TierPayload +from pydantic import Field +from ..enums import DivisionTier +from .base import BaseModel, LocalizedField __all__ = ( 'CompetitiveTier', @@ -42,118 +33,21 @@ ) -class Tier: - def __init__(self, state: CacheState, data: TierPayload) -> None: - self._state: CacheState = state - self.tier: int = data['tier'] - self._name: Union[str, Dict[str, str]] = data['tierName'] - self.division: Optional[str] = data['division'] - self._division_name: Union[str, Dict[str, str]] = data['divisionName'] - self.color: str = data['color'] - self.background_color: str = data['backgroundColor'] - self._small_icon: Optional[str] = data['smallIcon'] - self._large_icon: Optional[str] = data['largeIcon'] - self._rank_triangle_down_icon: Optional[str] = data['rankTriangleDownIcon'] - self._rank_triangle_up_icon: Optional[str] = data['rankTriangleUpIcon'] - self._name_locale: Localization = Localization(self._name, locale=self._state.locale) - self._division_name_localized: Localization = Localization(self._division_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.name.locale - - def __repr__(self) -> str: - return f'' - - def __hash__(self) -> int: - return hash(self.tier) - - def __eq__(self, other) -> bool: - return isinstance(other, Tier) and self.tier == other.tier - - def __ne__(self, other) -> bool: - return not self.__eq__(other) - - def __lt__(self, other: object) -> bool: - return isinstance(other, Tier) and self.tier < other.tier - - def __le__(self, other: object) -> bool: - return isinstance(other, Tier) and self.tier <= other.tier - - def __gt__(self, other: object) -> bool: - return isinstance(other, Tier) and self.tier > other.tier - - def __ge__(self, other: object) -> bool: - return isinstance(other, Tier) and self.tier >= other.tier - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._name_locale.from_locale(locale) - - def division_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._division_name_localized.from_locale(locale) - - @property - def name(self) -> Localization: - """:class: `str` Returns the tier's name.""" - return self._name_locale - - @property - def display_name(self) -> Localization: - """:class: `Localization` alias for :attr:`name`""" - return self.name - - @property - def division_name(self) -> Localization: - """:class: `str` Returns the tier's division.""" - return self._division_name_localized - - @property - def small_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the tier's small icon.""" - if self._small_icon is None: - return None - return Asset(self._state, self._small_icon) - - @property - def large_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the tier's large icon.""" - if self._large_icon is None: - return None - return Asset(self._state, self._large_icon) - - @property - def rank_triangle_down_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the tier's rank triangle down icon.""" - if self._rank_triangle_down_icon is None: - return None - return Asset(self._state, self._rank_triangle_down_icon) - - @property - def rank_triangle_up_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the tier's rank triangle up icon.""" - if self._rank_triangle_up_icon is None: - return None - return Asset(self._state, self._rank_triangle_up_icon) +class Tier(BaseModel): + tier: int + tier_name: LocalizedField = Field(alias='tierName') + division: DivisionTier + division_name: LocalizedField = Field(alias='divisionName') + color: str + background_color: str = Field(alias='backgroundColor') + small_icon: str | None = Field(alias='smallIcon') + large_icon: str | None = Field(alias='largeIcon') + rank_triangle_down_icon: str | None = Field(alias='rankTriangleDownIcon') + rank_triangle_up_icon: str | None = Field(alias='rankTriangleUpIcon') class CompetitiveTier(BaseModel): - def __init__(self, state: CacheState, data: CompetitiveTierPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self.asset_object_name: str = data['assetObjectName'] - self._tiers: Dict[int, Tier] = {tier['tier']: Tier(state=self._state, data=tier) for tier in data['tiers']} - self.asset_path: str = data['assetPath'] - - def __str__(self) -> str: - return self.asset_object_name - - def __repr__(self) -> str: - return f'' - - @property - def tiers(self) -> List[Tier]: - """:class: `list` Returns the competitive tier's tiers.""" - return list(self._tiers.values()) - - def get_tier(self, tier: int) -> Optional[Tier]: - """Returns the tier with the given tier number.""" - return self._tiers.get(tier) + uuid: str + asset_object_name: str = Field(alias='assetObjectName') + tiers: list[Tier] + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/content_tiers.py b/valorant/models/content_tiers.py index ec8b904..7580ff8 100644 --- a/valorant/models/content_tiers.py +++ b/valorant/models/content_tiers.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,62 +22,20 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, Optional, Union +from .base import BaseModel, LocalizedField -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.content_tiers import ContentTier as ContentTierPayload - -# fmt: off -__all__ = ( - 'ContentTier', -) -# fmt: on +__all__ = ('ContentTier',) class ContentTier(BaseModel): - def __init__(self, state: CacheState, data: ContentTierPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.dev_name: str = data['devName'] - self.rank: int = data['rank'] - self.juice_value: int = data['juiceValue'] - self.juice_cost: int = data['juiceCost'] - self.highlight_color: str = data['highlightColor'] - self._display_icon: str = data['displayIcon'] - # self._old_display_icon: str = '' - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the content tier's name.""" - return self._display_name_localized - - @property - def highlight_color_rgb(self) -> str: - """:class: `str` Returns the content tier's highlight color RGB.""" - # rgba to rgb - return self.highlight_color[:-1] - - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the content tier's icon.""" - return Asset._from_url(self._state, self._display_icon) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + dev_name: str = Field(alias='devName') + rank: int + juice_value: int = Field(alias='juiceValue') + juice_cost: int = Field(alias='juiceCost') + highlight_color: str = Field(alias='highlightColor') + display_icon: str = Field(alias='displayIcon') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index 4d5d821..31663bb 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,200 +22,57 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from ..asset import Asset -from ..enums import Locale, RelationType, RewardType, try_enum -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from uuid import UUID - - from ..cache import CacheState - from ..types.contracts import ( - Chapter as ChapterPayload, - Content as ContentPayload, - Contract as ContractPayload, - Level as LevelPayload, - Reward as RewardPayload, - ) - from .agents import Agent - from .buddies import BuddyLevel - from .currencies import Currency - from .events import Event - from .player_cards import PlayerCard - from .player_titles import PlayerTitle - from .seasons import Season - from .sprays import Spray - from .weapons import SkinLevel +from pydantic import Field + +from ..enums import RelationType, RewardType +from .base import BaseModel, LocalizedField __all__ = ( 'Chapter', - 'ChapterLevel', 'Content', 'Contract', 'Level', 'Reward', ) -_log = logging.getLogger(__name__) - class Reward(BaseModel): - def __init__(self, state: CacheState, data: RewardPayload, *, free_reward: bool = False) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self.type: RewardType = try_enum(RewardType, data['type']) - self.amount: int = data['amount'] - self._is_highlighted: bool = data['isHighlighted'] - self._is_free: bool = free_reward - - def get_item(self) -> Optional[Union[Agent, SkinLevel, BuddyLevel, Currency, PlayerCard, PlayerTitle, Spray]]: - if self.type is RewardType.skin_level: - return self._state.get_skin_level(str(self.uuid)) - elif self.type is RewardType.buddy_level: - return self._state.get_buddy_level(str(self.uuid)) - elif self.type is RewardType.player_card: - return self._state.get_player_card(str(self.uuid)) - elif self.type is RewardType.player_title: - return self._state.get_player_title(str(self.uuid)) - elif self.type is RewardType.spray: - return self._state.get_spray(str(self.uuid)) - elif self.type is RewardType.agent: - return self._state.get_agent(str(self.uuid)) - elif self.type is RewardType.currency: - return self._state.get_currency(str(self.uuid)) - _log.warning(f'Unknown reward type={self.type!r} uuid={self.uuid!r}') - return None - - def is_highlighted(self) -> bool: - return self._is_highlighted - - def is_free(self) -> bool: - return self._is_free - - -class Level: - def __init__(self, state: CacheState, data: LevelPayload) -> None: - self._state: CacheState = state - self.reward: Reward = Reward(self._state, data['reward']) - self.xp: int = data['xp'] - self.vp_cost: int = data['vpCost'] - self._is_purchasable_with_vp: bool = data['isPurchasableWithVP'] - self.dough_cost: int = data['doughCost'] - self._is_purchasable_with_dough: bool = data['isPurchasableWithDough'] - - def is_purchasable_with_vp(self) -> bool: - return self._is_purchasable_with_vp - - def is_purchasable_with_dough(self) -> bool: - return self._is_purchasable_with_dough - - -class Chapter: - def __init__(self, state: CacheState, data: ChapterPayload, index: int) -> None: - self._state: CacheState = state - self._is_epilogue: bool = data['isEpilogue'] - self.levels: List[Level] = [Level(self._state, level) for level in data['levels']] - self.free_rewards: Optional[List[Reward]] = None - if data['freeRewards'] is not None: - self.free_rewards = [Reward(self._state, reward, free_reward=True) for reward in data['freeRewards']] - self.index: int = index - - def is_epilogue(self) -> bool: - return self._is_epilogue - - # helper methods - - def get_all_rewards(self) -> List[Reward]: - """:class: `List[Reward]` Returns all rewards.""" - return [level.reward for level in self.levels] - - -class Content: - def __init__(self, state: CacheState, data: ContentPayload) -> None: - self._state: CacheState = state - if data['relationType'] is None: - self.relation_type: RelationType = RelationType.none - else: - self.relation_type = try_enum(RelationType, data['relationType']) - self._relation_uuid: Optional[str] = data['relationUuid'] - self._chapters: List[Chapter] = [ - Chapter(self._state, chapter, index) for index, chapter in enumerate(data['chapters']) - ] - self.premium_reward_schedule_uuid: Optional[str] = data['premiumRewardScheduleUuid'] - self.premium_vp_cost: int = data['premiumVPCost'] - - @property - def chapters(self) -> List[Chapter]: - return self._chapters - - @property - def relationship(self) -> Optional[Union[Agent, Event, Season]]: - if self.relation_type is RelationType.agent: - return self._state.get_agent(self._relation_uuid) - elif self.relation_type is RelationType.event: - return self._state.get_event(self._relation_uuid) - elif self.relation_type is RelationType.season: - return self._state.get_season(self._relation_uuid) - if self.relation_type and self._relation_uuid: - _log.warning(f'Unknown relationship type={self.relation_type!r} uuid={self._relation_uuid!r}') - return None - - # helper methods - - def get_all_rewards(self) -> List[Reward]: - """:class: `List[Reward]` Returns all rewards.""" - return [level.reward for chapter in self.chapters for level in chapter.levels] + type: RewardType + uuid: str + amount: int + is_highlighted: bool = Field(alias='isHighlighted') + + +class Level(BaseModel): + reward: Reward + xp: int + vp_cost: int = Field(alias='vpCost') + is_purchasable_with_vp: bool = Field(alias='isPurchasableWithVP') + dough_cost: int = Field(alias='doughCost') + is_purchasable_with_dough: bool = Field(alias='isPurchasableWithDough') + + +class Chapter(BaseModel): + is_epilogue: bool = Field(alias='isEpilogue') + levels: list[Level] + free_rewards: list[Reward] | None = Field(alias='freeRewards') + + +class Content(BaseModel): + relation_type: RelationType | None = Field(alias='relationType') + relation_uuid: str | None = Field(alias='relationUuid') + chapters: list[Chapter] + premium_reward_schedule_uuid: str | None = Field(alias='premiumRewardScheduleUuid') + premium_vp_cost: int = Field(alias='premiumVPCost') class Contract(BaseModel): - def __init__(self, state: CacheState, data: ContractPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_icon: Optional[str] = data['displayIcon'] - self.ship_it: bool = data['shipIt'] - self.free_reward_schedule_uuid: str = data['freeRewardScheduleUuid'] - self.content: Content = Content(self._state, data['content']) - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def __eq__(self, other: object) -> bool: - return isinstance(other, Contract) and self.uuid == other.uuid - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def id(self) -> UUID: - """:class: `UUID` Returns the contract id.""" - return self.uuid - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the contract's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the contract's icon.""" - if self._display_icon is None: - return None - return Asset._from_url(self._state, self._display_icon) - - -ChapterLevel = Level + uuid: str + display_name: LocalizedField = Field(alias='displayName') + display_icon: str | None = Field(alias='displayIcon') + ship_it: bool = Field(alias='shipIt') + use_level_vp_cost_override: bool = Field(alias='useLevelVPCostOverride') + level_vp_cost_override: int = Field(alias='levelVPCostOverride') + free_reward_schedule_uuid: str = Field(alias='freeRewardScheduleUuid') + content: Content + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/currencies.py b/valorant/models/currencies.py index c9db898..a226e93 100644 --- a/valorant/models/currencies.py +++ b/valorant/models/currencies.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,72 +22,18 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, Optional, Union +from .base import BaseModel, LocalizedField -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.currencies import Currency as CurrencyPayload - -# fmt: off -__all__ = ( - 'Currency', -) -# fmt: on +__all__ = ('Currency',) class Currency(BaseModel): - def __init__(self, state: CacheState, data: CurrencyPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_name_singular: Union[str, Dict[str, str]] = data['displayNameSingular'] - self._display_icon: Optional[str] = data['displayIcon'] - self._large_icon: Optional[str] = data['largeIcon'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._display_name_singular_localized: Localization = Localization( - self._display_name_singular, locale=self._state.locale - ) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def display_name_singular_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_singular_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the agent's name.""" - return self._display_name_localized - - @property - def display_name_singular(self) -> Localization: - """:class: `str` Returns the agent's singular name.""" - return self._display_name_singular_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Optional[Asset]` Returns the agent's icon.""" - if self._display_icon is None: - return None - return Asset._from_url(state=self._state, url=self._display_icon) - - @property - def large_icon(self) -> Optional[Asset]: - """:class: `Optional[Asset]` Returns the agent's large icon.""" - if self._large_icon is None: - return None - return Asset._from_url(state=self._state, url=self._large_icon) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + display_name_singular: LocalizedField = Field(alias='displayNameSingular') + display_icon: str = Field(alias='displayIcon') + large_icon: str = Field(alias='largeIcon') + reward_preview_icon: str = Field(alias='rewardPreviewIcon') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/events.py b/valorant/models/events.py index e875d7a..73dfd84 100644 --- a/valorant/models/events.py +++ b/valorant/models/events.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,70 +22,19 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from datetime import datetime -from typing import TYPE_CHECKING, Dict, Optional, Union +from pydantic import Field -from .. import utils -from ..localization import Localization -from .base import BaseModel +from .base import BaseModel, LocalizedField -if TYPE_CHECKING: - import datetime - - from ..cache import CacheState - from ..enums import Locale - from ..types.events import Event as EventPayload - -# fmt: off -__all__ = ( - 'Event', -) -# fmt: on +__all__ = ('Event',) class Event(BaseModel): - def __init__(self, state: CacheState, data: EventPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._short_display_name: Union[str, Dict[str, str]] = data['shortDisplayName'] - self._start_time_iso: str = data['startTime'] - self._end_time_iso: str = data['endTime'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._short_display_name_localized: Localization = Localization( - self._short_display_name, locale=self._state.locale - ) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def short_display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._short_display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the agent's name.""" - return self._display_name_localized - - @property - def short_display_name(self) -> Localization: - """:class: `str` Returns the agent's short name.""" - return self._short_display_name_localized - - @property - def start_time(self) -> datetime.datetime: - """:class: `datetime.datetime` Returns the event's start time.""" - return utils.parse_iso_datetime(self._start_time_iso) - - @property - def end_time(self) -> datetime.datetime: - """:class: `datetime.datetime` Returns the event's end time.""" - return utils.parse_iso_datetime(self._end_time_iso) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + short_display_name: LocalizedField = Field(alias='shortDisplayName') + start_time: datetime = Field(alias='startTime') + end_time: datetime = Field(alias='endTime') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index 43eafce..3d1308f 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,164 +22,52 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations - -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.gamemodes import ( - GameFeatureOverride as GameFeatureOverridePayload, - GameMode as GameModePayload, - GameModeEquippable as GameModeEquippablePayload, - GameRuleBoolOverride as GameRuleBoolOverridePayload, - ) - from .weapons import Weapon +from pydantic import Field +from ..enums import GameRule +from .base import BaseModel, LocalizedField __all__ = ( + 'Equippable', 'GameFeatureOverride', 'GameMode', - 'GameModeEquippable', 'GameRuleBoolOverride', ) -class GameFeatureOverride: - def __init__(self, data: GameFeatureOverridePayload) -> None: - self.feature_name: str = data['featureName'] - self.state: bool = data['state'] - - def __repr__(self) -> str: - return f'' - - def __bool__(self) -> bool: - return self.state - - -class GameRuleBoolOverride: - def __init__(self, data: GameRuleBoolOverridePayload) -> None: - self.rule_name: str = data['ruleName'] - self.state: bool = data['state'] +class GameFeatureOverride(BaseModel): + feature_name: str = Field(alias='featureName') + state: bool - def __bool__(self) -> bool: - return self.state - def __repr__(self) -> str: - return f'' +class GameRuleBoolOverride(BaseModel): + rule_name: GameRule = Field(alias='ruleName') + state: bool class GameMode(BaseModel): - def __init__(self, state: CacheState, data: GameModePayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._duration: Union[str, Dict[str, str]] = data['duration'] - self.allows_match_timeouts: bool = data['allowsMatchTimeouts'] - self._is_team_voice_allowed: bool = data['isTeamVoiceAllowed'] - self._is_minimap_hidden: bool = data['isMinimapHidden'] - self.orb_count: int = data['orbCount'] - self.rounds_per_half: int = data['roundsPerHalf'] - self.team_roles: Optional[List[str]] = data['teamRoles'] - self.game_feature_overrides: Optional[List[GameFeatureOverride]] = None - if data['gameFeatureOverrides'] is not None: - self.game_feature_overrides = [GameFeatureOverride(feature) for feature in data['gameFeatureOverrides']] - self.game_rule_bool_overrides: Optional[List[GameRuleBoolOverride]] = None - if data['gameRuleBoolOverrides'] is not None: - self.game_rule_bool_overrides = [GameRuleBoolOverride(rule) for rule in data['gameRuleBoolOverrides']] - self._display_icon: Optional[str] = data['displayIcon'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._duration_localized: Localization = Localization(self._duration, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def __eq__(self, other: object) -> bool: - return isinstance(other, GameMode) and self.uuid == other.uuid - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - # def __contains__(self, item: Union[GameMode, GameModeType]) -> bool: - # ... - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def duration_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._duration_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the game mode's name.""" - return self._display_name_localized - - @property - def duration(self) -> Localization: - """:class: `str` Returns the game mode's duration.""" - return self._duration_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the game mode's display icon.""" - if self._display_icon is None: - return None - return Asset(self._state, self._display_icon) - - def is_team_voice_allowed(self) -> bool: - """:class: `bool` Returns whether the game mode allows team voice.""" - return self._is_team_voice_allowed - - def is_minimap_hidden(self) -> bool: - """:class: `bool` Returns whether the game mode hides the minimap.""" - return self._is_minimap_hidden - - -class GameModeEquippable(BaseModel): - def __init__(self, state: CacheState, data: GameModeEquippablePayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.category: Optional[str] = data['category'] - self._display_icon: str = data['displayIcon'] - self._kill_stream_icon: str = data['killStreamIcon'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the game mode's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the game mode's display icon.""" - return Asset._from_url(self._state, url=self._display_icon) - - @property - def kill_stream_icon(self) -> Asset: - """:class: `Asset` Returns the game mode's kill stream icon.""" - return Asset._from_url(self._state, url=self._kill_stream_icon) - - def weapon(self) -> Optional[Weapon]: - """:class: `Weapon` Returns the game mode's weapon.""" - # special weapon for game mode - return self._state.get_weapon(self._uuid) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + description: LocalizedField | None + duration: LocalizedField | None + economy_type: str | None = Field(alias='economyType') + allows_match_timeouts: bool = Field(alias='allowsMatchTimeouts') + is_team_voice_allowed: bool = Field(alias='isTeamVoiceAllowed') + is_minimap_hidden: bool = Field(alias='isMinimapHidden') + orb_count: int = Field(alias='orbCount') + rounds_per_half: int = Field(alias='roundsPerHalf') + team_roles: list[str] | None = Field(alias='teamRoles') + game_feature_overrides: list[GameFeatureOverride] | None = Field(alias='gameFeatureOverrides') + game_rule_bool_overrides: list[GameRuleBoolOverride] | None = Field(alias='gameRuleBoolOverrides') + display_icon: str | None = Field(alias='displayIcon') + list_view_icon_tall: str | None = Field(alias='listViewIconTall') + asset_path: str = Field(alias='assetPath') + + +class Equippable(BaseModel): + uuid: str + display_name: LocalizedField = Field(alias='displayName') + category: str + display_icon: str = Field(alias='displayIcon') + kill_stream_icon: str = Field(alias='killStreamIcon') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/gear.py b/valorant/models/gear.py index dc5a3ab..ee927b8 100644 --- a/valorant/models/gear.py +++ b/valorant/models/gear.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,61 +22,43 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from typing import Any -from typing import TYPE_CHECKING, Dict, Optional, Union +from pydantic import Field -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel, ShopData +from .base import BaseModel, LocalizedField -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.gear import Gear_ as GearPayload - -# fmt: off __all__ = ( + 'Detail', 'Gear', + 'ShopData', ) -# fmt: on - - -class Gear(BaseModel): - def __init__(self, state: CacheState, data: GearPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._description: Union[str, Dict[str, str]] = data['description'] - self._display_icon: str = data['displayIcon'] - self.asset_path: str = data['assetPath'] - self.shop_data: ShopData = ShopData(state=self._state, item=self, data=data['shopData']) - self._display_name_localized = Localization(self._display_name, locale=self._state.locale) - self._description_localized = Localization(self._description, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - def __repr__(self) -> str: - return f'' - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) +class Detail(BaseModel): + name: LocalizedField + value: LocalizedField - def description_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._description_localized.from_locale(locale) - @property - def display_name(self) -> Localization: - """:class: `str` Returns the gear's name.""" - return self._display_name_localized +class ShopData(BaseModel): + cost: int + category: str + shop_order_priority: int = Field(alias='shopOrderPriority') + category_text: LocalizedField = Field(alias='categoryText') + grid_position: Any = Field(alias='gridPosition') + can_be_trashed: bool = Field(alias='canBeTrashed') + image: Any + new_image: str = Field(alias='newImage') + new_image2: Any = Field(alias='newImage2') + asset_path: str = Field(alias='assetPath') - @property - def description(self) -> Localization: - """:class: `str` Returns the gear's description.""" - return self._description_localized - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the gear's display icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) +class Gear(BaseModel): + uuid: str + display_name: LocalizedField = Field(alias='displayName') + description: LocalizedField + descriptions: list[LocalizedField] + details: list[Detail] + display_icon: str = Field(alias='displayIcon') + asset_path: str = Field(alias='assetPath') + shop_data: ShopData = Field(alias='shopData') diff --git a/valorant/models/level_borders.py b/valorant/models/level_borders.py index ede954b..0484dd7 100644 --- a/valorant/models/level_borders.py +++ b/valorant/models/level_borders.py @@ -1,7 +1,5 @@ - - """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -23,54 +21,18 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations - -from typing import TYPE_CHECKING -from ..asset import Asset -from .base import BaseModel +from pydantic import Field -# fmt: off -__all__ = ( - 'LevelBorder', -) -# fmt: on +from .base import BaseModel, LocalizedField -if TYPE_CHECKING: - from ..cache import CacheState - from ..types.level_borders import LevelBorder as LevelBorderPayload +__all__ = ('LevelBorder',) class LevelBorder(BaseModel): - def __init__(self, state: CacheState, data: LevelBorderPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self.starting_level: int = data['startingLevel'] - self._level_number_appearance: str = data['levelNumberAppearance'] - self._small_player_card_appearance: str = data['smallPlayerCardAppearance'] - self.asset_path: str = data['assetPath'] - - def __repr__(self) -> str: - return f'' - - def __lt__(self, other: object) -> bool: - return isinstance(other, LevelBorder) and self.starting_level < other.starting_level - - def __le__(self, other: object) -> bool: - return isinstance(other, LevelBorder) and self.starting_level <= other.starting_level - - def __gt__(self, other: object) -> bool: - return isinstance(other, LevelBorder) and self.starting_level > other.starting_level - - def __ge__(self, other: object) -> bool: - return isinstance(other, LevelBorder) and self.starting_level >= other.starting_level - - @property - def level_number_appearance(self) -> Asset: - """:class: `Asset` Returns the level number appearance of the level border.""" - return Asset._from_url(state=self._state, url=self._level_number_appearance) - - @property - def small_player_card_appearance(self) -> Asset: - """:class: `Asset` Returns the small player card appearance of the level border.""" - return Asset._from_url(state=self._state, url=self._small_player_card_appearance) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + starting_level: int = Field(alias='startingLevel') + level_number_appearance: str = Field(alias='levelNumberAppearance') + small_player_card_appearance: str = Field(alias='smallPlayerCardAppearance') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/maps.py b/valorant/models/maps.py index 5172926..abca280 100644 --- a/valorant/models/maps.py +++ b/valorant/models/maps.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,135 +22,46 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from typing import Any -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.maps import Callout as CalloutPayload, Location as LocationPayload, Map as MapPayload +from pydantic import Field +from .base import BaseModel, LocalizedField __all__ = ( - 'Map', 'Callout', 'Location', + 'Map', ) -class Location: - def __init__(self, data: LocationPayload) -> None: - self.x: float = data['x'] - self.y: float = data['y'] - - def __repr__(self) -> str: - return f'' - - def __eq__(self, other: object) -> bool: - return isinstance(other, Location) and self.x == other.x and self.y == other.y - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - -class Callout: - def __init__(self, state: CacheState, data: CalloutPayload) -> None: - self._state: CacheState = state - self._region_name: Union[str, Dict[str, str]] = data['regionName'] - self._super_region_name: Union[str, Dict[str, str]] = data['superRegionName'] - self.location: Location = Location(data['location']) - self._region_name_localized: Localization = Localization(self._region_name, locale=self._state.locale) - self._super_region_name_localized: Localization = Localization( - self._super_region_name, locale=self._state.locale - ) - - def __str__(self) -> str: - return self.region_name.locale - - def __repr__(self) -> str: - attrs = [ - ('region_name', self.region_name), - ('super_region_name', self.super_region_name), - ('location', self.location), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' +class Location(BaseModel): + x: float + y: float - def __eq__(self, other: object) -> bool: - return isinstance(other, Callout) and self.region_name == other.region_name and self.location == other.location - def region_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._region_name_localized.from_locale(locale) - - def super_region_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._super_region_name_localized.from_locale(locale) - - @property - def region_name(self) -> Localization: - return self._region_name_localized - - @property - def super_region_name(self) -> Localization: - return self._super_region_name_localized +class Callout(BaseModel): + region_name: LocalizedField = Field(alias='regionName') + super_region_name: LocalizedField = Field(alias='superRegionName') + location: Location class Map(BaseModel): - def __init__(self, state: CacheState, data: MapPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._coordinates: Union[str, Dict[str, str]] = data['coordinates'] - self._list_view_icon: Optional[str] = data['listViewIcon'] - self._splash: Optional[str] = data['splash'] - self.asset_path: str = data['assetPath'] - self.url: str = data['mapUrl'] - self.x_multiplier: float = data['xMultiplier'] - self.y_multiplier: float = data['yMultiplier'] - self.x_scalar_to_add: float = data['xScalarToAdd'] - self.y_scalar_to_add: float = data['yScalarToAdd'] - self.callouts: Optional[List[Callout]] = None - if data['callouts'] is not None: - self.callouts = [Callout(self._state, callout) for callout in data['callouts']] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._coordinates_localized: Localization = Localization(self._coordinates, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def coordinates_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._coordinates_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the mission's name.""" - return self._display_name_localized - - @property - def coordinates(self) -> Localization: - """:class: `str` Returns the mission's coordinates.""" - return self._coordinates_localized - - @property - def list_view_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the mission's list view icon.""" - if self._list_view_icon is None: - return None - return Asset._from_url(state=self._state, url=self._list_view_icon) - - @property - def splash(self) -> Optional[Asset]: - """:class: `Asset` Returns the mission's splash.""" - if self._splash is None: - return None - return Asset._from_url(state=self._state, url=self._splash) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + narrative_description: Any = Field(alias='narrativeDescription') + tactical_description: LocalizedField | None = Field(alias='tacticalDescription') + coordinates: LocalizedField | None + display_icon: str | None = Field(alias='displayIcon') + list_view_icon: str = Field(alias='listViewIcon') + list_view_icon_tall: str | None = Field(alias='listViewIconTall') + splash: str + stylized_background_image: str | None = Field(alias='stylizedBackgroundImage') + premier_background_image: str | None = Field(alias='premierBackgroundImage') + asset_path: str = Field(alias='assetPath') + map_url: str = Field(alias='mapUrl') + x_multiplier: float = Field(alias='xMultiplier') + y_multiplier: float = Field(alias='yMultiplier') + x_scalar_to_add: float = Field(alias='xScalarToAdd') + y_scalar_to_add: float = Field(alias='yScalarToAdd') + callouts: list[Callout] | None diff --git a/valorant/models/missions.py b/valorant/models/missions.py index 4ff3e9e..407de17 100644 --- a/valorant/models/missions.py +++ b/valorant/models/missions.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,118 +22,31 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from ..enums import MissionTag, MissionType +from .base import BaseModel, LocalizedField -from .. import utils -from ..enums import Locale, MissionType, try_enum -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - import datetime - - from ..cache import CacheState - from ..types.missions import Mission as MissionPayload, Objective as ObjectivePayload - -# fmt: off __all__ = ( 'Mission', + 'Objective', ) -# fmt: on class Objective(BaseModel): - def __init__(self, data: ObjectivePayload) -> None: - super().__init__(data['objectiveUuid']) - self.value: int = data['value'] - - def __repr__(self) -> str: - return f'' - - def __str__(self) -> str: - return str(self.value) - - def __int__(self) -> int: - return self.value - - def __eq__(self, other: object) -> bool: - return isinstance(other, Objective) and self.value == other.value - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __lt__(self, other: object) -> bool: - return isinstance(other, Objective) and self.value < other.value - - def __le__(self, other: object) -> bool: - return isinstance(other, Objective) and self.value <= other.value - - def __gt__(self, other: object) -> bool: - return isinstance(other, Objective) and self.value > other.value - - def __ge__(self, other: object) -> bool: - return isinstance(other, Objective) and self.value >= other.value + objective_uuid: str = Field(alias='objectiveUuid') + value: int class Mission(BaseModel): - def __init__(self, state: CacheState, data: MissionPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Optional[Union[str, Dict[str, str]]] = data['displayName'] - self._title: Optional[Union[str, Dict[str, str]]] = data['title'] - self.type: MissionType = MissionType.none - if data['type'] is not None: - self.type = try_enum(MissionType, data['type']) - self.xp_grant: int = data['xpGrant'] - self.progress_to_complete: int = data['progressToComplete'] - self._activation_date_iso: str = data['activationDate'] - self._expiration_date_iso: str = data['expirationDate'] - self.tags: Optional[List[str]] = data['tags'] - self.objectives: Optional[List[Objective]] = None - if data['objectives'] is not None: - self.objectives = [Objective(obj) for obj in data['objectives']] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._title_localized: Localization = Localization(self._title, locale=self._state.locale) - - def __str__(self) -> str: - return self.title.locale - - def __repr__(self) -> str: - return f'' - - def __int__(self) -> int: - return self.xp_grant - - @property - def xp(self) -> int: - """:class: `int` alias for :attr: `Mission.xp_grant`""" - return self.xp_grant - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def title_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._title_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the mission's name.""" - return self._display_name_localized - - @property - def title(self) -> Localization: - """:class: `str` Returns the mission's title.""" - return self._title_localized - - @property - def activation_date(self) -> Optional[datetime.datetime]: - """:class: `datetime.datetime` Returns the mission's activation date.""" - return utils.parse_iso_datetime(self._activation_date_iso) if self._activation_date_iso else None - - @property - def expiration_date(self) -> Optional[datetime.datetime]: - """:class: `datetime.datetime` Returns the mission's expiration date.""" - return utils.parse_iso_datetime(self._expiration_date_iso) if self._expiration_date_iso else None + uuid: str + display_name: LocalizedField | None = Field(alias='displayName') + title: LocalizedField | None + type: MissionType | None + xp_grant: int = Field(alias='xpGrant') + progress_to_complete: int = Field(alias='progressToComplete') + activation_date: str = Field(alias='activationDate') + expiration_date: str = Field(alias='expirationDate') + tags: list[MissionTag] | None + objectives: list[Objective] | None + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index e38e8d4..dcd61fc 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,89 +22,20 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, Optional, Union +from .base import BaseModel, LocalizedField -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.player_cards import PlayerCard as PlayerCardPayload - from .themes import Theme - -# fmt: off -__all__ = ( - 'PlayerCard', -) -# fmt: on +__all__ = ('PlayerCard',) class PlayerCard(BaseModel): - def __init__(self, *, state: CacheState, data: PlayerCardPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._is_hidden_if_not_owned: bool = data.get('isHiddenIfNotOwned', False) - self.theme_uuid: Optional[str] = data['themeUuid'] - self._display_icon: Optional[str] = data['displayIcon'] - self._small_art: Optional[str] = data['smallArt'] - self._wide_art: Optional[str] = data['wideArt'] - self._large_art: Optional[str] = data['largeArt'] - self.asset_path: str = data['assetPath'] - - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the player card's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the player card's icon.""" - if self._display_icon is None: - return None - return Asset._from_url(state=self._state, url=self._display_icon) - - @property - def small_art(self) -> Optional[Asset]: - """:class: `Asset` Returns the player card's small art.""" - if self._small_art is None: - return None - return Asset._from_url(state=self._state, url=self._small_art) - - @property - def wide_art(self) -> Optional[Asset]: - """:class: `Asset` Returns the player card's wide art.""" - if self._wide_art is None: - return None - return Asset._from_url(state=self._state, url=self._wide_art) - - @property - def large_art(self) -> Optional[Asset]: - """:class: `Asset` Returns the player card's large art.""" - if self._large_art is None: - return None - return Asset._from_url(state=self._state, url=self._large_art) - - @property - def theme(self) -> Optional[Theme]: - """:class: `Theme` Returns the player card's theme.""" - return self._state.get_theme(self.theme_uuid) - - def is_hidden_if_not_owned(self) -> bool: - """:class: `bool` Returns whether the player card is hidden if not owned.""" - return self._is_hidden_if_not_owned + uuid: str + display_name: LocalizedField = Field(alias='displayName') + is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') + theme_uuid: str | None = Field(alias='themeUuid') + display_icon: str = Field(alias='displayIcon') + small_art: str = Field(alias='smallArt') + wide_art: str = Field(alias='wideArt') + large_art: str = Field(alias='largeArt') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/player_titles.py b/valorant/models/player_titles.py index f56f074..0fd7b6e 100644 --- a/valorant/models/player_titles.py +++ b/valorant/models/player_titles.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,59 +22,16 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, Optional, Union +from .base import BaseModel, LocalizedField -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.player_titles import PlayerTitle as PlayerTitlePayload - - -# fmt: off -__all__ = ( - 'PlayerTitle', -) -# fmt: on +__all__ = ('PlayerTitle',) class PlayerTitle(BaseModel): - def __init__(self, *, state: CacheState, data: PlayerTitlePayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._title_text: Union[str, Dict[str, str]] = data['titleText'] - self._is_hidden_if_not_owned: bool = data.get('isHiddenIfNotOwned', False) - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - self._title_text_localized: Localization = Localization(self._title_text, locale=self._state.locale) - - def __str__(self) -> str: - return self.text.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - def title_text_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._title_text_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the player title's name.""" - return self._display_name_localized - - @property - def text(self) -> Localization: - """:class: `str` Returns the player title's title text.""" - return self._title_text_localized - - def is_hidden_if_not_owned(self) -> bool: - """:class: `bool` Returns whether the player title is hidden if not owned.""" - return self._is_hidden_if_not_owned + uuid: str + display_name: LocalizedField | None = Field(alias='displayName') + title_text: LocalizedField | None = Field(alias='titleText') + is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 737764e..ee937f8 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,157 +22,44 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from datetime import datetime -from typing import TYPE_CHECKING, Dict, List, Optional, Union - -from .. import utils -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - import datetime - from uuid import UUID - - from ..cache import CacheState - from ..enums import Locale - from ..types.seasons import ( - Border as BorderPayload, - CompetitiveSeason as CompetitiveSeasonPayload, - Season as SeasonPayload, - ) - from .competitive_tiers import CompetitiveTier +from pydantic import Field +from .base import BaseModel, LocalizedField __all__ = ( 'Border', - 'CompetitiveSeason', + 'Competitive', 'Season', ) class Season(BaseModel): - def __init__(self, state: CacheState, data: SeasonPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.type: Optional[str] = data['type'] - self._start_time_iso: Union[str, datetime.datetime] = data['startTime'] - self._end_time_iso: Union[str, datetime.datetime] = data['endTime'] - self.parent_uuid: Optional[str] = data['parentUuid'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - attrs = [('display_name', self.display_name), ('type', self.type)] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - def __eq__(self, other: object) -> bool: - return isinstance(other, Season) and other.id == self.id - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - @property - def id(self) -> UUID: - return self.uuid - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the season's name.""" - return self._display_name_localized - - @property - def start_time(self) -> datetime.datetime: - """:class: `datetime.datetime` Returns the season's start time.""" - return utils.parse_iso_datetime(str(self._start_time_iso)) - - @property - def end_time(self) -> datetime.datetime: - """:class: `datetime.datetime` Returns the season's end time.""" - return utils.parse_iso_datetime(str(self._end_time_iso)) - - @property - def parent(self) -> Optional[Season]: - """:class: `Season` Returns the season's parent.""" - return self._state.get_season(self.parent_uuid) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + title: LocalizedField | None + type: str | None + start_time: datetime = Field(alias='startTime') + end_time: datetime = Field(alias='endTime') + parent_uuid: str | None = Field(alias='parentUuid') + asset_path: str = Field(alias='assetPath') class Border(BaseModel): - def __init__(self, *, state: CacheState, data: BorderPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self.level: int = data['level'] - self.wins_required: int = data['winsRequired'] - self._display_icon: Optional[str] = data['displayIcon'] - self._small_icon: Optional[str] = data['smallIcon'] - self.asset_path: str = data['assetPath'] - - def __repr__(self) -> str: - attrs = [('level', self.level), ('wins_required', self.wins_required)] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the border's display icon.""" - if self._display_icon is None: - return None - return Asset._from_url(self._state, url=self._display_icon) - - @property - def small_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the border's small icon.""" - if self._small_icon is None: - return None - return Asset._from_url(self._state, url=self._small_icon) - - -class CompetitiveSeason(BaseModel): - def __init__(self, state: CacheState, data: CompetitiveSeasonPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._start_time_iso: Union[str, datetime.datetime] = data['startTime'] - self._end_time_iso: Union[str, datetime.datetime] = data['endTime'] - self.season_uuid: str = data['seasonUuid'] - self.competitive_tiers_uuid: str = data['competitiveTiersUuid'] - self.borders: Optional[List[Border]] = None - if data['borders'] is not None: - self.borders = [Border(state=self._state, data=border) for border in data['borders']] - self.asset_path: str = data['assetPath'] - - def __repr__(self) -> str: - attrs = [ - ('start_time', self.start_time), - ('end_time', self.end_time), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - @property - def start_time(self) -> datetime.datetime: - """:class: `datetime.datetime` Returns the season's start time.""" - return utils.parse_iso_datetime(str(self._start_time_iso)) - - @property - def end_time(self) -> datetime.datetime: - """:class: `datetime.datetime` Returns the season's end time.""" - return utils.parse_iso_datetime(str(self._end_time_iso)) - - @property - def season(self) -> Optional[Season]: - """:class: `Season` Returns the season.""" - return self._state.get_season(self.season_uuid) - - @property - def competitive_tiers(self) -> Optional[CompetitiveTier]: - """:class: `CompetitiveTier` Returns the competitive tiers.""" - return self._state.get_competitive_tier(self.competitive_tiers_uuid) + uuid: str + level: int + wins_required: int = Field(alias='winsRequired') + display_icon: str = Field(alias='displayIcon') + small_icon: str | None = Field(alias='smallIcon') + asset_path: str = Field(alias='assetPath') + + +class Competitive(BaseModel): + uuid: str + start_time: datetime = Field(alias='startTime') + end_time: datetime = Field(alias='endTime') + season_uuid: str = Field(alias='seasonUuid') + competitive_tiers_uuid: str = Field(alias='competitiveTiersUuid') + borders: list[Border] | None + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index 6848e3d..6f80cc4 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -24,133 +24,35 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from pydantic import Field -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.sprays import Spray as SprayPayload, SprayLevel as SprayLevelPayload - from .themes import Theme +from .base import BaseModel, LocalizedField __all__ = ( + 'Level', 'Spray', - 'SprayLevel', ) -class Spray(BaseModel): - def __init__(self, *, state: CacheState, data: SprayPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.category: Optional[str] = data['category'] - self.theme_uuid: Optional[str] = data['themeUuid'] - self._is_null_spray: bool = data['isNullSpray'] - self._display_icon: str = data['displayIcon'] - self._full_icon: Optional[str] = data['fullIcon'] - self._full_transparent_icon: Optional[str] = data['fullTransparentIcon'] - self._animation_png: Optional[str] = data['animationPng'] - self._animation_gif: Optional[str] = data['animationGif'] - self.asset_path: str = data['assetPath'] - self.levels: List[Level] = [Level(state=self._state, data=level, parent=self) for level in data['levels']] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the skin's name.""" - return self._display_name_localized - - @property - def theme(self) -> Optional[Theme]: - if self.theme_uuid is None: - return None - return self._state.get_theme(self.theme_uuid) - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) - - @property - def full_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's full icon.""" - if self._full_icon is None: - return None - return Asset._from_url(state=self._state, url=self._full_icon) - - @property - def full_transparent_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's full transparent icon.""" - if self._full_transparent_icon is None: - return None - return Asset._from_url(state=self._state, url=self._full_transparent_icon) - - @property - def animation_png(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's animation png.""" - if self._animation_png is None: - return None - return Asset._from_url(state=self._state, url=self._animation_png) - - @property - def animation_gif(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's animation gif.""" - if self._animation_gif is None: - return None - return Asset._from_url(state=self._state, url=self._animation_gif) - - def is_null_spray(self) -> bool: - """:class: `bool` Returns a boolean representing whether the skin is a null spray.""" - return self._is_null_spray - - class Level(BaseModel): - def __init__(self, state: CacheState, data: SprayLevelPayload, parent: Spray) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self.spray_level: int = data['sprayLevel'] - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_icon: Optional[str] = data['displayIcon'] - self.asset_path: str = data['assetPath'] - self.parent: Spray = parent - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' + uuid: str + spray_level: int = Field(alias='sprayLevel') + display_name: LocalizedField = Field(alias='displayName') + display_icon: str | None = Field(alias='displayIcon') + asset_path: str = Field(alias='assetPath') - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - @property - def display_name(self) -> Localization: - """:class: `str` Returns the spray's name.""" - return self._display_name_localized - - @property - def level(self) -> int: - """:class: `int` alias for :attr: `SprayLevel.spray_level`""" - return self.spray_level - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `str` Returns the spray's display icon.""" - return Asset._from_url(state=self._state, url=self._display_icon) if self._display_icon else None - - -SprayLevel = Level +class Spray(BaseModel): + uuid: str + display_name: LocalizedField = Field(alias='displayName') + category: str | None + theme_uuid: str | None = Field(alias='themeUuid') + is_null_spray: bool = Field(alias='isNullSpray') + hide_if_not_owned: bool = Field(alias='hideIfNotOwned') + display_icon: str | None = Field(alias='displayIcon') + full_icon: str | None = Field(alias='fullIcon') + full_transparent_icon: str | None = Field(alias='fullTransparentIcon') + animation_png: str | None = Field(alias='animationPng') + animation_gif: str | None = Field(alias='animationGif') + asset_path: str = Field(alias='assetPath') + levels: list[Level] diff --git a/valorant/models/themes.py b/valorant/models/themes.py index e883206..a7ce808 100644 --- a/valorant/models/themes.py +++ b/valorant/models/themes.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -22,60 +22,16 @@ DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations +from pydantic import Field -from typing import TYPE_CHECKING, Dict, Optional, Union +from .base import BaseModel, LocalizedField -from ..asset import Asset -from ..localization import Localization -from .base import BaseModel - -if TYPE_CHECKING: - from ..cache import CacheState - from ..enums import Locale - from ..types.themes import Theme as ThemePayload - -# fmt: off -__all__ = ( - 'Theme', -) -# fmt: on +__all__ = ('Theme',) class Theme(BaseModel): - def __init__(self, state: CacheState, data: ThemePayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_icon: Optional[str] = data['displayIcon'] - self._store_featured_image: Optional[str] = data['storeFeaturedImage'] - self.asset_path: str = data['assetPath'] - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the ceremony's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the ceremony's display icon.""" - if self._display_icon is None: - return None - return Asset._from_url(self._state, self._display_icon) - - @property - def store_featured_image(self) -> Optional[Asset]: - """:class: `Asset` Returns the ceremony's store featured image.""" - if self._store_featured_image is None: - return None - return Asset._from_url(self._state, self._store_featured_image) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + display_icon: str | None = Field(alias='displayIcon') + store_featured_image: str | None = Field(alias='storeFeaturedImage') + asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/version.py b/valorant/models/version.py index 418ee2b..5e0c7ee 100644 --- a/valorant/models/version.py +++ b/valorant/models/version.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -24,61 +24,19 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from datetime import datetime -from .. import utils +from pydantic import BaseModel, Field -if TYPE_CHECKING: - import datetime +__all__ = ('Version',) - from ..cache import CacheState - from ..types.version import Version_ as VersionPayload -# fmt: off -__all__ = ( - 'Version', -) -# fmt: on - - -class Version: - def __init__(self, state: Optional[CacheState], data: VersionPayload) -> None: - self._state: Optional[CacheState] = state - self.manifest_id: str = data['manifestId'] - self.branch: str = data['branch'] - self.version: str = data['version'] - self.build_version: str = data['buildVersion'] - self.engine_version: str = data['engineVersion'] - self.riot_client_version: str = data['riotClientVersion'] - self.riot_client_build: str = data['riotClientBuild'] - self._build_date_iso: str = data['buildDate'] - - def __eq__(self, other: object) -> bool: - return isinstance(other, Version) and other.manifest_id == self.manifest_id - - def __ne__(self, other: object) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return hash(self.version) - - def __str__(self) -> str: - return self.version - - def __repr__(self) -> str: - attrs = [ - ('manifest_id', self.manifest_id), - ('branch', self.branch), - ('version', self.version), - ('build_version', self.build_version), - ('engine_version', self.engine_version), - ('riot_client_version', self.riot_client_version), - ('build_date_iso', self.build_date), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - @property - def build_date(self) -> datetime.datetime: - """:class:`datetime.datetime`: The build date of the version.""" - return utils.parse_iso_datetime(self._build_date_iso) +class Version(BaseModel): + manifest_id: str = Field(alias='manifestId') + branch: str + version: str + build_version: str = Field(alias='buildVersion') + engine_version: str = Field(alias='engineVersion') + riot_client_version: str = Field(alias='riotClientVersion') + riot_client_build: str = Field(alias='riotClientBuild') + build_date: datetime = Field(alias='buildDate') diff --git a/valorant/models/weapons.py b/valorant/models/weapons.py index b5653e5..cd78e5e 100644 --- a/valorant/models/weapons.py +++ b/valorant/models/weapons.py @@ -1,5 +1,5 @@ """ -The MIT License (MIT) +The MIT License (MIT). Copyright (c) 2023-present STACiA @@ -24,507 +24,123 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from pydantic import Field -from .. import utils -from ..asset import Asset -from ..enums import MELEE_WEAPON_ID, Locale -from ..localization import Localization -from .base import BaseModel, ShopData - -if TYPE_CHECKING: - from ..cache import CacheState - from ..types.weapons import ( - AdsStats as AdsStatsPayload, - AirBurstStats as AirBurstStatsPayload, - AltShotgunStats as AltShotgunStatsPayload, - DamageRange as DamageRangePayload, - Skin as SkinPayload, - SkinChroma as SkinChromaPayload, - SkinLevel as SkinLevelPayload, - Weapon as WeaponPayload, - WeaponStats as WeaponStatsPayload, - ) - from .content_tiers import ContentTier - from .themes import Theme +from ..enums import WeaponCategory +from .base import BaseModel, LocalizedField __all__ = ( 'AdsStats', 'AirBurstStats', - 'AltShotgunStats', + 'AltShotgunStat', 'DamageRange', - 'Skin', - 'SkinChroma', - 'SkinLevel', - 'Weapon', + 'GridPosition', + 'ShopData', 'WeaponStats', ) -class AdsStats: - def __init__(self, data: AdsStatsPayload) -> None: - self.zoom_multiplier: float = data['zoomMultiplier'] - self.fire_rate: float = data['fireRate'] - self.run_speed_multiplier: float = data['runSpeedMultiplier'] - self.burst_count: float = data['burstCount'] - self.first_bullet_accuracy: float = data['firstBulletAccuracy'] - - def __repr__(self) -> str: - attrs = [ - ('zoom_multiplier', self.zoom_multiplier), - ('fire_rate', self.fire_rate), - ('run_speed_multiplier', self.run_speed_multiplier), - ('burst_count', self.burst_count), - ('first_bullet_accuracy', self.first_bullet_accuracy), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - -class AltShotgunStats: - def __init__(self, data: AltShotgunStatsPayload) -> None: - self.shotgun_pellet_count: float = data['shotgunPelletCount'] - self.burst_rate: float = data['burstRate'] - - def __repr__(self) -> str: - return f'' +class AdsStats(BaseModel): + zoom_multiplier: float = Field(alias='zoomMultiplier') + fire_rate: float = Field(alias='fireRate') + run_speed_multiplier: float = Field(alias='runSpeedMultiplier') + burst_count: int = Field(alias='burstCount') + first_bullet_accuracy: float = Field(alias='firstBulletAccuracy') -class AirBurstStats: - def __init__(self, data: AirBurstStatsPayload) -> None: - self.shotgun_pellet_count: float = data['shotgunPelletCount'] - self.burst_distance: float = data['burstDistance'] +class DamageRange(BaseModel): + range_start_meters: int = Field(alias='rangeStartMeters') + range_end_meters: int = Field(alias='rangeEndMeters') + head_damage: float = Field(alias='headDamage') + body_damage: int = Field(alias='bodyDamage') + leg_damage: float = Field(alias='legDamage') - def __repr__(self) -> str: - return f'' +class AltShotgunStat(BaseModel): + shotgun_pellet_count: int = Field(alias='shotgunPelletCount') + burst_rate: float = Field(alias='burstRate') -class DamageRange: - def __init__(self, data: DamageRangePayload) -> None: - self.range_start_meters: float = data['rangeStartMeters'] - self.range_end_meters: float = data['rangeEndMeters'] - self.head_damage: float = data['headDamage'] - self.body_damage: float = data['bodyDamage'] - self.leg_damage: float = data['legDamage'] - def __repr__(self) -> str: - attrs = [ - ('range_start_meters', self.range_start_meters), - ('range_end_meters', self.range_end_meters), - ('head_damage', self.head_damage), - ('body_damage', self.body_damage), - ('leg_damage', self.leg_damage), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' +class AirBurstStats(BaseModel): + shotgun_pellet_count: int = Field(alias='shotgunPelletCount') + burst_distance: float = Field(alias='burstDistance') -class WeaponStats: - def __init__(self, data: WeaponStatsPayload) -> None: - self.fire_rate: float = data['fireRate'] - self.magazine_size: int = data['magazineSize'] - self.run_speed_multiplier: float = data['runSpeedMultiplier'] - self.equip_time_seconds: float = data['equipTimeSeconds'] - self.reload_time_seconds: float = data['reloadTimeSeconds'] - self.first_bullet_accuracy: float = data['firstBulletAccuracy'] - self.shotgun_pellet_count: int = data['shotgunPelletCount'] - self.wall_penetration: Optional[str] = data['wallPenetration'] - self.feature: Optional[str] = data['feature'] - self.fire_mode: Optional[str] = data['fireMode'] - self.alt_fire_type: Optional[str] = data['altFireType'] - self.ads_stats: Optional[AdsStats] = None - if data['adsStats'] is not None: - self.ads_stats = AdsStats(data['adsStats']) - self.alt_shotgun_stats: Optional[AltShotgunStats] = None - if data['altShotgunStats'] is not None: - self.alt_shotgun_stats = AltShotgunStats(data['altShotgunStats']) - self.air_burst_stats: Optional[AirBurstStats] = None - if data['airBurstStats'] is not None: - self.air_burst_stats = AirBurstStats(data['airBurstStats']) - self.damage_ranges: List[DamageRange] = [DamageRange(x) for x in data['damageRanges']] - - def __repr__(self) -> str: - attrs = [ - ('fire_rate', self.fire_rate), - ('magazine_size', self.magazine_size), - ('run_speed_multiplier', self.run_speed_multiplier), - ('equip_time_seconds', self.equip_time_seconds), - ('reload_time_seconds', self.reload_time_seconds), - ('first_bullet_accuracy', self.first_bullet_accuracy), - ('shotgun_pellet_count', self.shotgun_pellet_count), - ('wall_penetration', self.wall_penetration), - ('feature', self.feature), - ('fire_mode', self.fire_mode), - ('alt_fire_type', self.alt_fire_type), - ('ads_stats', self.ads_stats), - ('alt_shotgun_stats', self.alt_shotgun_stats), - ('air_burst_stats', self.air_burst_stats), - ('damage_ranges', self.damage_ranges), - ] - joined = ' '.join('%s=%r' % t for t in attrs) - return f'<{self.__class__.__name__} {joined}>' - - -class Weapon(BaseModel): - def __init__(self, *, state: CacheState, data: WeaponPayload) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.category: str = data['category'] - self._default_skin_uuid: str = data['defaultSkinUuid'] - self._display_icon: str = data['displayIcon'] - self._kill_stream_icon: str = data['killStreamIcon'] - self.asset_path: str = data['assetPath'] - self.weapon_stats: Optional[WeaponStats] = None - if data['weaponStats'] is not None: - self.weapon_stats = WeaponStats(data['weaponStats']) - self.shop_data: Optional[ShopData] = None - if data['shopData'] is not None: - self.shop_data = ShopData(state=self._state, item=self, data=data['shopData']) - self.skins: List[Skin] = [Skin(state=self._state, data=skin, parent=self) for skin in data['skins']] - self._is_melee: bool = True if self.uuid == MELEE_WEAPON_ID else False - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale +class WeaponStats(BaseModel): + fire_rate: float = Field(alias='fireRate') + magazine_size: int = Field(alias='magazineSize') + run_speed_multiplier: float = Field(alias='runSpeedMultiplier') + equip_time_seconds: float = Field(alias='equipTimeSeconds') + reload_time_seconds: float = Field(alias='reloadTimeSeconds') + first_bullet_accuracy: float = Field(alias='firstBulletAccuracy') + shotgun_pellet_count: int = Field(alias='shotgunPelletCount') + wall_penetration: str = Field(alias='wallPenetration') + feature: str | None + fire_mode: str | None = Field(alias='fireMode') + alt_fire_type: str | None = Field(alias='altFireType') + ads_stats: AdsStats | None = Field(alias='adsStats') + alt_shotgun_stats: AltShotgunStat | None = Field(alias='altShotgunStats') + air_burst_stats: AirBurstStats | None = Field(alias='airBurstStats') + damage_ranges: list[DamageRange] = Field(alias='damageRanges') - def __repr__(self) -> str: - return f'' - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) +class GridPosition(BaseModel): + row: int + column: int - @property - def display_name(self) -> Localization: - """:class: `str` Returns the weapon's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Asset: - """:class: `Asset` Returns the weapon's icon.""" - return Asset._from_url(self._state, self._display_icon) - - @property - def kill_stream_icon(self) -> Asset: - """:class: `Asset` Returns the weapon's kill stream icon.""" - return Asset._from_url(self._state, self._kill_stream_icon) - - def is_melee(self) -> bool: - """:class: `bool` Returns whether the weapon is a melee weapon.""" - return self._is_melee - - @property - def stats(self) -> Optional[WeaponStats]: - """:class: `Optional[WeaponStats]` alias for :attr: `weapon_stats`""" - return self.weapon_stats - - # helpers - - def is_random(self) -> bool: - return 'random' in self.asset_path.lower() - - -class Skin(BaseModel): - def __init__(self, *, state: CacheState, data: SkinPayload, parent: Weapon) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.theme_uuid: str = data['themeUuid'] - self.content_tier_uuid: Optional[str] = data['contentTierUuid'] - self._display_icon: str = data['displayIcon'] - self._wallpaper: Optional[str] = data['wallpaper'] - self.asset_path: str = data['assetPath'] - self.chromas: List[Chroma] = [Chroma(state=self._state, data=chroma, parent=self) for chroma in data['chromas']] - self.levels: List[Level] = [ - Level(state=self._state, data=level, parent=self, level_number=index) - for index, level in enumerate(data['levels']) - ] - self.parent: Weapon = parent - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the skin's name.""" - return self._display_name_localized - - @property - def theme(self) -> Optional[Theme]: - """:class: `Theme` Returns the skin's theme uuid.""" - return self._state.get_theme(self.theme_uuid) - - @property - def content_tier(self) -> Optional[ContentTier]: - """:class: `ContentTier` Returns the skin's rarity.""" - if self.content_tier_uuid is None: - return None - return self._state.get_content_tier(self.content_tier_uuid) - - @property - def rarity(self) -> Optional[ContentTier]: - """:class: `ContentTier` alias for :attr: `content_tier`""" - return self.content_tier - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon.""" - if self._display_icon is None: - return None - return Asset._from_url(self._state, self._display_icon) - - @property - def display_icon_fix(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon.""" - display_icon = self._display_icon or (self.levels[0].display_icon if len(self.levels) > 0 else None) - if display_icon is None: - return None - return Asset._from_url(self._state, str(display_icon)) - - @property - def wallpaper(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's wallpaper.""" - if self._wallpaper is None: - return None - return Asset._from_url(self._state, url=self._wallpaper) - - def is_melee(self) -> bool: - """:class: `bool` Returns whether the bundle is a melee.""" - return self.parent.is_melee() - - def is_random(self) -> bool: - return 'random' in self.asset_path.lower() - - # helpers - - def get_skin_level(self, level: int) -> Optional[Level]: - """get the skin's level with the given level. - - Parameters - ---------- - level: :class: `int` - The level of the skin level to get. - - Returns - ------- - Optional[:class: `SkinLevel`] - The skin level with the given level. - """ - return next((skin_level for skin_level in self.levels if skin_level.level_number == level), None) +class ShopData(BaseModel): + cost: int + category: str + shop_order_priority: int = Field(alias='shopOrderPriority') + category_text: LocalizedField = Field(alias='categoryText') + grid_position: GridPosition | None = Field(alias='gridPosition') + can_be_trashed: bool = Field(alias='canBeTrashed') + image: str | None + new_image: str = Field(alias='newImage') + new_image2: str | None = Field(alias='newImage2') + asset_path: str = Field(alias='assetPath') class Chroma(BaseModel): - def __init__(self, *, state: CacheState, data: SkinChromaPayload, parent: Skin) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self._display_icon: Optional[str] = data['displayIcon'] - self._full_render: str = data['fullRender'] - self._swatch: Optional[str] = data['swatch'] - self._streamed_video: Optional[str] = data['streamedVideo'] - self.asset_path: str = data['assetPath'] - self.parent: Skin = parent - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return self.display_name.locale - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - """Returns the skin's display name localized to the given locale.""" - return self._display_name_localized.from_locale(locale=locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the skin's name.""" - return self._display_name_localized - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon.""" - if self._display_icon is None: - return None - return Asset._from_url(self._state, url=self._display_icon) - - @property - def display_icon_fix(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon with fixed white background.""" - - skin = self.parent - - if skin is None: - return self.display_icon - - display_icon = skin.display_icon - if len(skin.levels) > 0: - display_icon = skin.levels[0].display_icon - - weapon = skin.parent - if weapon is None: - return display_icon - - self_name = utils.removeprefix(self.display_name.default, 'Standard ') - if self_name.lower() == weapon.display_name.default.lower(): # check if skin name is same as weapon name - display_icon = weapon.display_icon or display_icon - - return display_icon - - @property - def full_render(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon full render.""" - if self._full_render is None: - return None - return Asset._from_url(self._state, url=self._full_render) - - @property - def swatch(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's swatch.""" - if self._swatch is None: - return None - return Asset._from_url(self._state, url=self._swatch) - - @property - def streamed_video(self) -> Optional[Asset]: - """:class: `Optional[Asset]` Returns the skin's video.""" - if self._streamed_video is None: - return None - return Asset._from_url(self._state, url=self._streamed_video) - - @property - def video(self) -> Optional[Asset]: - """:class: `Asset` alias for streamed_video.""" - return self.streamed_video - - # helpers - - @property - def theme(self) -> Optional[Theme]: - """:class: `Theme` Returns the skin's theme uuid.""" - return self.parent.theme - - @property - def content_tier(self) -> Optional[ContentTier]: - """:class: `ContentTier` Returns the skin's rarity.""" - return self.parent.content_tier - - @property - def rarity(self) -> Optional[ContentTier]: - """:class: `ContentTier` alias for content_tier.""" - return self.content_tier - - def is_melee(self) -> bool: - """:class: `bool` Returns whether the bundle is a melee.""" - return self.parent.is_melee() - - def is_random(self) -> bool: - return 'random' in self.asset_path.lower() + uuid: str + display_name: LocalizedField = Field(alias='displayName') + display_icon: str | None = Field(alias='displayIcon') + full_render: str = Field(alias='fullRender') + swatch: str | None + streamed_video: str | None = Field(alias='streamedVideo') + asset_path: str = Field(alias='assetPath') class Level(BaseModel): - def __init__(self, *, state: CacheState, data: SkinLevelPayload, parent: Skin, level_number: int) -> None: - super().__init__(data['uuid']) - self._state: CacheState = state - self._display_name: Union[str, Dict[str, str]] = data['displayName'] - self.level_item: Optional[str] = data['levelItem'] - self._display_icon: Optional[str] = data['displayIcon'] - self._streamed_video: Optional[str] = data['streamedVideo'] - self.asset_path: str = data['assetPath'] - self._level_number: int = level_number - self._is_level_one: bool = level_number == 0 - self.parent: Skin = parent - self._display_name_localized: Localization = Localization(self._display_name, locale=self._state.locale) - - def __str__(self) -> str: - return str(self.display_name) - - def __repr__(self) -> str: - return f'' - - def display_name_localized(self, locale: Optional[Union[Locale, str]] = None) -> str: - return self._display_name_localized.from_locale(locale=locale) - - @property - def display_name(self) -> Localization: - """:class: `str` Returns the skin's name.""" - return self._display_name_localized - - @property - def level(self) -> Optional[str]: - """:class: `str` alias for level_item.""" - return self.level_item - - @property - def display_icon(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon.""" - if self._display_icon is None: - return None - return Asset._from_url(self._state, url=self._display_icon) - - @property - def display_icon_fix(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's icon with fixed white background.""" - display_icon = self._display_icon or self.parent.display_icon or self.parent.parent.display_icon - if display_icon is None: - return None - if isinstance(display_icon, Asset): - return display_icon - return Asset._from_url(self._state, url=display_icon) - - @property - def streamed_video(self) -> Optional[Asset]: - """:class: `Asset` Returns the skin's video.""" - if self._streamed_video is None: - return None - return Asset._from_url(self._state, url=self._streamed_video) + uuid: str + display_name: LocalizedField = Field(alias='displayName') + level_item: str | None = Field(alias='levelItem') + display_icon: str | None = Field(alias='displayIcon') + streamed_video: str | None = Field(alias='streamedVideo') + asset_path: str = Field(alias='assetPath') - @property - def video(self) -> Optional[Asset]: - """:class: `Asset` alias for streamed_video.""" - return self.streamed_video - def is_level_one(self) -> bool: - """:class: `bool` Returns whether the skin is level one.""" - return self._is_level_one - - # helpers - - @property - def level_number(self) -> int: - """:class: `int` Returns the skin's level number.""" - return self._level_number - - @property - def theme(self) -> Optional[Theme]: - """:class: `Theme` Returns the skin's theme uuid.""" - return self.parent.theme - - @property - def content_tier(self) -> Optional[ContentTier]: - """:class: `ContentTier` Returns the skin's rarity.""" - return self.parent.content_tier - - @property - def rarity(self) -> Optional[ContentTier]: - """:class: `ContentTier` alias for content_tier.""" - return self.content_tier - - def is_melee(self) -> bool: - """:class: `bool` Returns whether the bundle is a melee.""" - return self.parent.is_melee() - - def is_random(self) -> bool: - """:class: `bool` Returns whether the skin is random.""" - return 'random' in self.asset_path.lower() +class Skin(BaseModel): + uuid: str + display_name: LocalizedField = Field(alias='displayName') + theme_uuid: str = Field(alias='themeUuid') + content_tier_uuid: str | None = Field(alias='contentTierUuid') + display_icon: str | None = Field(alias='displayIcon') + wallpaper: str | None + asset_path: str = Field(alias='assetPath') + chromas: list[Chroma] + levels: list[Level] -SkinChroma = Chroma -SkinLevel = Level +class Weapon(BaseModel): + uuid: str + display_name: LocalizedField = Field(alias='displayName') + category: WeaponCategory + default_skin_uuid: str = Field(alias='defaultSkinUuid') + display_icon: str = Field(alias='displayIcon') + kill_stream_icon: str = Field(alias='killStreamIcon') + asset_path: str = Field(alias='assetPath') + weapon_stats: WeaponStats | None = Field(alias='weaponStats') + shop_data: ShopData | None = Field(alias='shopData') + skins: list[Skin] diff --git a/valorant/types/__init__.py b/valorant/types/__init__.py deleted file mode 100644 index 173a639..0000000 --- a/valorant/types/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -valorant.types -~~~~~~~~~~~~~~ - -Typings for the Valorant API - -:copyright: (c) 2023-present STACiA -:license: MIT, see LICENSE for more details. - -""" diff --git a/valorant/types/agents.py b/valorant/types/agents.py deleted file mode 100644 index 5c44c92..0000000 --- a/valorant/types/agents.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, TypedDict, Union - -from .object import Object -from .response import Response - - -class Role(Object): - displayName: Union[str, Dict[str, str]] - description: Union[str, Dict[str, str]] - displayIcon: str - assetPath: str - - -class Ability(TypedDict): - slot: str - displayName: Union[str, Dict[str, str]] - description: Union[str, Dict[str, str]] - displayIcon: Optional[str] - - -class Media(TypedDict): - id: int - wwise: str - wave: str - - -class VoiceLine(TypedDict): - minDuration: float - maxDuration: float - mediaList: List[Media] - - -class Agent(Object): - displayName: Union[str, Dict[str, str]] - description: Union[str, Dict[str, str]] - developerName: str - characterTags: Optional[List[Union[str, Dict[str, str]]]] - displayIcon: str - displayIconSmall: str - bustPortrait: str - fullPortrait: str - fullPortraitV2: str - killfeedPortrait: str - background: str - backgroundGradientColors: List[str] - assetPath: str - isFullPortraitRightFacing: bool - isPlayableCharacter: bool - isAvailableForTest: bool - isBaseContent: bool - role: Role - abilities: List[Ability] - voiceLine: Optional[Union[VoiceLine, Dict[str, Optional[VoiceLine]]]] - - -Agents = Response[List[Agent]] -AgentUUID = Response[Agent] diff --git a/valorant/types/buddies.py b/valorant/types/buddies.py deleted file mode 100644 index 122832b..0000000 --- a/valorant/types/buddies.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class Level(Object): - charmLevel: int - displayName: Union[str, Dict[str, str]] - displayIcon: str - assetPath: str - - -class Buddy(Object): - displayName: Union[str, Dict[str, str]] - isHiddenIfNotOwned: bool - themeUuid: Optional[str] - displayIcon: str - assetPath: str - levels: List[Level] - - -BuddyLevel = Level -BuddyLevels = Response[List[Level]] -Buddies = Response[List[Buddy]] -BuddyUUID = Response[Buddy] -BuddyLevelUUID = Response[Level] diff --git a/valorant/types/bundles.py b/valorant/types/bundles.py deleted file mode 100644 index 5d3b366..0000000 --- a/valorant/types/bundles.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class Bundle(Object): - displayName: Union[str, Dict[str, str]] - displayNameSubText: Optional[Union[str, Dict[str, str]]] - description: Union[str, Dict[str, str]] - extraDescription: Optional[Union[str, Dict[str, str]]] - promoDescription: Optional[Union[str, Dict[str, str]]] - useAdditionalContext: bool - displayIcon: str - displayIcon2: str - verticalPromoImage: str - assetPath: str - - -Bundles = Response[List[Bundle]] -BundleUUID = Response[Bundle] - -# TODO: BundleItem diff --git a/valorant/types/ceremonies.py b/valorant/types/ceremonies.py deleted file mode 100644 index 2b76e39..0000000 --- a/valorant/types/ceremonies.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Union - -from .object import Object -from .response import Response - - -class Ceremony(Object): - displayName: Union[str, Dict[str, str]] - assetPath: str - - -Ceremonies = Response[List[Ceremony]] -CeremonyUUID = Response[Ceremony] diff --git a/valorant/types/competitive_tiers.py b/valorant/types/competitive_tiers.py deleted file mode 100644 index bc778af..0000000 --- a/valorant/types/competitive_tiers.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, TypedDict, Union - -from .object import Object -from .response import Response - - -class Tier(TypedDict): - tier: int - tierName: Union[str, Dict[str, str]] - division: str - divisionName: Union[str, Dict[str, str]] - color: str - backgroundColor: str - smallIcon: Optional[str] - largeIcon: Optional[str] - rankTriangleDownIcon: Optional[str] - rankTriangleUpIcon: Optional[str] - - -class CompetitiveTier(Object): - assetObjectName: str - tiers: List[Tier] - assetPath: str - - -CompetitiveTiers = Response[List[CompetitiveTier]] -CompetitiveTierUUID = Response[CompetitiveTier] diff --git a/valorant/types/content_tiers.py b/valorant/types/content_tiers.py deleted file mode 100644 index 09834d9..0000000 --- a/valorant/types/content_tiers.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Union - -from .object import Object -from .response import Response - - -class ContentTier(Object): - displayName: Union[str, Dict[str, str]] - devName: str - rank: int - juiceValue: int - juiceCost: int - highlightColor: str - displayIcon: str - assetPath: str - - -ContentTiers = Response[List[ContentTier]] -ContentTierUUID = Response[ContentTier] diff --git a/valorant/types/contracts.py b/valorant/types/contracts.py deleted file mode 100644 index 43b1d60..0000000 --- a/valorant/types/contracts.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, TypedDict, Union - -from .object import Object -from .response import Response - - -class Reward(Object): - type: str - amount: int - isHighlighted: bool - - -class Level(TypedDict): - reward: Reward - xp: int - vpCost: int - isPurchasableWithVP: bool - doughCost: int - isPurchasableWithDough: bool - - -class Chapter(TypedDict): - isEpilogue: bool - levels: List[Level] - freeRewards: Optional[List[Reward]] - - -class Content(TypedDict): - relationType: Optional[str] - relationUuid: Optional[str] - chapters: List[Chapter] - premiumRewardScheduleUuid: Optional[str] - premiumVPCost: int - - -class Contract(Object): - displayName: Union[str, Dict[str, str]] - displayIcon: Optional[str] - shipIt: bool - freeRewardScheduleUuid: str - content: Content - assetPath: str - - -Contracts = Response[List[Contract]] -ContractUUID = Response[Contract] diff --git a/valorant/types/currencies.py b/valorant/types/currencies.py deleted file mode 100644 index 242d61f..0000000 --- a/valorant/types/currencies.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Union - -from .object import Object -from .response import Response - - -class Currency(Object): - displayName: Union[str, Dict[str, str]] - displayNameSingular: Union[str, Dict[str, str]] - displayIcon: str - largeIcon: str - assetPath: str - - -Currencies = Response[List[Currency]] -CurrencyUUID = Response[Currency] diff --git a/valorant/types/events.py b/valorant/types/events.py deleted file mode 100644 index 6e2af6c..0000000 --- a/valorant/types/events.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Union - -from .object import Object -from .response import Response - - -class Event(Object): - displayName: Union[str, Dict[str, str]] - shortDisplayName: Union[str, Dict[str, str]] - startTime: str - endTime: str - assetPath: str - - -Events = Response[List[Event]] -EventUUID = Response[Event] diff --git a/valorant/types/gamemodes.py b/valorant/types/gamemodes.py deleted file mode 100644 index c89d559..0000000 --- a/valorant/types/gamemodes.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class GameFeatureOverride(Object): - featureName: str - state: bool - - -class GameRuleBoolOverride(Object): - ruleName: str - state: bool - - -class GameMode(Object): - displayName: Union[str, Dict[str, str]] - duration: Union[str, Dict[str, str]] - allowsMatchTimeouts: bool - isTeamVoiceAllowed: bool - isMinimapHidden: bool - orbCount: int - roundsPerHalf: int - teamRoles: Optional[List[str]] - gameFeatureOverrides: Optional[List[GameFeatureOverride]] - gameRuleBoolOverrides: Optional[List[GameRuleBoolOverride]] - displayIcon: Optional[str] - assetPath: str - - -GameModes = Response[List[GameMode]] -GameModeUUID = Response[GameMode] - - -class GameModeEquippable(Object): - displayName: Union[str, Dict[str, str]] - category: str - displayIcon: str - killStreamIcon: str - assetPath: str - - -GameModeEquippables = Response[List[GameModeEquippable]] -GameModeEquippableUUID = Response[GameModeEquippable] diff --git a/valorant/types/gear.py b/valorant/types/gear.py deleted file mode 100644 index d87b822..0000000 --- a/valorant/types/gear.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Union - -from .object import Object, ShopData -from .response import Response - - -class Gear_(Object): - displayName: Union[str, Dict[str, str]] - description: Union[str, Dict[str, str]] - displayIcon: str - assetPath: str - shopData: ShopData - - -Gear = Response[List[Gear_]] -GearUUID = Response[Gear_] diff --git a/valorant/types/level_borders.py b/valorant/types/level_borders.py deleted file mode 100644 index 7b38df1..0000000 --- a/valorant/types/level_borders.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import List - -from .object import Object -from .response import Response - - -class LevelBorder(Object): - startingLevel: int - levelNumberAppearance: str - smallPlayerCardAppearance: str - assetPath: str - - -LevelBorders = Response[List[LevelBorder]] -LevelBorderUUID = Response[LevelBorder] diff --git a/valorant/types/maps.py b/valorant/types/maps.py deleted file mode 100644 index 563904e..0000000 --- a/valorant/types/maps.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, TypedDict, Union - -from .object import Object -from .response import Response - - -class Location(TypedDict): - x: float - y: float - - -class Callout(TypedDict): - regionName: Union[str, Dict[str, str]] - superRegionName: Union[str, Dict[str, str]] - location: Location - - -class Map(Object): - displayName: Union[str, Dict[str, str]] - coordinates: Union[str, Dict[str, str]] - displayIcon: str - listViewIcon: str - splash: str - assetPath: str - mapUrl: str - xMultiplier: float - yMultiplier: float - xScalarToAdd: float - yScalarToAdd: float - callouts: Optional[List[Callout]] - - -Maps = Response[List[Map]] -MapUUID = Response[Map] diff --git a/valorant/types/missions.py b/valorant/types/missions.py deleted file mode 100644 index 317a734..0000000 --- a/valorant/types/missions.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, TypedDict, Union - -from .object import Object -from .response import Response - - -class Objective(TypedDict): - objectiveUuid: str - value: int - - -class Mission(Object): - displayName: Optional[Union[str, Dict[str, str]]] - title: Optional[Union[str, Dict[str, str]]] - type: Optional[str] - xpGrant: int - progressToComplete: int - activationDate: str - expirationDate: str - tags: Optional[List[str]] - objectives: Optional[List[Objective]] - assetPath: str - - -Missions = Response[List[Mission]] -MissionUUID = Response[Mission] diff --git a/valorant/types/object.py b/valorant/types/object.py deleted file mode 100644 index 0e92ef2..0000000 --- a/valorant/types/object.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, TypedDict, Union - - -class Object(TypedDict): - uuid: str - - -class GridPosition(TypedDict): - row: int - column: int - - -class ShopData(TypedDict): - cost: int - category: str - categoryText: Union[str, Dict[str, str]] - gridPosition: GridPosition - canBeTrashed: bool - image: str - newImage: str - newImage2: str - assetPath: str diff --git a/valorant/types/player_cards.py b/valorant/types/player_cards.py deleted file mode 100644 index 37f1e00..0000000 --- a/valorant/types/player_cards.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class PlayerCard(Object): - displayName: Union[str, Dict[str, str]] - isHiddenIfNotOwned: bool - themeUuid: Optional[str] - displayIcon: str - smallArt: str - wideArt: str - largeArt: str - assetPath: str - - -PlayerCards = Response[List[PlayerCard]] -PlayerCardUUID = Response[PlayerCard] diff --git a/valorant/types/player_titles.py b/valorant/types/player_titles.py deleted file mode 100644 index bcdca2f..0000000 --- a/valorant/types/player_titles.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Union - -from .object import Object -from .response import Response - - -class PlayerTitle(Object): - displayName: Union[str, Dict[str, str]] - titleText: Union[str, Dict[str, str]] - isHiddenIfNotOwned: bool - assetPath: str - - -PlayerTitles = Response[List[PlayerTitle]] -PlayerTitleUUID = Response[PlayerTitle] diff --git a/valorant/types/response.py b/valorant/types/response.py deleted file mode 100644 index dbafdf3..0000000 --- a/valorant/types/response.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Generic, TypedDict, TypeVar - -T = TypeVar('T') - - -class Response(TypedDict, Generic[T]): - data: T - status: int diff --git a/valorant/types/seasons.py b/valorant/types/seasons.py deleted file mode 100644 index db89037..0000000 --- a/valorant/types/seasons.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class Season(Object): - displayName: Union[str, Dict[str, str]] - type: Optional[str] - startTime: str - endTime: str - parentUuid: Optional[str] - assetPath: str - - -Seasons = Response[List[Season]] -SeasonUUID = Response[Season] - - -class Border(Object): - level: int - winsRequired: int - displayIcon: str - smallIcon: Optional[str] - assetPath: str - - -class CompetitiveSeason(Object): - startTime: str - endTime: str - seasonUuid: str - competitiveTiersUuid: str - borders: List[Border] - assetPath: str - - -CompetitiveSeasons = Response[List[CompetitiveSeason]] -CompetitiveSeasonUUID = Response[CompetitiveSeason] diff --git a/valorant/types/sprays.py b/valorant/types/sprays.py deleted file mode 100644 index e462a2e..0000000 --- a/valorant/types/sprays.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class Level(Object): - sprayLevel: int - displayName: Union[str, Dict[str, str]] - displayIcon: Optional[str] - assetPath: str - - -class Spray(Object): - displayName: Union[str, Dict[str, str]] - category: Optional[str] - themeUuid: Optional[str] - isNullSpray: bool - displayIcon: str - fullIcon: Optional[str] - fullTransparentIcon: Optional[str] - animationPng: Optional[str] - animationGif: Optional[str] - assetPath: str - levels: List[Level] - - -SprayLevel = Level -SprayLevels = Response[List[Level]] -Sprays = Response[List[Spray]] -SprayUUID = Response[Spray] -SprayLevelUUID = Response[Level] diff --git a/valorant/types/themes.py b/valorant/types/themes.py deleted file mode 100644 index 4c3c29a..0000000 --- a/valorant/types/themes.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, Union - -from .object import Object -from .response import Response - - -class Theme(Object): - displayName: Union[str, Dict[str, str]] - displayIcon: Optional[str] - storeFeaturedImage: Optional[str] - assetPath: str - - -Themes = Response[List[Theme]] -ThemeUUID = Response[Theme] diff --git a/valorant/types/version.py b/valorant/types/version.py deleted file mode 100644 index 9547ae2..0000000 --- a/valorant/types/version.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import TypedDict - -from .response import Response - - -class Version_(TypedDict): - manifestId: str - branch: str - version: str - buildVersion: str - engineVersion: str - riotClientVersion: str - riotClientBuild: str - buildDate: str - - -Version = Response[Version_] diff --git a/valorant/types/weapons.py b/valorant/types/weapons.py deleted file mode 100644 index be6289d..0000000 --- a/valorant/types/weapons.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Dict, List, Optional, TypedDict, Union - -from .object import Object, ShopData -from .response import Response - - -class AdsStats(TypedDict): - zoomMultiplier: float - fireRate: float - runSpeedMultiplier: float - burstCount: int - firstBulletAccuracy: float - - -class AltShotgunStats(TypedDict): - shotgunPelletCount: int - burstRate: float - - -class AirBurstStats(TypedDict): - shotgunPelletCount: int - burstDistance: float - - -class DamageRange(TypedDict): - rangeStartMeters: float - rangeEndMeters: float - headDamage: float - bodyDamage: float - legDamage: float - - -class WeaponStats(TypedDict): - fireRate: float - magazineSize: int - runSpeedMultiplier: float - equipTimeSeconds: float - reloadTimeSeconds: float - firstBulletAccuracy: float - shotgunPelletCount: int - wallPenetration: str - feature: Optional[str] - fireMode: Optional[str] - altFireType: Optional[str] - adsStats: Optional[AdsStats] - altShotgunStats: Optional[AltShotgunStats] - airBurstStats: Optional[AirBurstStats] - damageRanges: List[DamageRange] - - -class Chroma(Object): - displayName: Union[str, Dict[str, str]] - displayIcon: Optional[str] - fullRender: str - swatch: Optional[str] - streamedVideo: Optional[str] - assetPath: str - - -class Level(Object): - displayName: Union[str, Dict[str, str]] - levelItem: str - displayIcon: Optional[str] - streamedVideo: Optional[str] - assetPath: str - - -class Skin(Object): - displayName: Union[str, Dict[str, str]] - themeUuid: str - contentTierUuid: str - displayIcon: str - wallpaper: str - assetPath: str - chromas: List[Chroma] - levels: List[Level] - - -class Weapon(Object): - displayName: Union[str, Dict[str, str]] - category: str - defaultSkinUuid: str - displayIcon: str - killStreamIcon: str - assetPath: str - weaponStats: Optional[WeaponStats] - shopData: Optional[ShopData] - skins: List[Skin] - - -Weapons = Response[List[Weapon]] -WeaponUUID = Response[Weapon] - -Skins = Response[List[Skin]] -SkinUUID = Response[Skin] - -SkinLevels = Response[List[Level]] -SkinLevel = Level -SkinLevelUUID = Response[Level] - -SkinChromas = Response[List[Chroma]] -SkinChroma = Chroma -SkinChromaUUID = Response[Chroma] diff --git a/valorant/utils.py b/valorant/utils.py deleted file mode 100644 index 2627c88..0000000 --- a/valorant/utils.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -The MIT License (MIT) - -Copyright (c) 2015-present Rapptz -Copyright (c) 2023-present STACiA - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" -# - source: https://github.com/Rapptz/discord.py/blob/master/discord/utils.py - -from __future__ import annotations - -import datetime -import json -from typing import Any - -try: - import orjson # type: ignore -except ImportError: - HAS_ORJSON = False -else: - HAS_ORJSON = True # pragma: no cover - -if HAS_ORJSON: - _from_json = orjson.loads # type: ignore -else: - _from_json = json.loads - - -class _MissingSentinel: - __slots__ = () - - def __eq__(self, other: Any) -> bool: - return False - - def __bool__(self) -> bool: - return False - - def __hash__(self) -> int: - return 0 - - def __repr__(self) -> str: - return '...' - - -MISSING: Any = _MissingSentinel() - -# - end - - -def string_escape(string: str) -> str: - # string = string.encode('raw_unicode_escape').decode('unicode_escape') - string = string.replace('\r\n', ' ') - string = string.replace('\t', ' ') - string = string.replace('\r', ' ') - string = string.replace('\n', ' ') - string = string.replace('"', '\\"') - return string - - -def parse_iso_datetime(iso: str) -> datetime.datetime: - """Convert ISO8601 string to datetime""" - try: - dt = datetime.datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S.%fZ') - except ValueError: - dt = datetime.datetime.strptime(iso, '%Y-%m-%dT%H:%M:%SZ') - return dt.replace(tzinfo=datetime.timezone.utc) - - -def removeprefix(string: str, prefix: str) -> str: - """Remove prefix from string""" - # python 3.8 is not supported .removeprefix() - if string.startswith(prefix): - return string[len(prefix) :] - return string From 5623927b57febf31ae3757ec0b951d2d94b84483 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Sun, 29 Dec 2024 19:10:28 +0700 Subject: [PATCH 02/60] update quick example in readme --- README.md | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 46a8a52..6c11fe3 100644 --- a/README.md +++ b/README.md @@ -21,33 +21,17 @@ $ python3 -m pip install -U valorant.py import asyncio import valorant -async def main(): - client = valorant.Client(valorant.Locale.thai) # set default locale to thai - async with client: - weapon = client.get_weapon('9c82e19d-4575-0200-1a81-3eacf00cf872') # Vandal - assert weapon is not None - - for skin in weapon.skins: - print(skin.display_name) # default locale - print(skin.display_icon) - - # specify locale - print(skin.display_name.ja_JP) - print(skin.display_name.japanese) - print(skin.display_name.from_locale(valorant.Locale.japanese)) - if skin.theme is not None: - print(skin.theme.display_name) - print(skin.theme.display_icon) +async def main() -> None: + client = valorant.Client() - if skin.content_tier is not None: - print(skin.content_tier.display_name) - print(skin.content_tier.display_icon) - - for level in skin.levels: - print(level.display_name) - for chroma in skin.chromas: - print(chroma.display_name) + async with client: + weapons = await client.fetch_weapons() + for weapon in weapons: + print(weapon.display_name) + print(weapon.display_name.ja_JP) + print(weapon.display_name.japanese) + print(weapon.display_icon) asyncio.run(main()) From d7d5666b897186e5e3042a6f22444bb2efc6556f Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Sun, 29 Dec 2024 19:14:19 +0700 Subject: [PATCH 03/60] update README to include link to Valorant API --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c11fe3..0b18b5c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # valorant -A modern, easy to use, feature-rich, and async ready API wrapper for Valorant API written in Python. +A modern, easy to use, feature-rich, and async ready API wrapper for [Valorant API](https://valorant-api.com) written in Python. ## Key Features - Modern Pythonic API using `async` and `await`. From 68ff71fd160b76fddaa8e9c113d8adb9bc8b1534 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Sun, 29 Dec 2024 19:15:09 +0700 Subject: [PATCH 04/60] update Python version requirement to 3.10 or higher --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b18b5c..627f0c6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A modern, easy to use, feature-rich, and async ready API wrapper for [Valorant A - Modern Pythonic API using `async` and `await`. ## Installing -Python 3.8 or higher is required +Python 3.10 or higher is required Windows:
``` From af2ce473ec06b63930ebe2e2ed272ecc7dfdd54a Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Sun, 29 Dec 2024 20:07:29 +0700 Subject: [PATCH 05/60] add useful methods for fetching any in models --- valorant/models/buddies.py | 17 +++++++++-- valorant/models/contracts.py | 54 +++++++++++++++++++++++++++++++++ valorant/models/gamemodes.py | 13 ++++++++ valorant/models/player_cards.py | 15 +++++++++ valorant/models/seasons.py | 16 ++++++++++ valorant/models/sprays.py | 13 ++++++++ 6 files changed, 126 insertions(+), 2 deletions(-) diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index b38042a..2203387 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -22,7 +22,9 @@ DEALINGS IN THE SOFTWARE. """ -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING from pydantic import Field @@ -33,6 +35,10 @@ 'Level', ) +if TYPE_CHECKING: + from ..client import Client + from .themes import Theme + class Level(BaseModel): uuid: str @@ -50,10 +56,17 @@ class Buddy(BaseModel): uuid: str display_name: LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') - theme_uuid: Any = Field(alias='themeUuid') + theme_uuid: str | None = Field(alias='themeUuid') display_icon: str = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') levels: list[Level] def __repr__(self) -> str: return f'' + + # useful methods + + async def fetch_theme(self, *, client: Client) -> Theme | None: + if self.theme_uuid is None: + return None + return await client.fetch_theme(self.theme_uuid) diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index 31663bb..87580a7 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -22,6 +22,11 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, TypeAlias + from pydantic import Field from ..enums import RelationType, RewardType @@ -35,6 +40,22 @@ 'Reward', ) +if TYPE_CHECKING: + from ..client import Client + from .agents import Agent + from .buddies import Level as BuddyLevel + from .currencies import Currency + from .events import Event + from .player_cards import PlayerCard + from .player_titles import PlayerTitle + from .seasons import Season + from .sprays import Spray + from .weapons import Level as SkinLevel + + RewardItemType: TypeAlias = SkinLevel | BuddyLevel | Currency | PlayerCard | PlayerTitle | Spray + +log = logging.getLogger(__name__) + class Reward(BaseModel): type: RewardType @@ -42,6 +63,24 @@ class Reward(BaseModel): amount: int is_highlighted: bool = Field(alias='isHighlighted') + # useful methods + + async def fetch_item(self, *, client: Client) -> RewardItemType | None: # noqa: PLR0911 + if self.type is RewardType.skin_level: + return await client.fetch_weapon_skin_level(str(self.uuid)) + if self.type is RewardType.buddy_level: + return await client.fetch_buddy_level(str(self.uuid)) + if self.type is RewardType.player_card: + return await client.fetch_player_card(str(self.uuid)) + if self.type is RewardType.player_title: + return await client.fetch_player_title(str(self.uuid)) + if self.type is RewardType.spray: + return await client.fetch_spray(str(self.uuid)) + if self.type is RewardType.currency: + return await client.fetch_currency(str(self.uuid)) + log.warning('Unknown reward type: %s', self.type) + return None + class Level(BaseModel): reward: Reward @@ -52,6 +91,7 @@ class Level(BaseModel): is_purchasable_with_dough: bool = Field(alias='isPurchasableWithDough') +# TODO: add index chapter class Chapter(BaseModel): is_epilogue: bool = Field(alias='isEpilogue') levels: list[Level] @@ -65,6 +105,20 @@ class Content(BaseModel): premium_reward_schedule_uuid: str | None = Field(alias='premiumRewardScheduleUuid') premium_vp_cost: int = Field(alias='premiumVPCost') + # useful methods + + async def fetch_relationship(self, *, client: Client) -> Agent | Event | Season | None: + if self.relation_type is None or self.relation_uuid is None: + return None + if self.relation_type is RelationType.agent: + return await client.fetch_agent(self.relation_uuid) + if self.relation_type is RelationType.event: + return await client.fetch_event(self.relation_uuid) + if self.relation_type is RelationType.season: + return await client.fetch_season(self.relation_uuid) + log.warning('Unknown relation type: %s, uuid: %s', self.relation_type, self.relation_uuid) + return None + class Contract(BaseModel): uuid: str diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index 3d1308f..a190aec 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -22,6 +22,10 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + +from typing import TYPE_CHECKING + from pydantic import Field from ..enums import GameRule @@ -34,6 +38,10 @@ 'GameRuleBoolOverride', ) +if TYPE_CHECKING: + from ..client import Client + from .weapons import Weapon + class GameFeatureOverride(BaseModel): feature_name: str = Field(alias='featureName') @@ -71,3 +79,8 @@ class Equippable(BaseModel): display_icon: str = Field(alias='displayIcon') kill_stream_icon: str = Field(alias='killStreamIcon') asset_path: str = Field(alias='assetPath') + + # useful methods + + async def fetch_weapon(self, *, client: Client) -> Weapon | None: + return await client.fetch_weapon(self.uuid) diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index dcd61fc..46d9503 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -22,12 +22,20 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + +from typing import TYPE_CHECKING + from pydantic import Field from .base import BaseModel, LocalizedField __all__ = ('PlayerCard',) +if TYPE_CHECKING: + from ..client import Client + from .themes import Theme + class PlayerCard(BaseModel): uuid: str @@ -39,3 +47,10 @@ class PlayerCard(BaseModel): wide_art: str = Field(alias='wideArt') large_art: str = Field(alias='largeArt') asset_path: str = Field(alias='assetPath') + + # useful methods + + async def fetch_theme(self, *, client: Client) -> Theme | None: + if self.theme_uuid is None: + return None + return await client.fetch_theme(self.theme_uuid) diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index ee937f8..45b152d 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -22,7 +22,10 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + from datetime import datetime +from typing import TYPE_CHECKING from pydantic import Field @@ -34,6 +37,9 @@ 'Season', ) +if TYPE_CHECKING: + from ..client import Client + class Season(BaseModel): uuid: str @@ -45,6 +51,13 @@ class Season(BaseModel): parent_uuid: str | None = Field(alias='parentUuid') asset_path: str = Field(alias='assetPath') + # useful methods + + async def fetch_parent(self, *, client: Client) -> Season | None: + if self.parent_uuid is None: + return None + return await client.fetch_season(self.parent_uuid) + class Border(BaseModel): uuid: str @@ -63,3 +76,6 @@ class Competitive(BaseModel): competitive_tiers_uuid: str = Field(alias='competitiveTiersUuid') borders: list[Border] | None asset_path: str = Field(alias='assetPath') + + async def fetch_season(self, *, client: Client) -> Season | None: + return await client.fetch_season(self.season_uuid) diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index 6f80cc4..6426690 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -24,6 +24,8 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from pydantic import Field from .base import BaseModel, LocalizedField @@ -33,6 +35,10 @@ 'Spray', ) +if TYPE_CHECKING: + from ..client import Client + from .themes import Theme + class Level(BaseModel): uuid: str @@ -56,3 +62,10 @@ class Spray(BaseModel): animation_gif: str | None = Field(alias='animationGif') asset_path: str = Field(alias='assetPath') levels: list[Level] + + # useful methods + + async def fetch_theme(self, *, client: Client) -> Theme | None: + if self.theme_uuid is None: + return None + return await client.fetch_theme(self.theme_uuid) From c12f9e32bbd647401c1a41688b943f04bee2911e Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Sun, 29 Dec 2024 21:22:38 +0700 Subject: [PATCH 06/60] refactor: model BaseUUIDModel for UUID handling --- pyproject.toml | 1 + valorant/models/agents.py | 10 +++++----- valorant/models/base.py | 17 +++++++++++++++++ valorant/models/buddies.py | 10 +++++----- valorant/models/bundles.py | 6 +++--- valorant/models/ceremonies.py | 6 +++--- valorant/models/competitive_tiers.py | 6 +++--- valorant/models/content_tiers.py | 6 +++--- valorant/models/contracts.py | 10 +++++----- valorant/models/currencies.py | 6 +++--- valorant/models/events.py | 6 +++--- valorant/models/gamemodes.py | 10 +++++----- valorant/models/gear.py | 6 +++--- valorant/models/level_borders.py | 6 +++--- valorant/models/maps.py | 6 +++--- valorant/models/missions.py | 6 +++--- valorant/models/player_cards.py | 6 +++--- valorant/models/player_titles.py | 6 +++--- valorant/models/seasons.py | 6 +++--- valorant/models/sprays.py | 10 +++++----- valorant/models/themes.py | 4 ++-- valorant/models/weapons.py | 18 +++++++++--------- 22 files changed, 93 insertions(+), 75 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f076118..b5c3e36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,6 +146,7 @@ quote-annotations = true runtime-evaluated-base-classes = [ "pydantic.BaseModel", "valorant.models.base.BaseModel", + "valorant.models.base.BaseUUIDModel", ] [tool.ruff.lint.flake8-unused-arguments] diff --git a/valorant/models/agents.py b/valorant/models/agents.py index 3e217a0..3b29d0e 100644 --- a/valorant/models/agents.py +++ b/valorant/models/agents.py @@ -28,7 +28,7 @@ from pydantic import Field from ..enums import AbilitySlot -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Ability', @@ -38,8 +38,8 @@ ) -class Role(BaseModel): - uuid: str +class Role(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') description: LocalizedField display_icon: str = Field(alias='displayIcon') @@ -69,8 +69,8 @@ def __repr__(self) -> str: return f'' -class Agent(BaseModel): - uuid: str +class Agent(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') description: LocalizedField developer_name: str = Field(alias='developerName') diff --git a/valorant/models/base.py b/valorant/models/base.py index 324b623..49b0de5 100644 --- a/valorant/models/base.py +++ b/valorant/models/base.py @@ -22,7 +22,10 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + from typing import Generic, TypeVar +from uuid import UUID from pydantic import BaseModel as PydanticBaseModel, Field @@ -30,6 +33,7 @@ __all__ = ( 'BaseModel', + 'BaseUUIDModel', 'LocalizedField', 'Response', ) @@ -42,6 +46,19 @@ def __repr__(self) -> str: return f'<{self.__class__.__name__}>' +class BaseUUIDModel(BaseModel): + uuid: UUID = Field(alias='uuid') + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) and self.uuid == other.uuid + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return hash(self.uuid) + + class LocalizedField(BaseModel): de_DE: str = Field(alias='de-DE') es_ES: str = Field(alias='es-ES') diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index 2203387..7e9a5ad 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -28,7 +28,7 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ( 'Buddy', @@ -40,8 +40,8 @@ from .themes import Theme -class Level(BaseModel): - uuid: str +class Level(BaseUUIDModel): + # uuid: str charm_level: int = Field(alias='charmLevel') hide_if_not_owned: bool = Field(alias='hideIfNotOwned') display_name: LocalizedField = Field(alias='displayName') @@ -52,8 +52,8 @@ def __repr__(self) -> str: return f'' -class Buddy(BaseModel): - uuid: str +class Buddy(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') theme_uuid: str | None = Field(alias='themeUuid') diff --git a/valorant/models/bundles.py b/valorant/models/bundles.py index a51116b..d13bf67 100644 --- a/valorant/models/bundles.py +++ b/valorant/models/bundles.py @@ -26,13 +26,13 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('Bundle',) -class Bundle(BaseModel): - uuid: str +class Bundle(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') display_name_sub_text: Any = Field(alias='displayNameSubText') description: LocalizedField diff --git a/valorant/models/ceremonies.py b/valorant/models/ceremonies.py index 3351068..4509d41 100644 --- a/valorant/models/ceremonies.py +++ b/valorant/models/ceremonies.py @@ -24,12 +24,12 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('Ceremony',) -class Ceremony(BaseModel): - uuid: str +class Ceremony(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/competitive_tiers.py b/valorant/models/competitive_tiers.py index acf68b8..a204976 100644 --- a/valorant/models/competitive_tiers.py +++ b/valorant/models/competitive_tiers.py @@ -25,7 +25,7 @@ from pydantic import Field from ..enums import DivisionTier -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'CompetitiveTier', @@ -46,8 +46,8 @@ class Tier(BaseModel): rank_triangle_up_icon: str | None = Field(alias='rankTriangleUpIcon') -class CompetitiveTier(BaseModel): - uuid: str +class CompetitiveTier(BaseUUIDModel): + # uuid: str asset_object_name: str = Field(alias='assetObjectName') tiers: list[Tier] asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/content_tiers.py b/valorant/models/content_tiers.py index 7580ff8..7ca2afa 100644 --- a/valorant/models/content_tiers.py +++ b/valorant/models/content_tiers.py @@ -24,13 +24,13 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('ContentTier',) -class ContentTier(BaseModel): - uuid: str +class ContentTier(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') dev_name: str = Field(alias='devName') rank: int diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index 87580a7..462f444 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -30,7 +30,7 @@ from pydantic import Field from ..enums import RelationType, RewardType -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Chapter', @@ -57,9 +57,9 @@ log = logging.getLogger(__name__) -class Reward(BaseModel): +class Reward(BaseUUIDModel): + # uuid: str type: RewardType - uuid: str amount: int is_highlighted: bool = Field(alias='isHighlighted') @@ -120,8 +120,8 @@ async def fetch_relationship(self, *, client: Client) -> Agent | Event | Season return None -class Contract(BaseModel): - uuid: str +class Contract(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') ship_it: bool = Field(alias='shipIt') diff --git a/valorant/models/currencies.py b/valorant/models/currencies.py index a226e93..66f4db2 100644 --- a/valorant/models/currencies.py +++ b/valorant/models/currencies.py @@ -24,13 +24,13 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('Currency',) -class Currency(BaseModel): - uuid: str +class Currency(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') display_name_singular: LocalizedField = Field(alias='displayNameSingular') display_icon: str = Field(alias='displayIcon') diff --git a/valorant/models/events.py b/valorant/models/events.py index 73dfd84..3a2b01d 100644 --- a/valorant/models/events.py +++ b/valorant/models/events.py @@ -26,13 +26,13 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('Event',) -class Event(BaseModel): - uuid: str +class Event(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') short_display_name: LocalizedField = Field(alias='shortDisplayName') start_time: datetime = Field(alias='startTime') diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index a190aec..04d0cd0 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -29,7 +29,7 @@ from pydantic import Field from ..enums import GameRule -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Equippable', @@ -53,8 +53,8 @@ class GameRuleBoolOverride(BaseModel): state: bool -class GameMode(BaseModel): - uuid: str +class GameMode(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') description: LocalizedField | None duration: LocalizedField | None @@ -72,8 +72,8 @@ class GameMode(BaseModel): asset_path: str = Field(alias='assetPath') -class Equippable(BaseModel): - uuid: str +class Equippable(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') category: str display_icon: str = Field(alias='displayIcon') diff --git a/valorant/models/gear.py b/valorant/models/gear.py index ee927b8..0b558bf 100644 --- a/valorant/models/gear.py +++ b/valorant/models/gear.py @@ -26,7 +26,7 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Detail', @@ -53,8 +53,8 @@ class ShopData(BaseModel): asset_path: str = Field(alias='assetPath') -class Gear(BaseModel): - uuid: str +class Gear(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') description: LocalizedField descriptions: list[LocalizedField] diff --git a/valorant/models/level_borders.py b/valorant/models/level_borders.py index 0484dd7..fa1970d 100644 --- a/valorant/models/level_borders.py +++ b/valorant/models/level_borders.py @@ -24,13 +24,13 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('LevelBorder',) -class LevelBorder(BaseModel): - uuid: str +class LevelBorder(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') starting_level: int = Field(alias='startingLevel') level_number_appearance: str = Field(alias='levelNumberAppearance') diff --git a/valorant/models/maps.py b/valorant/models/maps.py index abca280..ceb1d9e 100644 --- a/valorant/models/maps.py +++ b/valorant/models/maps.py @@ -26,7 +26,7 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Callout', @@ -46,8 +46,8 @@ class Callout(BaseModel): location: Location -class Map(BaseModel): - uuid: str +class Map(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') narrative_description: Any = Field(alias='narrativeDescription') tactical_description: LocalizedField | None = Field(alias='tacticalDescription') diff --git a/valorant/models/missions.py b/valorant/models/missions.py index 407de17..172f72b 100644 --- a/valorant/models/missions.py +++ b/valorant/models/missions.py @@ -25,7 +25,7 @@ from pydantic import Field from ..enums import MissionTag, MissionType -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Mission', @@ -38,8 +38,8 @@ class Objective(BaseModel): value: int -class Mission(BaseModel): - uuid: str +class Mission(BaseUUIDModel): + # uuid: str display_name: LocalizedField | None = Field(alias='displayName') title: LocalizedField | None type: MissionType | None diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index 46d9503..88a6a44 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -28,7 +28,7 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('PlayerCard',) @@ -37,8 +37,8 @@ from .themes import Theme -class PlayerCard(BaseModel): - uuid: str +class PlayerCard(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') theme_uuid: str | None = Field(alias='themeUuid') diff --git a/valorant/models/player_titles.py b/valorant/models/player_titles.py index 0fd7b6e..e8163d7 100644 --- a/valorant/models/player_titles.py +++ b/valorant/models/player_titles.py @@ -24,13 +24,13 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('PlayerTitle',) -class PlayerTitle(BaseModel): - uuid: str +class PlayerTitle(BaseUUIDModel): + # uuid: str display_name: LocalizedField | None = Field(alias='displayName') title_text: LocalizedField | None = Field(alias='titleText') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 45b152d..4ddbab8 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -29,7 +29,7 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'Border', @@ -41,8 +41,8 @@ from ..client import Client -class Season(BaseModel): - uuid: str +class Season(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') title: LocalizedField | None type: str | None diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index 6426690..4ba8e31 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -28,7 +28,7 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ( 'Level', @@ -40,16 +40,16 @@ from .themes import Theme -class Level(BaseModel): - uuid: str +class Level(BaseUUIDModel): + # uuid: str spray_level: int = Field(alias='sprayLevel') display_name: LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') -class Spray(BaseModel): - uuid: str +class Spray(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') category: str | None theme_uuid: str | None = Field(alias='themeUuid') diff --git a/valorant/models/themes.py b/valorant/models/themes.py index a7ce808..7873ddb 100644 --- a/valorant/models/themes.py +++ b/valorant/models/themes.py @@ -24,12 +24,12 @@ from pydantic import Field -from .base import BaseModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ('Theme',) -class Theme(BaseModel): +class Theme(BaseUUIDModel): uuid: str display_name: LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') diff --git a/valorant/models/weapons.py b/valorant/models/weapons.py index cd78e5e..4c00c66 100644 --- a/valorant/models/weapons.py +++ b/valorant/models/weapons.py @@ -27,7 +27,7 @@ from pydantic import Field from ..enums import WeaponCategory -from .base import BaseModel, LocalizedField +from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( 'AdsStats', @@ -102,8 +102,8 @@ class ShopData(BaseModel): asset_path: str = Field(alias='assetPath') -class Chroma(BaseModel): - uuid: str +class Chroma(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') full_render: str = Field(alias='fullRender') @@ -112,8 +112,8 @@ class Chroma(BaseModel): asset_path: str = Field(alias='assetPath') -class Level(BaseModel): - uuid: str +class Level(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') level_item: str | None = Field(alias='levelItem') display_icon: str | None = Field(alias='displayIcon') @@ -121,8 +121,8 @@ class Level(BaseModel): asset_path: str = Field(alias='assetPath') -class Skin(BaseModel): - uuid: str +class Skin(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') theme_uuid: str = Field(alias='themeUuid') content_tier_uuid: str | None = Field(alias='contentTierUuid') @@ -133,8 +133,8 @@ class Skin(BaseModel): levels: list[Level] -class Weapon(BaseModel): - uuid: str +class Weapon(BaseUUIDModel): + # uuid: str display_name: LocalizedField = Field(alias='displayName') category: WeaponCategory default_skin_uuid: str = Field(alias='defaultSkinUuid') From 522772668909331d697bf6766bb3218352d28e0d Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:10:36 +0700 Subject: [PATCH 07/60] update uv setup action to version 5 in lint --- .github/workflows/lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f0fbe9e..14215bc 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -21,7 +21,7 @@ jobs: with: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v5 with: version: "0.5.x" enable-cache: true From e08234a82d8459489a93f36a15999eb36974fc0c Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:14:52 +0700 Subject: [PATCH 08/60] fix: lint --- valorant/models/themes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/models/themes.py b/valorant/models/themes.py index 7873ddb..0bca0a2 100644 --- a/valorant/models/themes.py +++ b/valorant/models/themes.py @@ -30,7 +30,7 @@ class Theme(BaseUUIDModel): - uuid: str + # uuid: str display_name: LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') store_featured_image: str | None = Field(alias='storeFeaturedImage') From 094db6031c18b7ca3303d66175df2af9263b1649 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:22:05 +0700 Subject: [PATCH 09/60] Python version handling in workflows to use version file --- .github/workflows/lint.yaml | 6 ++++-- .github/workflows/publish.yml | 39 ++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 14215bc..12467cb 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,14 +16,16 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version-file: ".python-version" + - name: Install uv uses: astral-sh/setup-uv@v5 with: version: "0.5.x" enable-cache: true + - run: uv run make lint - \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a188121..9611af1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,25 +7,30 @@ on: permissions: contents: read +# TDDO: migrate to uv for building + jobs: deploy: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build package + run: python -m build + + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} From 5da6e8084a748a88dfd7acf5eb9da7a07b9128d6 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:24:06 +0700 Subject: [PATCH 10/60] fix: lint --- valorant/models/gamemodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index 04d0cd0..c0444d1 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -83,4 +83,4 @@ class Equippable(BaseUUIDModel): # useful methods async def fetch_weapon(self, *, client: Client) -> Weapon | None: - return await client.fetch_weapon(self.uuid) + return await client.fetch_weapon(str(self.uuid)) From 2e4d418417336ef797282116ea3e2363f0810957 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:46:30 +0700 Subject: [PATCH 11/60] refactor: use UUID instead of str --- valorant/models/buddies.py | 3 ++- valorant/models/contracts.py | 3 ++- valorant/models/missions.py | 4 +++- valorant/models/player_cards.py | 3 ++- valorant/models/sprays.py | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index 7e9a5ad..b5cdb9c 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -25,6 +25,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from uuid import UUID from pydantic import Field @@ -56,7 +57,7 @@ class Buddy(BaseUUIDModel): # uuid: str display_name: LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') - theme_uuid: str | None = Field(alias='themeUuid') + theme_uuid: UUID | None = Field(alias='themeUuid') display_icon: str = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') levels: list[Level] diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index 462f444..e267ed7 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -26,6 +26,7 @@ import logging from typing import TYPE_CHECKING, TypeAlias +from uuid import UUID from pydantic import Field @@ -100,7 +101,7 @@ class Chapter(BaseModel): class Content(BaseModel): relation_type: RelationType | None = Field(alias='relationType') - relation_uuid: str | None = Field(alias='relationUuid') + relation_uuid: UUID | None = Field(alias='relationUuid') chapters: list[Chapter] premium_reward_schedule_uuid: str | None = Field(alias='premiumRewardScheduleUuid') premium_vp_cost: int = Field(alias='premiumVPCost') diff --git a/valorant/models/missions.py b/valorant/models/missions.py index 172f72b..dcc2a8e 100644 --- a/valorant/models/missions.py +++ b/valorant/models/missions.py @@ -22,6 +22,8 @@ DEALINGS IN THE SOFTWARE. """ +from uuid import UUID + from pydantic import Field from ..enums import MissionTag, MissionType @@ -34,7 +36,7 @@ class Objective(BaseModel): - objective_uuid: str = Field(alias='objectiveUuid') + objective_uuid: UUID = Field(alias='objectiveUuid') value: int diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index 88a6a44..1936dd8 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -25,6 +25,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from uuid import UUID from pydantic import Field @@ -41,7 +42,7 @@ class PlayerCard(BaseUUIDModel): # uuid: str display_name: LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') - theme_uuid: str | None = Field(alias='themeUuid') + theme_uuid: UUID | None = Field(alias='themeUuid') display_icon: str = Field(alias='displayIcon') small_art: str = Field(alias='smallArt') wide_art: str = Field(alias='wideArt') diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index 4ba8e31..d88cc15 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -25,6 +25,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from uuid import UUID from pydantic import Field @@ -52,7 +53,7 @@ class Spray(BaseUUIDModel): # uuid: str display_name: LocalizedField = Field(alias='displayName') category: str | None - theme_uuid: str | None = Field(alias='themeUuid') + theme_uuid: UUID | None = Field(alias='themeUuid') is_null_spray: bool = Field(alias='isNullSpray') hide_if_not_owned: bool = Field(alias='hideIfNotOwned') display_icon: str | None = Field(alias='displayIcon') From 734b6dc82726d139a8b32d70a25873a32dacce7a Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:48:44 +0700 Subject: [PATCH 12/60] refactor: use BaseUUIDModel instead of BaseModel --- valorant/models/seasons.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 4ddbab8..6b76166 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -29,7 +29,7 @@ from pydantic import Field -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel, LocalizedField __all__ = ( 'Border', @@ -59,8 +59,8 @@ async def fetch_parent(self, *, client: Client) -> Season | None: return await client.fetch_season(self.parent_uuid) -class Border(BaseModel): - uuid: str +class Border(BaseUUIDModel): + # uuid: str level: int wins_required: int = Field(alias='winsRequired') display_icon: str = Field(alias='displayIcon') @@ -68,8 +68,8 @@ class Border(BaseModel): asset_path: str = Field(alias='assetPath') -class Competitive(BaseModel): - uuid: str +class Competitive(BaseUUIDModel): + # uuid: str start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') season_uuid: str = Field(alias='seasonUuid') From 9821dac846047c1d1bee3171691e33ae186048c8 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:55:07 +0700 Subject: [PATCH 13/60] update readme --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 627f0c6..1aeeed2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,21 @@ # valorant -A modern, easy to use, feature-rich, and async ready API wrapper for [Valorant API](https://valorant-api.com) written in Python. +A modern, easy to use, and async ready API wrapper for [Valorant API](https://valorant-api.com) written in Python. -## Key Features +## Features +- Fully type annotated. - Modern Pythonic API using `async` and `await`. +- Supports Python 3.10+. +- Supports localizations. + ## Installing -Python 3.10 or higher is required - -Windows:
-``` -$ pip install -U valorant.py +To install the library, you can just run the following command: ``` -Linux/MacOS: -``` -$ python3 -m pip install -U valorant.py +# uv +uv add valorant.py + +# pip +pip install valorant.py ``` ## Quick Example From c76af765613f916d6803e5c8d1e5b982dfface10 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:59:04 +0700 Subject: [PATCH 14/60] add test actions --- .github/workflows/test.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..a47150b --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,31 @@ +name: test + +on: + push: + pull_request: + branches: + - master + types: + - opened + - reopened + - synchronize + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.5.x" + enable-cache: true + + - run: uv run make test From 31f321534d6b54bc995335a04136a748451eaea9 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 01:06:52 +0700 Subject: [PATCH 15/60] refactor: rename job in publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9611af1..ec63ca9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,7 @@ permissions: # TDDO: migrate to uv for building jobs: - deploy: + release-build: runs-on: ubuntu-latest steps: From 46fe1b10b59259e65955aea89f00045834e24368 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 01:27:11 +0700 Subject: [PATCH 16/60] refactor: renamed jon in test.yaml --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a47150b..8c47418 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,7 +11,7 @@ on: - synchronize jobs: - pytest: + test: runs-on: ubuntu-latest steps: - name: Checkout From 49ca441c4a25dbd345c5ed4a7de326f2dd2a475d Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 01:31:02 +0700 Subject: [PATCH 17/60] refactor: change activation_date and expiration_date types to datetime --- valorant/models/missions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/valorant/models/missions.py b/valorant/models/missions.py index dcc2a8e..976055a 100644 --- a/valorant/models/missions.py +++ b/valorant/models/missions.py @@ -22,6 +22,7 @@ DEALINGS IN THE SOFTWARE. """ +from datetime import datetime from uuid import UUID from pydantic import Field @@ -47,8 +48,8 @@ class Mission(BaseUUIDModel): type: MissionType | None xp_grant: int = Field(alias='xpGrant') progress_to_complete: int = Field(alias='progressToComplete') - activation_date: str = Field(alias='activationDate') - expiration_date: str = Field(alias='expirationDate') + activation_date: datetime = Field(alias='activationDate') + expiration_date: datetime = Field(alias='expirationDate') tags: list[MissionTag] | None objectives: list[Objective] | None asset_path: str = Field(alias='assetPath') From 6e9230002ceb251481c202c399e0bff584817152 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 01:54:59 +0700 Subject: [PATCH 18/60] fix: lint --- tests/test_client.py | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 9c196c4..568c516 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,7 +16,7 @@ async def test_agents(client: Client) -> None: assert len(agents) > 0 agent_id = agents[0].uuid - agent = await client.fetch_agent(agent_id) + agent = await client.fetch_agent(str(agent_id)) assert agent is not None assert agent_id == agent.uuid @@ -30,7 +30,7 @@ async def test_buddies(client: Client) -> None: assert len(buddies) > 0 buddy_id = buddies[0].uuid - buddy = await client.fetch_buddy(buddy_id) + buddy = await client.fetch_buddy(str(buddy_id)) assert buddy is not None assert buddy_id == buddy.uuid @@ -44,7 +44,7 @@ async def test_buddy_levels(client: Client) -> None: assert len(buddy_levels) > 0 buddy_level_id = buddy_levels[0].uuid - buddy_level = await client.fetch_buddy_level(buddy_level_id) + buddy_level = await client.fetch_buddy_level(str(buddy_level_id)) assert buddy_level is not None assert buddy_level_id == buddy_level.uuid @@ -58,7 +58,7 @@ async def test_bundles(client: Client) -> None: assert len(bundles) > 0 bundle_id = bundles[0].uuid - bundle = await client.fetch_bundle(bundle_id) + bundle = await client.fetch_bundle(str(bundle_id)) assert bundle is not None assert bundle_id == bundle.uuid @@ -72,7 +72,7 @@ async def test_ceremonies(client: Client) -> None: assert len(ceremonies) > 0 ceremony_id = ceremonies[0].uuid - ceremony = await client.fetch_ceremony(ceremony_id) + ceremony = await client.fetch_ceremony(str(ceremony_id)) assert ceremony is not None assert ceremony_id == ceremony.uuid @@ -86,7 +86,7 @@ async def test_competitive_tiers(client: Client) -> None: assert len(competitive_tiers) > 0 competitive_tier_id = competitive_tiers[0].uuid - competitive_tier = await client.fetch_competitive_tier(competitive_tier_id) + competitive_tier = await client.fetch_competitive_tier(str(competitive_tier_id)) assert competitive_tier is not None assert competitive_tier_id == competitive_tier.uuid @@ -100,7 +100,7 @@ async def test_content_tiers(client: Client) -> None: assert len(content_tiers) > 0 content_tier_id = content_tiers[0].uuid - content_tier = await client.fetch_content_tier(content_tier_id) + content_tier = await client.fetch_content_tier(str(content_tier_id)) assert content_tier is not None assert content_tier_id == content_tier.uuid @@ -114,7 +114,7 @@ async def test_contracts(client: Client) -> None: assert len(contracts) > 0 contract_id = contracts[0].uuid - contract = await client.fetch_contract(contract_id) + contract = await client.fetch_contract(str(contract_id)) assert contract is not None assert contract_id == contract.uuid @@ -128,7 +128,7 @@ async def test_currencies(client: Client) -> None: assert len(currencies) > 0 currency_id = currencies[0].uuid - currency = await client.fetch_currency(currency_id) + currency = await client.fetch_currency(str(currency_id)) assert currency is not None assert currency_id == currency.uuid @@ -142,7 +142,7 @@ async def test_events(client: Client) -> None: assert len(events) > 0 event_id = events[0].uuid - event = await client.fetch_event(event_id) + event = await client.fetch_event(str(event_id)) assert event is not None assert event_id == event.uuid @@ -156,7 +156,7 @@ async def test_game_modes(client: Client) -> None: assert len(game_modes) > 0 game_mode_id = game_modes[0].uuid - game_mode = await client.fetch_game_mode(game_mode_id) + game_mode = await client.fetch_game_mode(str(game_mode_id)) assert game_mode is not None assert game_mode_id == game_mode.uuid @@ -170,7 +170,7 @@ async def test_game_mode_equippables(client: Client) -> None: assert len(game_mode_equippables) > 0 game_mode_equippable_id = game_mode_equippables[0].uuid - game_mode_equippable = await client.fetch_game_mode_equippable(game_mode_equippable_id) + game_mode_equippable = await client.fetch_game_mode_equippable(str(game_mode_equippable_id)) assert game_mode_equippable is not None assert game_mode_equippable_id == game_mode_equippable.uuid @@ -184,7 +184,7 @@ async def test_gears(client: Client) -> None: assert len(gears) > 0 gear_id = gears[0].uuid - gear = await client.fetch_gear(gear_id) + gear = await client.fetch_gear(str(gear_id)) assert gear is not None assert gear_id == gear.uuid @@ -198,7 +198,7 @@ async def test_level_borders(client: Client) -> None: assert len(level_borders) > 0 level_border_id = level_borders[0].uuid - level_border = await client.fetch_level_border(level_border_id) + level_border = await client.fetch_level_border(str(level_border_id)) assert level_border is not None assert level_border_id == level_border.uuid @@ -212,7 +212,7 @@ async def test_maps(client: Client) -> None: assert len(maps) > 0 map_id = maps[0].uuid - map_ = await client.fetch_map(map_id) + map_ = await client.fetch_map(str(map_id)) assert map_ is not None assert map_id == map_.uuid @@ -226,7 +226,7 @@ async def test_missions(client: Client) -> None: assert len(missions) > 0 mission_id = missions[0].uuid - mission = await client.fetch_mission(mission_id) + mission = await client.fetch_mission(str(mission_id)) assert mission is not None assert mission_id == mission.uuid @@ -240,7 +240,7 @@ async def test_player_cards(client: Client) -> None: assert len(player_cards) > 0 player_card_id = player_cards[0].uuid - player_card = await client.fetch_player_card(player_card_id) + player_card = await client.fetch_player_card(str(player_card_id)) assert player_card is not None assert player_card_id == player_card.uuid @@ -254,7 +254,7 @@ async def test_player_titles(client: Client) -> None: assert len(player_titles) > 0 player_title_id = player_titles[0].uuid - player_title = await client.fetch_player_title(player_title_id) + player_title = await client.fetch_player_title(str(player_title_id)) assert player_title is not None assert player_title_id == player_title.uuid @@ -268,7 +268,7 @@ async def test_seasons(client: Client) -> None: assert len(seasons) > 0 season_id = seasons[0].uuid - season = await client.fetch_season(season_id) + season = await client.fetch_season(str(season_id)) assert season is not None assert season_id == season.uuid @@ -282,7 +282,7 @@ async def test_competitive_seasons(client: Client) -> None: assert len(competitive_seasons) > 0 competitive_season_id = competitive_seasons[0].uuid - competitive_season = await client.competitive_season(competitive_season_id) + competitive_season = await client.competitive_season(str(competitive_season_id)) assert competitive_season is not None assert competitive_season_id == competitive_season.uuid @@ -296,7 +296,7 @@ async def test_sprays(client: Client) -> None: assert len(sprays) > 0 spray_id = sprays[0].uuid - spray = await client.fetch_spray(spray_id) + spray = await client.fetch_spray(str(spray_id)) assert spray is not None assert spray_id == spray.uuid @@ -310,7 +310,7 @@ async def test_spray_levels(client: Client) -> None: assert len(spray_levels) > 0 spray_level_id = spray_levels[0].uuid - spray_level = await client.fetch_spray_level(spray_level_id) + spray_level = await client.fetch_spray_level(str(spray_level_id)) assert spray_level is not None assert spray_level_id == spray_level.uuid @@ -324,7 +324,7 @@ async def test_themes(client: Client) -> None: assert len(themes) > 0 theme_id = themes[0].uuid - theme = await client.fetch_theme(theme_id) + theme = await client.fetch_theme(str(theme_id)) assert theme is not None assert theme_id == theme.uuid @@ -338,7 +338,7 @@ async def test_weapons(client: Client) -> None: assert len(weapons) > 0 weapon_id = weapons[0].uuid - weapon = await client.fetch_weapon(weapon_id) + weapon = await client.fetch_weapon(str(weapon_id)) assert weapon is not None assert weapon_id == weapon.uuid @@ -352,7 +352,7 @@ async def test_weapon_skins(client: Client) -> None: assert len(weapon_skins) > 0 weapon_skin_id = weapon_skins[0].uuid - weapon_skin = await client.fetch_weapon_skin(weapon_skin_id) + weapon_skin = await client.fetch_weapon_skin(str(weapon_skin_id)) assert weapon_skin is not None assert weapon_skin_id == weapon_skin.uuid @@ -366,7 +366,7 @@ async def test_weapon_skin_chromas(client: Client) -> None: assert len(weapon_skin_chromas) > 0 weapon_skin_chroma_id = weapon_skin_chromas[0].uuid - weapon_skin_chroma = await client.fetch_weapon_skin_chroma(weapon_skin_chroma_id) + weapon_skin_chroma = await client.fetch_weapon_skin_chroma(str(weapon_skin_chroma_id)) assert weapon_skin_chroma is not None assert weapon_skin_chroma_id == weapon_skin_chroma.uuid @@ -380,7 +380,7 @@ async def test_weapon_skin_levels(client: Client) -> None: assert len(weapon_skin_levels) > 0 weapon_skin_level_id = weapon_skin_levels[0].uuid - weapon_skin_level = await client.fetch_weapon_skin_level(weapon_skin_level_id) + weapon_skin_level = await client.fetch_weapon_skin_level(str(weapon_skin_level_id)) assert weapon_skin_level is not None assert weapon_skin_level_id == weapon_skin_level.uuid From f4b147afa85cd68fdfefe2b1d97bc77259f34144 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 01:55:37 +0700 Subject: [PATCH 19/60] update linting, formatting to include tests --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d930520..437c73e 100644 --- a/Makefile +++ b/Makefile @@ -15,15 +15,15 @@ report: # See the coverage report .PHONY: lint .SILENT: lint lint: # Run the linter - mypy valorant - ruff check valorant - ruff format valorant --check + mypy valorant tests + ruff check valorant tests + ruff format valorant tests --check .PHONY: format .SILENT: format format: # Format the code - ruff check valorant --fix - ruff format valorant + ruff check valorant tests --fix + ruff format valorant tests .PHONY: test .SILENT: test From bb00268e8ad6fc3a4c9b821c22cf383ddd1f8fcf Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:08:11 +0700 Subject: [PATCH 20/60] fix: lint --- valorant/models/buddies.py | 2 +- valorant/models/contracts.py | 6 +++--- valorant/models/player_cards.py | 2 +- valorant/models/sprays.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index b5cdb9c..b1c85bc 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -70,4 +70,4 @@ def __repr__(self) -> str: async def fetch_theme(self, *, client: Client) -> Theme | None: if self.theme_uuid is None: return None - return await client.fetch_theme(self.theme_uuid) + return await client.fetch_theme(str(self.theme_uuid)) diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index e267ed7..883fbd6 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -112,11 +112,11 @@ async def fetch_relationship(self, *, client: Client) -> Agent | Event | Season if self.relation_type is None or self.relation_uuid is None: return None if self.relation_type is RelationType.agent: - return await client.fetch_agent(self.relation_uuid) + return await client.fetch_agent(str(self.relation_uuid)) if self.relation_type is RelationType.event: - return await client.fetch_event(self.relation_uuid) + return await client.fetch_event(str(self.relation_uuid)) if self.relation_type is RelationType.season: - return await client.fetch_season(self.relation_uuid) + return await client.fetch_season(str(self.relation_uuid)) log.warning('Unknown relation type: %s, uuid: %s', self.relation_type, self.relation_uuid) return None diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index 1936dd8..e42df45 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -54,4 +54,4 @@ class PlayerCard(BaseUUIDModel): async def fetch_theme(self, *, client: Client) -> Theme | None: if self.theme_uuid is None: return None - return await client.fetch_theme(self.theme_uuid) + return await client.fetch_theme(str(self.theme_uuid)) diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index d88cc15..3414e4a 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -69,4 +69,4 @@ class Spray(BaseUUIDModel): async def fetch_theme(self, *, client: Client) -> Theme | None: if self.theme_uuid is None: return None - return await client.fetch_theme(self.theme_uuid) + return await client.fetch_theme(str(self.theme_uuid)) From a785016e1355cac961055f84288e99769558056d Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:11:17 +0700 Subject: [PATCH 21/60] refactor: change parent_uuid, season_uuid types to UUID --- valorant/models/seasons.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 6b76166..c8dd798 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -26,6 +26,7 @@ from datetime import datetime from typing import TYPE_CHECKING +from uuid import UUID from pydantic import Field @@ -48,7 +49,7 @@ class Season(BaseUUIDModel): type: str | None start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') - parent_uuid: str | None = Field(alias='parentUuid') + parent_uuid: UUID | None = Field(alias='parentUuid') asset_path: str = Field(alias='assetPath') # useful methods @@ -72,7 +73,7 @@ class Competitive(BaseUUIDModel): # uuid: str start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') - season_uuid: str = Field(alias='seasonUuid') + season_uuid: UUID = Field(alias='seasonUuid') competitive_tiers_uuid: str = Field(alias='competitiveTiersUuid') borders: list[Border] | None asset_path: str = Field(alias='assetPath') From 9490dc9c035db6d04bd6ae0865291cc40561620f Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:11:32 +0700 Subject: [PATCH 22/60] fix: lint --- valorant/models/seasons.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index c8dd798..6b643bf 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -57,7 +57,7 @@ class Season(BaseUUIDModel): async def fetch_parent(self, *, client: Client) -> Season | None: if self.parent_uuid is None: return None - return await client.fetch_season(self.parent_uuid) + return await client.fetch_season(str(self.parent_uuid)) class Border(BaseUUIDModel): @@ -79,4 +79,4 @@ class Competitive(BaseUUIDModel): asset_path: str = Field(alias='assetPath') async def fetch_season(self, *, client: Client) -> Season | None: - return await client.fetch_season(self.season_uuid) + return await client.fetch_season(str(self.season_uuid)) From 8ac952a2432b0b3c912efa67564f831cee97c487 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:16:54 +0700 Subject: [PATCH 23/60] add SeasonType enum and update Season model --- valorant/__init__.py | 2 ++ valorant/enums.py | 5 +++++ valorant/models/seasons.py | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/valorant/__init__.py b/valorant/__init__.py index dae1613..a09450b 100644 --- a/valorant/__init__.py +++ b/valorant/__init__.py @@ -23,6 +23,7 @@ MissionType, RelationType, RewardType, + SeasonType, WeaponCategory, ) from .errors import BadRequest, Forbidden, HTTPException, InternalServerError, RateLimited, ValorantError @@ -42,6 +43,7 @@ 'RateLimited', 'RelationType', 'RewardType', + 'SeasonType', 'ValorantError', 'WeaponCategory', 'models', diff --git a/valorant/enums.py b/valorant/enums.py index 73c773e..d07dc2d 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -33,6 +33,7 @@ 'MissionType', 'RelationType', 'RewardType', + 'SeasonType', 'WeaponCategory', ) @@ -108,6 +109,10 @@ class RewardType(str, Enum): spray = 'Spray' +class SeasonType(str, Enum): + act = 'EAresSeasonType::Act' + + class WeaponCategory(str, Enum): melee = 'EEquippableCategory::Melee' sidearm = 'EEquippableCategory::Sidearm' diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 6b643bf..25be148 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -30,6 +30,7 @@ from pydantic import Field +from ..enums import SeasonType from .base import BaseUUIDModel, LocalizedField __all__ = ( @@ -46,7 +47,7 @@ class Season(BaseUUIDModel): # uuid: str display_name: LocalizedField = Field(alias='displayName') title: LocalizedField | None - type: str | None + type: SeasonType | None start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') parent_uuid: UUID | None = Field(alias='parentUuid') From ed0f38defd14b792fd522c11391b6893dd4c3a11 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:17:17 +0700 Subject: [PATCH 24/60] refactor: competitive_tiers_uuid types to UUID --- valorant/models/seasons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 25be148..9f10a0e 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -75,7 +75,7 @@ class Competitive(BaseUUIDModel): start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') season_uuid: UUID = Field(alias='seasonUuid') - competitive_tiers_uuid: str = Field(alias='competitiveTiersUuid') + competitive_tiers_uuid: UUID = Field(alias='competitiveTiersUuid') borders: list[Border] | None asset_path: str = Field(alias='assetPath') From 58e5f0bfcdb229800af3c980c25b0a5cc99a16f6 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:32:18 +0700 Subject: [PATCH 25/60] add ShopCategory enum and update ShopData model to use it --- valorant/__init__.py | 2 ++ valorant/enums.py | 11 +++++++++++ valorant/models/gear.py | 3 ++- valorant/models/weapons.py | 4 ++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/valorant/__init__.py b/valorant/__init__.py index a09450b..d0cdd57 100644 --- a/valorant/__init__.py +++ b/valorant/__init__.py @@ -24,6 +24,7 @@ RelationType, RewardType, SeasonType, + ShopCategory, WeaponCategory, ) from .errors import BadRequest, Forbidden, HTTPException, InternalServerError, RateLimited, ValorantError @@ -44,6 +45,7 @@ 'RelationType', 'RewardType', 'SeasonType', + 'ShopCategory', 'ValorantError', 'WeaponCategory', 'models', diff --git a/valorant/enums.py b/valorant/enums.py index d07dc2d..612dc0c 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -34,6 +34,7 @@ 'RelationType', 'RewardType', 'SeasonType', + 'ShopCategory', 'WeaponCategory', ) @@ -113,6 +114,16 @@ class SeasonType(str, Enum): act = 'EAresSeasonType::Act' +class ShopCategory(str, Enum): + armor = 'Armor' + pistols = 'Pistols' + smgs = 'SMGs' + shotguns = 'Shotguns' + heavy_weapons = 'Heavy Weapons' + rifles = 'Rifles' + sniper_rifles = 'Sniper Rifles' + + class WeaponCategory(str, Enum): melee = 'EEquippableCategory::Melee' sidearm = 'EEquippableCategory::Sidearm' diff --git a/valorant/models/gear.py b/valorant/models/gear.py index 0b558bf..7cbffbc 100644 --- a/valorant/models/gear.py +++ b/valorant/models/gear.py @@ -26,6 +26,7 @@ from pydantic import Field +from ..enums import ShopCategory from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( @@ -42,7 +43,7 @@ class Detail(BaseModel): class ShopData(BaseModel): cost: int - category: str + category: ShopCategory shop_order_priority: int = Field(alias='shopOrderPriority') category_text: LocalizedField = Field(alias='categoryText') grid_position: Any = Field(alias='gridPosition') diff --git a/valorant/models/weapons.py b/valorant/models/weapons.py index 4c00c66..ca95c12 100644 --- a/valorant/models/weapons.py +++ b/valorant/models/weapons.py @@ -26,7 +26,7 @@ from pydantic import Field -from ..enums import WeaponCategory +from ..enums import ShopCategory, WeaponCategory from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( @@ -91,7 +91,7 @@ class GridPosition(BaseModel): class ShopData(BaseModel): cost: int - category: str + category: ShopCategory shop_order_priority: int = Field(alias='shopOrderPriority') category_text: LocalizedField = Field(alias='categoryText') grid_position: GridPosition | None = Field(alias='gridPosition') From 056c018203c893f34cbf1fc88bb09fd839ccff5b Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:31:25 +0700 Subject: [PATCH 26/60] add GameFeature enum and update GameFeatureOverride model --- valorant/__init__.py | 2 ++ valorant/enums.py | 12 ++++++++++++ valorant/models/gamemodes.py | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/valorant/__init__.py b/valorant/__init__.py index d0cdd57..b76f2ea 100644 --- a/valorant/__init__.py +++ b/valorant/__init__.py @@ -17,6 +17,7 @@ from .enums import ( AbilitySlot, DivisionTier, + GameFeature, GameRule, Locale, MissionTag, @@ -35,6 +36,7 @@ 'Client', 'DivisionTier', 'Forbidden', + 'GameFeature', 'GameRule', 'HTTPException', 'InternalServerError', diff --git a/valorant/enums.py b/valorant/enums.py index 612dc0c..c5136e4 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -27,6 +27,7 @@ __all__ = ( 'AbilitySlot', 'DivisionTier', + 'GameFeature', 'GameRule', 'Locale', 'MissionTag', @@ -61,6 +62,17 @@ class DivisionTier(str, Enum): radiant = 'ECompetitiveDivision::RADIANT' +class GameFeature(str, Enum): + deathmatch_encourage_far_spawning = 'EGameFeatureToggleName::DeathmatchEncourageFarSpawning' + reuse_actor_on_respawn = 'EGameFeatureToggleName::ReuseActorOnRespawn' + allow_shopping_while_dead = 'EGameFeatureToggleName::AllowShoppingWhileDead' + use_server_authoritative_drop_out = 'EGameFeatureToggleName::UseServerAuthoritativeDropOut' + equippable_cache_recycling = 'EGameFeatureToggleName::EquippableCacheRecycling' + use_mesh_material_manager_alt = 'EGameFeatureToggleName::UseMeshMaterialManagerAlt' + disable_fog_of_war = 'EGameFeatureToggleName::DisableFogOfWar' + remove_deleted_fx_cs_from_pool = 'EGameFeatureToggleName::RemoveDeletedFXCsFromPool' + + class GameRule(str, Enum): accolades_enabled = 'EGameRuleBoolName::AccoladesEnabled' allow_drop_out = 'EGameRuleBoolName::AllowDropOut' diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index c0444d1..91fba60 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -28,7 +28,7 @@ from pydantic import Field -from ..enums import GameRule +from ..enums import GameFeature, GameRule from .base import BaseModel, BaseUUIDModel, LocalizedField __all__ = ( @@ -44,7 +44,7 @@ class GameFeatureOverride(BaseModel): - feature_name: str = Field(alias='featureName') + feature_name: GameFeature = Field(alias='featureName') state: bool From 31cb547b66f70ddfeef4337c55be0a0a8c3ab426 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:32:13 +0700 Subject: [PATCH 27/60] add tests for enums --- tests/test_enums.py | 158 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 tests/test_enums.py diff --git a/tests/test_enums.py b/tests/test_enums.py new file mode 100644 index 0000000..df83e88 --- /dev/null +++ b/tests/test_enums.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from valorant.enums import ( + AbilitySlot, + DivisionTier, + GameFeature, + GameRule, + MissionTag, + MissionType, + RelationType, + RewardType, + SeasonType, + ShopCategory, + WeaponCategory, +) + +if TYPE_CHECKING: + from valorant import Client + + +@pytest.mark.anyio +async def test_ability_slot(client: Client) -> None: + agents = await client.fetch_agents() + # fmt: off + used_ability_slots = { + ability.slot + for agent in agents + for ability in agent.abilities + } + # fmt: on + all_ability_slots = set(AbilitySlot) + assert used_ability_slots == all_ability_slots + + +@pytest.mark.anyio +async def test_division_tier(client: Client) -> None: + competitive_tiers = await client.fetch_competitive_tiers() + # fmt: off + used_division_tiers = { + division.division + for competitive_tier in competitive_tiers + for division in competitive_tier.tiers + } + # fmt: on + all_division_tiers = set(DivisionTier) + assert used_division_tiers == all_division_tiers + + +def test_locale() -> None: ... +@pytest.mark.anyio +async def test_game_feature(client: Client) -> None: + game_modes = await client.fetch_game_modes() + # fmt: off + used_game_features = { + feature.feature_name + for game_mode in game_modes if game_mode.game_feature_overrides is not None + for feature in game_mode.game_feature_overrides + } + # fmt: on + all_game_features = set(GameFeature) + assert used_game_features == all_game_features + + +@pytest.mark.anyio +async def test_game_rule(client: Client) -> None: + game_modes = await client.fetch_game_modes() + # fmt: off + used_game_rules = { + rule.rule_name + for game_mode in game_modes if game_mode.game_rule_bool_overrides is not None + for rule in game_mode.game_rule_bool_overrides + } + # fmt: on + all_game_rules = set(GameRule) + assert used_game_rules == all_game_rules + + +@pytest.mark.anyio +async def test_mission_type(client: Client) -> None: + missions = await client.fetch_missions() + used_mission_types = {mission.type for mission in missions if mission.type is not None} + all_mission_types = set(MissionType) + assert used_mission_types == all_mission_types + + +@pytest.mark.anyio +async def test_mission_tag(client: Client) -> None: + missions = await client.fetch_missions() + # fmt: off + used_mission_tags = { + tag + for mission in missions if mission.tags is not None + for tag in mission.tags + } + # fmt: on + all_mission_tags = set(MissionTag) + assert used_mission_tags == all_mission_tags + + +@pytest.mark.anyio +async def test_relation_type(client: Client) -> None: + contracts = await client.fetch_contracts() + # fmt: off + used_relation_types = { + contract.content.relation_type + for contract in contracts if contract.content.relation_type is not None + } + # fmt: on + all_relation_types = set(RelationType) + assert used_relation_types == all_relation_types + + +@pytest.mark.anyio +async def test_reward_type(client: Client) -> None: + contracts = await client.fetch_contracts() + # fmt: off + used_reward_types = { + level.reward.type + for contract in contracts + for chapter in contract.content.chapters + for level in chapter.levels + } + # fmt: on + all_reward_types = set(RewardType) + assert used_reward_types == all_reward_types + + +@pytest.mark.anyio +async def test_season_type(client: Client) -> None: + seasons = await client.fetch_seasons() + used_season_types = {season.type for season in seasons if season.type is not None} + all_season_types = set(SeasonType) + assert used_season_types == all_season_types + + +@pytest.mark.anyio +async def test_shop_category(client: Client) -> None: + gears = await client.fetch_gears() + weapons = await client.fetch_weapons() + + gear_used_shop_categories = {gear.shop_data.category for gear in gears} + weapon_used_shop_categories = {weapon.shop_data.category for weapon in weapons if weapon.shop_data is not None} + used_shop_categories = gear_used_shop_categories | weapon_used_shop_categories + + all_shop_categories = set(ShopCategory) + assert used_shop_categories == all_shop_categories + + +@pytest.mark.anyio +async def test_weapon_category(client: Client) -> None: + weapons = await client.fetch_weapons() + used_categories = {weapon.category for weapon in weapons} + all_categories = set(WeaponCategory) + assert used_categories == all_categories From 26f204b8fbf9a753c73eb7b1f890c08176a8f424 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:32:35 +0700 Subject: [PATCH 28/60] refactor: remove unused GameRule enum values --- valorant/enums.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/valorant/enums.py b/valorant/enums.py index c5136e4..d3b76f4 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -77,14 +77,12 @@ class GameRule(str, Enum): accolades_enabled = 'EGameRuleBoolName::AccoladesEnabled' allow_drop_out = 'EGameRuleBoolName::AllowDropOut' assign_random_agents = 'EGameRuleBoolName::AssignRandomAgents' - assign_random_agent = 'EGameRuleBoolName::AssignRandomAgent' combat_report_only_show_last_life = 'EGameRuleBoolName::CombatReportOnlyShowLastLife' context_aware_module_enabled = 'EGameRuleBoolName::ContextAwareModuleEnabled' destroy_abilities_on_death = 'EGameRuleBoolName::DestroyAbilitiesOnDeath' disable_shop_selling = 'EGameRuleBoolName::DisableShopSelling' downed_characters_can_give_up = 'EGameRuleBoolName::DownedCharactersCanGiveUp' fill_with_bots = 'EGameRuleBoolName::FillWithBots' - game_rule_bool_name = 'EGameRuleBoolName::EGameRuleBoolName' is_overtime_win_by_two = 'EGameRuleBoolName::IsOvertimeWinByTwo' majority_vote_agents = 'EGameRuleBoolName::MajorityVoteAgents' pip_ability_casting = 'EGameRuleBoolName::PipAbilityCasting' From 040f32f3eab3b281bbd454e0297f453f2c95e585 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:34:41 +0700 Subject: [PATCH 29/60] refactor: remove unused test_locale --- tests/test_enums.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_enums.py b/tests/test_enums.py index df83e88..360f2c4 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -50,7 +50,6 @@ async def test_division_tier(client: Client) -> None: assert used_division_tiers == all_division_tiers -def test_locale() -> None: ... @pytest.mark.anyio async def test_game_feature(client: Client) -> None: game_modes = await client.fetch_game_modes() From 8f616534e2e8d99efbddcecb6060e00c1d26bcf5 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:46:44 +0700 Subject: [PATCH 30/60] sorted GameFeature enum --- valorant/enums.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/valorant/enums.py b/valorant/enums.py index d3b76f4..b72d963 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -63,14 +63,14 @@ class DivisionTier(str, Enum): class GameFeature(str, Enum): - deathmatch_encourage_far_spawning = 'EGameFeatureToggleName::DeathmatchEncourageFarSpawning' - reuse_actor_on_respawn = 'EGameFeatureToggleName::ReuseActorOnRespawn' allow_shopping_while_dead = 'EGameFeatureToggleName::AllowShoppingWhileDead' - use_server_authoritative_drop_out = 'EGameFeatureToggleName::UseServerAuthoritativeDropOut' - equippable_cache_recycling = 'EGameFeatureToggleName::EquippableCacheRecycling' - use_mesh_material_manager_alt = 'EGameFeatureToggleName::UseMeshMaterialManagerAlt' + deathmatch_encourage_far_spawning = 'EGameFeatureToggleName::DeathmatchEncourageFarSpawning' disable_fog_of_war = 'EGameFeatureToggleName::DisableFogOfWar' + equippable_cache_recycling = 'EGameFeatureToggleName::EquippableCacheRecycling' + reuse_actor_on_respawn = 'EGameFeatureToggleName::ReuseActorOnRespawn' remove_deleted_fx_cs_from_pool = 'EGameFeatureToggleName::RemoveDeletedFXCsFromPool' + use_mesh_material_manager_alt = 'EGameFeatureToggleName::UseMeshMaterialManagerAlt' + use_server_authoritative_drop_out = 'EGameFeatureToggleName::UseServerAuthoritativeDropOut' class GameRule(str, Enum): From 2d264f95cf9c9c26d0b5fc1a7872e2665c21a7b0 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:47:54 +0700 Subject: [PATCH 31/60] sorted MissionType, RewardType enum --- valorant/enums.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/valorant/enums.py b/valorant/enums.py index b72d963..5745702 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -93,8 +93,8 @@ class GameRule(str, Enum): class MissionType(str, Enum): - weekly = 'EAresMissionType::Weekly' daily = 'EAresMissionType::Daily' + weekly = 'EAresMissionType::Weekly' tutorial = 'EAresMissionType::Tutorial' npe = 'EAresMissionType::NPE' bte = 'EAresMissionType::BTE' @@ -112,11 +112,11 @@ class RelationType(str, Enum): class RewardType(str, Enum): - skin_level = 'EquippableSkinLevel' buddy_level = 'EquippableCharmLevel' currency = 'Currency' player_card = 'PlayerCard' player_title = 'Title' + skin_level = 'EquippableSkinLevel' spray = 'Spray' From 30ee25c778f267c1bed7f0a38dc754f4fc75a444 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:56:39 +0700 Subject: [PATCH 32/60] feat: add methods to fetch theme, content tier in Skin model --- valorant/models/weapons.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/valorant/models/weapons.py b/valorant/models/weapons.py index ca95c12..b639ac7 100644 --- a/valorant/models/weapons.py +++ b/valorant/models/weapons.py @@ -24,6 +24,9 @@ from __future__ import annotations +from typing import TYPE_CHECKING +from uuid import UUID + from pydantic import Field from ..enums import ShopCategory, WeaponCategory @@ -40,6 +43,12 @@ ) +if TYPE_CHECKING: + from ..client import Client + from .content_tiers import ContentTier + from .themes import Theme + + class AdsStats(BaseModel): zoom_multiplier: float = Field(alias='zoomMultiplier') fire_rate: float = Field(alias='fireRate') @@ -124,14 +133,26 @@ class Level(BaseUUIDModel): class Skin(BaseUUIDModel): # uuid: str display_name: LocalizedField = Field(alias='displayName') - theme_uuid: str = Field(alias='themeUuid') - content_tier_uuid: str | None = Field(alias='contentTierUuid') + theme_uuid: UUID = Field(alias='themeUuid') + content_tier_uuid: UUID | None = Field(alias='contentTierUuid') display_icon: str | None = Field(alias='displayIcon') wallpaper: str | None asset_path: str = Field(alias='assetPath') chromas: list[Chroma] levels: list[Level] + # useful methods + + async def fetch_theme(self, *, client: Client) -> Theme | None: + if self.theme_uuid is None: + return None + return await client.fetch_theme(str(self.theme_uuid)) + + async def fetch_content_tier(self, *, client: Client) -> ContentTier | None: + if self.content_tier_uuid is None: + return None + return await client.fetch_content_tier(str(self.content_tier_uuid)) + class Weapon(BaseUUIDModel): # uuid: str From abaeadd985838df3cf12c249237d60e1b9f242a8 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:57:51 +0700 Subject: [PATCH 33/60] update example basic --- examples/basic.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/basic.py b/examples/basic.py index 198546d..b76cecf 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -9,27 +9,30 @@ async def main() -> None: weapon = await client.fetch_weapon('9c82e19d-4575-0200-1a81-3eacf00cf872') # Vandal assert weapon is not None - for skin in weapon.skins: - print(skin.display_name) # default locale + for skin in weapon.skins[:5]: + print(skin.display_name) print(skin.display_icon) - # specify locale + # display_name locale print(skin.display_name.ja_JP) print(skin.display_name.japanese) - # print(skin.display_name.from_locale(valorant.Locale.japanese)) - # if skin.theme is not None: - # print(skin.theme.display_name) - # print(skin.theme.display_icon) + skin_theme = await skin.fetch_theme(client=client) + if skin_theme is not None: + print(skin_theme.display_name) + print(skin_theme.display_icon) - # if skin.content_tier is not None: - # print(skin.content_tier.display_name) - # print(skin.content_tier.display_icon) + skin_content_tier = await skin.fetch_content_tier(client=client) + if skin_content_tier is not None: + print(skin_content_tier.display_name) + print(skin_content_tier.display_icon) for level in skin.levels: print(level.display_name) for chroma in skin.chromas: print(chroma.display_name) + print('-' * 40) + asyncio.run(main()) From 29c308a0e8c5c10ce78976ebd384b52f198baa52 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:04:47 +0700 Subject: [PATCH 34/60] add version_info computed property to Version model --- valorant/models/version.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/valorant/models/version.py b/valorant/models/version.py index 5e0c7ee..ee4a917 100644 --- a/valorant/models/version.py +++ b/valorant/models/version.py @@ -25,12 +25,21 @@ from __future__ import annotations from datetime import datetime +from functools import cached_property +from typing import NamedTuple -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, computed_field __all__ = ('Version',) +class VersionInfo(NamedTuple): + major: int + minor: int + patch: int + build: int + + class Version(BaseModel): manifest_id: str = Field(alias='manifestId') branch: str @@ -40,3 +49,9 @@ class Version(BaseModel): riot_client_version: str = Field(alias='riotClientVersion') riot_client_build: str = Field(alias='riotClientBuild') build_date: datetime = Field(alias='buildDate') + + @computed_field # type: ignore[misc] + @cached_property + def version_info(self) -> VersionInfo: + major, minor, patch, build = map(int, self.version.split('.')) + return VersionInfo(major=major, minor=minor, patch=patch, build=build) From 677bd9db92baa076fc87b07adc85a68c89cc6bb8 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:54:14 +0700 Subject: [PATCH 35/60] update mypy ignore --- valorant/models/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/models/version.py b/valorant/models/version.py index ee4a917..2ec7af4 100644 --- a/valorant/models/version.py +++ b/valorant/models/version.py @@ -50,7 +50,7 @@ class Version(BaseModel): riot_client_build: str = Field(alias='riotClientBuild') build_date: datetime = Field(alias='buildDate') - @computed_field # type: ignore[misc] + @computed_field # type: ignore[prop-decorator] @cached_property def version_info(self) -> VersionInfo: major, minor, patch, build = map(int, self.version.split('.')) From 87e66c18c3c3746279a859dee10fd69371bcd2bd Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:24:21 +0700 Subject: [PATCH 36/60] refactor: rename init method to start in Client and HTTPClient --- examples/http_client.py | 2 +- valorant/client.py | 8 ++++---- valorant/http.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/http_client.py b/examples/http_client.py index 2878738..5986628 100644 --- a/examples/http_client.py +++ b/examples/http_client.py @@ -5,7 +5,7 @@ async def main() -> None: http = HTTPClient() - await http.init() + await http.start() # agents diff --git a/valorant/client.py b/valorant/client.py index 83f9bfe..a8fd2c6 100644 --- a/valorant/client.py +++ b/valorant/client.py @@ -79,7 +79,7 @@ def __init__( self._closed: bool = False async def __aenter__(self) -> Self: - await self.init() + await self.start() return self async def __aexit__( @@ -91,9 +91,9 @@ async def __aexit__( if not self.is_closed(): await self.close() - async def init(self) -> None: - await self.http.init() - _log.info('client initialized') + async def start(self) -> None: + await self.http.start() + _log.info('client started') def is_closed(self) -> bool: return self._closed diff --git a/valorant/http.py b/valorant/http.py index a44967f..fbd0eee 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -74,7 +74,7 @@ def __init__(self, session: aiohttp.ClientSession | None = None) -> None: user_agent = 'valorantx (https://github.com/staciax/valorant {0}) Python/{1[0]}.{1[1]} aiohttp/{2}' self.user_agent: str = user_agent.format(__version__, sys.version_info, aiohttp.__version__) - async def init(self) -> None: + async def start(self) -> None: if self.__session is None: self.__session = aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=0)) From 2e71864c4bb10bbd9743d4a5fe8fb40ed29eeef2 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:47:03 +0700 Subject: [PATCH 37/60] add tests dir to coverage options --- pyproject.toml | 2 +- uv.lock | 254 ++++++++++++++++++++++++++++++++++++++++++++++- valorant/http.py | 10 +- 3 files changed, 259 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b5c3e36..0401ca6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -162,7 +162,7 @@ ignore-variadic-names = true ] [tool.pytest.ini_options] -addopts = "-vv --showlocals --tb=short --strict -p no:warnings --cov=valorant" +addopts = "-vv --showlocals --tb=short --strict -p no:warnings --cov=valorant --cov=tests" [tool.coverage.report] show_missing = true diff --git a/uv.lock b/uv.lock index f5a8cfa..afef6e8 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,18 @@ version = 1 requires-python = ">=3.10" +[[package]] +name = "aiodns" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycares" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/84/41a6a2765abc124563f5380e76b9b24118977729e25a84112f8dfb2b33dc/aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72", size = 7823 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/14/13c65b1bd59f7e707e0cc0964fbab45c003f90292ed267d159eeeeaa2224/aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5", size = 5735 }, +] + [[package]] name = "aiohappyeyeballs" version = "2.4.4" @@ -88,6 +100,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/62/c9fa5bafe03186a0e4699150a7fed9b1e73240996d0d2f0e5f70f3fdf471/aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99", size = 436081 }, ] +[package.optional-dependencies] +speedups = [ + { name = "aiodns", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, + { name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" }, +] + [[package]] name = "aiosignal" version = "1.3.2" @@ -142,6 +161,155 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045 }, + { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218 }, + { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872 }, + { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254 }, + { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293 }, + { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385 }, + { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104 }, + { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981 }, + { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297 }, + { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735 }, + { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107 }, + { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400 }, + { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985 }, + { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099 }, + { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172 }, + { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255 }, + { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 }, + { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 }, + { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 }, + { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 }, + { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 }, + { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 }, + { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 }, + { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 }, + { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 }, + { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 }, + { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 }, + { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 }, + { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 }, + { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 }, + { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 }, + { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 }, + { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 }, + { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 }, + { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 }, + { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 }, + { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 }, + { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 }, + { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 }, + { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 }, + { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 }, + { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 }, + { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 }, + { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 }, + { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 }, + { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 }, + { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 }, + { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 }, + { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681 }, + { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475 }, + { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173 }, + { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803 }, + { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946 }, + { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707 }, + { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231 }, + { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157 }, + { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122 }, + { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206 }, + { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804 }, + { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517 }, +] + +[[package]] +name = "brotlicffi" +version = "1.1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786 }, + { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165 }, + { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895 }, + { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834 }, + { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731 }, + { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783 }, + { url = "https://files.pythonhosted.org/packages/e5/3b/bd4f3d2bcf2306ae66b0346f5b42af1962480b200096ffc7abc3bd130eca/brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca", size = 397397 }, + { url = "https://files.pythonhosted.org/packages/54/10/1fd57864449360852c535c2381ee7120ba8f390aa3869df967c44ca7eba1/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391", size = 379698 }, + { url = "https://files.pythonhosted.org/packages/e5/95/15aa422aa6450e6556e54a5fd1650ff59f470aed77ac739aa90ab63dc611/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8", size = 378635 }, + { url = "https://files.pythonhosted.org/packages/6c/a7/f254e13b2cb43337d6d99a4ec10394c134e41bfda8a2eff15b75627f4a3d/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35", size = 385719 }, + { url = "https://files.pythonhosted.org/packages/72/a9/0971251c4427c14b2a827dba3d910d4d3330dabf23d4278bf6d06a978847/brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d", size = 361760 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -517,6 +685,82 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, ] +[[package]] +name = "pycares" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/b1/94daaa50b6d2fa14c6b4981ca24fa4e7aa33a7519962c76170072ffb06ee/pycares-4.5.0.tar.gz", hash = "sha256:025b6c2ffea4e9fb8f9a097381c2fecb24aff23fbd6906e70da22ec9ba60e19d", size = 821554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/e9/109c7ed1db9a9b30720c528f54a219ef1c07f16f364bc28b1f6bbcdb7932/pycares-4.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13a82fad8239d6fbcf916099bee17d8b5666d0ddb77dace431e0f7961c9427ab", size = 74833 }, + { url = "https://files.pythonhosted.org/packages/e2/05/3b01a1f890cb270896ee6898a07a33cb6abe9fcc2c1cef8d719f5a2f2d1f/pycares-4.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fefc7bebbe39b2e3b4b9615471233a8f7356b96129a7db9030313a3ae4ecc42d", size = 71320 }, + { url = "https://files.pythonhosted.org/packages/34/15/4d2e3da5b3ed93d0f8cca785729ce50279c6f8a0855b040f19ff6ca57b12/pycares-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e322e8ce810026f6e0c7c2a254b9ed02191ab8d42fa2ce6808ede1bdccab8e65", size = 289130 }, + { url = "https://files.pythonhosted.org/packages/86/b0/8d137cf324e0991fd0a70e2b289126b706b71ca2cbad294039ef54819e04/pycares-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723ba0803b016294430e40e544503fed9164949b694342c2552ab189e2b688ef", size = 303788 }, + { url = "https://files.pythonhosted.org/packages/ce/b6/c82bb7f5b04278b294f802713fd19c00cab761b8c3b5f0179e5f75829cd1/pycares-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e48b20b59cdc929cc712a8b22e89c273256e482b49bb8999af98d2c6fc4563c2", size = 293243 }, + { url = "https://files.pythonhosted.org/packages/d3/b6/6673cb88e128bedb3d6c675429d5de4b17f35ffc45dc5b5508393c38626a/pycares-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6e55bd9af595b112ac6080ac0a0d52b5853d0d8e6d01ac65ff09e51e62490a", size = 288571 }, + { url = "https://files.pythonhosted.org/packages/41/7d/dd90ec643b28941575afcbcb29ce01d25c3eac199e674445349b77ace327/pycares-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6f4b9063e3dd70460400367917698f209c10aabb68bf70b09e364895444487d", size = 269914 }, + { url = "https://files.pythonhosted.org/packages/ab/53/82f52dc86d00a2df90acef03d549b2afedba00184228f2420c112e5146d6/pycares-4.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95522d4840d702fd766439a7c7cd747935aa54cf0b8675e9fadd8414dd9dd0df", size = 277881 }, + { url = "https://files.pythonhosted.org/packages/2e/bb/24fe9fed26c110fd2c219cdf133bab7e18e73896bda8960be50c896cfc66/pycares-4.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4709ce4fd9dbee24b1397f71a2adb3267323bb5ad5e7fde3f87873d172dd156", size = 263704 }, + { url = "https://files.pythonhosted.org/packages/83/1b/c33e94fd0de4a67a833818e920ab4541aeca8adf067d780fa031ffef0d7c/pycares-4.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8addbf3408af1010f50fd67ef634a6cb239ccb9c534c32a40713f3b8d306a98e", size = 297410 }, + { url = "https://files.pythonhosted.org/packages/87/90/266f05bb0781d1705a14140c9a6742dcbd7be6fad96a2d2c807193b1b285/pycares-4.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d0428ef42fcf575e197047e6a47892404faa34231902a453b3dfed66af4178b3", size = 290483 }, + { url = "https://files.pythonhosted.org/packages/18/5f/e8b5b142075c0dfeb7f980fec17c8f970955ede842f0234f360e5a3c5610/pycares-4.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aed5c2732f3a6bdbbfab202267d37044ca1162f690b9d34b7ece97ba43f27453", size = 279970 }, + { url = "https://files.pythonhosted.org/packages/b3/0a/e42a4aa2c456e62485128c984a2c8e078966b4047d016651e1570f34c027/pycares-4.5.0-cp310-cp310-win32.whl", hash = "sha256:b1859ea770a7abec40a6d02b5ab03c2396c4900c01f4e50ddb6c0dca4c2a6a7c", size = 61603 }, + { url = "https://files.pythonhosted.org/packages/23/b4/013b9a19b589f936730a1d858cad25c9fdabf8d91968ec6918c3228842cf/pycares-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f87d8da20a3a80ab05fe80c14a62bf078bd726ca6af609edbeb376fb97d50ab", size = 76534 }, + { url = "https://files.pythonhosted.org/packages/65/72/0dbd1169058924af1f8f1c1421fba64b7fb0ab2b8a489789f17c4b8527ee/pycares-4.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ca7a1dba7b88290710db45012e0903c21c839fa0a2b9ddc100bba8e66bfb251", size = 74837 }, + { url = "https://files.pythonhosted.org/packages/4a/55/97d90ab79e16e641c35a9b53b437e6556993ea904af2e1ce8982a6b8191b/pycares-4.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:160e92588cdf1a0fa3a7015f47990b508d50efd9109ea4d719dee31c058f0648", size = 71314 }, + { url = "https://files.pythonhosted.org/packages/52/91/c152c9e90e9ea7b2bc6381abecdc52ccf1f18b5aa5e67c3a4264f9ae1b22/pycares-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f38e45d23660ed1dafdb956fd263ae4735530ef1578aa2bf2caabb94cee4523", size = 289122 }, + { url = "https://files.pythonhosted.org/packages/eb/af/e28bd3aa1c87b624ba8a422f5f86ff7a2eee6d2137c3f43c5de543b5d0e3/pycares-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f742acc6d29a99ffc14e3f154b3848ea05c5533b71065e0f0a0fd99c527491b2", size = 303778 }, + { url = "https://files.pythonhosted.org/packages/30/cd/7b584e8aba8efb33bd1447cd66cf30512a9d989aae76c7baaf7ffd87c9b1/pycares-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceaf71bcd7b6447705e689b8fee8836c20c6148511a90122981f524a84bfcca9", size = 293217 }, + { url = "https://files.pythonhosted.org/packages/bb/06/a3def79dcf29671bb5582c0654d9461f865001254b1b52226dd2958270e4/pycares-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdc3c0be7b5b83e78e28818fecd0405bd401110dd6e2e66f7f10713c1188362c", size = 288559 }, + { url = "https://files.pythonhosted.org/packages/6b/fb/fe3c26ba45d0a0c3bd16a296725760c9787e828839dab8458ef96ab43a92/pycares-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd458ee69800195247aa19b5675c5914cbc091c5a220e4f0e96777a31bb555c1", size = 269861 }, + { url = "https://files.pythonhosted.org/packages/a5/05/163aedb018de14b6b94ebd183ff94b914d900cdc7c3ee65ebc845e44463e/pycares-4.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6649d713df73266708642fc3d04f110c0a66bee510fbce4cc5fed79df42083", size = 277840 }, + { url = "https://files.pythonhosted.org/packages/fa/6e/ed28d24cf7a8ade789a25db4e3885bdf8ffb1276e00f958c7de452f6383f/pycares-4.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ac57d7bda925c10b997434e7ce30a2c3689c2e96bab9fd0a1165d5577378eecd", size = 263680 }, + { url = "https://files.pythonhosted.org/packages/55/76/ae8e641c0f9ea6a98b9143a4b6a619ed4b35451b121265f4fded7c8d638d/pycares-4.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba17d8e5eeec4b2e0eb1a6a840bae9e62cd1c1c9cbc8dc9db9d1b9fdf33d0b54", size = 297460 }, + { url = "https://files.pythonhosted.org/packages/2c/16/a6f6cf9ca531cfce8d9e0b635a779c514e7aa7eccf9a4d89fb0d32eb102d/pycares-4.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9e9b7d1a8de703283e4735c0e532ba4bc600e88de872dcd1a9a4950cf74d9f4f", size = 290514 }, + { url = "https://files.pythonhosted.org/packages/8e/61/83f4ae4c6222766ac9326ebd383d985b016b9875d7412e01a5d18550e19d/pycares-4.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c6922ecbe458c13a4a2c1177bbce38abc44b5f086bc82115a92eab34418915f", size = 279964 }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d6e68eca2cb05d6dce0a5ca8aa8720f4aa2abc97679ebe649c97f3b6e4e/pycares-4.5.0-cp311-cp311-win32.whl", hash = "sha256:1004b8a17614e33410b4b1bb68360977667f1cc9ab2dbcfb27240d6703e4cb6a", size = 61604 }, + { url = "https://files.pythonhosted.org/packages/75/d6/a5d3752b62a4a728c03ce39e4aa9689fc275f1a408fa4f887ff31f01ee52/pycares-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:2c9c1055c622258a0f315560b2880a372363484b87cbef48af092624804caa72", size = 76534 }, + { url = "https://files.pythonhosted.org/packages/d2/d7/86d62dec9edb3cbba1a11ef0b9558483659f049d14175d41cbef156cde4c/pycares-4.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:506efbe5017807747ccd1bdcb3c2f6e64635bc01fee01a50c0b97d649018c162", size = 74921 }, + { url = "https://files.pythonhosted.org/packages/3c/db/6aa863a70eec7989b70bd8b666b1b7cb13e446deaad416e2acf69f227e21/pycares-4.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c469ec9fbe0526f45a98f67c1ea55be03abf30809c4f9c9be4bc93fb6806304d", size = 71331 }, + { url = "https://files.pythonhosted.org/packages/26/64/2a5338c4d5c72211a7d5bda9c8350086ad9d02c66a2f3cb4933be8daa6ba/pycares-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597c0950ede240c3a779f023fcf2442207fc11e570d3ca4ccdbb0db5bbaf2588", size = 290337 }, + { url = "https://files.pythonhosted.org/packages/30/62/e9186156e2fc88b1687c2e926b8987c6d11b568a23d9ff7be1863b7050fd/pycares-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aa0da03c4df6ed0f87dd52a293bd0508734515041cc5be0f85d9edc1814914f", size = 304297 }, + { url = "https://files.pythonhosted.org/packages/ed/88/823401c364d38dc5079502516bbb44a8884f92b55bb219cf91208a7b9fb0/pycares-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1ebf52767c777d10a1b3d03844b9b05cc892714b3ee177d5d9fbff74fb9fa", size = 294281 }, + { url = "https://files.pythonhosted.org/packages/e3/48/5a1b9b944436a8274918b9b6247fe3e2456993955f597992a0ed1e480b9f/pycares-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb20d84269ddffb177b6048e3bc03d0b9ffe17592093d900d5544805958d86b3", size = 289879 }, + { url = "https://files.pythonhosted.org/packages/1e/96/d32dab745bd2c50d696e463d65ae72c3bd2f5dbd909aead41d2080f7b33d/pycares-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3125df81b657971ee5c0333f8f560ba0151db1eb7cf04aea7d783bb433b306c1", size = 270617 }, + { url = "https://files.pythonhosted.org/packages/49/be/02485d69c5f883e24cf13880b9dd85cb90e353470f3f12f3708fbefce953/pycares-4.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:525c77ea44546c12f379641aee163585d403cf50e29b04a06059d6aac894e956", size = 278552 }, + { url = "https://files.pythonhosted.org/packages/4b/ae/88d4359c73adf7458595184ee620fbed2d6e53abbd1440b221696d3888e5/pycares-4.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1fd87cb26b317a9988abfcfa4e4dbc55d5f20177e5979ad4d854468a9246c187", size = 263719 }, + { url = "https://files.pythonhosted.org/packages/4e/24/6a799f616558f0ea2380af76e73c6f246af8bce81dec0107cf2b14493f47/pycares-4.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a90aecd41188884e57ae32507a2c6b010c60b791a253083761bbb37a488ecaed", size = 297729 }, + { url = "https://files.pythonhosted.org/packages/14/c5/d00ea25938e441081608a4c1a4ee91b3e6a021606a109cb251752aa3bfed/pycares-4.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0d3de65cab653979dcc491e03f596566c9d40346c9deb088e0f9fe70600d8737", size = 291630 }, + { url = "https://files.pythonhosted.org/packages/bd/bc/1425763b3d2883039842272e3d295226f91f79d9936d70f93da1b25c371a/pycares-4.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27a77b43604b3ba24e4fc49fd3ea59f50f7d89c7255f1f1ea46928b26cccacfa", size = 281153 }, + { url = "https://files.pythonhosted.org/packages/e5/ba/c8ab1c930a93442e363d49660329fc597b8e84b4d81eee64129f9611f24a/pycares-4.5.0-cp312-cp312-win32.whl", hash = "sha256:6028cb8766f0fea1d2caa69fac23621fbe2cff9ce6968374e165737258703a33", size = 61624 }, + { url = "https://files.pythonhosted.org/packages/c0/d7/1cc9403bf599c3f30d51d37c9639794a607c448a121a0a7447d59dbb6f07/pycares-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ce10672c4cfd1c5fb6718e8b25f0336ca11c89aab88aa6df53dafc4e41df740", size = 76546 }, + { url = "https://files.pythonhosted.org/packages/32/8b/b48b6a7c4234d682fb9f0d6904a09333711642b760c79a6a7b2f53cbe76e/pycares-4.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:011cd670da7caf55664c944abb71ec39af82b837f8d48da7cf0eec80f5682c4c", size = 74919 }, + { url = "https://files.pythonhosted.org/packages/fb/57/3bec0d830efe9521a4706581fa9cc74825d7996104d773c8c971925a3989/pycares-4.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b5c67930497fb2b1dbcaa85f8c4188fc2cb62e41d787deeed2d33cfe9dd6bf52", size = 71334 }, + { url = "https://files.pythonhosted.org/packages/b7/94/1e6e41f0f72f32e8e32922b085e5df6a9d0a6dd3f5d41cfe213d8849b15e/pycares-4.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d435a3b8468c656a7e7180dd7c4794510f6c612c33ad61a0fff6e440621f8b5", size = 290310 }, + { url = "https://files.pythonhosted.org/packages/1a/24/524ea7bd19edb9392f32a4fcd2eaf11c98fdaeffd28588b4d2bec4ed9b08/pycares-4.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8371f5ee1efb33d6276e275d152c9c5605e5f2e58a9e168519ec1f9e13dd95ae", size = 304230 }, + { url = "https://files.pythonhosted.org/packages/ec/07/a019facc86438181af5cfb045b8d8fe8eeed41117c0abdb6c81acc430e17/pycares-4.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76a9096fd5dc49c61c5235ea7032e8b43f4382800d64ca1e0e0cda700c082aa", size = 294256 }, + { url = "https://files.pythonhosted.org/packages/a3/90/50f08b6970a40861e5466ec965f9cf505812d7db51d7d4f15b84102dc2c6/pycares-4.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b604af76b57469ff68b44e9e4c857eaee43bc5035f4f183f07f4f7149191fe1b", size = 289866 }, + { url = "https://files.pythonhosted.org/packages/b9/44/76696e6dba0f166990d033c7889c47dc14db830ed424552020c830d5d409/pycares-4.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c589bd4f9160bfdb2f8080cf564bb120a4312cf091db07fe417f8e58a896a63c", size = 270588 }, + { url = "https://files.pythonhosted.org/packages/00/a1/788ffea09b7a57cd9e66610ba5b189d2a0d85853e95b79de547351bd6665/pycares-4.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:361262805bb09742c364ec0117842043c950339e38561009bcabbb6ac89458ef", size = 278568 }, + { url = "https://files.pythonhosted.org/packages/39/ba/dfc4d79a7aefc034d79c9ccf32f47ce9e4a2a5d42f26f2cae7627aafefd1/pycares-4.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d2afb3c0776467055bf33db843ef483d25639be0f32e3a13ef5d4dc64098bf5", size = 263702 }, + { url = "https://files.pythonhosted.org/packages/20/78/6819d2c283ce7b53ef639fff44f15ea46dd5dd57307f25d46fb41d83966c/pycares-4.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bc7a1d8ed7c7a4de17706a3c89b305b02eb64c778897e6727c043e5b9dd0d853", size = 297722 }, + { url = "https://files.pythonhosted.org/packages/bd/0d/6776b00eb6ea3d2742ac4f4e1d4bf170168d0923effffa1963e3b4619019/pycares-4.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5703ec878b5c1efacdbf24ceaedfa606112fc67af5564f4db99c2c210f3ffadc", size = 291628 }, + { url = "https://files.pythonhosted.org/packages/13/29/c329ec7ef6cd88335676d71ed8a059d0fd2f5adcff3ab3537e03327955fa/pycares-4.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d87758e09dbf52c27ed7cf7bc7eaf8b3226217d10c52b03d61a14d59f40fcae1", size = 281161 }, + { url = "https://files.pythonhosted.org/packages/02/c4/aadb6b4a60b040bd6c4de31d971d4a07f54af274594a368de06170ede457/pycares-4.5.0-cp313-cp313-win32.whl", hash = "sha256:3316d490b4ce1a69f034881ac1ea7608f5f24ea5293db24ab574ac70b7d7e407", size = 61624 }, + { url = "https://files.pythonhosted.org/packages/32/38/33889749dde2c3ecab5039d72459769d8714665ba3038e718f514c556517/pycares-4.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:018e700fb0d1a2db5ec96e404ffa85ed97cc96e96d6af0bb9548111e37cf36a3", size = 76546 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + [[package]] name = "pydantic" version = "2.10.4" @@ -723,7 +967,8 @@ name = "valorant-py" version = "2.0.0a0" source = { virtual = "." } dependencies = [ - { name = "aiohttp" }, + { name = "aiodns" }, + { name = "aiohttp", extra = ["speedups"] }, { name = "pydantic" }, ] @@ -739,7 +984,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "aiohttp", specifier = ">=3.11.11,<4.0" }, + { name = "aiodns", specifier = ">=3.2.0" }, + { name = "aiohttp", extras = ["speedups"], specifier = ">=3.11.11,<4.0" }, { name = "pydantic", specifier = ">2.0,<3.0" }, ] @@ -747,10 +993,10 @@ requires-dist = [ dev = [ { name = "anyio", specifier = ">=4.7.0,<5.0" }, { name = "coverage", specifier = ">=7.6.10,<8.0" }, - { name = "mypy", specifier = ">=1.14.0,<1.15.0" }, + { name = "mypy", specifier = ">=1.14.0,<1.15" }, { name = "pytest", specifier = ">=8.3.4,<9.0" }, { name = "pytest-cov", specifier = ">=6.0.0,<7.0" }, - { name = "ruff", specifier = ">=0.8.4,<0.9.0" }, + { name = "ruff", specifier = ">=0.8.4,<0.9" }, ] [[package]] diff --git a/valorant/http.py b/valorant/http.py index fbd0eee..aa99e9e 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -32,7 +32,7 @@ import aiohttp -from . import __version__ +from . import __version__, utils from .errors import BadRequest, HTTPException, InternalServerError, NotFound, RateLimited if TYPE_CHECKING: @@ -47,6 +47,11 @@ # http-client inspired by https://github.com/Rapptz/discord.py/blob/master/discord/http.py +async def to_json(response: aiohttp.ClientResponse) -> dict[str, Any] | str: + text = await response.text(encoding='utf-8') + return utils._from_json(text) + + class Route: BASE: ClassVar[str] = 'https://valorant-api.com/v1' @@ -91,7 +96,8 @@ async def request(self, route: Route, **kwargs: Any) -> Any: async with self.__session.request(method, url, **kwargs) as response: # noqa: F811 _log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), response.status) - data = await response.json() + # data = await response.json() + data = await to_json(response) if 300 > response.status >= 200: _log.debug('%s %s has received %s', method, url, data) From 434c46c6524ef8e31d114c96c31c466c334cc9cb Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:48:09 +0700 Subject: [PATCH 38/60] fmt --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0401ca6..6b0b874 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,6 @@ select = [ "BLE", # flake8-blind-except "A", # flake8-builtins "COM", # flake8-commas - # "DTZ", # flake8-datetimez "FA", # flake8-future-annotations "ISC", # flake8-implicit-str-concat "LOG", # flake8-logging @@ -107,6 +106,8 @@ select = [ "FURB", # refurb "RUF", # ruff-specific rules + + # "DTZ", # flake8-datetimez ] ignore = [ "ANN401", # any-type From 0c64393a27e952e7960b727c364aa7952869b3b2 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:57:08 +0700 Subject: [PATCH 39/60] chore: update project description in README and pyproject.toml --- README.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1aeeed2..f63dc60 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # valorant -A modern, easy to use, and async ready API wrapper for [Valorant API](https://valorant-api.com) written in Python. +An Asynchronous API wrapper for [Valorant API](https://valorant-api.com) written in Python. ## Features - Fully type annotated. diff --git a/pyproject.toml b/pyproject.toml index 6b0b874..63960d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "valorant.py" -description = "A modern, easy to use, feature-rich, and async ready API wrapper for Valorant API written in Python." +description = "An Asynchronous API wrapper for Valorant API written in Python." readme = "README.md" requires-python = ">=3.10" authors = [{ name = "STACiA", email = "stacia.dev@gmail.com" }] From 8f510d014d35ba305925614bdc5b543f2f3f1204 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Mon, 30 Dec 2024 18:03:53 +0700 Subject: [PATCH 40/60] fix --- valorant/http.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/valorant/http.py b/valorant/http.py index aa99e9e..fbd0eee 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -32,7 +32,7 @@ import aiohttp -from . import __version__, utils +from . import __version__ from .errors import BadRequest, HTTPException, InternalServerError, NotFound, RateLimited if TYPE_CHECKING: @@ -47,11 +47,6 @@ # http-client inspired by https://github.com/Rapptz/discord.py/blob/master/discord/http.py -async def to_json(response: aiohttp.ClientResponse) -> dict[str, Any] | str: - text = await response.text(encoding='utf-8') - return utils._from_json(text) - - class Route: BASE: ClassVar[str] = 'https://valorant-api.com/v1' @@ -96,8 +91,7 @@ async def request(self, route: Route, **kwargs: Any) -> Any: async with self.__session.request(method, url, **kwargs) as response: # noqa: F811 _log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), response.status) - # data = await response.json() - data = await to_json(response) + data = await response.json() if 300 > response.status >= 200: _log.debug('%s %s has received %s', method, url, data) From 2ccc62eb671e887c39a6364a9daf5432e25284da Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:26:22 +0700 Subject: [PATCH 41/60] refactor: change color fields to use Pydantic Color type --- pyproject.toml | 6 +- uv.lock | 265 ++------------------------- valorant/models/agents.py | 3 +- valorant/models/competitive_tiers.py | 5 +- valorant/models/content_tiers.py | 3 +- 5 files changed, 29 insertions(+), 253 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 63960d5..c4e6e1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,11 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet :: WWW/HTTP", ] -dependencies = ["aiohttp>=3.11.11,<4.0", "pydantic>2.0,<3.0"] +dependencies = [ + "aiohttp>=3.11.11,<4.0", + "pydantic>2.0,<3.0", + "pydantic-extra-types>=2.10.1,<3.0", +] dynamic = ["version"] [dependency-groups] diff --git a/uv.lock b/uv.lock index afef6e8..97482c7 100644 --- a/uv.lock +++ b/uv.lock @@ -1,18 +1,6 @@ version = 1 requires-python = ">=3.10" -[[package]] -name = "aiodns" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycares" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/84/41a6a2765abc124563f5380e76b9b24118977729e25a84112f8dfb2b33dc/aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72", size = 7823 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/14/13c65b1bd59f7e707e0cc0964fbab45c003f90292ed267d159eeeeaa2224/aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5", size = 5735 }, -] - [[package]] name = "aiohappyeyeballs" version = "2.4.4" @@ -100,13 +88,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/62/c9fa5bafe03186a0e4699150a7fed9b1e73240996d0d2f0e5f70f3fdf471/aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99", size = 436081 }, ] -[package.optional-dependencies] -speedups = [ - { name = "aiodns", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, - { name = "brotlicffi", marker = "platform_python_implementation != 'CPython'" }, -] - [[package]] name = "aiosignal" version = "1.3.2" @@ -161,155 +142,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] -[[package]] -name = "brotli" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045 }, - { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218 }, - { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872 }, - { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254 }, - { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293 }, - { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385 }, - { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104 }, - { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981 }, - { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297 }, - { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735 }, - { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107 }, - { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400 }, - { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985 }, - { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099 }, - { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172 }, - { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255 }, - { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 }, - { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 }, - { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 }, - { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 }, - { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 }, - { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 }, - { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 }, - { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 }, - { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 }, - { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 }, - { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 }, - { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 }, - { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 }, - { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 }, - { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 }, - { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 }, - { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 }, - { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 }, - { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 }, - { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 }, - { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 }, - { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 }, - { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 }, - { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 }, - { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 }, - { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 }, - { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 }, - { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 }, - { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 }, - { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 }, - { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 }, - { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 }, - { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681 }, - { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475 }, - { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173 }, - { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803 }, - { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946 }, - { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707 }, - { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231 }, - { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157 }, - { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122 }, - { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206 }, - { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804 }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517 }, -] - -[[package]] -name = "brotlicffi" -version = "1.1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786 }, - { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165 }, - { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895 }, - { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834 }, - { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731 }, - { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783 }, - { url = "https://files.pythonhosted.org/packages/e5/3b/bd4f3d2bcf2306ae66b0346f5b42af1962480b200096ffc7abc3bd130eca/brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca", size = 397397 }, - { url = "https://files.pythonhosted.org/packages/54/10/1fd57864449360852c535c2381ee7120ba8f390aa3869df967c44ca7eba1/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391", size = 379698 }, - { url = "https://files.pythonhosted.org/packages/e5/95/15aa422aa6450e6556e54a5fd1650ff59f470aed77ac739aa90ab63dc611/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8", size = 378635 }, - { url = "https://files.pythonhosted.org/packages/6c/a7/f254e13b2cb43337d6d99a4ec10394c134e41bfda8a2eff15b75627f4a3d/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35", size = 385719 }, - { url = "https://files.pythonhosted.org/packages/72/a9/0971251c4427c14b2a827dba3d910d4d3330dabf23d4278bf6d06a978847/brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d", size = 361760 }, -] - -[[package]] -name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -685,82 +517,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, ] -[[package]] -name = "pycares" -version = "4.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d7/b1/94daaa50b6d2fa14c6b4981ca24fa4e7aa33a7519962c76170072ffb06ee/pycares-4.5.0.tar.gz", hash = "sha256:025b6c2ffea4e9fb8f9a097381c2fecb24aff23fbd6906e70da22ec9ba60e19d", size = 821554 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/e9/109c7ed1db9a9b30720c528f54a219ef1c07f16f364bc28b1f6bbcdb7932/pycares-4.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13a82fad8239d6fbcf916099bee17d8b5666d0ddb77dace431e0f7961c9427ab", size = 74833 }, - { url = "https://files.pythonhosted.org/packages/e2/05/3b01a1f890cb270896ee6898a07a33cb6abe9fcc2c1cef8d719f5a2f2d1f/pycares-4.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fefc7bebbe39b2e3b4b9615471233a8f7356b96129a7db9030313a3ae4ecc42d", size = 71320 }, - { url = "https://files.pythonhosted.org/packages/34/15/4d2e3da5b3ed93d0f8cca785729ce50279c6f8a0855b040f19ff6ca57b12/pycares-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e322e8ce810026f6e0c7c2a254b9ed02191ab8d42fa2ce6808ede1bdccab8e65", size = 289130 }, - { url = "https://files.pythonhosted.org/packages/86/b0/8d137cf324e0991fd0a70e2b289126b706b71ca2cbad294039ef54819e04/pycares-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723ba0803b016294430e40e544503fed9164949b694342c2552ab189e2b688ef", size = 303788 }, - { url = "https://files.pythonhosted.org/packages/ce/b6/c82bb7f5b04278b294f802713fd19c00cab761b8c3b5f0179e5f75829cd1/pycares-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e48b20b59cdc929cc712a8b22e89c273256e482b49bb8999af98d2c6fc4563c2", size = 293243 }, - { url = "https://files.pythonhosted.org/packages/d3/b6/6673cb88e128bedb3d6c675429d5de4b17f35ffc45dc5b5508393c38626a/pycares-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6e55bd9af595b112ac6080ac0a0d52b5853d0d8e6d01ac65ff09e51e62490a", size = 288571 }, - { url = "https://files.pythonhosted.org/packages/41/7d/dd90ec643b28941575afcbcb29ce01d25c3eac199e674445349b77ace327/pycares-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6f4b9063e3dd70460400367917698f209c10aabb68bf70b09e364895444487d", size = 269914 }, - { url = "https://files.pythonhosted.org/packages/ab/53/82f52dc86d00a2df90acef03d549b2afedba00184228f2420c112e5146d6/pycares-4.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95522d4840d702fd766439a7c7cd747935aa54cf0b8675e9fadd8414dd9dd0df", size = 277881 }, - { url = "https://files.pythonhosted.org/packages/2e/bb/24fe9fed26c110fd2c219cdf133bab7e18e73896bda8960be50c896cfc66/pycares-4.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4709ce4fd9dbee24b1397f71a2adb3267323bb5ad5e7fde3f87873d172dd156", size = 263704 }, - { url = "https://files.pythonhosted.org/packages/83/1b/c33e94fd0de4a67a833818e920ab4541aeca8adf067d780fa031ffef0d7c/pycares-4.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8addbf3408af1010f50fd67ef634a6cb239ccb9c534c32a40713f3b8d306a98e", size = 297410 }, - { url = "https://files.pythonhosted.org/packages/87/90/266f05bb0781d1705a14140c9a6742dcbd7be6fad96a2d2c807193b1b285/pycares-4.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d0428ef42fcf575e197047e6a47892404faa34231902a453b3dfed66af4178b3", size = 290483 }, - { url = "https://files.pythonhosted.org/packages/18/5f/e8b5b142075c0dfeb7f980fec17c8f970955ede842f0234f360e5a3c5610/pycares-4.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aed5c2732f3a6bdbbfab202267d37044ca1162f690b9d34b7ece97ba43f27453", size = 279970 }, - { url = "https://files.pythonhosted.org/packages/b3/0a/e42a4aa2c456e62485128c984a2c8e078966b4047d016651e1570f34c027/pycares-4.5.0-cp310-cp310-win32.whl", hash = "sha256:b1859ea770a7abec40a6d02b5ab03c2396c4900c01f4e50ddb6c0dca4c2a6a7c", size = 61603 }, - { url = "https://files.pythonhosted.org/packages/23/b4/013b9a19b589f936730a1d858cad25c9fdabf8d91968ec6918c3228842cf/pycares-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f87d8da20a3a80ab05fe80c14a62bf078bd726ca6af609edbeb376fb97d50ab", size = 76534 }, - { url = "https://files.pythonhosted.org/packages/65/72/0dbd1169058924af1f8f1c1421fba64b7fb0ab2b8a489789f17c4b8527ee/pycares-4.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ca7a1dba7b88290710db45012e0903c21c839fa0a2b9ddc100bba8e66bfb251", size = 74837 }, - { url = "https://files.pythonhosted.org/packages/4a/55/97d90ab79e16e641c35a9b53b437e6556993ea904af2e1ce8982a6b8191b/pycares-4.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:160e92588cdf1a0fa3a7015f47990b508d50efd9109ea4d719dee31c058f0648", size = 71314 }, - { url = "https://files.pythonhosted.org/packages/52/91/c152c9e90e9ea7b2bc6381abecdc52ccf1f18b5aa5e67c3a4264f9ae1b22/pycares-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f38e45d23660ed1dafdb956fd263ae4735530ef1578aa2bf2caabb94cee4523", size = 289122 }, - { url = "https://files.pythonhosted.org/packages/eb/af/e28bd3aa1c87b624ba8a422f5f86ff7a2eee6d2137c3f43c5de543b5d0e3/pycares-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f742acc6d29a99ffc14e3f154b3848ea05c5533b71065e0f0a0fd99c527491b2", size = 303778 }, - { url = "https://files.pythonhosted.org/packages/30/cd/7b584e8aba8efb33bd1447cd66cf30512a9d989aae76c7baaf7ffd87c9b1/pycares-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceaf71bcd7b6447705e689b8fee8836c20c6148511a90122981f524a84bfcca9", size = 293217 }, - { url = "https://files.pythonhosted.org/packages/bb/06/a3def79dcf29671bb5582c0654d9461f865001254b1b52226dd2958270e4/pycares-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdc3c0be7b5b83e78e28818fecd0405bd401110dd6e2e66f7f10713c1188362c", size = 288559 }, - { url = "https://files.pythonhosted.org/packages/6b/fb/fe3c26ba45d0a0c3bd16a296725760c9787e828839dab8458ef96ab43a92/pycares-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd458ee69800195247aa19b5675c5914cbc091c5a220e4f0e96777a31bb555c1", size = 269861 }, - { url = "https://files.pythonhosted.org/packages/a5/05/163aedb018de14b6b94ebd183ff94b914d900cdc7c3ee65ebc845e44463e/pycares-4.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6649d713df73266708642fc3d04f110c0a66bee510fbce4cc5fed79df42083", size = 277840 }, - { url = "https://files.pythonhosted.org/packages/fa/6e/ed28d24cf7a8ade789a25db4e3885bdf8ffb1276e00f958c7de452f6383f/pycares-4.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ac57d7bda925c10b997434e7ce30a2c3689c2e96bab9fd0a1165d5577378eecd", size = 263680 }, - { url = "https://files.pythonhosted.org/packages/55/76/ae8e641c0f9ea6a98b9143a4b6a619ed4b35451b121265f4fded7c8d638d/pycares-4.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba17d8e5eeec4b2e0eb1a6a840bae9e62cd1c1c9cbc8dc9db9d1b9fdf33d0b54", size = 297460 }, - { url = "https://files.pythonhosted.org/packages/2c/16/a6f6cf9ca531cfce8d9e0b635a779c514e7aa7eccf9a4d89fb0d32eb102d/pycares-4.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9e9b7d1a8de703283e4735c0e532ba4bc600e88de872dcd1a9a4950cf74d9f4f", size = 290514 }, - { url = "https://files.pythonhosted.org/packages/8e/61/83f4ae4c6222766ac9326ebd383d985b016b9875d7412e01a5d18550e19d/pycares-4.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c6922ecbe458c13a4a2c1177bbce38abc44b5f086bc82115a92eab34418915f", size = 279964 }, - { url = "https://files.pythonhosted.org/packages/09/a8/2d6e68eca2cb05d6dce0a5ca8aa8720f4aa2abc97679ebe649c97f3b6e4e/pycares-4.5.0-cp311-cp311-win32.whl", hash = "sha256:1004b8a17614e33410b4b1bb68360977667f1cc9ab2dbcfb27240d6703e4cb6a", size = 61604 }, - { url = "https://files.pythonhosted.org/packages/75/d6/a5d3752b62a4a728c03ce39e4aa9689fc275f1a408fa4f887ff31f01ee52/pycares-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:2c9c1055c622258a0f315560b2880a372363484b87cbef48af092624804caa72", size = 76534 }, - { url = "https://files.pythonhosted.org/packages/d2/d7/86d62dec9edb3cbba1a11ef0b9558483659f049d14175d41cbef156cde4c/pycares-4.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:506efbe5017807747ccd1bdcb3c2f6e64635bc01fee01a50c0b97d649018c162", size = 74921 }, - { url = "https://files.pythonhosted.org/packages/3c/db/6aa863a70eec7989b70bd8b666b1b7cb13e446deaad416e2acf69f227e21/pycares-4.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c469ec9fbe0526f45a98f67c1ea55be03abf30809c4f9c9be4bc93fb6806304d", size = 71331 }, - { url = "https://files.pythonhosted.org/packages/26/64/2a5338c4d5c72211a7d5bda9c8350086ad9d02c66a2f3cb4933be8daa6ba/pycares-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597c0950ede240c3a779f023fcf2442207fc11e570d3ca4ccdbb0db5bbaf2588", size = 290337 }, - { url = "https://files.pythonhosted.org/packages/30/62/e9186156e2fc88b1687c2e926b8987c6d11b568a23d9ff7be1863b7050fd/pycares-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aa0da03c4df6ed0f87dd52a293bd0508734515041cc5be0f85d9edc1814914f", size = 304297 }, - { url = "https://files.pythonhosted.org/packages/ed/88/823401c364d38dc5079502516bbb44a8884f92b55bb219cf91208a7b9fb0/pycares-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1ebf52767c777d10a1b3d03844b9b05cc892714b3ee177d5d9fbff74fb9fa", size = 294281 }, - { url = "https://files.pythonhosted.org/packages/e3/48/5a1b9b944436a8274918b9b6247fe3e2456993955f597992a0ed1e480b9f/pycares-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb20d84269ddffb177b6048e3bc03d0b9ffe17592093d900d5544805958d86b3", size = 289879 }, - { url = "https://files.pythonhosted.org/packages/1e/96/d32dab745bd2c50d696e463d65ae72c3bd2f5dbd909aead41d2080f7b33d/pycares-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3125df81b657971ee5c0333f8f560ba0151db1eb7cf04aea7d783bb433b306c1", size = 270617 }, - { url = "https://files.pythonhosted.org/packages/49/be/02485d69c5f883e24cf13880b9dd85cb90e353470f3f12f3708fbefce953/pycares-4.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:525c77ea44546c12f379641aee163585d403cf50e29b04a06059d6aac894e956", size = 278552 }, - { url = "https://files.pythonhosted.org/packages/4b/ae/88d4359c73adf7458595184ee620fbed2d6e53abbd1440b221696d3888e5/pycares-4.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1fd87cb26b317a9988abfcfa4e4dbc55d5f20177e5979ad4d854468a9246c187", size = 263719 }, - { url = "https://files.pythonhosted.org/packages/4e/24/6a799f616558f0ea2380af76e73c6f246af8bce81dec0107cf2b14493f47/pycares-4.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a90aecd41188884e57ae32507a2c6b010c60b791a253083761bbb37a488ecaed", size = 297729 }, - { url = "https://files.pythonhosted.org/packages/14/c5/d00ea25938e441081608a4c1a4ee91b3e6a021606a109cb251752aa3bfed/pycares-4.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0d3de65cab653979dcc491e03f596566c9d40346c9deb088e0f9fe70600d8737", size = 291630 }, - { url = "https://files.pythonhosted.org/packages/bd/bc/1425763b3d2883039842272e3d295226f91f79d9936d70f93da1b25c371a/pycares-4.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27a77b43604b3ba24e4fc49fd3ea59f50f7d89c7255f1f1ea46928b26cccacfa", size = 281153 }, - { url = "https://files.pythonhosted.org/packages/e5/ba/c8ab1c930a93442e363d49660329fc597b8e84b4d81eee64129f9611f24a/pycares-4.5.0-cp312-cp312-win32.whl", hash = "sha256:6028cb8766f0fea1d2caa69fac23621fbe2cff9ce6968374e165737258703a33", size = 61624 }, - { url = "https://files.pythonhosted.org/packages/c0/d7/1cc9403bf599c3f30d51d37c9639794a607c448a121a0a7447d59dbb6f07/pycares-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ce10672c4cfd1c5fb6718e8b25f0336ca11c89aab88aa6df53dafc4e41df740", size = 76546 }, - { url = "https://files.pythonhosted.org/packages/32/8b/b48b6a7c4234d682fb9f0d6904a09333711642b760c79a6a7b2f53cbe76e/pycares-4.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:011cd670da7caf55664c944abb71ec39af82b837f8d48da7cf0eec80f5682c4c", size = 74919 }, - { url = "https://files.pythonhosted.org/packages/fb/57/3bec0d830efe9521a4706581fa9cc74825d7996104d773c8c971925a3989/pycares-4.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b5c67930497fb2b1dbcaa85f8c4188fc2cb62e41d787deeed2d33cfe9dd6bf52", size = 71334 }, - { url = "https://files.pythonhosted.org/packages/b7/94/1e6e41f0f72f32e8e32922b085e5df6a9d0a6dd3f5d41cfe213d8849b15e/pycares-4.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d435a3b8468c656a7e7180dd7c4794510f6c612c33ad61a0fff6e440621f8b5", size = 290310 }, - { url = "https://files.pythonhosted.org/packages/1a/24/524ea7bd19edb9392f32a4fcd2eaf11c98fdaeffd28588b4d2bec4ed9b08/pycares-4.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8371f5ee1efb33d6276e275d152c9c5605e5f2e58a9e168519ec1f9e13dd95ae", size = 304230 }, - { url = "https://files.pythonhosted.org/packages/ec/07/a019facc86438181af5cfb045b8d8fe8eeed41117c0abdb6c81acc430e17/pycares-4.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76a9096fd5dc49c61c5235ea7032e8b43f4382800d64ca1e0e0cda700c082aa", size = 294256 }, - { url = "https://files.pythonhosted.org/packages/a3/90/50f08b6970a40861e5466ec965f9cf505812d7db51d7d4f15b84102dc2c6/pycares-4.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b604af76b57469ff68b44e9e4c857eaee43bc5035f4f183f07f4f7149191fe1b", size = 289866 }, - { url = "https://files.pythonhosted.org/packages/b9/44/76696e6dba0f166990d033c7889c47dc14db830ed424552020c830d5d409/pycares-4.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c589bd4f9160bfdb2f8080cf564bb120a4312cf091db07fe417f8e58a896a63c", size = 270588 }, - { url = "https://files.pythonhosted.org/packages/00/a1/788ffea09b7a57cd9e66610ba5b189d2a0d85853e95b79de547351bd6665/pycares-4.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:361262805bb09742c364ec0117842043c950339e38561009bcabbb6ac89458ef", size = 278568 }, - { url = "https://files.pythonhosted.org/packages/39/ba/dfc4d79a7aefc034d79c9ccf32f47ce9e4a2a5d42f26f2cae7627aafefd1/pycares-4.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d2afb3c0776467055bf33db843ef483d25639be0f32e3a13ef5d4dc64098bf5", size = 263702 }, - { url = "https://files.pythonhosted.org/packages/20/78/6819d2c283ce7b53ef639fff44f15ea46dd5dd57307f25d46fb41d83966c/pycares-4.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bc7a1d8ed7c7a4de17706a3c89b305b02eb64c778897e6727c043e5b9dd0d853", size = 297722 }, - { url = "https://files.pythonhosted.org/packages/bd/0d/6776b00eb6ea3d2742ac4f4e1d4bf170168d0923effffa1963e3b4619019/pycares-4.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5703ec878b5c1efacdbf24ceaedfa606112fc67af5564f4db99c2c210f3ffadc", size = 291628 }, - { url = "https://files.pythonhosted.org/packages/13/29/c329ec7ef6cd88335676d71ed8a059d0fd2f5adcff3ab3537e03327955fa/pycares-4.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d87758e09dbf52c27ed7cf7bc7eaf8b3226217d10c52b03d61a14d59f40fcae1", size = 281161 }, - { url = "https://files.pythonhosted.org/packages/02/c4/aadb6b4a60b040bd6c4de31d971d4a07f54af274594a368de06170ede457/pycares-4.5.0-cp313-cp313-win32.whl", hash = "sha256:3316d490b4ce1a69f034881ac1ea7608f5f24ea5293db24ab574ac70b7d7e407", size = 61624 }, - { url = "https://files.pythonhosted.org/packages/32/38/33889749dde2c3ecab5039d72459769d8714665ba3038e718f514c556517/pycares-4.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:018e700fb0d1a2db5ec96e404ffa85ed97cc96e96d6af0bb9548111e37cf36a3", size = 76546 }, -] - -[[package]] -name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, -] - [[package]] name = "pydantic" version = "2.10.4" @@ -850,6 +606,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, ] +[[package]] +name = "pydantic-extra-types" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/12/844a4796dbbc814ef0a706f403cb0f3029bf324c2bca2bf0681f4f7d8618/pydantic_extra_types-2.10.1.tar.gz", hash = "sha256:e4f937af34a754b8f1fa228a2fac867091a51f56ed0e8a61d5b3a6719b13c923", size = 85694 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/f1/92f7b4711d3d0d08981c2677ec9cdde6cb114205a69814bf803e0be5ae9b/pydantic_extra_types-2.10.1-py3-none-any.whl", hash = "sha256:db2c86c04a837bbac0d2d79bbd6f5d46c4c9253db11ca3fdd36a2b282575f1e2", size = 35155 }, +] + [[package]] name = "pytest" version = "8.3.4" @@ -967,9 +736,9 @@ name = "valorant-py" version = "2.0.0a0" source = { virtual = "." } dependencies = [ - { name = "aiodns" }, - { name = "aiohttp", extra = ["speedups"] }, + { name = "aiohttp" }, { name = "pydantic" }, + { name = "pydantic-extra-types" }, ] [package.dev-dependencies] @@ -984,9 +753,9 @@ dev = [ [package.metadata] requires-dist = [ - { name = "aiodns", specifier = ">=3.2.0" }, - { name = "aiohttp", extras = ["speedups"], specifier = ">=3.11.11,<4.0" }, + { name = "aiohttp", specifier = ">=3.11.11,<4.0" }, { name = "pydantic", specifier = ">2.0,<3.0" }, + { name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0" }, ] [package.metadata.requires-dev] diff --git a/valorant/models/agents.py b/valorant/models/agents.py index 3b29d0e..e9a7229 100644 --- a/valorant/models/agents.py +++ b/valorant/models/agents.py @@ -26,6 +26,7 @@ from typing import Any from pydantic import Field +from pydantic.color import Color from ..enums import AbilitySlot from .base import BaseModel, BaseUUIDModel, LocalizedField @@ -82,7 +83,7 @@ class Agent(BaseUUIDModel): full_portrait_v2: str | None = Field(alias='fullPortraitV2') killfeed_portrait: str = Field(alias='killfeedPortrait') background: str | None - background_gradient_colors: list[str] = Field(alias='backgroundGradientColors') + background_gradient_colors: list[Color] = Field(alias='backgroundGradientColors') asset_path: str = Field(alias='assetPath') is_full_portrait_right_facing: bool = Field(alias='isFullPortraitRightFacing') is_playable_character: bool = Field(alias='isPlayableCharacter') diff --git a/valorant/models/competitive_tiers.py b/valorant/models/competitive_tiers.py index a204976..e29dcec 100644 --- a/valorant/models/competitive_tiers.py +++ b/valorant/models/competitive_tiers.py @@ -23,6 +23,7 @@ """ from pydantic import Field +from pydantic.color import Color from ..enums import DivisionTier from .base import BaseModel, BaseUUIDModel, LocalizedField @@ -38,8 +39,8 @@ class Tier(BaseModel): tier_name: LocalizedField = Field(alias='tierName') division: DivisionTier division_name: LocalizedField = Field(alias='divisionName') - color: str - background_color: str = Field(alias='backgroundColor') + color: Color + background_color: Color = Field(alias='backgroundColor') small_icon: str | None = Field(alias='smallIcon') large_icon: str | None = Field(alias='largeIcon') rank_triangle_down_icon: str | None = Field(alias='rankTriangleDownIcon') diff --git a/valorant/models/content_tiers.py b/valorant/models/content_tiers.py index 7ca2afa..f24831d 100644 --- a/valorant/models/content_tiers.py +++ b/valorant/models/content_tiers.py @@ -23,6 +23,7 @@ """ from pydantic import Field +from pydantic.color import Color from .base import BaseUUIDModel, LocalizedField @@ -36,6 +37,6 @@ class ContentTier(BaseUUIDModel): rank: int juice_value: int = Field(alias='juiceValue') juice_cost: int = Field(alias='juiceCost') - highlight_color: str = Field(alias='highlightColor') + highlight_color: Color = Field(alias='highlightColor') display_icon: str = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') From fe7617a2c1bc9439163ffda028f317c60000faf1 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:40:17 +0700 Subject: [PATCH 42/60] docs: update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f63dc60..3b8610d 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,10 @@ An Asynchronous API wrapper for [Valorant API](https://valorant-api.com) written ## Features - Fully type annotated. +- [Pydantic V2](https://docs.pydantic.dev/latest/) models. - Modern Pythonic API using `async` and `await`. - Supports Python 3.10+. -- Supports localizations. +- Supports all languages. ## Installing From 8f28379375b6263d694f181c549b21986c6569ba Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:42:29 +0700 Subject: [PATCH 43/60] docs: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b8610d..8b7956c 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ An Asynchronous API wrapper for [Valorant API](https://valorant-api.com) written ## Features - Fully type annotated. - [Pydantic V2](https://docs.pydantic.dev/latest/) models. -- Modern Pythonic API using `async` and `await`. - Supports Python 3.10+. - Supports all languages. + ## Installing To install the library, you can just run the following command: From bebe02ae4ab6b0aa5205e2fbcd9ef215b04d2c4a Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:47:03 +0700 Subject: [PATCH 44/60] refactor: update Bundle model fields to use LocalizedField --- valorant/models/bundles.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/valorant/models/bundles.py b/valorant/models/bundles.py index d13bf67..3525201 100644 --- a/valorant/models/bundles.py +++ b/valorant/models/bundles.py @@ -22,8 +22,6 @@ DEALINGS IN THE SOFTWARE. """ -from typing import Any - from pydantic import Field from .base import BaseUUIDModel, LocalizedField @@ -34,13 +32,13 @@ class Bundle(BaseUUIDModel): # uuid: str display_name: LocalizedField = Field(alias='displayName') - display_name_sub_text: Any = Field(alias='displayNameSubText') + display_name_sub_text: LocalizedField | None = Field(alias='displayNameSubText') description: LocalizedField - extra_description: Any = Field(alias='extraDescription') - promo_description: Any = Field(alias='promoDescription') + extra_description: LocalizedField | None = Field(alias='extraDescription') + promo_description: LocalizedField | None = Field(alias='promoDescription') use_additional_context: bool = Field(alias='useAdditionalContext') display_icon: str = Field(alias='displayIcon') display_icon2: str = Field(alias='displayIcon2') - logo_icon: Any = Field(alias='logoIcon') + logo_icon: str | None = Field(alias='logoIcon') vertical_promo_image: str | None = Field(alias='verticalPromoImage') asset_path: str = Field(alias='assetPath') From 97911995fbcabc437bceaf90875b5ee4c7d34efe Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:50:49 +0700 Subject: [PATCH 45/60] fmt --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c4e6e1b..65bf301 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,9 +23,9 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "aiohttp>=3.11.11,<4.0", - "pydantic>2.0,<3.0", - "pydantic-extra-types>=2.10.1,<3.0", + "aiohttp>=3.11.11,<4.0", + "pydantic>2.0,<3.0", + "pydantic-extra-types>=2.10.1,<3.0", ] dynamic = ["version"] From a18723c96f592d31accd97b2743eec449c0f5a80 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 21:39:47 +0700 Subject: [PATCH 46/60] refactor: simplify uuid field --- valorant/models/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/models/base.py b/valorant/models/base.py index 49b0de5..8545c36 100644 --- a/valorant/models/base.py +++ b/valorant/models/base.py @@ -47,7 +47,7 @@ def __repr__(self) -> str: class BaseUUIDModel(BaseModel): - uuid: UUID = Field(alias='uuid') + uuid: UUID def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) and self.uuid == other.uuid From 6f2844867d72edceb35dc94fe57e62e6d8b2787d Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 21:45:56 +0700 Subject: [PATCH 47/60] refactor: update publish workflow to use 'uv' for package build --- .github/workflows/publish.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ec63ca9..ab39e8b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,13 +21,13 @@ jobs: with: python-version-file: ".python-version" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.5.x" + enable-cache: true - - name: Build package - run: python -m build + - run: uv build - name: Publish package uses: pypa/gh-action-pypi-publish@release/v1 From c805610ee4925d6b3046ee56fc5648211ac26585 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 21:48:11 +0700 Subject: [PATCH 48/60] fix invalid import --- valorant/models/agents.py | 2 +- valorant/models/competitive_tiers.py | 2 +- valorant/models/content_tiers.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/valorant/models/agents.py b/valorant/models/agents.py index e9a7229..19fa973 100644 --- a/valorant/models/agents.py +++ b/valorant/models/agents.py @@ -26,7 +26,7 @@ from typing import Any from pydantic import Field -from pydantic.color import Color +from pydantic_extra_types.color import Color from ..enums import AbilitySlot from .base import BaseModel, BaseUUIDModel, LocalizedField diff --git a/valorant/models/competitive_tiers.py b/valorant/models/competitive_tiers.py index e29dcec..b35476e 100644 --- a/valorant/models/competitive_tiers.py +++ b/valorant/models/competitive_tiers.py @@ -23,7 +23,7 @@ """ from pydantic import Field -from pydantic.color import Color +from pydantic_extra_types.color import Color from ..enums import DivisionTier from .base import BaseModel, BaseUUIDModel, LocalizedField diff --git a/valorant/models/content_tiers.py b/valorant/models/content_tiers.py index f24831d..05911f0 100644 --- a/valorant/models/content_tiers.py +++ b/valorant/models/content_tiers.py @@ -23,7 +23,7 @@ """ from pydantic import Field -from pydantic.color import Color +from pydantic_extra_types.color import Color from .base import BaseUUIDModel, LocalizedField From 7c6026b1b6ee973f35a4b47a2b84035df39daf17 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 22:06:02 +0700 Subject: [PATCH 49/60] fix: ensure proper URI quoting i --- valorant/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/http.py b/valorant/http.py index fbd0eee..95ea96b 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -63,7 +63,7 @@ def __init__( url = Route.BASE + path if parameters: - url = url.format_map({k: _uriquote(v) if isinstance(v, str) else v for k, v in parameters.items()}) + url = url.format_map({k: _uriquote(v, safe='') if isinstance(v, str) else v for k, v in parameters.items()}) self.url: str = url From 85de3e966203cc681a57414083e7ddd7e7cea2b1 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 22:07:41 +0700 Subject: [PATCH 50/60] refactor: simplify logging --- valorant/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valorant/http.py b/valorant/http.py index 95ea96b..72e87b6 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -89,7 +89,7 @@ async def request(self, route: Route, **kwargs: Any) -> Any: data: dict[str, Any] | str | None = None async with self.__session.request(method, url, **kwargs) as response: # noqa: F811 - _log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), response.status) + _log.debug('%s %s with returned %s', method, url, response.status) data = await response.json() From 8e300496c7711b429ed262133c0389e93b4cdf95 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:06:31 +0700 Subject: [PATCH 51/60] refactor: replace Enum with StrEnum for better string handling --- valorant/enums.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/valorant/enums.py b/valorant/enums.py index 5745702..5b8a266 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -22,14 +22,23 @@ DEALINGS IN THE SOFTWARE. """ -from enum import Enum +import sys + +if sys.version_info >= (3, 11): + from enum import StrEnum + +else: + from enum import Enum + + class StrEnum(str, Enum): ... + __all__ = ( 'AbilitySlot', 'DivisionTier', 'GameFeature', 'GameRule', - 'Locale', + 'Language', 'MissionTag', 'MissionType', 'RelationType', @@ -40,7 +49,7 @@ ) -class AbilitySlot(str, Enum): +class AbilitySlot(StrEnum): # type: ignore[misc] passive = 'Passive' grenade = 'Grenade' ability_1 = 'Ability1' @@ -48,7 +57,7 @@ class AbilitySlot(str, Enum): ultimate = 'Ultimate' -class DivisionTier(str, Enum): +class DivisionTier(StrEnum): # type: ignore[misc] unranked = 'ECompetitiveDivision::UNRANKED' invalid = 'ECompetitiveDivision::INVALID' iron = 'ECompetitiveDivision::IRON' @@ -62,7 +71,7 @@ class DivisionTier(str, Enum): radiant = 'ECompetitiveDivision::RADIANT' -class GameFeature(str, Enum): +class GameFeature(StrEnum): allow_shopping_while_dead = 'EGameFeatureToggleName::AllowShoppingWhileDead' deathmatch_encourage_far_spawning = 'EGameFeatureToggleName::DeathmatchEncourageFarSpawning' disable_fog_of_war = 'EGameFeatureToggleName::DisableFogOfWar' @@ -73,7 +82,7 @@ class GameFeature(str, Enum): use_server_authoritative_drop_out = 'EGameFeatureToggleName::UseServerAuthoritativeDropOut' -class GameRule(str, Enum): +class GameRule(StrEnum): accolades_enabled = 'EGameRuleBoolName::AccoladesEnabled' allow_drop_out = 'EGameRuleBoolName::AllowDropOut' assign_random_agents = 'EGameRuleBoolName::AssignRandomAgents' @@ -92,7 +101,7 @@ class GameRule(str, Enum): use_in_dev_weapons = 'EGameRuleBoolName::UseInDevWeapons' -class MissionType(str, Enum): +class MissionType(StrEnum): daily = 'EAresMissionType::Daily' weekly = 'EAresMissionType::Weekly' tutorial = 'EAresMissionType::Tutorial' @@ -100,18 +109,18 @@ class MissionType(str, Enum): bte = 'EAresMissionType::BTE' -class MissionTag(str, Enum): +class MissionTag(StrEnum): econ = 'EAresMissionTag::Econ' combat = 'EAresMissionTag::Combat' -class RelationType(str, Enum): +class RelationType(StrEnum): agent = 'Agent' event = 'Event' season = 'Season' -class RewardType(str, Enum): +class RewardType(StrEnum): buddy_level = 'EquippableCharmLevel' currency = 'Currency' player_card = 'PlayerCard' @@ -120,11 +129,11 @@ class RewardType(str, Enum): spray = 'Spray' -class SeasonType(str, Enum): +class SeasonType(StrEnum): act = 'EAresSeasonType::Act' -class ShopCategory(str, Enum): +class ShopCategory(StrEnum): armor = 'Armor' pistols = 'Pistols' smgs = 'SMGs' @@ -134,7 +143,7 @@ class ShopCategory(str, Enum): sniper_rifles = 'Sniper Rifles' -class WeaponCategory(str, Enum): +class WeaponCategory(StrEnum): melee = 'EEquippableCategory::Melee' sidearm = 'EEquippableCategory::Sidearm' smg = 'EEquippableCategory::SMG' @@ -144,7 +153,7 @@ class WeaponCategory(str, Enum): sniper = 'EEquippableCategory::Sniper' -class Locale(str, Enum): +class Locale(StrEnum): arabic = 'ar-AE' german = 'de-DE' american_english = 'en-US' From 0450f78e533aaa3e159360c2e827ab586dcf6eea Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:07:02 +0700 Subject: [PATCH 52/60] rename Locale class to Language --- valorant/__init__.py | 4 ++-- valorant/enums.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/valorant/__init__.py b/valorant/__init__.py index b76f2ea..ab80c1b 100644 --- a/valorant/__init__.py +++ b/valorant/__init__.py @@ -19,7 +19,7 @@ DivisionTier, GameFeature, GameRule, - Locale, + Language, MissionTag, MissionType, RelationType, @@ -40,7 +40,7 @@ 'GameRule', 'HTTPException', 'InternalServerError', - 'Locale', + 'Language', 'MissionTag', 'MissionType', 'RateLimited', diff --git a/valorant/enums.py b/valorant/enums.py index 5b8a266..c71c7ae 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -153,7 +153,7 @@ class WeaponCategory(StrEnum): sniper = 'EEquippableCategory::Sniper' -class Locale(StrEnum): +class Language(StrEnum): arabic = 'ar-AE' german = 'de-DE' american_english = 'en-US' From d70a7fdbe888351443dcb1de50bf541fb724c831 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:51:36 +0700 Subject: [PATCH 53/60] fix invalid function name --- tests/test_client.py | 6 +++--- valorant/client.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 568c516..191b920 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -278,16 +278,16 @@ async def test_seasons(client: Client) -> None: @pytest.mark.anyio async def test_competitive_seasons(client: Client) -> None: - competitive_seasons = await client.competitive_seasons() + competitive_seasons = await client.fetch_competitive_seasons() assert len(competitive_seasons) > 0 competitive_season_id = competitive_seasons[0].uuid - competitive_season = await client.competitive_season(str(competitive_season_id)) + competitive_season = await client.fetch_competitive_season(str(competitive_season_id)) assert competitive_season is not None assert competitive_season_id == competitive_season.uuid with pytest.raises(NotFound): - await client.competitive_season('fake-competitive-season-id') + await client.fetch_competitive_season('fake-competitive-season-id') @pytest.mark.anyio diff --git a/valorant/client.py b/valorant/client.py index a8fd2c6..1414791 100644 --- a/valorant/client.py +++ b/valorant/client.py @@ -333,12 +333,12 @@ async def fetch_seasons(self) -> list[Season]: seasons = Response[list[Season]].model_validate(data) return seasons.data - async def competitive_season(self, uuid: str, /) -> CompetitiveSeason | None: + async def fetch_competitive_season(self, uuid: str, /) -> CompetitiveSeason | None: data = await self.http.get_competitive_season(uuid) competitive_season = Response[CompetitiveSeason].model_validate(data) return competitive_season.data - async def competitive_seasons(self) -> list[CompetitiveSeason]: + async def fetch_competitive_seasons(self) -> list[CompetitiveSeason]: data = await self.http.get_competitive_seasons() competitive_seasons = Response[list[CompetitiveSeason]].model_validate(data) return competitive_seasons.data From b9a81c373d46f457024bf3ed0ecce65fabfcebc2 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:56:59 +0700 Subject: [PATCH 54/60] set default all endpoint param to None --- valorant/http.py | 394 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 274 insertions(+), 120 deletions(-) diff --git a/valorant/http.py b/valorant/http.py index 72e87b6..6042625 100644 --- a/valorant/http.py +++ b/valorant/http.py @@ -121,153 +121,265 @@ def clear(self) -> None: # valorant-api.com - def get_agents(self, *, language: str | None = 'all', is_playable_character: bool = True) -> Response[Any]: - params = {'isPlayableCharacter': str(is_playable_character), 'language': language} + def get_agents(self, *, language: str | None = None, is_playable_character: bool = True) -> Response[Any]: + params = {'isPlayableCharacter': str(is_playable_character)} + if language is not None: + params['language'] = language return self.request(Route('GET', '/agents'), params=params) - def get_agent(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - params = {'language': language} + def get_agent(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language is not None: + params['language'] = language return self.request(Route('GET', '/agents/{uuid}', uuid=uuid), params=params) # # - - def get_buddies(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/buddies'), params={'language': language}) - - def get_buddy(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/buddies/{uuid}', uuid=uuid), params={'language': language}) - - def get_buddy_levels(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/buddies/levels'), params={'language': language}) - - def get_buddy_level(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/buddies/levels/{uuid}', uuid=uuid), params={'language': language}) + def get_buddies(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/buddies'), params=params) + + def get_buddy(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/buddies/{uuid}', uuid=uuid), params=params) + + def get_buddy_levels(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/buddies/levels'), params=params) + + def get_buddy_level(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/buddies/levels/{uuid}', uuid=uuid), params=params) # - - def get_bundles(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/bundles'), params={'language': language}) + def get_bundles(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/bundles'), params=params) - def get_bundle(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/bundles/{uuid}', uuid=uuid), params={'language': language}) + def get_bundle(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/bundles/{uuid}', uuid=uuid), params=params) # - - def get_ceremonies(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/ceremonies'), params={'language': language}) + def get_ceremonies(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/ceremonies'), params=params) - def get_ceremony(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/ceremonies/{uuid}', uuid=uuid), params={'language': language}) + def get_ceremony(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/ceremonies/{uuid}', uuid=uuid), params=params) # - - def get_competitive_tiers(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/competitivetiers'), params={'language': language}) + def get_competitive_tiers(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/competitivetiers'), params=params) - def get_competitive_tier(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/competitivetiers/{uuid}', uuid=uuid), params={'language': language}) + def get_competitive_tier(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/competitivetiers/{uuid}', uuid=uuid), params=params) # - - def get_content_tiers(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/contenttiers'), params={'language': language}) + def get_content_tiers(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/contenttiers'), params=params) - def get_content_tier(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/contenttiers/{uuid}', uuid=uuid), params={'language': language}) + def get_content_tier(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/contenttiers/{uuid}', uuid=uuid), params=params) # - - def get_contracts(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/contracts'), params={'language': language}) + def get_contracts(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/contracts'), params=params) - def get_contract(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/contracts/{uuid}', uuid=uuid), params={'language': language}) + def get_contract(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/contracts/{uuid}', uuid=uuid), params=params) # - - def get_currencies(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/currencies'), params={'language': language}) + def get_currencies(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/currencies'), params=params) - def get_currency(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/currencies/{uuid}', uuid=uuid), params={'language': language}) + def get_currency(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/currencies/{uuid}', uuid=uuid), params=params) # - - def get_events(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/events'), params={'language': language}) + def get_events(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/events'), params=params) - def get_event(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/events/{uuid}', uuid=uuid), params={'language': language}) + def get_event(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/events/{uuid}', uuid=uuid), params=params) # - - def get_game_modes(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/gamemodes'), params={'language': language}) - - def get_game_mode(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/gamemodes/{uuid}', uuid=uuid), params={'language': language}) - - def get_game_mode_equippables(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/gamemodes/equippables'), params={'language': language}) - - def get_game_mode_equippable(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/gamemodes/equippables/{uuid}', uuid=uuid), params={'language': language}) + def get_game_modes(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/gamemodes'), params=params) + + def get_game_mode(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/gamemodes/{uuid}', uuid=uuid), params=params) + + def get_game_mode_equippables(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/gamemodes/equippables'), params=params) + + def get_game_mode_equippable(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/gamemodes/equippables/{uuid}', uuid=uuid), params=params) # - - def get_all_gear(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/gear'), params={'language': language}) + def get_all_gear(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/gear'), params=params) - def get_gear(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/gear/{uuid}', uuid=uuid), params={'language': language}) + def get_gear(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/gear/{uuid}', uuid=uuid), params=params) # - - def get_level_borders(self) -> Response[Any]: - return self.request(Route('GET', '/levelborders'), params={'language': 'all'}) + def get_level_borders(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/levelborders'), params=params) - def get_level_border(self, uuid: str) -> Response[Any]: - return self.request(Route('GET', '/levelborders/{uuid}', uuid=uuid), params={'language': 'all'}) + def get_level_border(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/levelborders/{uuid}', uuid=uuid), params=params) # - - def get_maps(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/maps'), params={'language': language}) + def get_maps(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/maps'), params=params) - def get_map(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/maps/{uuid}', uuid=uuid), params={'language': language}) + def get_map(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/maps/{uuid}', uuid=uuid), params=params) # - - def get_missions(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/missions'), params={'language': language}) + def get_missions(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/missions'), params=params) - def get_mission(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/missions/{uuid}', uuid=uuid), params={'language': language}) + def get_mission(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/missions/{uuid}', uuid=uuid), params=params) # - - def get_player_cards(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/playercards'), params={'language': language}) + def get_player_cards(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/playercards'), params=params) - def get_player_card(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/playercards/{uuid}', uuid=uuid), params={'language': language}) + def get_player_card(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/playercards/{uuid}', uuid=uuid), params=params) # - - def get_player_titles(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/playertitles'), params={'language': language}) + def get_player_titles(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/playertitles'), params=params) - def get_player_title(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/playertitles/{uuid}', uuid=uuid), params={'language': language}) + def get_player_title(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/playertitles/{uuid}', uuid=uuid), params=params) # - - def get_seasons(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/seasons'), params={'language': language}) + def get_seasons(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/seasons'), params=params) - def get_season(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/seasons/{uuid}', uuid=uuid), params={'language': language}) + def get_season(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/seasons/{uuid}', uuid=uuid), params=params) def get_competitive_seasons(self) -> Response[Any]: return self.request(Route('GET', '/seasons/competitive')) @@ -277,51 +389,93 @@ def get_competitive_season(self, uuid: str) -> Response[Any]: # - - def get_sprays(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/sprays'), params={'language': language}) - - def get_spray(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/sprays/{uuid}', uuid=uuid), params={'language': language}) - - def get_spray_levels(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/sprays/levels'), params={'language': language}) - - def get_spray_level(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/sprays/levels/{uuid}', uuid=uuid), params={'language': language}) + def get_sprays(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/sprays'), params=params) + + def get_spray(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/sprays/{uuid}', uuid=uuid), params=params) + + def get_spray_levels(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/sprays/levels'), params=params) + + def get_spray_level(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/sprays/levels/{uuid}', uuid=uuid), params=params) # - - def get_themes(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/themes'), params={'language': language}) + def get_themes(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/themes'), params=params) - def get_theme(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/themes/{uuid}', uuid=uuid), params={'language': language}) + def get_theme(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/themes/{uuid}', uuid=uuid), params=params) # - - def get_weapons(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons'), params={'language': language}) - - def get_weapon(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/{uuid}', uuid=uuid), params={'language': language}) - - def get_weapon_skins(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/skins'), params={'language': language}) - - def get_weapon_skin(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/skins/{uuid}', uuid=uuid), params={'language': language}) - - def get_weapon_skin_chromas(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/skinchromas'), params={'language': language}) - - def get_weapon_skin_chroma(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/skinchromas/{uuid}', uuid=uuid), params={'language': language}) - - def get_weapon_skin_levels(self, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/skinlevels'), params={'language': language}) - - def get_weapon_skin_level(self, uuid: str, *, language: str | None = 'all') -> Response[Any]: - return self.request(Route('GET', '/weapons/skinlevels/{uuid}', uuid=uuid), params={'language': language}) + def get_weapons(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons'), params=params) + + def get_weapon(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/{uuid}', uuid=uuid), params=params) + + def get_weapon_skins(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/skins'), params=params) + + def get_weapon_skin(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/skins/{uuid}', uuid=uuid), params=params) + + def get_weapon_skin_chromas(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/skinchromas'), params=params) + + def get_weapon_skin_chroma(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/skinchromas/{uuid}', uuid=uuid), params=params) + + def get_weapon_skin_levels(self, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/skinlevels'), params=params) + + def get_weapon_skin_level(self, uuid: str, *, language: str | None = None) -> Response[Any]: + params = {} + if language: + params['language'] = language + return self.request(Route('GET', '/weapons/skinlevels/{uuid}', uuid=uuid), params=params) # - From 8c30599121f33e13bec063c19ff76d222b8d2d77 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:58:10 +0700 Subject: [PATCH 55/60] remove type ignore --- valorant/enums.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/valorant/enums.py b/valorant/enums.py index c71c7ae..3815d2d 100644 --- a/valorant/enums.py +++ b/valorant/enums.py @@ -49,7 +49,7 @@ class StrEnum(str, Enum): ... ) -class AbilitySlot(StrEnum): # type: ignore[misc] +class AbilitySlot(StrEnum): passive = 'Passive' grenade = 'Grenade' ability_1 = 'Ability1' @@ -57,7 +57,7 @@ class AbilitySlot(StrEnum): # type: ignore[misc] ultimate = 'Ultimate' -class DivisionTier(StrEnum): # type: ignore[misc] +class DivisionTier(StrEnum): unranked = 'ECompetitiveDivision::UNRANKED' invalid = 'ECompetitiveDivision::INVALID' iron = 'ECompetitiveDivision::IRON' From c5380d42d032e6c779dc67217a385541b604b421 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:08:58 +0700 Subject: [PATCH 56/60] feat: add language param support to all fetch methods - Add language parameter with type LanguageOption | None to all fetch methods - Pass language parameter through to HTTP layer with fallback to client's default language --- valorant/client.py | 240 ++++++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 114 deletions(-) diff --git a/valorant/client.py b/valorant/client.py index 1414791..e653a64 100644 --- a/valorant/client.py +++ b/valorant/client.py @@ -25,14 +25,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from types import TracebackType - - from aiohttp import ClientSession - from typing_extensions import Self - +from typing import TYPE_CHECKING, Literal from .http import HTTPClient from .models.agents import Agent @@ -66,16 +59,27 @@ ) # fmt: on -# TODO: support default locale +if TYPE_CHECKING: + from types import TracebackType + from typing import TypeAlias + + from aiohttp import ClientSession + from typing_extensions import Self + + from .enums import Language + + LanguageOption: TypeAlias = Language | Literal['all'] class Client: def __init__( self, + language: LanguageOption | None = None, *, session: ClientSession | None = None, ) -> None: - self.http: HTTPClient = HTTPClient(session) + self.language = language + self.http = HTTPClient(session) self._closed: bool = False async def __aenter__(self) -> Self: @@ -111,225 +115,229 @@ def clear(self) -> None: # agents - async def fetch_agent(self, uuid: str, /) -> Agent | None: - data = await self.http.get_agent(uuid) + async def fetch_agent(self, uuid: str, /, *, language: LanguageOption | None = None) -> Agent | None: + data = await self.http.get_agent(uuid, language=language or self.language) agent = Response[Agent].model_validate(data) return agent.data - async def fetch_agents(self) -> list[Agent]: - data = await self.http.get_agents() + async def fetch_agents(self, *, language: LanguageOption | None = None) -> list[Agent]: + data = await self.http.get_agents(language=language or self.language) agents = Response[list[Agent]].model_validate(data) return agents.data # buddies - async def fetch_buddy(self, uuid: str, /) -> Buddy | None: - data = await self.http.get_buddy(uuid) + async def fetch_buddy(self, uuid: str, /, *, language: LanguageOption | None = None) -> Buddy | None: + data = await self.http.get_buddy(uuid, language=language or self.language) buddy = Response[Buddy].model_validate(data) return buddy.data - async def fetch_buddies(self) -> list[Buddy]: - data = await self.http.get_buddies() + async def fetch_buddies(self, *, language: LanguageOption | None = None) -> list[Buddy]: + data = await self.http.get_buddies(language=language or self.language) buddies = Response[list[Buddy]].model_validate(data) return buddies.data - async def fetch_buddy_level(self, uuid: str, /) -> BuddyLevel | None: - data = await self.http.get_buddy_level(uuid) + async def fetch_buddy_level(self, uuid: str, /, *, language: LanguageOption | None = None) -> BuddyLevel | None: + data = await self.http.get_buddy_level(uuid, language=language or self.language) buddy_level = Response[BuddyLevel].model_validate(data) return buddy_level.data - async def fetch_buddy_levels(self) -> list[BuddyLevel]: - data = await self.http.get_buddy_levels() + async def fetch_buddy_levels(self, *, language: LanguageOption | None = None) -> list[BuddyLevel]: + data = await self.http.get_buddy_levels(language=language or self.language) buddy_levels = Response[list[BuddyLevel]].model_validate(data) return buddy_levels.data # bundles - async def fetch_bundle(self, uuid: str, /) -> Bundle | None: - data = await self.http.get_bundle(uuid) + async def fetch_bundle(self, uuid: str, /, *, language: LanguageOption | None = None) -> Bundle | None: + data = await self.http.get_bundle(uuid, language=language or self.language) bundle = Response[Bundle].model_validate(data) return bundle.data - async def fetch_bundles(self) -> list[Bundle]: - data = await self.http.get_bundles() + async def fetch_bundles(self, *, language: LanguageOption | None = None) -> list[Bundle]: + data = await self.http.get_bundles(language=language or self.language) bundles = Response[list[Bundle]].model_validate(data) return bundles.data # ceremonies - async def fetch_ceremony(self, uuid: str, /) -> Ceremony | None: - data = await self.http.get_ceremony(uuid) + async def fetch_ceremony(self, uuid: str, /, *, language: LanguageOption | None = None) -> Ceremony | None: + data = await self.http.get_ceremony(uuid, language=language or self.language) ceremony = Response[Ceremony].model_validate(data) return ceremony.data - async def fetch_ceremonies(self) -> list[Ceremony]: - data = await self.http.get_ceremonies() + async def fetch_ceremonies(self, *, language: LanguageOption | None = None) -> list[Ceremony]: + data = await self.http.get_ceremonies(language=language or self.language) ceremonies = Response[list[Ceremony]].model_validate(data) return ceremonies.data # competitive_tiers - async def fetch_competitive_tier(self, uuid: str, /) -> CompetitiveTier | None: - data = await self.http.get_competitive_tier(uuid) + async def fetch_competitive_tier( + self, uuid: str, /, *, language: LanguageOption | None = None + ) -> CompetitiveTier | None: + data = await self.http.get_competitive_tier(uuid, language=language or self.language) competitive_tier = Response[CompetitiveTier].model_validate(data) return competitive_tier.data - async def fetch_competitive_tiers(self) -> list[CompetitiveTier]: - data = await self.http.get_competitive_tiers() + async def fetch_competitive_tiers(self, *, language: LanguageOption | None = None) -> list[CompetitiveTier]: + data = await self.http.get_competitive_tiers(language=language or self.language) competitive_tiers = Response[list[CompetitiveTier]].model_validate(data) return competitive_tiers.data # content_tiers - async def fetch_content_tier(self, uuid: str, /) -> ContentTier | None: - data = await self.http.get_content_tier(uuid) + async def fetch_content_tier(self, uuid: str, /, *, language: LanguageOption | None = None) -> ContentTier | None: + data = await self.http.get_content_tier(uuid, language=language or self.language) content_tier = Response[ContentTier].model_validate(data) return content_tier.data - async def fetch_content_tiers(self) -> list[ContentTier]: - data = await self.http.get_content_tiers() + async def fetch_content_tiers(self, *, language: LanguageOption | None = None) -> list[ContentTier]: + data = await self.http.get_content_tiers(language=language or self.language) content_tiers = Response[list[ContentTier]].model_validate(data) return content_tiers.data # contracts - async def fetch_contract(self, uuid: str, /) -> Contract | None: - data = await self.http.get_contract(uuid) + async def fetch_contract(self, uuid: str, /, *, language: LanguageOption | None = None) -> Contract | None: + data = await self.http.get_contract(uuid, language=language or self.language) contract = Response[Contract].model_validate(data) return contract.data - async def fetch_contracts(self) -> list[Contract]: - data = await self.http.get_contracts() + async def fetch_contracts(self, *, language: LanguageOption | None = None) -> list[Contract]: + data = await self.http.get_contracts(language=language or self.language) contracts = Response[list[Contract]].model_validate(data) return contracts.data # currencies - async def fetch_currency(self, uuid: str, /) -> Currency | None: - data = await self.http.get_currency(uuid) + async def fetch_currency(self, uuid: str, /, *, language: LanguageOption | None = None) -> Currency | None: + data = await self.http.get_currency(uuid, language=language or self.language) currency = Response[Currency].model_validate(data) return currency.data - async def fetch_currencies(self) -> list[Currency]: - data = await self.http.get_currencies() + async def fetch_currencies(self, *, language: LanguageOption | None = None) -> list[Currency]: + data = await self.http.get_currencies(language=language or self.language) currencies = Response[list[Currency]].model_validate(data) return currencies.data # events - async def fetch_event(self, uuid: str, /) -> Event | None: - data = await self.http.get_event(uuid) + async def fetch_event(self, uuid: str, /, *, language: LanguageOption | None = None) -> Event | None: + data = await self.http.get_event(uuid, language=language or self.language) event = Response[Event].model_validate(data) return event.data - async def fetch_events(self) -> list[Event]: - data = await self.http.get_events() + async def fetch_events(self, *, language: LanguageOption | None = None) -> list[Event]: + data = await self.http.get_events(language=language or self.language) events = Response[list[Event]].model_validate(data) return events.data # game_modes - async def fetch_game_mode(self, uuid: str, /) -> GameMode | None: - data = await self.http.get_game_mode(uuid) + async def fetch_game_mode(self, uuid: str, /, *, language: LanguageOption | None = None) -> GameMode | None: + data = await self.http.get_game_mode(uuid, language=language or self.language) game_mode = Response[GameMode].model_validate(data) return game_mode.data - async def fetch_game_modes(self) -> list[GameMode]: - data = await self.http.get_game_modes() + async def fetch_game_modes(self, *, language: LanguageOption | None = None) -> list[GameMode]: + data = await self.http.get_game_modes(language=language or self.language) game_modes = Response[list[GameMode]].model_validate(data) return game_modes.data - async def fetch_game_mode_equippable(self, uuid: str, /) -> GameModeEquippable | None: - data = await self.http.get_game_mode_equippable(uuid) + async def fetch_game_mode_equippable( + self, uuid: str, /, *, language: LanguageOption | None = None + ) -> GameModeEquippable | None: + data = await self.http.get_game_mode_equippable(uuid, language=language or self.language) game_mode_equippable = Response[GameModeEquippable].model_validate(data) return game_mode_equippable.data - async def fetch_game_mode_equippables(self) -> list[GameModeEquippable]: - data = await self.http.get_game_mode_equippables() + async def fetch_game_mode_equippables(self, *, language: LanguageOption | None = None) -> list[GameModeEquippable]: + data = await self.http.get_game_mode_equippables(language=language or self.language) game_mode_equippables = Response[list[GameModeEquippable]].model_validate(data) return game_mode_equippables.data # gear - async def fetch_gear(self, uuid: str, /) -> Gear | None: - data = await self.http.get_gear(uuid) + async def fetch_gear(self, uuid: str, /, *, language: LanguageOption | None = None) -> Gear | None: + data = await self.http.get_gear(uuid, language=language or self.language) gear = Response[Gear].model_validate(data) return gear.data - async def fetch_gears(self) -> list[Gear]: - data = await self.http.get_all_gear() + async def fetch_gears(self, *, language: LanguageOption | None = None) -> list[Gear]: + data = await self.http.get_all_gear(language=language or self.language) gears = Response[list[Gear]].model_validate(data) return gears.data # level_borders - async def fetch_level_border(self, uuid: str, /) -> LevelBorder | None: - data = await self.http.get_level_border(uuid) + async def fetch_level_border(self, uuid: str, /, *, language: LanguageOption | None = None) -> LevelBorder | None: + data = await self.http.get_level_border(uuid, language=language or self.language) level_border = Response[LevelBorder].model_validate(data) return level_border.data - async def fetch_level_borders(self) -> list[LevelBorder]: - data = await self.http.get_level_borders() + async def fetch_level_borders(self, *, language: LanguageOption | None = None) -> list[LevelBorder]: + data = await self.http.get_level_borders(language=language or self.language) level_borders = Response[list[LevelBorder]].model_validate(data) return level_borders.data # maps - async def fetch_map(self, uuid: str, /) -> Map | None: - data = await self.http.get_map(uuid) + async def fetch_map(self, uuid: str, /, *, language: LanguageOption | None = None) -> Map | None: + data = await self.http.get_map(uuid, language=language or self.language) map_ = Response[Map].model_validate(data) return map_.data - async def fetch_maps(self) -> list[Map]: - data = await self.http.get_maps() + async def fetch_maps(self, *, language: LanguageOption | None = None) -> list[Map]: + data = await self.http.get_maps(language=language or self.language) maps = Response[list[Map]].model_validate(data) return maps.data # missions - async def fetch_mission(self, uuid: str, /) -> Mission | None: - data = await self.http.get_mission(uuid) + async def fetch_mission(self, uuid: str, /, *, language: LanguageOption | None = None) -> Mission | None: + data = await self.http.get_mission(uuid, language=language or self.language) mission = Response[Mission].model_validate(data) return mission.data - async def fetch_missions(self) -> list[Mission]: - data = await self.http.get_missions() + async def fetch_missions(self, *, language: LanguageOption | None = None) -> list[Mission]: + data = await self.http.get_missions(language=language or self.language) missions = Response[list[Mission]].model_validate(data) return missions.data # player cards - async def fetch_player_card(self, uuid: str, /) -> PlayerCard | None: - data = await self.http.get_player_card(uuid) + async def fetch_player_card(self, uuid: str, /, *, language: LanguageOption | None = None) -> PlayerCard | None: + data = await self.http.get_player_card(uuid, language=language or self.language) player_card = Response[PlayerCard].model_validate(data) return player_card.data - async def fetch_player_cards(self) -> list[PlayerCard]: - data = await self.http.get_player_cards() + async def fetch_player_cards(self, *, language: LanguageOption | None = None) -> list[PlayerCard]: + data = await self.http.get_player_cards(language=language or self.language) player_cards = Response[list[PlayerCard]].model_validate(data) return player_cards.data # player titles - async def fetch_player_title(self, uuid: str, /) -> PlayerTitle | None: - data = await self.http.get_player_title(uuid) + async def fetch_player_title(self, uuid: str, /, *, language: LanguageOption | None = None) -> PlayerTitle | None: + data = await self.http.get_player_title(uuid, language=language or self.language) player_title = Response[PlayerTitle].model_validate(data) return player_title.data - async def fetch_player_titles(self) -> list[PlayerTitle]: - data = await self.http.get_player_titles() + async def fetch_player_titles(self, *, language: LanguageOption | None = None) -> list[PlayerTitle]: + data = await self.http.get_player_titles(language=language or self.language) player_titles = Response[list[PlayerTitle]].model_validate(data) return player_titles.data # seasons - async def fetch_season(self, uuid: str, /) -> Season | None: - data = await self.http.get_season(uuid) + async def fetch_season(self, uuid: str, /, *, language: LanguageOption | None = None) -> Season | None: + data = await self.http.get_season(uuid, language=language or self.language) season = Response[Season].model_validate(data) return season.data - async def fetch_seasons(self) -> list[Season]: - data = await self.http.get_seasons() + async def fetch_seasons(self, *, language: LanguageOption | None = None) -> list[Season]: + data = await self.http.get_seasons(language=language or self.language) seasons = Response[list[Season]].model_validate(data) return seasons.data @@ -345,77 +353,81 @@ async def fetch_competitive_seasons(self) -> list[CompetitiveSeason]: # sprays - async def fetch_spray(self, uuid: str, /) -> Spray | None: - data = await self.http.get_spray(uuid) + async def fetch_spray(self, uuid: str, /, *, language: LanguageOption | None = None) -> Spray | None: + data = await self.http.get_spray(uuid, language=language or self.language) spray = Response[Spray].model_validate(data) return spray.data - async def fetch_sprays(self) -> list[Spray]: - data = await self.http.get_sprays() + async def fetch_sprays(self, *, language: LanguageOption | None = None) -> list[Spray]: + data = await self.http.get_sprays(language=language or self.language) sprays = Response[list[Spray]].model_validate(data) return sprays.data - async def fetch_spray_level(self, uuid: str, /) -> SprayLevel | None: - data = await self.http.get_spray_level(uuid) + async def fetch_spray_level(self, uuid: str, /, *, language: LanguageOption | None = None) -> SprayLevel | None: + data = await self.http.get_spray_level(uuid, language=language or self.language) spray_level = Response[SprayLevel].model_validate(data) return spray_level.data - async def fetch_spray_levels(self) -> list[SprayLevel]: - data = await self.http.get_spray_levels() + async def fetch_spray_levels(self, *, language: LanguageOption | None = None) -> list[SprayLevel]: + data = await self.http.get_spray_levels(language=language or self.language) spray_levels = Response[list[SprayLevel]].model_validate(data) return spray_levels.data # themes - async def fetch_theme(self, uuid: str, /) -> Theme | None: - data = await self.http.get_theme(uuid) + async def fetch_theme(self, uuid: str, /, *, language: LanguageOption | None = None) -> Theme | None: + data = await self.http.get_theme(uuid, language=language or self.language) theme = Response[Theme].model_validate(data) return theme.data - async def fetch_themes(self) -> list[Theme]: - data = await self.http.get_themes() + async def fetch_themes(self, *, language: LanguageOption | None = None) -> list[Theme]: + data = await self.http.get_themes(language=language or self.language) themes = Response[list[Theme]].model_validate(data) return themes.data # weapons - async def fetch_weapon(self, uuid: str, /) -> Weapon | None: - data = await self.http.get_weapon(uuid) + async def fetch_weapon(self, uuid: str, /, *, language: LanguageOption | None = None) -> Weapon | None: + data = await self.http.get_weapon(uuid, language=language or self.language) weapon = Response[Weapon].model_validate(data) return weapon.data - async def fetch_weapons(self) -> list[Weapon]: - data = await self.http.get_weapons() + async def fetch_weapons(self, *, language: LanguageOption | None = None) -> list[Weapon]: + data = await self.http.get_weapons(language=language or self.language) weapons = Response[list[Weapon]].model_validate(data) return weapons.data - async def fetch_weapon_skin(self, uuid: str, /) -> Skin | None: - data = await self.http.get_weapon_skin(uuid) + async def fetch_weapon_skin(self, uuid: str, /, *, language: LanguageOption | None = None) -> Skin | None: + data = await self.http.get_weapon_skin(uuid, language=language or self.language) skin = Response[Skin].model_validate(data) return skin.data - async def fetch_weapon_skins(self) -> list[Skin]: - data = await self.http.get_weapon_skins() + async def fetch_weapon_skins(self, *, language: LanguageOption | None = None) -> list[Skin]: + data = await self.http.get_weapon_skins(language=language or self.language) skins = Response[list[Skin]].model_validate(data) return skins.data - async def fetch_weapon_skin_chroma(self, uuid: str, /) -> SkinChroma | None: - data = await self.http.get_weapon_skin_chroma(uuid) + async def fetch_weapon_skin_chroma( + self, uuid: str, /, *, language: LanguageOption | None = None + ) -> SkinChroma | None: + data = await self.http.get_weapon_skin_chroma(uuid, language=language or self.language) skin_chroma = Response[SkinChroma].model_validate(data) return skin_chroma.data - async def fetch_weapon_skin_chromas(self) -> list[SkinChroma]: - data = await self.http.get_weapon_skin_chromas() + async def fetch_weapon_skin_chromas(self, *, language: LanguageOption | None = None) -> list[SkinChroma]: + data = await self.http.get_weapon_skin_chromas(language=language or self.language) skin_chromas = Response[list[SkinChroma]].model_validate(data) return skin_chromas.data - async def fetch_weapon_skin_level(self, uuid: str, /) -> SkinLevel | None: - data = await self.http.get_weapon_skin_level(uuid) + async def fetch_weapon_skin_level( + self, uuid: str, /, *, language: LanguageOption | None = None + ) -> SkinLevel | None: + data = await self.http.get_weapon_skin_level(uuid, language=language or self.language) skin_level = Response[SkinLevel].model_validate(data) return skin_level.data - async def fetch_weapon_skin_levels(self) -> list[SkinLevel]: - data = await self.http.get_weapon_skin_levels() + async def fetch_weapon_skin_levels(self, *, language: LanguageOption | None = None) -> list[SkinLevel]: + data = await self.http.get_weapon_skin_levels(language=language or self.language) skin_levels = Response[list[SkinLevel]].model_validate(data) return skin_levels.data From c769c591b25365f69c2d796b0e03639634a1b7c1 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:32:42 +0700 Subject: [PATCH 57/60] fix: update display_name field type to support str and LocalizedField --- valorant/models/agents.py | 14 +++++++------- valorant/models/buddies.py | 4 ++-- valorant/models/bundles.py | 10 +++++----- valorant/models/ceremonies.py | 2 +- valorant/models/competitive_tiers.py | 4 ++-- valorant/models/content_tiers.py | 2 +- valorant/models/contracts.py | 2 +- valorant/models/currencies.py | 4 ++-- valorant/models/events.py | 4 ++-- valorant/models/gamemodes.py | 8 ++++---- valorant/models/gear.py | 12 ++++++------ valorant/models/level_borders.py | 2 +- valorant/models/maps.py | 10 +++++----- valorant/models/missions.py | 4 ++-- valorant/models/player_cards.py | 2 +- valorant/models/player_titles.py | 4 ++-- valorant/models/seasons.py | 4 ++-- valorant/models/sprays.py | 4 ++-- valorant/models/themes.py | 2 +- valorant/models/weapons.py | 10 +++++----- 20 files changed, 54 insertions(+), 54 deletions(-) diff --git a/valorant/models/agents.py b/valorant/models/agents.py index 19fa973..663be77 100644 --- a/valorant/models/agents.py +++ b/valorant/models/agents.py @@ -41,8 +41,8 @@ class Role(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - description: LocalizedField + display_name: str | LocalizedField = Field(alias='displayName') + description: str | LocalizedField display_icon: str = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') @@ -62,8 +62,8 @@ class Recruitment(BaseModel): class Ability(BaseModel): slot: AbilitySlot - display_name: LocalizedField = Field(alias='displayName') - description: LocalizedField + display_name: str | LocalizedField = Field(alias='displayName') + description: str | LocalizedField display_icon: str | None = Field(alias='displayIcon') def __repr__(self) -> str: @@ -72,10 +72,10 @@ def __repr__(self) -> str: class Agent(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - description: LocalizedField + display_name: str | LocalizedField = Field(alias='displayName') + description: str | LocalizedField developer_name: str = Field(alias='developerName') - character_tags: list[LocalizedField] | None = Field(alias='characterTags') + character_tags: list[str | LocalizedField] | None = Field(alias='characterTags') display_icon: str = Field(alias='displayIcon') display_icon_small: str = Field(alias='displayIconSmall') bust_portrait: str | None = Field(alias='bustPortrait') diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index b1c85bc..c8443ca 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -45,7 +45,7 @@ class Level(BaseUUIDModel): # uuid: str charm_level: int = Field(alias='charmLevel') hide_if_not_owned: bool = Field(alias='hideIfNotOwned') - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') display_icon: str = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') @@ -55,7 +55,7 @@ def __repr__(self) -> str: class Buddy(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') theme_uuid: UUID | None = Field(alias='themeUuid') display_icon: str = Field(alias='displayIcon') diff --git a/valorant/models/bundles.py b/valorant/models/bundles.py index 3525201..7fde6b0 100644 --- a/valorant/models/bundles.py +++ b/valorant/models/bundles.py @@ -31,11 +31,11 @@ class Bundle(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - display_name_sub_text: LocalizedField | None = Field(alias='displayNameSubText') - description: LocalizedField - extra_description: LocalizedField | None = Field(alias='extraDescription') - promo_description: LocalizedField | None = Field(alias='promoDescription') + display_name: str | LocalizedField = Field(alias='displayName') + display_name_sub_text: str | LocalizedField | None = Field(alias='displayNameSubText') + description: str | LocalizedField + extra_description: str | LocalizedField | None = Field(alias='extraDescription') + promo_description: str | LocalizedField | None = Field(alias='promoDescription') use_additional_context: bool = Field(alias='useAdditionalContext') display_icon: str = Field(alias='displayIcon') display_icon2: str = Field(alias='displayIcon2') diff --git a/valorant/models/ceremonies.py b/valorant/models/ceremonies.py index 4509d41..54649fe 100644 --- a/valorant/models/ceremonies.py +++ b/valorant/models/ceremonies.py @@ -31,5 +31,5 @@ class Ceremony(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/competitive_tiers.py b/valorant/models/competitive_tiers.py index b35476e..787d919 100644 --- a/valorant/models/competitive_tiers.py +++ b/valorant/models/competitive_tiers.py @@ -36,9 +36,9 @@ class Tier(BaseModel): tier: int - tier_name: LocalizedField = Field(alias='tierName') + tier_name: str | LocalizedField = Field(alias='tierName') division: DivisionTier - division_name: LocalizedField = Field(alias='divisionName') + division_name: str | LocalizedField = Field(alias='divisionName') color: Color background_color: Color = Field(alias='backgroundColor') small_icon: str | None = Field(alias='smallIcon') diff --git a/valorant/models/content_tiers.py b/valorant/models/content_tiers.py index 05911f0..387b59c 100644 --- a/valorant/models/content_tiers.py +++ b/valorant/models/content_tiers.py @@ -32,7 +32,7 @@ class ContentTier(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') dev_name: str = Field(alias='devName') rank: int juice_value: int = Field(alias='juiceValue') diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index 883fbd6..f92d61b 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -123,7 +123,7 @@ async def fetch_relationship(self, *, client: Client) -> Agent | Event | Season class Contract(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') ship_it: bool = Field(alias='shipIt') use_level_vp_cost_override: bool = Field(alias='useLevelVPCostOverride') diff --git a/valorant/models/currencies.py b/valorant/models/currencies.py index 66f4db2..f39e29a 100644 --- a/valorant/models/currencies.py +++ b/valorant/models/currencies.py @@ -31,8 +31,8 @@ class Currency(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - display_name_singular: LocalizedField = Field(alias='displayNameSingular') + display_name: str | LocalizedField = Field(alias='displayName') + display_name_singular: str | LocalizedField = Field(alias='displayNameSingular') display_icon: str = Field(alias='displayIcon') large_icon: str = Field(alias='largeIcon') reward_preview_icon: str = Field(alias='rewardPreviewIcon') diff --git a/valorant/models/events.py b/valorant/models/events.py index 3a2b01d..d61660b 100644 --- a/valorant/models/events.py +++ b/valorant/models/events.py @@ -33,8 +33,8 @@ class Event(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - short_display_name: LocalizedField = Field(alias='shortDisplayName') + display_name: str | LocalizedField = Field(alias='displayName') + short_display_name: str | LocalizedField = Field(alias='shortDisplayName') start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index 91fba60..3861613 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -55,9 +55,9 @@ class GameRuleBoolOverride(BaseModel): class GameMode(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - description: LocalizedField | None - duration: LocalizedField | None + display_name: str | LocalizedField = Field(alias='displayName') + description: str | LocalizedField | None + duration: str | LocalizedField | None economy_type: str | None = Field(alias='economyType') allows_match_timeouts: bool = Field(alias='allowsMatchTimeouts') is_team_voice_allowed: bool = Field(alias='isTeamVoiceAllowed') @@ -74,7 +74,7 @@ class GameMode(BaseUUIDModel): class Equippable(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') category: str display_icon: str = Field(alias='displayIcon') kill_stream_icon: str = Field(alias='killStreamIcon') diff --git a/valorant/models/gear.py b/valorant/models/gear.py index 7cbffbc..08749fb 100644 --- a/valorant/models/gear.py +++ b/valorant/models/gear.py @@ -37,15 +37,15 @@ class Detail(BaseModel): - name: LocalizedField - value: LocalizedField + name: str | LocalizedField + value: str | LocalizedField class ShopData(BaseModel): cost: int category: ShopCategory shop_order_priority: int = Field(alias='shopOrderPriority') - category_text: LocalizedField = Field(alias='categoryText') + category_text: str | LocalizedField = Field(alias='categoryText') grid_position: Any = Field(alias='gridPosition') can_be_trashed: bool = Field(alias='canBeTrashed') image: Any @@ -56,9 +56,9 @@ class ShopData(BaseModel): class Gear(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - description: LocalizedField - descriptions: list[LocalizedField] + display_name: str | LocalizedField = Field(alias='displayName') + description: str | LocalizedField + descriptions: list[str | LocalizedField] details: list[Detail] display_icon: str = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/level_borders.py b/valorant/models/level_borders.py index fa1970d..c0ff090 100644 --- a/valorant/models/level_borders.py +++ b/valorant/models/level_borders.py @@ -31,7 +31,7 @@ class LevelBorder(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') starting_level: int = Field(alias='startingLevel') level_number_appearance: str = Field(alias='levelNumberAppearance') small_player_card_appearance: str = Field(alias='smallPlayerCardAppearance') diff --git a/valorant/models/maps.py b/valorant/models/maps.py index ceb1d9e..bd793fc 100644 --- a/valorant/models/maps.py +++ b/valorant/models/maps.py @@ -41,17 +41,17 @@ class Location(BaseModel): class Callout(BaseModel): - region_name: LocalizedField = Field(alias='regionName') - super_region_name: LocalizedField = Field(alias='superRegionName') + region_name: str | LocalizedField = Field(alias='regionName') + super_region_name: str | LocalizedField = Field(alias='superRegionName') location: Location class Map(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') narrative_description: Any = Field(alias='narrativeDescription') - tactical_description: LocalizedField | None = Field(alias='tacticalDescription') - coordinates: LocalizedField | None + tactical_description: str | LocalizedField | None = Field(alias='tacticalDescription') + coordinates: str | LocalizedField | None display_icon: str | None = Field(alias='displayIcon') list_view_icon: str = Field(alias='listViewIcon') list_view_icon_tall: str | None = Field(alias='listViewIconTall') diff --git a/valorant/models/missions.py b/valorant/models/missions.py index 976055a..25c950a 100644 --- a/valorant/models/missions.py +++ b/valorant/models/missions.py @@ -43,8 +43,8 @@ class Objective(BaseModel): class Mission(BaseUUIDModel): # uuid: str - display_name: LocalizedField | None = Field(alias='displayName') - title: LocalizedField | None + display_name: str | LocalizedField | None = Field(alias='displayName') + title: str | LocalizedField | None type: MissionType | None xp_grant: int = Field(alias='xpGrant') progress_to_complete: int = Field(alias='progressToComplete') diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index e42df45..ddc7535 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -40,7 +40,7 @@ class PlayerCard(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') theme_uuid: UUID | None = Field(alias='themeUuid') display_icon: str = Field(alias='displayIcon') diff --git a/valorant/models/player_titles.py b/valorant/models/player_titles.py index e8163d7..1e50213 100644 --- a/valorant/models/player_titles.py +++ b/valorant/models/player_titles.py @@ -31,7 +31,7 @@ class PlayerTitle(BaseUUIDModel): # uuid: str - display_name: LocalizedField | None = Field(alias='displayName') - title_text: LocalizedField | None = Field(alias='titleText') + display_name: str | LocalizedField | None = Field(alias='displayName') + title_text: str | LocalizedField | None = Field(alias='titleText') is_hidden_if_not_owned: bool = Field(alias='isHiddenIfNotOwned') asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 9f10a0e..27ca813 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -45,8 +45,8 @@ class Season(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') - title: LocalizedField | None + display_name: str | LocalizedField = Field(alias='displayName') + title: str | LocalizedField | None type: SeasonType | None start_time: datetime = Field(alias='startTime') end_time: datetime = Field(alias='endTime') diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index 3414e4a..38d9c10 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -44,14 +44,14 @@ class Level(BaseUUIDModel): # uuid: str spray_level: int = Field(alias='sprayLevel') - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') asset_path: str = Field(alias='assetPath') class Spray(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') category: str | None theme_uuid: UUID | None = Field(alias='themeUuid') is_null_spray: bool = Field(alias='isNullSpray') diff --git a/valorant/models/themes.py b/valorant/models/themes.py index 0bca0a2..0fbb737 100644 --- a/valorant/models/themes.py +++ b/valorant/models/themes.py @@ -31,7 +31,7 @@ class Theme(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') store_featured_image: str | None = Field(alias='storeFeaturedImage') asset_path: str = Field(alias='assetPath') diff --git a/valorant/models/weapons.py b/valorant/models/weapons.py index b639ac7..2739d99 100644 --- a/valorant/models/weapons.py +++ b/valorant/models/weapons.py @@ -102,7 +102,7 @@ class ShopData(BaseModel): cost: int category: ShopCategory shop_order_priority: int = Field(alias='shopOrderPriority') - category_text: LocalizedField = Field(alias='categoryText') + category_text: str | LocalizedField = Field(alias='categoryText') grid_position: GridPosition | None = Field(alias='gridPosition') can_be_trashed: bool = Field(alias='canBeTrashed') image: str | None @@ -113,7 +113,7 @@ class ShopData(BaseModel): class Chroma(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') display_icon: str | None = Field(alias='displayIcon') full_render: str = Field(alias='fullRender') swatch: str | None @@ -123,7 +123,7 @@ class Chroma(BaseUUIDModel): class Level(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') level_item: str | None = Field(alias='levelItem') display_icon: str | None = Field(alias='displayIcon') streamed_video: str | None = Field(alias='streamedVideo') @@ -132,7 +132,7 @@ class Level(BaseUUIDModel): class Skin(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') theme_uuid: UUID = Field(alias='themeUuid') content_tier_uuid: UUID | None = Field(alias='contentTierUuid') display_icon: str | None = Field(alias='displayIcon') @@ -156,7 +156,7 @@ async def fetch_content_tier(self, *, client: Client) -> ContentTier | None: class Weapon(BaseUUIDModel): # uuid: str - display_name: LocalizedField = Field(alias='displayName') + display_name: str | LocalizedField = Field(alias='displayName') category: WeaponCategory default_skin_uuid: str = Field(alias='defaultSkinUuid') display_icon: str = Field(alias='displayIcon') From 7a57959888305c1068e07657721ecc3f2bb81b88 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:46:25 +0700 Subject: [PATCH 58/60] update example --- examples/basic.py | 8 +++++++- examples/basic2.py | 39 ++++++++++++--------------------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/examples/basic.py b/examples/basic.py index b76cecf..e6f39ea 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,12 +1,16 @@ import asyncio import valorant +from valorant.models.base import LocalizedField async def main() -> None: client = valorant.Client() async with client: - weapon = await client.fetch_weapon('9c82e19d-4575-0200-1a81-3eacf00cf872') # Vandal + weapon = await client.fetch_weapon( + '9c82e19d-4575-0200-1a81-3eacf00cf872', # Vandal + language='all', # all languages (LocalizedField) + ) # Vandal assert weapon is not None for skin in weapon.skins[:5]: @@ -14,6 +18,8 @@ async def main() -> None: print(skin.display_icon) # display_name locale + assert skin.display_name is not None + assert isinstance(skin.display_name, LocalizedField) print(skin.display_name.ja_JP) print(skin.display_name.japanese) diff --git a/examples/basic2.py b/examples/basic2.py index 4b2ceb0..313fd46 100644 --- a/examples/basic2.py +++ b/examples/basic2.py @@ -1,26 +1,18 @@ import asyncio import valorant +from valorant.enums import Language async def main() -> None: client = valorant.Client() async with client: # agent - agents = await client.fetch_agents() - for agent in agents: - print(agent.display_name) - # role = agent.role - # print(role.display_name) - # print(role.display_icon) - - print(agent.display_icon) - print(agent.display_icon_small) - print(agent.full_portrait_v2) - - # grenade = agent.get_ability(valorant.AbilitySlot.grenade) - # if grenade is not None: - # print(grenade.display_name) + agents = await client.fetch_agents(language=Language.japanese) + for agent in agents[:1]: + if agent.role is not None: + print(agent.role.display_name) + print(agent.role.display_icon) # buddy buddies = await client.fetch_buddies() @@ -49,29 +41,22 @@ async def main() -> None: for chroma in skin.chromas: print(chroma.display_name) - # skin child of weapon + # skin skins = await client.fetch_weapon_skins() for skin in skins: - # weapon = skin.parent - print(weapon.display_name, skin.display_name) + print(skin.display_name) - # skin level child of skin + # skin level skin_levels = await client.fetch_weapon_skin_levels() for level in skin_levels: - # skin = level.parent - # weapon = skin.parent - # print(skin.display_name, str(level.display_name).strip()) - print(level.display_icon) + print(level.display_name) - # skin chroma child of skin + # skin chroma skin_chromas = await client.fetch_weapon_skin_chromas() for chroma in skin_chromas: - # skin = chroma.parent - # weapon = skin.parent - # print(weapon.display_name, skin.display_name, str(chroma.display_name).strip()) - print(chroma.display_icon) + print(chroma.display_name) asyncio.run(main()) From 56df9212b2e0bfd253c22ad2ccdd76f83bdeda4f Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:58:21 +0700 Subject: [PATCH 59/60] refactor: move LocalizedField to a separate localization module --- valorant/models/agents.py | 3 +- valorant/models/base.py | 51 +------------------------- valorant/models/buddies.py | 3 +- valorant/models/bundles.py | 3 +- valorant/models/ceremonies.py | 3 +- valorant/models/competitive_tiers.py | 3 +- valorant/models/content_tiers.py | 3 +- valorant/models/contracts.py | 3 +- valorant/models/currencies.py | 3 +- valorant/models/events.py | 3 +- valorant/models/gamemodes.py | 3 +- valorant/models/gear.py | 3 +- valorant/models/level_borders.py | 3 +- valorant/models/localization.py | 53 ++++++++++++++++++++++++++++ valorant/models/maps.py | 3 +- valorant/models/missions.py | 3 +- valorant/models/player_cards.py | 3 +- valorant/models/player_titles.py | 3 +- valorant/models/seasons.py | 3 +- valorant/models/sprays.py | 3 +- valorant/models/themes.py | 3 +- valorant/models/weapons.py | 3 +- 22 files changed, 94 insertions(+), 70 deletions(-) create mode 100644 valorant/models/localization.py diff --git a/valorant/models/agents.py b/valorant/models/agents.py index 663be77..2e4167e 100644 --- a/valorant/models/agents.py +++ b/valorant/models/agents.py @@ -29,7 +29,8 @@ from pydantic_extra_types.color import Color from ..enums import AbilitySlot -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField # Changed from language to localization __all__ = ( 'Ability', diff --git a/valorant/models/base.py b/valorant/models/base.py index 8545c36..5d2beae 100644 --- a/valorant/models/base.py +++ b/valorant/models/base.py @@ -27,14 +27,13 @@ from typing import Generic, TypeVar from uuid import UUID -from pydantic import BaseModel as PydanticBaseModel, Field +from pydantic import BaseModel as PydanticBaseModel T = TypeVar('T') __all__ = ( 'BaseModel', 'BaseUUIDModel', - 'LocalizedField', 'Response', ) @@ -59,54 +58,6 @@ def __hash__(self) -> int: return hash(self.uuid) -class LocalizedField(BaseModel): - de_DE: str = Field(alias='de-DE') - es_ES: str = Field(alias='es-ES') - ar_AE: str = Field(alias='ar-AE') - id_ID: str = Field(alias='id-ID') - es_MX: str = Field(alias='es-MX') - fr_FR: str = Field(alias='fr-FR') - en_US: str = Field(alias='en-US') - it_IT: str = Field(alias='it-IT') - ja_JP: str = Field(alias='ja-JP') - ko_KR: str = Field(alias='ko-KR') - th_TH: str = Field(alias='th-TH') - pl_PL: str = Field(alias='pl-PL') - pt_BR: str = Field(alias='pt-BR') - ru_RU: str = Field(alias='ru-RU') - tr_TR: str = Field(alias='tr-TR') - vi_VN: str = Field(alias='vi-VN') - zh_TW: str = Field(alias='zh-TW') - zh_CN: str = Field(alias='zh-CN') - - # aliases - - arabic: str = ar_AE - german: str = de_DE - american_english: str = en_US - spain_spanish: str = es_ES - spanish_mexican: str = es_MX - french: str = fr_FR - indonesian: str = id_ID - italian: str = it_IT - japanese: str = ja_JP - korean: str = ko_KR - polish: str = pl_PL - brazil_portuguese: str = pt_BR - russian: str = ru_RU - thai: str = th_TH - turkish: str = tr_TR - vietnamese: str = vi_VN - chinese: str = zh_CN - taiwan_chinese: str = zh_TW - - def __str__(self) -> str: - return self.en_US - - def __repr__(self) -> str: - return f'' - - class Response(BaseModel, Generic[T]): status: int data: T diff --git a/valorant/models/buddies.py b/valorant/models/buddies.py index c8443ca..715b636 100644 --- a/valorant/models/buddies.py +++ b/valorant/models/buddies.py @@ -29,7 +29,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Buddy', diff --git a/valorant/models/bundles.py b/valorant/models/bundles.py index 7fde6b0..26a0de0 100644 --- a/valorant/models/bundles.py +++ b/valorant/models/bundles.py @@ -24,7 +24,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('Bundle',) diff --git a/valorant/models/ceremonies.py b/valorant/models/ceremonies.py index 54649fe..514b965 100644 --- a/valorant/models/ceremonies.py +++ b/valorant/models/ceremonies.py @@ -24,7 +24,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('Ceremony',) diff --git a/valorant/models/competitive_tiers.py b/valorant/models/competitive_tiers.py index 787d919..39f6a6c 100644 --- a/valorant/models/competitive_tiers.py +++ b/valorant/models/competitive_tiers.py @@ -26,7 +26,8 @@ from pydantic_extra_types.color import Color from ..enums import DivisionTier -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'CompetitiveTier', diff --git a/valorant/models/content_tiers.py b/valorant/models/content_tiers.py index 387b59c..f1c8938 100644 --- a/valorant/models/content_tiers.py +++ b/valorant/models/content_tiers.py @@ -25,7 +25,8 @@ from pydantic import Field from pydantic_extra_types.color import Color -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('ContentTier',) diff --git a/valorant/models/contracts.py b/valorant/models/contracts.py index f92d61b..0e0a7ca 100644 --- a/valorant/models/contracts.py +++ b/valorant/models/contracts.py @@ -31,7 +31,8 @@ from pydantic import Field from ..enums import RelationType, RewardType -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Chapter', diff --git a/valorant/models/currencies.py b/valorant/models/currencies.py index f39e29a..8e02bae 100644 --- a/valorant/models/currencies.py +++ b/valorant/models/currencies.py @@ -24,7 +24,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('Currency',) diff --git a/valorant/models/events.py b/valorant/models/events.py index d61660b..987c22c 100644 --- a/valorant/models/events.py +++ b/valorant/models/events.py @@ -26,7 +26,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('Event',) diff --git a/valorant/models/gamemodes.py b/valorant/models/gamemodes.py index 3861613..777988c 100644 --- a/valorant/models/gamemodes.py +++ b/valorant/models/gamemodes.py @@ -29,7 +29,8 @@ from pydantic import Field from ..enums import GameFeature, GameRule -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Equippable', diff --git a/valorant/models/gear.py b/valorant/models/gear.py index 08749fb..b70f2e8 100644 --- a/valorant/models/gear.py +++ b/valorant/models/gear.py @@ -27,7 +27,8 @@ from pydantic import Field from ..enums import ShopCategory -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Detail', diff --git a/valorant/models/level_borders.py b/valorant/models/level_borders.py index c0ff090..ff3579e 100644 --- a/valorant/models/level_borders.py +++ b/valorant/models/level_borders.py @@ -24,7 +24,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('LevelBorder',) diff --git a/valorant/models/localization.py b/valorant/models/localization.py new file mode 100644 index 0000000..4064256 --- /dev/null +++ b/valorant/models/localization.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from pydantic import Field + +from .base import BaseModel + + +class LocalizedField(BaseModel): + de_DE: str = Field(alias='de-DE') + es_ES: str = Field(alias='es-ES') + ar_AE: str = Field(alias='ar-AE') + id_ID: str = Field(alias='id-ID') + es_MX: str = Field(alias='es-MX') + fr_FR: str = Field(alias='fr-FR') + en_US: str = Field(alias='en-US') + it_IT: str = Field(alias='it-IT') + ja_JP: str = Field(alias='ja-JP') + ko_KR: str = Field(alias='ko-KR') + th_TH: str = Field(alias='th-TH') + pl_PL: str = Field(alias='pl-PL') + pt_BR: str = Field(alias='pt-BR') + ru_RU: str = Field(alias='ru-RU') + tr_TR: str = Field(alias='tr-TR') + vi_VN: str = Field(alias='vi-VN') + zh_TW: str = Field(alias='zh-TW') + zh_CN: str = Field(alias='zh-CN') + + # aliases + + arabic: str = ar_AE + german: str = de_DE + american_english: str = en_US + spain_spanish: str = es_ES + spanish_mexican: str = es_MX + french: str = fr_FR + indonesian: str = id_ID + italian: str = it_IT + japanese: str = ja_JP + korean: str = ko_KR + polish: str = pl_PL + brazil_portuguese: str = pt_BR + russian: str = ru_RU + thai: str = th_TH + turkish: str = tr_TR + vietnamese: str = vi_VN + chinese: str = zh_CN + taiwan_chinese: str = zh_TW + + def __str__(self) -> str: + return self.en_US + + def __repr__(self) -> str: + return f'' diff --git a/valorant/models/maps.py b/valorant/models/maps.py index bd793fc..d1320b3 100644 --- a/valorant/models/maps.py +++ b/valorant/models/maps.py @@ -26,7 +26,8 @@ from pydantic import Field -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Callout', diff --git a/valorant/models/missions.py b/valorant/models/missions.py index 25c950a..df290de 100644 --- a/valorant/models/missions.py +++ b/valorant/models/missions.py @@ -28,7 +28,8 @@ from pydantic import Field from ..enums import MissionTag, MissionType -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Mission', diff --git a/valorant/models/player_cards.py b/valorant/models/player_cards.py index ddc7535..16d6ee1 100644 --- a/valorant/models/player_cards.py +++ b/valorant/models/player_cards.py @@ -29,7 +29,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('PlayerCard',) diff --git a/valorant/models/player_titles.py b/valorant/models/player_titles.py index 1e50213..158dd90 100644 --- a/valorant/models/player_titles.py +++ b/valorant/models/player_titles.py @@ -24,7 +24,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('PlayerTitle',) diff --git a/valorant/models/seasons.py b/valorant/models/seasons.py index 27ca813..973265f 100644 --- a/valorant/models/seasons.py +++ b/valorant/models/seasons.py @@ -31,7 +31,8 @@ from pydantic import Field from ..enums import SeasonType -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Border', diff --git a/valorant/models/sprays.py b/valorant/models/sprays.py index 38d9c10..f514cc5 100644 --- a/valorant/models/sprays.py +++ b/valorant/models/sprays.py @@ -29,7 +29,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ( 'Level', diff --git a/valorant/models/themes.py b/valorant/models/themes.py index 0fbb737..cff8f4b 100644 --- a/valorant/models/themes.py +++ b/valorant/models/themes.py @@ -24,7 +24,8 @@ from pydantic import Field -from .base import BaseUUIDModel, LocalizedField +from .base import BaseUUIDModel +from .localization import LocalizedField __all__ = ('Theme',) diff --git a/valorant/models/weapons.py b/valorant/models/weapons.py index 2739d99..59dc5ae 100644 --- a/valorant/models/weapons.py +++ b/valorant/models/weapons.py @@ -30,7 +30,8 @@ from pydantic import Field from ..enums import ShopCategory, WeaponCategory -from .base import BaseModel, BaseUUIDModel, LocalizedField +from .base import BaseModel, BaseUUIDModel +from .localization import LocalizedField # Changed from language to localization __all__ = ( 'AdsStats', From 6242d0f26ae7f76567aaed451e556e574d41b221 Mon Sep 17 00:00:00 2001 From: STACiA <69411527+staciax@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:58:36 +0700 Subject: [PATCH 60/60] fix import invalid --- examples/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic.py b/examples/basic.py index e6f39ea..d758a48 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,7 +1,7 @@ import asyncio import valorant -from valorant.models.base import LocalizedField +from valorant.models.localization import LocalizedField async def main() -> None: