diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..b371864 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,38 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.12" + - name: Install Poetry + run: pip install poetry + - name: Install dependencies + run: poetry install + - name: Build package + run: poetry build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90f8fbb --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# 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 + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__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 maintained 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/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dc3f727 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "basic" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c932b2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# cursedforged + +CurseForge API written in Python with the help of [Pydantic](https://docs.pydantic.dev/). diff --git a/cursedforged/__init__.py b/cursedforged/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cursedforged/api/__init__.py b/cursedforged/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cursedforged/api/base.py b/cursedforged/api/base.py new file mode 100644 index 0000000..d8d3caa --- /dev/null +++ b/cursedforged/api/base.py @@ -0,0 +1,42 @@ +import requests +from typing import Any + +from abc import ABC, abstractmethod + + +class BaseAPIClient(ABC): + def __init__( + self, + api_key: str, + base_url: str = "https://api.curseforge.com", + client: requests.Session | None = None, + ): + self.api_key = api_key + self.base_url = base_url + self.client = client or requests.Session() + + @abstractmethod + def get(self, endpoint: str, params: dict[str, Any] | None = None) -> Any: + """Send a GET request to the specified endpoint. + + Args: + endpoint (str): The endpoint to send the request to. + params (dict[str, Any] | None, optional): The parameters to send with the request. Defaults to None. + + Returns: + Any: The response data. + """ + pass + + @abstractmethod + def post(self, endpoint: str, data: dict[str, Any] | None = None) -> Any: + """Send a POST request to the specified endpoint. + + Args: + endpoint (str): The endpoint to send the request to. + data (dict[str, Any] | None, optional): The data to send with the request. Defaults to None. + + Returns: + Any: The response data. + """ + pass diff --git a/cursedforged/api/client.py b/cursedforged/api/client.py new file mode 100644 index 0000000..8c8d119 --- /dev/null +++ b/cursedforged/api/client.py @@ -0,0 +1,84 @@ +import requests +from typing import Any + +from .base import BaseAPIClient +from .v1 import API_v1 +from .v2 import API_v2 + + +class APIClient(BaseAPIClient): + def __init__( + self, + api_key: str, + base_url: str = "https://api.curseforge.com", + client: requests.Session | None = None, + ): + self.api_key = api_key + self.base_url = base_url + self.client = self._init_client(client) + + self.v1 = API_v1(self) + self.v2 = API_v2(self) + + def _init_client(self, client: requests.Session | None) -> requests.Session: + """Initialize the client with the specified client or create a new one if none is provided. + + Args: + client (requests.Session | None, optional): The client to use. Defaults to None. + + Returns: + requests.Session: The initialized client. + """ + _client = client or requests.Session() + _client.headers.update( + { + "x-api-key": self.api_key, + "Accept": "application/json", + } + ) + return _client + + def _build_request_uri(self, endpoint: str) -> str: + """Build the request URI for the specified endpoint. + + Args: + endpoint (str): The endpoint to build the URI for. + + Returns: + str: The built URI. + """ + return "{}/{}/".format(self.base_url, endpoint.strip("/")) + + def get(self, endpoint: str, params: dict[str, Any] | None = None) -> Any: + """Send a GET request to the specified endpoint. + + Args: + endpoint (str): The endpoint to send the request to. + params (dict[str, Any] | None, optional): The parameters to send with the request. Defaults to None. + + Returns: + Any: The response data. + """ + response = self.client.get( + url=self._build_request_uri(endpoint), + params=params or {}, + ) + + return response.json() + + def post(self, endpoint: str, data: dict[str, Any] | None = None) -> Any: + """Send a POST request to the specified endpoint. + + Args: + endpoint (str): The endpoint to send the request to. + data (dict[str, Any] | None, optional): The data to send with the request. Defaults to None. + + Returns: + Any: The response data. + """ + response = self.client.post( + url=self._build_request_uri(endpoint), + data=data or {}, + ) + + return response.json() diff --git a/cursedforged/api/v1/__init__.py b/cursedforged/api/v1/__init__.py new file mode 100644 index 0000000..53200b2 --- /dev/null +++ b/cursedforged/api/v1/__init__.py @@ -0,0 +1,571 @@ +from ..base import BaseAPIClient + +from cursedforged.types import ( + Game, + ModsSearchSortField, + SortOrder, + ModLoaderType, + GetGamesResponse, + GetVersionsResponse, + GetVersionTypesResponse, + GetCategoriesResponse, + GetModResponse, + GetModsResponse, + GetFeaturedModsResponse, + StringResponse, + GetModFileResponse, + GetModFilesResponse, + GetFilesResponse, + ApiResponseOfListOfMinecraftGameVersion, + ApiResponseOfMinecraftGameVersion, + ApiResponseOfListOfMinecraftModLoaderIndex, + ApiResponseOfMinecraftModLoaderVersion, + GetFingerprintMatchesResponse, + FolderFingerprint, + GetFingerprintsFuzzyMatchesResponse, +) + + +class API_v1: + def __init__(self, client: BaseAPIClient): + self.client = client + + def get_games(self, index: int = 0, page_size: int = 50) -> GetGamesResponse: + """Get all games + + https://docs.curseforge.com/#get-games + + Args: + index (int, optional): A zero based index of the first item to include in the response, the limit is: (index + pageSize <= 10,000). + page_size (int, optional): The number of items to include in the response, the default/maximum value is 50. + + Returns: + GetGamesResponse: A response object + """ + response = self.client.get( + "v1/games", + params={ + "index": index, + "pageSize": page_size, + }, + ) + + return GetGamesResponse(**response) + + def get_game(self, game_id: int) -> Game: + """Get a game + + https://docs.curseforge.com/#get-game + + Args: + game_id (int): A game unique id + + Returns: + Game: A game object + """ + response = self.client.get( + "v1/games/{}".format(game_id), + ) + return Game(**dict(response).get("data", {})) + + def get_game_versions(self, game_id: int) -> GetVersionsResponse: + """Get all available versions for each known version type of the specified game. A private game is only accessible to its respective API key. + + https://docs.curseforge.com/#get-versions + + Args: + game_id (int): A game unique id + + Returns: + GetVersionsResponse: A response object + """ + response = self.client.get( + "v1/games/{}/versions".format(game_id), + ) + + return GetVersionsResponse(**response) + + def get_game_version_types(self, game_id: int) -> GetVersionTypesResponse: + """Get all available version types of the specified game. + + https://docs.curseforge.com/#get-version-types + + A private game is only accessible to its respective API key. + + Currently, when creating games via the CurseForge for Studios Console, you are limited to a single game version type. This means that this endpoint is probably not useful in most cases and is relevant mostly when handling existing games that have multiple game versions such as World of Warcraft and Minecraft (e.g. 517 for wow_retail). + + Args: + game_id (int): A game unique id + + Returns: + GetVersionTypesResponse: A response object + """ + response = self.client.get( + "v1/games/{}/version-types".format(game_id), + ) + return GetVersionTypesResponse(**response) + + def get_categories( + self, game_id: int, class_id: int | None = None, classes_only: bool = False + ) -> GetCategoriesResponse: + """Get all available classes and categories of the specified game. Specify a game id for a list of all game categories, or a class id for a list of categories under that class. specifiy the classes Only flag to just get the classes for a given game. + + https://docs.curseforge.com/#get-categories + + Args: + game_id (int): A game unique id + class_id (int | None, optional): A class unique id. Defaults to None. + classes_only (bool, optional): If true, only return the classes for the specified game. Defaults to False. + + Returns: + GetCategoriesResponse: A response object + """ + + response = self.client.get( + "v1/categories", + params={ + "gameId": game_id, + "classId": class_id, + "classesOnly": classes_only, + }, + ) + return GetCategoriesResponse(**response) + + def search_mods( + self, + game_id: int, + class_id: int | None = None, + category_id: int | None = None, + category_ids: list[int] | None = None, + game_version: str | None = None, + game_versions: list[str] | None = None, + search_filter: str | None = None, + sort_field: ModsSearchSortField | None = None, + sort_order: SortOrder | None = None, + mod_loader_type: ModLoaderType | None = None, + mod_loader_types: list[ModLoaderType] | None = None, + game_version_type_id: int | None = None, + author_id: int | None = None, + primary_author_id: int | None = None, + slug: str | None = None, + index: int | None = 0, + page_size: int | None = 50, + ): + """Get all mods that match the search criteria. + + https://docs.curseforge.com/#search-mods + + Args: + game_id (int): A game unique id + class_id (int | None, optional): Filter by section id (discoverable via Categories) + category_id (int | None, optional): Filter by category id + category_ids (list[int] | None, optional): Filter by a list of category ids - this will override categoryId + game_version (str | None, optional): Filter by game version string + game_versions (list[str] | None, optional): Filter by a list of game version strings - this will override + search_filter (str | None, optional): Filter by free text search in the mod name and author + sort_field (ModsSearchSortField | None, optional): Filter by ModsSearchSortField enumeration + sort_order (SortOrder | None, optional): 'asc' if sort is in ascending order, 'desc' if sort is in descending order + mod_loader_type (ModLoaderType | None, optional): Filter only mods associated to a given modloader (Forge, Fabric ...). Must be coupled with gameVersion. + mod_loader_types (list[ModLoaderType] | None, optional): Filter by a list of mod loader types - this will override modLoaderType + game_version_type_id (int | None, optional): Filter only mods that contain files tagged with versions of the given gameVersionTypeId + author_id (int | None, optional): Filter only mods that the given authorId is a member of. + primary_author_id (int | None, optional): Filter only mods that the given primaryAuthorId is the owner of. + slug (str | None, optional): Filter by slug (coupled with classId will result in a unique result). + index (int | None, optional): A zero based index of the first item to include in the response, the limit is: (index + pageSize <= 10,000). + page_size (int | None, optional): The number of items to include in the response, the default/maximum value is 50. + + Returns: + GetCategoriesResponse: A response object + """ + response = self.client.get( + "v1/mods/search", + params={ + "gameId": game_id, + "classId": class_id, + "categoryId": category_id, + "categoryIds": category_ids, + "gameVersion": game_version, + "gameVersions": game_versions, + "searchFilter": search_filter, + "sortField": sort_field, + "sortOrder": sort_order, + "modLoaderType": mod_loader_type, + "modLoaderTypes": mod_loader_types, + "gameVersionTypeId": game_version_type_id, + "authorId": author_id, + "primaryAuthorId": primary_author_id, + "slug": slug, + "index": index, + "pageSize": page_size, + }, + ) + return GetCategoriesResponse(**response) + + def get_mod(self, mod_id: int) -> GetModResponse: + """Get a single mod. + + https://docs.curseforge.com/#get-mod + + Args: + mod_id (int): The mod id + + Returns: + GetModResponse: A response object + """ + response = self.client.get( + "v1/mods/{}".format(mod_id), + ) + return GetModResponse(**response) + + def get_mods( + self, mod_ids: list[int], filter_pc_only: bool | None = False + ) -> GetModsResponse: + """Get a list of mods belonging the the same game. + + https://docs.curseforge.com/#get-mods + + Args: + mod_ids (list[int]): A list of mod ids + filter_pc_only (bool | None, optional): Filter mods that are only available for PC. Defaults to False. + + Returns: + GetModsResponse: A response object + """ + response = self.client.post( + "v1/mods", + data={ + "modIds": mod_ids, + "filterPcOnly": filter_pc_only, + }, + ) + return GetModsResponse(**response) + + def get_featured_mods( + self, + game_id: int, + excluded_mod_ids: list[int] | None = None, + game_version_type_id: int | None = None, + ) -> GetFeaturedModsResponse: + """Get a list of featured, popular and recently updated mods. + + https://docs.curseforge.com/#get-featured-mods + + Args: + game_id (int): A game unique id + excluded_mod_ids (list[int] | None, optional): A list of mod ids to exclude from the response. Defaults to None. + game_version_type_id (int | None, optional): Filter mods by game version type. Defaults to None. + + Returns: + GetFeaturedModsResponse: A response object + """ + response = self.client.post( + "v1/mods/featured", + data={ + "gameId": game_id, + "excludedModIds": excluded_mod_ids, + "gameVersionTypeId": game_version_type_id, + }, + ) + return GetFeaturedModsResponse(**response) + + def get_mod_description( + self, + mod_id: int, + raw: bool | None = None, + stripped: bool | None = None, + markup: bool | None = None, + ) -> StringResponse: + """Get the full description of a mod in HTML format. + + https://docs.curseforge.com/#get-mod-description + + Args: + mod_id (int): The mod id + raw (bool | None, optional): If true, the description will be returned as it is stored in the database. Defaults to None. + stripped (bool | None, optional): If true, the description will be stripped of all HTML tags. Defaults to None. + markup (bool | None, optional): If true, the description will be returned as it is stored in the database. Defaults to None. + + Returns: + StringResponse: The mod description + """ + response = self.client.get( + "v1/mods/{}/description".format(mod_id), + params={ + "raw": raw, + "stripped": stripped, + "markup": markup, + }, + ) + return StringResponse(**response) + + def get_mod_file(self, mod_id: int, file_id: int) -> GetModFileResponse: + """Get a single file of the specified mod. + + https://docs.curseforge.com/#get-mod-file + + Args: + mod_id (int): The mod id the file belongs to + file_id (int): The file id + + Returns: + GetModFileResponse: The mod file + """ + response = self.client.get( + "v1/mods/{}/files/{}".format(mod_id, file_id), + ) + return GetModFileResponse(**response) + + def get_mod_files( + self, + mod_id: int, + game_version: str | None = None, + mod_loader_type: ModLoaderType | None = None, + game_version_type_id: int | None = None, + index: int | None = 0, + page_size: int | None = 50, + ) -> GetModFilesResponse: + """Get all files of the specified mod. + + https://docs.curseforge.com/#get-mod-files + + Args: + mod_id (int): The mod id the files belong to + game_version (str | None, optional): Filter by game version string. Defaults to None. + mod_loader_type (ModLoaderType | None, optional): ModLoaderType enumeration. Defaults to None. + game_version_type_id (int | None, optional): Filter only files that are tagged with versions of the given gameVersionTypeId. Defaults to None. + index (int | None, optional): A zero based index of the first item to include in the response, the limit is: (index + pageSize <= 10,000). + page_size (int | None, optional): The number of items to include in the response, the default/maximum value is 50. + + Returns: + GetModFilesResponse: A response object + """ + response = self.client.post( + "v1/mods/{}/files".format(mod_id), + data={ + "gameVersion": game_version, + "modLoaderType": mod_loader_type, + "gameVersionTypeId": game_version_type_id, + "index": index, + "pageSize": page_size, + }, + ) + return GetModFilesResponse(**response) + + def get_files(self, file_ids: list[int]) -> GetFilesResponse: + """Get a list of files. + + https://docs.curseforge.com/#get-files + + Args: + file_ids (list[int]): A list of file ids + + Returns: + GetFilesResponse: A response object + """ + response = self.client.post( + "v1/mods/files", + data={ + "fileIds": file_ids, + }, + ) + return GetFilesResponse(**response) + + def get_mod_file_changelog(self, mod_id: int, file_id: int) -> StringResponse: + """Get the changelog of a file in HTML format. + + https://docs.curseforge.com/#get-mod-file-changelog + + Args: + mod_id (int): The mod id the file belongs to + file_id (int): The file id + + Returns: + StringResponse: A response object + """ + response = self.client.get( + "v1/mods/{}/files/{}/changelog".format(mod_id, file_id), + ) + return StringResponse(**response) + + def get_mod_file_download_url(self, mod_id: int, file_id: int) -> StringResponse: + """Get a download url for a specific file. + + https://docs.curseforge.com/#get-mod-file-download-url + + Args: + mod_id (int): The mod id the file belongs to + file_id (int): The file id + + Returns: + StringResponse: A response object + """ + response = self.client.get( + "v1/mods/{}/files/{}/download-url".format(mod_id, file_id), + ) + return StringResponse(**response) + + def get_fingerprints_matches_by_game_id( + self, game_id: int, fingerprints: list[int] + ) -> GetFingerprintMatchesResponse: + """Get mod files that match a list of fingerprints for a given game id. + + https://docs.curseforge.com/#get-fingerprints-matches-by-game-id + + Args: + game_id (int): The game id for matching fingerprints + fingerprints (list[int]): The request body containing an array of fingerprints + + Returns: + GetFingerprintMatchesResponse: A response object + """ + response = self.client.post( + "v1/fingerprints/{}".format(game_id), + data={ + "fingerprints": fingerprints, + }, + ) + return GetFingerprintMatchesResponse(**response) + + def get_fingerprints_matches( + self, fingerprints: list[int] + ) -> GetFingerprintMatchesResponse: + """Get mod files that match a list of fingerprints. + + https://docs.curseforge.com/#get-fingerprints-matches + + Args: + fingerprints (list[int]): The request body containing an array of fingerprints + + Returns: + GetFingerprintMatchesResponse: A response object + """ + response = self.client.post( + "v1/fingerprints", + data={ + "fingerprints": fingerprints, + }, + ) + return GetFingerprintMatchesResponse(**response) + + def get_fingerprints_fuzzy_matches_by_game_id( + self, game_id: int, fingerprints: list[FolderFingerprint] + ) -> GetFingerprintsFuzzyMatchesResponse: + """Get mod files that match a list of fingerprints using fuzzy matching. + + https://docs.curseforge.com/#get-fingerprints-fuzzy-matches-by-game-id + + Args: + game_id (int): The game id for matching fingerprints + fingerprints (list[FolderFingerprint]): Game id and folder fingerprints options for the fuzzy matching + + Returns: + GetFingerprintsFuzzyMatchesResponse: A response object + """ + response = self.client.post( + "/v1/fingerprints/fuzzy/{}".format(game_id), + data={ + "fingerprints": fingerprints, + }, + ) + return GetFingerprintsFuzzyMatchesResponse(**response) + + def get_fingerprints_fuzzy_matches( + self, fingerprints: list[FolderFingerprint] + ) -> GetFingerprintsFuzzyMatchesResponse: + """Get mod files that match a list of fingerprints using fuzzy matching. + + https://docs.curseforge.com/#get-fingerprints-fuzzy-matches + + Args: + fingerprints (list[FolderFingerprint]): Game id and folder fingerprints options for the fuzzy matching + + Returns: + GetFingerprintsFuzzyMatchesResponse: A response object + """ + response = self.client.post( + "/v1/fingerprints/fuzzy", + data={ + "fingerprints": fingerprints, + }, + ) + return GetFingerprintsFuzzyMatchesResponse(**response) + + def get_minecraft_versions( + self, sort_descending: bool | None = None + ) -> ApiResponseOfListOfMinecraftGameVersion: + """Get a list of all available Minecraft versions. + + https://docs.curseforge.com/#get-minecraft-versions + + Args: + sort_descending (bool, optional): If true, the list will be sorted in descending order. Defaults to None. + + Returns: + ApiResponseOfListOfMinecraftGameVersion: A response object + """ + response = self.client.get( + "v1/minecraft/version", + params={ + "sortDescending": sort_descending, + }, + ) + return ApiResponseOfListOfMinecraftGameVersion(**response) + + def get_minecraft_version( + self, game_version_string: str + ) -> ApiResponseOfMinecraftGameVersion: + """Get a single Minecraft version. + + https://docs.curseforge.com/#get-specific-minecraft-version + + Args: + game_version_string (str): The game version string + + Returns: + ApiResponseOfMinecraftGameVersion: A response object + """ + response = self.client.get( + "v1/minecraft/version/{}".format(game_version_string), + ) + return ApiResponseOfMinecraftGameVersion(**response) + + def get_minecraft_modloaders( + self, version: str | None = None, include_all: bool | None = None + ) -> ApiResponseOfListOfMinecraftModLoaderIndex: + """Get a list of all available Minecraft modloaders. + + https://docs.curseforge.com/#get-minecraft-modloaders + + Args: + version (str | None, optional): Filter by game version string. Defaults to None. + include_all (bool | None, optional): If true, include all modloaders. Defaults to None. + + Returns: + ApiResponseOfListOfMinecraftModLoaderIndex: A response object + """ + response = self.client.get( + "v1/minecraft/modloader", + params={ + "version": version, + "includeAll": include_all, + }, + ) + return ApiResponseOfListOfMinecraftModLoaderIndex(**response) + + def get_minecraft_modloader( + self, mod_loader_name: str + ) -> ApiResponseOfMinecraftModLoaderVersion: + """Get a single Minecraft modloader. + + https://docs.curseforge.com/#get-specific-minecraft-modloader + + Args: + modloader_id (str): The modloader name + + Returns: + ApiResponseOfMinecraftModLoaderVersion: A response object + """ + response = self.client.get( + "v1/minecraft/modloader/{}".format(mod_loader_name), + ) + return ApiResponseOfMinecraftModLoaderVersion(**response) diff --git a/cursedforged/api/v2/__init__.py b/cursedforged/api/v2/__init__.py new file mode 100644 index 0000000..190d9a8 --- /dev/null +++ b/cursedforged/api/v2/__init__.py @@ -0,0 +1,25 @@ +from cursedforged.api.base import BaseAPIClient + +from cursedforged.types import GetVersionsResponse2 + + +class API_v2: + def __init__(self, client: BaseAPIClient): + self.client = client + + def get_game_versions(self, game_id: int) -> GetVersionsResponse2: + """Get all available versions for each known version type of the specified game. A private game is only accessible to its respective API key. + + https://docs.curseforge.com/#get-versions-v2 + + Args: + game_id (int): A game unique id + + Returns: + GetVersionsResponse2: A response object + """ + response = self.client.get( + "v2/games/{}/versions".format(game_id), + ) + + return GetVersionsResponse2(**response) diff --git a/cursedforged/types/__init__.py b/cursedforged/types/__init__.py new file mode 100644 index 0000000..a474099 --- /dev/null +++ b/cursedforged/types/__init__.py @@ -0,0 +1,8 @@ +from .category import * +from .enums import * +from .files import * +from .games import * +from .minecraft import * +from .mods import * +from .responses import * +from .fingerprints import * diff --git a/cursedforged/types/category.py b/cursedforged/types/category.py new file mode 100644 index 0000000..543dd78 --- /dev/null +++ b/cursedforged/types/category.py @@ -0,0 +1,41 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + + +class Category(BaseModel): + """https://docs.curseforge.com/#tocS_Category""" + + id: int = Field(alias="id", description="The category id") + game_id: int = Field( + alias="gameId", description="The game id related to the category" + ) + name: str = Field(alias="name", description="Category name") + slug: str = Field( + alias="slug", description="The category slug as it appears in the URL" + ) + url: str = Field(alias="url", description="The category URL") + icon_url: str = Field(alias="iconUrl", description="URL for the category icon") + date_modified: datetime = Field( + alias="dateModified", description="Last modified date of the category" + ) + is_class: bool | None = Field( + alias="isClass", + description="A top level category for other categories", + default=None, + ) + class_id: int | None = Field( + alias="classId", + description="The class id of the category, meaning - the class of which this category is under", + default=None, + ) + parent_category_id: int | None = Field( + alias="parentCategoryId", + description="The parent category for this category", + default=None, + ) + display_index: int | None = Field( + alias="displayIndex", + description="The display index for this category", + default=None, + ) diff --git a/cursedforged/types/enums.py b/cursedforged/types/enums.py new file mode 100644 index 0000000..3957509 --- /dev/null +++ b/cursedforged/types/enums.py @@ -0,0 +1,139 @@ +from enum import IntEnum, StrEnum + + +class CoreApiStatus(IntEnum): + """https://docs.curseforge.com/#tocS_CoreApiStatus""" + + PRIVATE = 1 + PUBLIC = 2 + + +class CoreStatus(IntEnum): + """https://docs.curseforge.com/#tocS_CoreStatus""" + + DRAFT = 1 + TEST = 2 + PENDING_REVIEW = 3 + REJECTED = 4 + APPROVED = 5 + LIVE = 6 + + +class GameVersionStatus(IntEnum): + """https://docs.curseforge.com/#tocS_GameVersionStatus""" + + APPROVED = 1 + DELETED = 2 + NEW = 3 + + +class GameVersionTypeStatus(IntEnum): + """https://docs.curseforge.com/#tocS_GameVersionTypeStatus""" + + NORMAL = 1 + DELETED = 2 + + +class ModStatus(IntEnum): + """https://docs.curseforge.com/#tocS_ModStatus""" + + NEW = 1 + CHANGES_REQUIRED = 2 + UNDER_SOFT_REVIEW = 3 + APPROVED = 4 + REJECTED = 5 + CHANGES_MADE = 6 + INACTIVE = 7 + ABANDONED = 8 + DELETED = 9 + UNDER_REVIEW = 10 + + +class ModsSearchSortField(IntEnum): + """https://docs.curseforge.com/#tocS_ModsSearchSortField""" + + FEATURED = 1 + POPULARITY = 2 + LAST_UPDATED = 3 + NAME = 4 + AUTHOR = 5 + TOTAL_DOWNLOADS = 6 + CATEGORY = 7 + GAME_VERSION = 8 + EARLY_ACCESS = 9 + FEATURED_RELEASED = 10 + RELEASED_DATE = 11 + RATING = 12 + + +class ModLoaderInstallMethod(IntEnum): + """https://docs.curseforge.com/#tocS_ModLoaderInstallMethod""" + + FORGE_INSTALLER = 1 + FORGE_JAR_INSTALL = 2 + FORGE_INSTALLER_V2 = 3 + + +class ModLoaderType(IntEnum): + """https://docs.curseforge.com/#tocS_ModLoaderType""" + + ANY = 0 + FORGE = 1 + CAULDRON = 2 + LITELOADER = 3 + FABRIC = 4 + QUILT = 5 + NEOFORGE = 6 + + +class FileRelationType(IntEnum): + """https://docs.curseforge.com/#tocS_FileRelationType""" + + EMBEDDED_LIBRARY = 1 + OPTIONAL_DEPENDENCY = 2 + REQUIRED_DEPENDENCY = 3 + TOOL = 4 + INCOMPATIBLE = 5 + INCLUDE = 6 + + +class FileReleaseType(IntEnum): + """https://docs.curseforge.com/#tocS_FileReleaseType""" + + RELEASE = 1 + BETA = 2 + ALPHA = 3 + + +class FileStatus(IntEnum): + """https://docs.curseforge.com/#tocS_FileStatus""" + + PROCESSING = 1 + CHANGES_REQUIRED = 2 + UNDER_REVIEW = 3 + APPROVED = 4 + REJECTED = 5 + MALWARE_DETECTED = 6 + DELETED = 7 + ARCHIVED = 8 + TESTING = 9 + RELEASED = 10 + READY_FOR_REVIEW = 11 + DEPRECATED = 12 + BAKING = 13 + AWAITING_PUBLISHING = 14 + FAILED_PUBLISHING = 15 + + +class HashAlgo(IntEnum): + """https://docs.curseforge.com/#tocS_HashAlgo""" + + SHA1 = 1 + MD5 = 2 + + +class SortOrder(StrEnum): + """https://docs.curseforge.com/#tocS_SortOrder""" + + ASC = "asc" + DESC = "desc" diff --git a/cursedforged/types/files.py b/cursedforged/types/files.py new file mode 100644 index 0000000..a5c73f5 --- /dev/null +++ b/cursedforged/types/files.py @@ -0,0 +1,128 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + +from .enums import ( + FileRelationType, + FileReleaseType, + FileStatus, + HashAlgo, + ModLoaderType, +) + + +class FileDependency(BaseModel): + """https://docs.curseforge.com/#tocS_FileDependency""" + + mod_id: int = Field(alias="modId") + relation_type: FileRelationType = Field(alias="relationType") + + +class FileHash(BaseModel): + """https://docs.curseforge.com/#tocS_FileHash""" + + value: str = Field(alias="value") + algo: HashAlgo = Field(alias="algo") + + +class FileIndex(BaseModel): + """https://docs.curseforge.com/#tocS_FileIndex""" + + game_version: str = Field(alias="gameVersion") + file_id: int = Field(alias="fileId") + filename: str = Field(alias="filename") + release_type: FileReleaseType = Field(alias="releaseType") + game_version_type_id: int | None = Field(alias="gameVersionTypeId", default=None) + mod_loader: ModLoaderType = Field(alias="modLoader") + + +class FileModule(BaseModel): + """https://docs.curseforge.com/#tocS_FileModule""" + + name: str = Field(alias="name") + fingerprint: int = Field(alias="fingerprint") + + +class SortableGameVersion(BaseModel): + """https://docs.curseforge.com/#tocS_SortableGameVersion""" + + game_version_name: str = Field( + alias="gameVersionName", description="Original version name (e.g. 1.5b)" + ) + game_version_padded: str = Field( + alias="gameVersionPadded", + description="Used for sorting (e.g. 0000000001.0000000005)", + ) + game_version: str = Field( + alias="gameVersion", description="Game version clean name (e.g. 1.5)" + ) + game_version_release_date: datetime = Field( + alias="gameVersionReleaseDate", description="Game version release date" + ) + game_version_type_id: int | None = Field( + alias="gameVersionTypeId", description="Game version type id", default=None + ) + + +class File(BaseModel): + """https://docs.curseforge.com/#tocS_File""" + + id: int = Field(alias="id", description="The file id") + game_id: int = Field( + alias="gameId", + description="The game id related to the mod that this file belongs to", + ) + mod_id: int = Field(alias="modId", description="The mod id") + is_available: bool = Field( + alias="isAvailable", description="Whether the file is available to download" + ) + display_name: str = Field( + alias="displayName", description="Display name of the file" + ) + file_name: str = Field(alias="fileName", description="Exact file name") + release_type: FileReleaseType = Field( + alias="releaseType", description="The file release type" + ) + file_status: FileStatus = Field( + alias="fileStatus", description="Status of the file" + ) + hashes: list[FileHash] = Field( + alias="hashes", description="The file hash (i.e. md5 or sha1)" + ) + file_date: datetime = Field(alias="fileDate", description="The file timestamp") + file_length: int = Field(alias="fileLength", description="The file length in bytes") + download_count: int = Field( + alias="downloadCount", description="The number of downloads for the file" + ) + file_size_on_disk: int | None = Field( + alias="fileSizeOnDisk", description="The file's size on disk", default=None + ) + download_url: str = Field(alias="downloadUrl", description="The file download URL") + game_versions: list[str] = Field( + alias="gameVersions", + description="List of game versions this file is relevant for", + ) + sortable_game_versions: list[SortableGameVersion] = Field( + alias="sortableGameVersions", + description="Metadata used for sorting by game versions", + ) + dependencies: list[FileDependency] = Field( + alias="dependencies", description="List of dependencies files" + ) + expose_as_alternative: bool | None = Field( + alias="exposeAsAlternative", default=None + ) + parent_project_file_id: int | None = Field( + alias="parentProjectFileId", default=None + ) + alternate_file_id: int | None = Field(alias="alternateFileId", default=None) + is_server_pack: bool | None = Field(alias="isServerPack", default=None) + server_pack_file_id: int | None = Field(alias="serverPackFileId", default=None) + is_early_access_content: bool | None = Field( + alias="isEarlyAccessContent", default=None + ) + early_access_end_date: datetime | None = Field( + alias="earlyAccessEndDate", default=None + ) + file_fingerprint: int = Field(alias="fileFingerprint") + modules: list[FileModule] = Field(alias="modules") diff --git a/cursedforged/types/fingerprints.py b/cursedforged/types/fingerprints.py new file mode 100644 index 0000000..e6c8345 --- /dev/null +++ b/cursedforged/types/fingerprints.py @@ -0,0 +1,48 @@ +from pydantic import BaseModel, Field + +from .files import File + + +class FingerprintMatch(BaseModel): + """https://docs.curseforge.com/#tocS_FingerprintMatch""" + + id: int = Field(alias="id") + file: File = Field(alias="file") + latest_files: list[File] = Field(alias="latestFiles") + + +class FingerprintMatchesResult(BaseModel): + """https://docs.curseforge.com/#tocS_FingerprintsMatchesResult""" + + is_cache_built: bool = Field(alias="isCacheBuilt") + exact_matches: list[FingerprintMatch] = Field(alias="exactMatches") + exact_fingerprints: list[int] = Field(alias="exactFingerprints") + partial_matches: list[FingerprintMatch] = Field(alias="partialMatches") + partial_match_fingerprints: dict[str, list[int]] = Field( + alias="partialMatchFingerprints" + ) + additional_properties: list[int] = Field(alias="additionalProperties") + installed_fingerprints: list[int] = Field(alias="installedFingerprints") + unmatched_fingerprints: list[int] = Field(alias="unmatchedFingerprints") + + +class FolderFingerprint(BaseModel): + """https://docs.curseforge.com/#tocS_FolderFingerprint""" + + foldername: str = Field(alias="foldername") + fingerprints: list[int] = Field(alias="fingerprints") + + +class FingerprintFuzzyMatch(BaseModel): + """https://docs.curseforge.com/#tocS_FingerprintFuzzyMatch""" + + id: int = Field(alias="id") + file: File = Field(alias="file") + latest_files: list[File] = Field(alias="latestFiles") + fingerprints: list[int] = Field(alias="fingerprints") + + +class FingerprintFuzzyMatchResult(BaseModel): + """https://docs.curseforge.com/#tocS_FingerprintFuzzyMatchResult""" + + fuzzyMatches: list[FingerprintMatch] = Field(alias="fuzzyMatches") diff --git a/cursedforged/types/games.py b/cursedforged/types/games.py new file mode 100644 index 0000000..2712966 --- /dev/null +++ b/cursedforged/types/games.py @@ -0,0 +1,58 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + +from .enums import CoreApiStatus, CoreStatus, GameVersionTypeStatus + + +class GameAssets(BaseModel): + """https://docs.curseforge.com/#tocS_GameAssets""" + + icon_url: str | None = Field(alias="iconUrl") + tile_url: str | None = Field(alias="tileUrl") + cover_url: str | None = Field(alias="coverUrl") + + +class GameVersion(BaseModel): + """https://docs.curseforge.com/#tocS_GameVersion""" + + id: int = Field(alias="id") + slug: str = Field(alias="slug") + name: str = Field(alias="name") + + +class GameVersionsByType(BaseModel): + """https://docs.curseforge.com/#tocS_GameVersionsByType""" + + type: int = Field(alias="type") + versions: list[str] = Field(alias="versions") + + +class GameVersionsByType2(BaseModel): + """https://docs.curseforge.com/#tocS_GameVersionsByType2""" + + type: int = Field(alias="type") + versions: list[str] = Field(alias="versions") + + +class GameVersionType(BaseModel): + """https://docs.curseforge.com/#tocS_GameVersionType""" + + id: int = Field(alias="id") + game_id: int = Field(alias="gameId") + name: str = Field(alias="name") + slug: str = Field(alias="slug") + is_syncable: bool = Field(alias="isSyncable") + status: GameVersionTypeStatus = Field(alias="status") + + +class Game(BaseModel): + """https://docs.curseforge.com/#tocS_Game""" + + id: int = Field(alias="id") + name: str = Field(alias="name") + slug: str = Field(alias="slug") + date_modified: datetime = Field(alias="dateModified") + assets: GameAssets = Field(alias="assets") + status: CoreStatus = Field(alias="status") + api_status: CoreApiStatus = Field(alias="apiStatus") diff --git a/cursedforged/types/minecraft.py b/cursedforged/types/minecraft.py new file mode 100644 index 0000000..80cc955 --- /dev/null +++ b/cursedforged/types/minecraft.py @@ -0,0 +1,75 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + +from .enums import ( + GameVersionStatus, + GameVersionTypeStatus, + ModLoaderInstallMethod, + ModLoaderType, +) + + +class MinecraftGameVersion(BaseModel): + """https://docs.curseforge.com/#tocS_MinecraftGameVersion""" + + id: int = Field(alias="id") + game_version_id: int = Field(alias="gameVersionId") + version_string: str = Field(alias="versionString") + jar_download_url: str = Field(alias="jarDownloadUrl") + json_download_url: str = Field(alias="jsonDownloadUrl") + approved: bool = Field(alias="approved") + date_modified: datetime = Field(alias="dateModified") + game_version_type_id: int = Field(alias="gameVersionTypeId") + game_version_status: GameVersionStatus = Field(alias="gameVersionStatus") + game_version_type_status: GameVersionTypeStatus = Field( + alias="gameVersionTypeStatus" + ) + + +class MinecraftModLoaderIndex(BaseModel): + """https://docs.curseforge.com/#tocS_MinecraftModLoaderIndex""" + + name: str = Field(alias="name") + game_version: str = Field(alias="gameVersion") + latest: bool = Field(alias="latest") + recommended: bool = Field(alias="recommended") + date_modified: datetime = Field(alias="dateModified") + type: ModLoaderType = Field(alias="type") + + +class MinecraftModLoaderVersion(BaseModel): + """https://docs.curseforge.com/#tocS_MinecraftModLoaderVersion""" + + id: int = Field(alias="id") + game_version_id: int = Field(alias="gameVersionId") + minecraft_game_version_id: int = Field(alias="minecraftGameVersionId") + forge_version: str = Field(alias="forgeVersion") + name: str = Field(alias="name") + type: ModLoaderType = Field(alias="type") + download_url: str = Field(alias="downloadUrl") + filename: str = Field(alias="filename") + install_method: ModLoaderInstallMethod = Field(alias="installMethod") + latest: bool = Field(alias="latest") + recommended: bool = Field(alias="recommended") + approved: bool = Field(alias="approved") + date_modified: datetime = Field(alias="dateModified") + maven_version_string: str = Field(alias="mavenVersionString") + version_json: str = Field(alias="versionJson") + libraries_install_location: str = Field(alias="librariesInstallLocation") + minecraft_version: str = Field(alias="minecraftVersion") + additional_files_json: str = Field(alias="additionalFilesJson") + mod_loader_game_version_id: int = Field(alias="modLoaderGameVersionId") + mod_loader_game_version_type_id: int = Field(alias="modLoaderGameVersionTypeId") + mod_loader_game_version_status: GameVersionStatus = Field( + alias="modLoaderGameVersionStatus" + ) + mod_loader_game_version_type_status: GameVersionTypeStatus = Field( + alias="modLoaderGameVersionTypeStatus" + ) + mc_game_version_id: int = Field(alias="mcGameVersionId") + mc_game_version_type_id: int = Field(alias="mcGameVersionTypeId") + mc_game_version_status: GameVersionStatus = Field(alias="mcGameVersionStatus") + mc_game_version_type_status: GameVersionTypeStatus = Field( + alias="mcGameVersionTypeStatus" + ) diff --git a/cursedforged/types/mods.py b/cursedforged/types/mods.py new file mode 100644 index 0000000..b064bd6 --- /dev/null +++ b/cursedforged/types/mods.py @@ -0,0 +1,117 @@ +from datetime import datetime + +from pydantic import BaseModel, Field + +from .category import Category +from .enums import ModStatus +from .files import File, FileIndex + + +class ModAsset(BaseModel): + """https://docs.curseforge.com/#tocS_ModAsset""" + + id: int = Field(alias="id") + mod_id: int = Field(alias="modId") + title: str = Field(alias="title") + description: str = Field(alias="description") + thumbnail_url: str = Field(alias="thumbnailUrl") + url: str = Field(alias="url") + + +class ModAuthor(BaseModel): + """https://docs.curseforge.com/#tocS_ModAuthor""" + + id: int = Field(alias="id") + name: str = Field(alias="name") + url: str = Field(alias="url") + + +class ModLinks(BaseModel): + """https://docs.curseforge.com/#tocS_ModLinks""" + + website_url: str | None = Field(alias="websiteUrl") + wiki_url: str | None = Field(alias="wikiUrl") + issues_url: str | None = Field(alias="issuesUrl") + source_url: str | None = Field(alias="sourceUrl") + + +class Mod(BaseModel): + """https://docs.curseforge.com/#tocS_Mod""" + + id: int = Field(alias="id", description="The mod id") + game_id: int = Field(alias="gameId", description="The game id this mod is for") + name: str = Field(alias="name", description="The name of the mod") + slug: str = Field( + alias="slug", description="The mod slug that would appear in the URL" + ) + links: ModLinks = Field( + alias="links", + description="Relevant links for the mod such as Issue tracker and Wiki", + ) + summary: str = Field(alias="summary", description="Mod summary") + status: ModStatus = Field(alias="status", description="Current mod status") + download_count: int = Field( + alias="downloadCount", description="Number of downloads for the mod" + ) + is_featured: bool = Field( + alias="isFeatured", + description="Whether the mod is included in the featured mods list", + ) + primary_category_id: int = Field( + alias="primaryCategoryId", + description="The main category of the mod as it was chosen by the mod author", + ) + categories: list[Category] = Field( + alias="categories", description="List of categories that this mod is related to" + ) + class_id: int = Field( + alias="classId", description="The class id this mod belongs to" + ) + authors: list[ModAuthor] = Field( + alias="authors", description="List of the mod's authors" + ) + logo: ModAsset = Field(alias="logo", description="The mod's logo asset") + screenshots: list[ModAsset] = Field( + alias="screenshots", description="List of screenshots assets" + ) + main_file_id: int = Field( + alias="mainFileId", description="The id of the main file of the mod" + ) + latest_files: list[File] = Field( + alias="latestFiles", description="List of latest files of the mod" + ) + latest_files_indexes: list[FileIndex] = Field( + alias="latestFilesIndexes", + description="List of file related details for the latest files of the mod", + ) + latest_early_access_files_indexes: list[FileIndex] = Field( + alias="latestEarlyAccessFilesIndexes", + description="List of file related details for the latest early access files of the mod", + ) + date_created: datetime = Field( + alias="dateCreated", description="The creation date of the mod" + ) + date_modified: datetime = Field( + alias="dateModified", description="The last time the mod was modified" + ) + date_released: datetime = Field( + alias="dateReleased", description="The release date of the mod" + ) + allow_mod_distribution: bool | None = Field( + alias="allowModDistribution", + description="Is mod allowed to be distributed", + default=None, + ) + game_popularity_rank: int = Field( + alias="gamePopularityRank", description="The mod popularity rank for the game" + ) + is_available: bool = Field( + alias="isAvailable", + description="Is the mod available for search. This can be false when a mod is experimental, in a deleted state or has only alpha files", + ) + thumbs_up_count: int = Field( + alias="thumbsUpCount", description="The mod's thumbs up count" + ) + rating: float | None = Field( + alias="rating", description="The mod's Rating", default=None + ) diff --git a/cursedforged/types/responses.py b/cursedforged/types/responses.py new file mode 100644 index 0000000..cd5c118 --- /dev/null +++ b/cursedforged/types/responses.py @@ -0,0 +1,156 @@ +from pydantic import BaseModel, Field + +from .category import Category +from .minecraft import ( + MinecraftGameVersion, + MinecraftModLoaderIndex, + MinecraftModLoaderVersion, +) +from .mods import Mod +from .files import File +from .games import Game, GameVersionType, GameVersionsByType +from .fingerprints import FingerprintMatchesResult, FingerprintFuzzyMatchResult + + +class Pagination(BaseModel): + """https://docs.curseforge.com/#tocS_Pagination""" + + index: int = Field( + alias="index", + description="A zero based index of the first item that is included in the response", + ) + page_size: int = Field( + alias="pageSize", + description="The requested number of items to be included in the response", + ) + result_count: int = Field( + alias="resultCount", + description="The actual number of items that were included in the response", + ) + total_count: int = Field( + alias="totalCount", + description="The total number of items available by the request", + ) + + +class ApiResponseOfListOfMinecraftModLoaderIndex(BaseModel): + """https://docs.curseforge.com/#tocS_ApiResponseOfListOfMinecraftModLoaderIndex""" + + data: list[MinecraftModLoaderIndex] + + +class ApiResponseOfMinecraftGameVersion(BaseModel): + """https://docs.curseforge.com/#tocS_ApiResponseOfMinecraftGameVersion""" + + data: MinecraftGameVersion + + +class ApiResponseOfMinecraftModLoaderVersion(BaseModel): + """https://docs.curseforge.com/#tocS_ApiResponseOfMinecraftModLoaderVersion""" + + data: MinecraftModLoaderVersion + + +class GetCategoriesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Categories%20Response""" + + data: list[Category] + + +class GetFeaturedModsResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Featured%20Mods%20Response""" + + featured: list[Mod] = Field(alias="featured") + popular: list[Mod] = Field(alias="popular") + recently_updated: list[Mod] = Field(alias="recentlyUpdated") + + +class GetFilesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Files%20Response""" + + data: list[File] + + +class GetGameResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Game%20Response""" + + data: Game + + +class GetGamesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Games%20Response""" + + data: list[Game] + pagination: Pagination + + +class GetModFileResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Mod%20File%20Response""" + + data: File + + +class GetModFilesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Mod%20Files%20Response""" + + data: list[File] + pagination: Pagination + + +class GetModResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Mod%20Response""" + + data: Mod + + +class GetModsResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Mods%20Response""" + + data: list[Mod] + + +class GetVersionTypesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Version%20Types%20Response""" + + data: list[GameVersionType] + + +class GetVersionsResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Versions%20Response%20-%20V1""" + + data: list[GameVersionsByType] + + +class GetVersionsResponse2(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Versions%20Response%20-%20V2""" + + data: list[GameVersionsByType] + + +class SearchModsResponse(BaseModel): + data: list[Mod] + pagination: Pagination + + +class ApiResponseOfListOfMinecraftGameVersion(BaseModel): + """https://docs.curseforge.com/#tocS_ApiResponseOfListOfMinecraftGameVersion""" + + data: list[MinecraftGameVersion] + + +class StringResponse(BaseModel): + """https://docs.curseforge.com/#tocS_String%20Response""" + + data: str + + +class GetFingerprintMatchesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Fingerprint%20Matches%20Response""" + + data: list[FingerprintMatchesResult] + + +class GetFingerprintsFuzzyMatchesResponse(BaseModel): + """https://docs.curseforge.com/#tocS_Get%20Fingerprints%20Fuzzy%20Matches%20Response""" + + data: list[FingerprintFuzzyMatchResult] diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..f056fc9 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,419 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "idna" +version = "3.8" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, +] + +[[package]] +name = "mypy" +version = "1.11.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, +] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.6.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"}, + {file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"}, + {file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"}, + {file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"}, + {file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"}, + {file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"}, + {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20240712" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, + {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "3dd284c7d2913bf4f41e69808598dba635ba79de43670f9189cd73e8fd073bb7" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8545e28 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[tool.poetry] +name = "cursedforged" +version = "0.9.1" +description = "CurseForge API Wrapper" +authors = ["tengueax "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +requests = "^2.32.3" +pydantic = "^2.8.2" + + +[tool.poetry.group.dev.dependencies] +mypy = "^1.11.2" +pycodestyle = "^2.12.1" +ruff = "^0.6.2" +types-requests = "^2.32.0.20240712" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d8e1918 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +annotated-types==0.7.0 +certifi==2024.7.4 +charset-normalizer==3.3.2 +idna==3.8 +mypy==1.11.2 +mypy-extensions==1.0.0 +pycodestyle==2.12.1 +pydantic==2.8.2 +pydantic_core==2.20.1 +requests==2.32.3 +ruff==0.6.2 +types-requests==2.32.0.20240712 +typing_extensions==4.12.2 +urllib3==2.2.2 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29