diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6f4ad1..a068879 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: name: Lint Python API SDK defaults: run: - working-directory: api/python + working-directory: api/python/airthings-sdk steps: - name: Checkout uses: actions/checkout@v6 diff --git a/api/python/openapi.yaml b/api/openapi.yaml similarity index 87% rename from api/python/openapi.yaml rename to api/openapi.yaml index 5064bc0..df520bd 100644 --- a/api/python/openapi.yaml +++ b/api/openapi.yaml @@ -9,13 +9,18 @@ externalDocs: description: Find out more about consumerAPI url: https://consumer-api-doc.airthings.com/ servers: - - url: https://consumer-api.airthings.com/v1 + - url: https://consumer-api.airthings.com description: Airthings for Consumer API (prod) paths: /v1/accounts/{accountId}/sensors: get: summary: Get sensors for a set of devices + description: | + Get sensors for a set of devices. The response will contain the latest sensor values for the devices. + The sensor values are updated depending on the device types sampling rate. + It is recommended to poll the API at a regular interval to get the latest sensor values. + The response will be paginated with a maximum of 50 records per page. operationId: getMultipleSensors parameters: - $ref: '#/components/parameters/deviceSerialNumbers' @@ -78,6 +83,7 @@ paths: /v1/accounts/{accountId}/devices: get: summary: Get all devices connected to a user + description: List all devices (and their sensor abilities) connected to a user’s account. The data returned by this endpoint changes when a device is registered, unregistered or renamed. operationId: getDevices parameters: - $ref: '#/components/parameters/accountId' @@ -101,6 +107,7 @@ paths: /v1/accounts: get: summary: List all accounts the current user is member of + description: Lists all accounts the current user is member of. The data returned by this endpoint changes when a user is added or removed from business accounts. It is safe to assume that the accountId remains constant for Consumer users. The accountId returned by this endpoint is used to fetch the devices and sensors from the other endpoints. operationId: getAccountsIds tags: - Accounts diff --git a/api/python/README.md b/api/python/README.md deleted file mode 100644 index 0205f0a..0000000 --- a/api/python/README.md +++ /dev/null @@ -1,18 +0,0 @@ -[![Airthings][logo]](https://www.airthings.com) - -# Airthings API SDK for Python - -API SDK for Airthings consumer products. - -Prerequisites: - -- [Python](https://www.python.org/downloads/) with version 3.11 that is required by Home Assistant ([docs](https://developers.home-assistant.io/docs/development_environment?_highlight=python&_highlight=versi#manual-environment) or [reference](https://github.com/home-assistant/architecture/blob/master/adr/0002-minimum-supported-python-version.md)) -- [Poetry](https://python-poetry.org/docs/#installation) - -Install dependencies: - -```bash -poetry install -``` - -[logo]: https://upload.wikimedia.org/wikipedia/commons/d/d1/Airthings_logo.svg \ No newline at end of file diff --git a/api/python/airthings-sdk/.gitignore b/api/python/airthings-sdk/.gitignore new file mode 100644 index 0000000..79a2c3d --- /dev/null +++ b/api/python/airthings-sdk/.gitignore @@ -0,0 +1,23 @@ +__pycache__/ +build/ +dist/ +*.egg-info/ +.pytest_cache/ + +# pyenv +.python-version + +# Environments +.env +.venv + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# JetBrains +.idea/ + +/coverage.xml +/.coverage diff --git a/api/python/airthings-sdk/README.md b/api/python/airthings-sdk/README.md new file mode 100644 index 0000000..aabdbb7 --- /dev/null +++ b/api/python/airthings-sdk/README.md @@ -0,0 +1,124 @@ +# airthings-sdk +A client library for accessing Airthings for Consumer API + +## Usage +First, create a client: + +```python +from airthings_api_client import Client + +client = Client(base_url="https://api.example.com") +``` + +If the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead: + +```python +from airthings_api_client import AuthenticatedClient + +client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken") +``` + +Now call your endpoint and use your models: + +```python +from airthings_api_client.models import MyDataModel +from airthings_api_client.api.my_tag import get_my_data_model +from airthings_api_client.types import Response + +with client as client: + my_data: MyDataModel = get_my_data_model.sync(client=client) + # or if you need more info (e.g. status_code) + response: Response[MyDataModel] = get_my_data_model.sync_detailed(client=client) +``` + +Or do the same thing with an async version: + +```python +from airthings_api_client.models import MyDataModel +from airthings_api_client.api.my_tag import get_my_data_model +from airthings_api_client.types import Response + +async with client as client: + my_data: MyDataModel = await get_my_data_model.asyncio(client=client) + response: Response[MyDataModel] = await get_my_data_model.asyncio_detailed(client=client) +``` + +By default, when you're calling an HTTPS API it will attempt to verify that SSL is working correctly. Using certificate verification is highly recommended most of the time, but sometimes you may need to authenticate to a server (especially an internal server) using a custom certificate bundle. + +```python +client = AuthenticatedClient( + base_url="https://internal_api.example.com", + token="SuperSecretToken", + verify_ssl="/path/to/certificate_bundle.pem", +) +``` + +You can also disable certificate validation altogether, but beware that **this is a security risk**. + +```python +client = AuthenticatedClient( + base_url="https://internal_api.example.com", + token="SuperSecretToken", + verify_ssl=False +) +``` + +Things to know: +1. Every path/method combo becomes a Python module with four functions: + 1. `sync`: Blocking request that returns parsed data (if successful) or `None` + 1. `sync_detailed`: Blocking request that always returns a `Request`, optionally with `parsed` set if the request was successful. + 1. `asyncio`: Like `sync` but async instead of blocking + 1. `asyncio_detailed`: Like `sync_detailed` but async instead of blocking + +1. All path/query params, and bodies become method arguments. +1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above) +1. Any endpoint which did not have a tag will be in `airthings_api_client.api.default` + +## Advanced customizations + +There are more settings on the generated `Client` class which let you control more runtime behavior, check out the docstring on that class for more info. You can also customize the underlying `httpx.Client` or `httpx.AsyncClient` (depending on your use-case): + +```python +from airthings_api_client import Client + +def log_request(request): + print(f"Request event hook: {request.method} {request.url} - Waiting for response") + +def log_response(response): + request = response.request + print(f"Response event hook: {request.method} {request.url} - Status {response.status_code}") + +client = Client( + base_url="https://api.example.com", + httpx_args={"event_hooks": {"request": [log_request], "response": [log_response]}}, +) + +# Or get the underlying httpx client to modify directly with client.get_httpx_client() or client.get_async_httpx_client() +``` + +You can even set the httpx client directly, but beware that this will override any existing settings (e.g., base_url): + +```python +import httpx +from airthings_api_client import Client + +client = Client( + base_url="https://api.example.com", +) +# Note that base_url needs to be re-set, as would any shared cookies, headers, etc. +client.set_httpx_client(httpx.Client(base_url="https://api.example.com", proxies="http://localhost:8030")) +``` + +## Building / publishing this package +This project uses [Poetry](https://python-poetry.org/) to manage dependencies and packaging. Here are the basics: +1. Update the metadata in pyproject.toml (e.g. authors, version) +1. If you're using a private repository, configure it with Poetry + 1. `poetry config repositories. ` + 1. `poetry config http-basic. ` +1. Publish the client with `poetry publish --build -r ` or, if for public PyPI, just `poetry publish --build` + +If you want to install this client into another project without publishing it (e.g. for development) then: +1. If that project **is using Poetry**, you can simply do `poetry add ` from that project +1. If that project is not using Poetry: + 1. Build a wheel with `poetry build -f wheel` + 1. Install that wheel from the other project `pip install ` diff --git a/api/python/airthings_api_client/__init__.py b/api/python/airthings-sdk/airthings_api_client/__init__.py similarity index 61% rename from api/python/airthings_api_client/__init__.py rename to api/python/airthings-sdk/airthings_api_client/__init__.py index 9321122..dd495af 100644 --- a/api/python/airthings_api_client/__init__.py +++ b/api/python/airthings-sdk/airthings_api_client/__init__.py @@ -1,4 +1,5 @@ -""" A client library for accessing Airthings for Consumer API """ +"""A client library for accessing Airthings for Consumer API""" + from .client import AuthenticatedClient, Client __all__ = ( diff --git a/api/python/airthings-sdk/airthings_api_client/api/__init__.py b/api/python/airthings-sdk/airthings_api_client/api/__init__.py new file mode 100644 index 0000000..81f9fa2 --- /dev/null +++ b/api/python/airthings-sdk/airthings_api_client/api/__init__.py @@ -0,0 +1 @@ +"""Contains methods for accessing the API""" diff --git a/api/python/airthings-sdk/airthings_api_client/api/accounts/__init__.py b/api/python/airthings-sdk/airthings_api_client/api/accounts/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/api/python/airthings-sdk/airthings_api_client/api/accounts/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/api/python/airthings_api_client/api/accounts/get_accounts_ids.py b/api/python/airthings-sdk/airthings_api_client/api/accounts/get_accounts_ids.py similarity index 67% rename from api/python/airthings_api_client/api/accounts/get_accounts_ids.py rename to api/python/airthings-sdk/airthings_api_client/api/accounts/get_accounts_ids.py index c965a20..f4ff98c 100644 --- a/api/python/airthings_api_client/api/accounts/get_accounts_ids.py +++ b/api/python/airthings-sdk/airthings_api_client/api/accounts/get_accounts_ids.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx @@ -9,8 +9,8 @@ from ...types import Response -def _get_kwargs() -> Dict[str, Any]: - _kwargs: Dict[str, Any] = { +def _get_kwargs() -> dict[str, Any]: + _kwargs: dict[str, Any] = { "method": "get", "url": "/v1/accounts", } @@ -21,7 +21,7 @@ def _get_kwargs() -> Dict[str, Any]: def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[AccountsResponse]: - if response.status_code == HTTPStatus.OK: + if response.status_code == 200: response_200 = AccountsResponse.from_dict(response.json()) return response_200 @@ -48,6 +48,11 @@ def sync_detailed( ) -> Response[AccountsResponse]: """List all accounts the current user is member of + Lists all accounts the current user is member of. The data returned by this endpoint changes when a + user is added or removed from business accounts. It is safe to assume that the accountId remains + constant for Consumer users. The accountId returned by this endpoint is used to fetch the devices + and sensors from the other endpoints. + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -71,6 +76,11 @@ def sync( ) -> Optional[AccountsResponse]: """List all accounts the current user is member of + Lists all accounts the current user is member of. The data returned by this endpoint changes when a + user is added or removed from business accounts. It is safe to assume that the accountId remains + constant for Consumer users. The accountId returned by this endpoint is used to fetch the devices + and sensors from the other endpoints. + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -90,6 +100,11 @@ async def asyncio_detailed( ) -> Response[AccountsResponse]: """List all accounts the current user is member of + Lists all accounts the current user is member of. The data returned by this endpoint changes when a + user is added or removed from business accounts. It is safe to assume that the accountId remains + constant for Consumer users. The accountId returned by this endpoint is used to fetch the devices + and sensors from the other endpoints. + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -111,6 +126,11 @@ async def asyncio( ) -> Optional[AccountsResponse]: """List all accounts the current user is member of + Lists all accounts the current user is member of. The data returned by this endpoint changes when a + user is added or removed from business accounts. It is safe to assume that the accountId remains + constant for Consumer users. The accountId returned by this endpoint is used to fetch the devices + and sensors from the other endpoints. + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. diff --git a/api/python/airthings-sdk/airthings_api_client/api/device/__init__.py b/api/python/airthings-sdk/airthings_api_client/api/device/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/api/python/airthings-sdk/airthings_api_client/api/device/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/api/python/airthings_api_client/api/device/get_devices.py b/api/python/airthings-sdk/airthings_api_client/api/device/get_devices.py similarity index 80% rename from api/python/airthings_api_client/api/device/get_devices.py rename to api/python/airthings-sdk/airthings_api_client/api/device/get_devices.py index 00c4141..5580b0d 100644 --- a/api/python/airthings_api_client/api/device/get_devices.py +++ b/api/python/airthings-sdk/airthings_api_client/api/device/get_devices.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx @@ -11,8 +11,8 @@ def _get_kwargs( account_id: str, -) -> Dict[str, Any]: - _kwargs: Dict[str, Any] = { +) -> dict[str, Any]: + _kwargs: dict[str, Any] = { "method": "get", "url": f"/v1/accounts/{account_id}/devices", } @@ -23,7 +23,7 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[DevicesResponse]: - if response.status_code == HTTPStatus.OK: + if response.status_code == 200: response_200 = DevicesResponse.from_dict(response.json()) return response_200 @@ -51,6 +51,9 @@ def sync_detailed( ) -> Response[DevicesResponse]: """Get all devices connected to a user + List all devices (and their sensor abilities) connected to a user’s account. The data returned by + this endpoint changes when a device is registered, unregistered or renamed. + Args: account_id (str): @@ -80,6 +83,9 @@ def sync( ) -> Optional[DevicesResponse]: """Get all devices connected to a user + List all devices (and their sensor abilities) connected to a user’s account. The data returned by + this endpoint changes when a device is registered, unregistered or renamed. + Args: account_id (str): @@ -104,6 +110,9 @@ async def asyncio_detailed( ) -> Response[DevicesResponse]: """Get all devices connected to a user + List all devices (and their sensor abilities) connected to a user’s account. The data returned by + this endpoint changes when a device is registered, unregistered or renamed. + Args: account_id (str): @@ -131,6 +140,9 @@ async def asyncio( ) -> Optional[DevicesResponse]: """Get all devices connected to a user + List all devices (and their sensor abilities) connected to a user’s account. The data returned by + this endpoint changes when a device is registered, unregistered or renamed. + Args: account_id (str): diff --git a/api/python/airthings-sdk/airthings_api_client/api/health/__init__.py b/api/python/airthings-sdk/airthings_api_client/api/health/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/api/python/airthings-sdk/airthings_api_client/api/health/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/api/python/airthings_api_client/api/health/get_health.py b/api/python/airthings-sdk/airthings_api_client/api/health/get_health.py similarity index 92% rename from api/python/airthings_api_client/api/health/get_health.py rename to api/python/airthings-sdk/airthings_api_client/api/health/get_health.py index 7f7173c..4453538 100644 --- a/api/python/airthings_api_client/api/health/get_health.py +++ b/api/python/airthings-sdk/airthings_api_client/api/health/get_health.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx @@ -8,8 +8,8 @@ from ...types import Response -def _get_kwargs() -> Dict[str, Any]: - _kwargs: Dict[str, Any] = { +def _get_kwargs() -> dict[str, Any]: + _kwargs: dict[str, Any] = { "method": "get", "url": "/v1/health", } @@ -18,7 +18,7 @@ def _get_kwargs() -> Dict[str, Any]: def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: - if response.status_code == HTTPStatus.OK: + if response.status_code == 200: return None if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) diff --git a/api/python/airthings-sdk/airthings_api_client/api/sensor/__init__.py b/api/python/airthings-sdk/airthings_api_client/api/sensor/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/api/python/airthings-sdk/airthings_api_client/api/sensor/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/api/python/airthings_api_client/api/sensor/get_multiple_sensors.py b/api/python/airthings-sdk/airthings_api_client/api/sensor/get_multiple_sensors.py similarity index 74% rename from api/python/airthings_api_client/api/sensor/get_multiple_sensors.py rename to api/python/airthings-sdk/airthings_api_client/api/sensor/get_multiple_sensors.py index 7075bcd..5c1d0b1 100644 --- a/api/python/airthings_api_client/api/sensor/get_multiple_sensors.py +++ b/api/python/airthings-sdk/airthings_api_client/api/sensor/get_multiple_sensors.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union import httpx @@ -14,13 +14,13 @@ def _get_kwargs( account_id: str, *, - sn: Union[Unset, List[str]] = UNSET, + sn: Union[Unset, list[str]] = UNSET, page_number: Union[Unset, int] = 1, unit: Union[Unset, GetMultipleSensorsUnit] = GetMultipleSensorsUnit.METRIC, -) -> Dict[str, Any]: - params: Dict[str, Any] = {} +) -> dict[str, Any]: + params: dict[str, Any] = {} - json_sn: Union[Unset, List[str]] = UNSET + json_sn: Union[Unset, list[str]] = UNSET if not isinstance(sn, Unset): json_sn = sn @@ -36,7 +36,7 @@ def _get_kwargs( params = {k: v for k, v in params.items() if v is not UNSET and v is not None} - _kwargs: Dict[str, Any] = { + _kwargs: dict[str, Any] = { "method": "get", "url": f"/v1/accounts/{account_id}/sensors", "params": params, @@ -48,11 +48,11 @@ def _get_kwargs( def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[Union[Error, GetMultipleSensorsResponse200]]: - if response.status_code == HTTPStatus.OK: + if response.status_code == 200: response_200 = GetMultipleSensorsResponse200.from_dict(response.json()) return response_200 - if response.status_code == HTTPStatus.TOO_MANY_REQUESTS: + if response.status_code == 429: response_429 = Error.from_dict(response.json()) return response_429 @@ -77,15 +77,21 @@ def sync_detailed( account_id: str, *, client: AuthenticatedClient, - sn: Union[Unset, List[str]] = UNSET, + sn: Union[Unset, list[str]] = UNSET, page_number: Union[Unset, int] = 1, unit: Union[Unset, GetMultipleSensorsUnit] = GetMultipleSensorsUnit.METRIC, ) -> Response[Union[Error, GetMultipleSensorsResponse200]]: """Get sensors for a set of devices + Get sensors for a set of devices. The response will contain the latest sensor values for the + devices. + The sensor values are updated depending on the device types sampling rate. + It is recommended to poll the API at a regular interval to get the latest sensor values. + The response will be paginated with a maximum of 50 records per page. + Args: account_id (str): - sn (Union[Unset, List[str]]): + sn (Union[Unset, list[str]]): page_number (Union[Unset, int]): Default: 1. unit (Union[Unset, GetMultipleSensorsUnit]): Default: GetMultipleSensorsUnit.METRIC. @@ -115,15 +121,21 @@ def sync( account_id: str, *, client: AuthenticatedClient, - sn: Union[Unset, List[str]] = UNSET, + sn: Union[Unset, list[str]] = UNSET, page_number: Union[Unset, int] = 1, unit: Union[Unset, GetMultipleSensorsUnit] = GetMultipleSensorsUnit.METRIC, ) -> Optional[Union[Error, GetMultipleSensorsResponse200]]: """Get sensors for a set of devices + Get sensors for a set of devices. The response will contain the latest sensor values for the + devices. + The sensor values are updated depending on the device types sampling rate. + It is recommended to poll the API at a regular interval to get the latest sensor values. + The response will be paginated with a maximum of 50 records per page. + Args: account_id (str): - sn (Union[Unset, List[str]]): + sn (Union[Unset, list[str]]): page_number (Union[Unset, int]): Default: 1. unit (Union[Unset, GetMultipleSensorsUnit]): Default: GetMultipleSensorsUnit.METRIC. @@ -148,15 +160,21 @@ async def asyncio_detailed( account_id: str, *, client: AuthenticatedClient, - sn: Union[Unset, List[str]] = UNSET, + sn: Union[Unset, list[str]] = UNSET, page_number: Union[Unset, int] = 1, unit: Union[Unset, GetMultipleSensorsUnit] = GetMultipleSensorsUnit.METRIC, ) -> Response[Union[Error, GetMultipleSensorsResponse200]]: """Get sensors for a set of devices + Get sensors for a set of devices. The response will contain the latest sensor values for the + devices. + The sensor values are updated depending on the device types sampling rate. + It is recommended to poll the API at a regular interval to get the latest sensor values. + The response will be paginated with a maximum of 50 records per page. + Args: account_id (str): - sn (Union[Unset, List[str]]): + sn (Union[Unset, list[str]]): page_number (Union[Unset, int]): Default: 1. unit (Union[Unset, GetMultipleSensorsUnit]): Default: GetMultipleSensorsUnit.METRIC. @@ -184,15 +202,21 @@ async def asyncio( account_id: str, *, client: AuthenticatedClient, - sn: Union[Unset, List[str]] = UNSET, + sn: Union[Unset, list[str]] = UNSET, page_number: Union[Unset, int] = 1, unit: Union[Unset, GetMultipleSensorsUnit] = GetMultipleSensorsUnit.METRIC, ) -> Optional[Union[Error, GetMultipleSensorsResponse200]]: """Get sensors for a set of devices + Get sensors for a set of devices. The response will contain the latest sensor values for the + devices. + The sensor values are updated depending on the device types sampling rate. + It is recommended to poll the API at a regular interval to get the latest sensor values. + The response will be paginated with a maximum of 50 records per page. + Args: account_id (str): - sn (Union[Unset, List[str]]): + sn (Union[Unset, list[str]]): page_number (Union[Unset, int]): Default: 1. unit (Union[Unset, GetMultipleSensorsUnit]): Default: GetMultipleSensorsUnit.METRIC. diff --git a/api/python/airthings_api_client/client.py b/api/python/airthings-sdk/airthings_api_client/client.py similarity index 89% rename from api/python/airthings_api_client/client.py rename to api/python/airthings-sdk/airthings_api_client/client.py index 74b476c..e80446f 100644 --- a/api/python/airthings_api_client/client.py +++ b/api/python/airthings-sdk/airthings_api_client/client.py @@ -1,5 +1,5 @@ import ssl -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union import httpx from attrs import define, evolve, field @@ -35,17 +35,17 @@ class Client: """ raise_on_unexpected_status: bool = field(default=False, kw_only=True) - _base_url: str - _cookies: Dict[str, str] = field(factory=dict, kw_only=True) - _headers: Dict[str, str] = field(factory=dict, kw_only=True) - _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True) - _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True) - _follow_redirects: bool = field(default=False, kw_only=True) - _httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True) + _base_url: str = field(alias="base_url") + _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") + _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers") + _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout") + _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl") + _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects") + _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") _client: Optional[httpx.Client] = field(default=None, init=False) _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False) - def with_headers(self, headers: Dict[str, str]) -> "Client": + def with_headers(self, headers: dict[str, str]) -> "Client": """Get a new client matching this one with additional headers""" if self._client is not None: self._client.headers.update(headers) @@ -53,7 +53,7 @@ def with_headers(self, headers: Dict[str, str]) -> "Client": self._async_client.headers.update(headers) return evolve(self, headers={**self._headers, **headers}) - def with_cookies(self, cookies: Dict[str, str]) -> "Client": + def with_cookies(self, cookies: dict[str, str]) -> "Client": """Get a new client matching this one with additional cookies""" if self._client is not None: self._client.cookies.update(cookies) @@ -70,7 +70,7 @@ def with_timeout(self, timeout: httpx.Timeout) -> "Client": return evolve(self, timeout=timeout) def set_httpx_client(self, client: httpx.Client) -> "Client": - """Manually the underlying httpx.Client + """Manually set the underlying httpx.Client **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. """ @@ -165,13 +165,13 @@ class AuthenticatedClient: """ raise_on_unexpected_status: bool = field(default=False, kw_only=True) - _base_url: str - _cookies: Dict[str, str] = field(factory=dict, kw_only=True) - _headers: Dict[str, str] = field(factory=dict, kw_only=True) - _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True) - _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True) - _follow_redirects: bool = field(default=False, kw_only=True) - _httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True) + _base_url: str = field(alias="base_url") + _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") + _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers") + _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout") + _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl") + _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects") + _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") _client: Optional[httpx.Client] = field(default=None, init=False) _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False) @@ -179,7 +179,7 @@ class AuthenticatedClient: prefix: str = "Bearer" auth_header_name: str = "Authorization" - def with_headers(self, headers: Dict[str, str]) -> "AuthenticatedClient": + def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient": """Get a new client matching this one with additional headers""" if self._client is not None: self._client.headers.update(headers) @@ -187,7 +187,7 @@ def with_headers(self, headers: Dict[str, str]) -> "AuthenticatedClient": self._async_client.headers.update(headers) return evolve(self, headers={**self._headers, **headers}) - def with_cookies(self, cookies: Dict[str, str]) -> "AuthenticatedClient": + def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient": """Get a new client matching this one with additional cookies""" if self._client is not None: self._client.cookies.update(cookies) @@ -204,7 +204,7 @@ def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient": return evolve(self, timeout=timeout) def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient": - """Manually the underlying httpx.Client + """Manually set the underlying httpx.Client **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. """ diff --git a/api/python/airthings_api_client/errors.py b/api/python/airthings-sdk/airthings_api_client/errors.py similarity index 60% rename from api/python/airthings_api_client/errors.py rename to api/python/airthings-sdk/airthings_api_client/errors.py index 426f8a2..5f92e76 100644 --- a/api/python/airthings_api_client/errors.py +++ b/api/python/airthings-sdk/airthings_api_client/errors.py @@ -1,4 +1,4 @@ -""" Contains shared errors types that can be raised from API functions """ +"""Contains shared errors types that can be raised from API functions""" class UnexpectedStatus(Exception): @@ -8,7 +8,9 @@ def __init__(self, status_code: int, content: bytes): self.status_code = status_code self.content = content - super().__init__(f"Unexpected status code: {status_code}") + super().__init__( + f"Unexpected status code: {status_code}\n\nResponse content:\n{content.decode(errors='ignore')}" + ) __all__ = ["UnexpectedStatus"] diff --git a/api/python/airthings_api_client/models/__init__.py b/api/python/airthings-sdk/airthings_api_client/models/__init__.py similarity index 80% rename from api/python/airthings_api_client/models/__init__.py rename to api/python/airthings-sdk/airthings_api_client/models/__init__.py index 0b8be20..7510e01 100644 --- a/api/python/airthings_api_client/models/__init__.py +++ b/api/python/airthings-sdk/airthings_api_client/models/__init__.py @@ -1,4 +1,4 @@ -""" Contains all the data models used in inputs/outputs """ +"""Contains all the data models used in inputs/outputs""" from .account_response import AccountResponse from .accounts_response import AccountsResponse @@ -7,7 +7,7 @@ from .error import Error from .get_multiple_sensors_response_200 import GetMultipleSensorsResponse200 from .get_multiple_sensors_unit import GetMultipleSensorsUnit -from .sensor_response_type_0 import SensorResponseType0 +from .sensor_response import SensorResponse from .sensors_response import SensorsResponse __all__ = ( @@ -18,6 +18,6 @@ "Error", "GetMultipleSensorsResponse200", "GetMultipleSensorsUnit", - "SensorResponseType0", + "SensorResponse", "SensorsResponse", ) diff --git a/api/python/airthings_api_client/models/account_response.py b/api/python/airthings-sdk/airthings_api_client/models/account_response.py similarity index 76% rename from api/python/airthings_api_client/models/account_response.py rename to api/python/airthings-sdk/airthings_api_client/models/account_response.py index 6a31ca6..5479ba8 100644 --- a/api/python/airthings_api_client/models/account_response.py +++ b/api/python/airthings-sdk/airthings_api_client/models/account_response.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from collections.abc import Mapping +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,12 +17,12 @@ class AccountResponse: """ id: Union[Unset, str] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: id = self.id - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if id is not UNSET: @@ -30,8 +31,8 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) id = d.pop("id", UNSET) account_response = cls( @@ -42,7 +43,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return account_response @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/accounts_response.py b/api/python/airthings-sdk/airthings_api_client/models/accounts_response.py similarity index 75% rename from api/python/airthings_api_client/models/accounts_response.py rename to api/python/airthings-sdk/airthings_api_client/models/accounts_response.py index 2b8cabe..96231b3 100644 --- a/api/python/airthings_api_client/models/accounts_response.py +++ b/api/python/airthings-sdk/airthings_api_client/models/accounts_response.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,21 +17,21 @@ class AccountsResponse: """ Attributes: - accounts (Union[Unset, List['AccountResponse']]): + accounts (Union[Unset, list['AccountResponse']]): """ - accounts: Union[Unset, List["AccountResponse"]] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + accounts: Union[Unset, list["AccountResponse"]] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: - accounts: Union[Unset, List[Dict[str, Any]]] = UNSET + def to_dict(self) -> dict[str, Any]: + accounts: Union[Unset, list[dict[str, Any]]] = UNSET if not isinstance(self.accounts, Unset): accounts = [] for accounts_item_data in self.accounts: accounts_item = accounts_item_data.to_dict() accounts.append(accounts_item) - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if accounts is not UNSET: @@ -39,10 +40,10 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.account_response import AccountResponse - d = src_dict.copy() + d = dict(src_dict) accounts = [] _accounts = d.pop("accounts", UNSET) for accounts_item_data in _accounts or []: @@ -58,7 +59,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return accounts_response @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/device_response.py b/api/python/airthings-sdk/airthings_api_client/models/device_response.py similarity index 74% rename from api/python/airthings_api_client/models/device_response.py rename to api/python/airthings-sdk/airthings_api_client/models/device_response.py index 311adde..2d4b331 100644 --- a/api/python/airthings_api_client/models/device_response.py +++ b/api/python/airthings-sdk/airthings_api_client/models/device_response.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, List, Type, TypeVar, Union, cast +from collections.abc import Mapping +from typing import Any, TypeVar, Union, cast from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -15,18 +16,18 @@ class DeviceResponse: serial_number (Union[Unset, str]): home (Union[None, Unset, str]): name (Union[Unset, str]): - type (Union[Unset, str]): - sensors (Union[Unset, List[str]]): + type_ (Union[Unset, str]): + sensors (Union[Unset, list[str]]): """ serial_number: Union[Unset, str] = UNSET home: Union[None, Unset, str] = UNSET name: Union[Unset, str] = UNSET - type: Union[Unset, str] = UNSET - sensors: Union[Unset, List[str]] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + type_: Union[Unset, str] = UNSET + sensors: Union[Unset, list[str]] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: serial_number = self.serial_number home: Union[None, Unset, str] @@ -37,13 +38,13 @@ def to_dict(self) -> Dict[str, Any]: name = self.name - type = self.type + type_ = self.type_ - sensors: Union[Unset, List[str]] = UNSET + sensors: Union[Unset, list[str]] = UNSET if not isinstance(self.sensors, Unset): sensors = self.sensors - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if serial_number is not UNSET: @@ -52,16 +53,16 @@ def to_dict(self) -> Dict[str, Any]: field_dict["home"] = home if name is not UNSET: field_dict["name"] = name - if type is not UNSET: - field_dict["type"] = type + if type_ is not UNSET: + field_dict["type"] = type_ if sensors is not UNSET: field_dict["sensors"] = sensors return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) serial_number = d.pop("serialNumber", UNSET) def _parse_home(data: object) -> Union[None, Unset, str]: @@ -75,15 +76,15 @@ def _parse_home(data: object) -> Union[None, Unset, str]: name = d.pop("name", UNSET) - type = d.pop("type", UNSET) + type_ = d.pop("type", UNSET) - sensors = cast(List[str], d.pop("sensors", UNSET)) + sensors = cast(list[str], d.pop("sensors", UNSET)) device_response = cls( serial_number=serial_number, home=home, name=name, - type=type, + type_=type_, sensors=sensors, ) @@ -91,7 +92,7 @@ def _parse_home(data: object) -> Union[None, Unset, str]: return device_response @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/devices_response.py b/api/python/airthings-sdk/airthings_api_client/models/devices_response.py similarity index 75% rename from api/python/airthings_api_client/models/devices_response.py rename to api/python/airthings-sdk/airthings_api_client/models/devices_response.py index fd9c967..b2c6c5d 100644 --- a/api/python/airthings_api_client/models/devices_response.py +++ b/api/python/airthings-sdk/airthings_api_client/models/devices_response.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,21 +17,21 @@ class DevicesResponse: """ Attributes: - devices (Union[Unset, List['DeviceResponse']]): + devices (Union[Unset, list['DeviceResponse']]): """ - devices: Union[Unset, List["DeviceResponse"]] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + devices: Union[Unset, list["DeviceResponse"]] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: - devices: Union[Unset, List[Dict[str, Any]]] = UNSET + def to_dict(self) -> dict[str, Any]: + devices: Union[Unset, list[dict[str, Any]]] = UNSET if not isinstance(self.devices, Unset): devices = [] for devices_item_data in self.devices: devices_item = devices_item_data.to_dict() devices.append(devices_item) - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if devices is not UNSET: @@ -39,10 +40,10 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.device_response import DeviceResponse - d = src_dict.copy() + d = dict(src_dict) devices = [] _devices = d.pop("devices", UNSET) for devices_item_data in _devices or []: @@ -58,7 +59,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return devices_response @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/error.py b/api/python/airthings-sdk/airthings_api_client/models/error.py similarity index 77% rename from api/python/airthings_api_client/models/error.py rename to api/python/airthings-sdk/airthings_api_client/models/error.py index e4750eb..6d34b00 100644 --- a/api/python/airthings_api_client/models/error.py +++ b/api/python/airthings-sdk/airthings_api_client/models/error.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from collections.abc import Mapping +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,12 +17,12 @@ class Error: """ message: Union[Unset, str] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: message = self.message - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if message is not UNSET: @@ -30,8 +31,8 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) message = d.pop("message", UNSET) error = cls( @@ -42,7 +43,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return error @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/get_multiple_sensors_response_200.py b/api/python/airthings-sdk/airthings_api_client/models/get_multiple_sensors_response_200.py similarity index 81% rename from api/python/airthings_api_client/models/get_multiple_sensors_response_200.py rename to api/python/airthings-sdk/airthings_api_client/models/get_multiple_sensors_response_200.py index daa9473..ec900a4 100644 --- a/api/python/airthings_api_client/models/get_multiple_sensors_response_200.py +++ b/api/python/airthings-sdk/airthings_api_client/models/get_multiple_sensors_response_200.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -16,18 +17,18 @@ class GetMultipleSensorsResponse200: """ Attributes: - results (Union[Unset, List['SensorsResponse']]): + results (Union[Unset, list['SensorsResponse']]): has_next (Union[Unset, bool]): True if next pages can be fetched, false otherwise. total_pages (Union[Unset, int]): """ - results: Union[Unset, List["SensorsResponse"]] = UNSET + results: Union[Unset, list["SensorsResponse"]] = UNSET has_next: Union[Unset, bool] = UNSET total_pages: Union[Unset, int] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: - results: Union[Unset, List[Dict[str, Any]]] = UNSET + def to_dict(self) -> dict[str, Any]: + results: Union[Unset, list[dict[str, Any]]] = UNSET if not isinstance(self.results, Unset): results = [] for results_item_data in self.results: @@ -38,7 +39,7 @@ def to_dict(self) -> Dict[str, Any]: total_pages = self.total_pages - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if results is not UNSET: @@ -51,10 +52,10 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.sensors_response import SensorsResponse - d = src_dict.copy() + d = dict(src_dict) results = [] _results = d.pop("results", UNSET) for results_item_data in _results or []: @@ -76,7 +77,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: return get_multiple_sensors_response_200 @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/get_multiple_sensors_unit.py b/api/python/airthings-sdk/airthings_api_client/models/get_multiple_sensors_unit.py similarity index 100% rename from api/python/airthings_api_client/models/get_multiple_sensors_unit.py rename to api/python/airthings-sdk/airthings_api_client/models/get_multiple_sensors_unit.py diff --git a/api/python/airthings_api_client/models/sensor_response_type_0.py b/api/python/airthings-sdk/airthings_api_client/models/sensor_response.py similarity index 73% rename from api/python/airthings_api_client/models/sensor_response_type_0.py rename to api/python/airthings-sdk/airthings_api_client/models/sensor_response.py index e4c01a7..1f76c59 100644 --- a/api/python/airthings_api_client/models/sensor_response_type_0.py +++ b/api/python/airthings-sdk/airthings_api_client/models/sensor_response.py @@ -1,15 +1,16 @@ -from typing import Any, Dict, List, Type, TypeVar, Union +from collections.abc import Mapping +from typing import Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field from ..types import UNSET, Unset -T = TypeVar("T", bound="SensorResponseType0") +T = TypeVar("T", bound="SensorResponse") @_attrs_define -class SensorResponseType0: +class SensorResponse: """ Attributes: sensor_type (Union[Unset, str]): @@ -20,16 +21,16 @@ class SensorResponseType0: sensor_type: Union[Unset, str] = UNSET value: Union[Unset, float] = UNSET unit: Union[Unset, str] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: sensor_type = self.sensor_type value = self.value unit = self.unit - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if sensor_type is not UNSET: @@ -42,25 +43,25 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - d = src_dict.copy() + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) sensor_type = d.pop("sensorType", UNSET) value = d.pop("value", UNSET) unit = d.pop("unit", UNSET) - sensor_response_type_0 = cls( + sensor_response = cls( sensor_type=sensor_type, value=value, unit=unit, ) - sensor_response_type_0.additional_properties = d - return sensor_response_type_0 + sensor_response.additional_properties = d + return sensor_response @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/models/sensors_response.py b/api/python/airthings-sdk/airthings_api_client/models/sensors_response.py similarity index 78% rename from api/python/airthings_api_client/models/sensors_response.py rename to api/python/airthings-sdk/airthings_api_client/models/sensors_response.py index fb31f5f..a8b41d0 100644 --- a/api/python/airthings_api_client/models/sensors_response.py +++ b/api/python/airthings-sdk/airthings_api_client/models/sensors_response.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union, cast +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, Union, cast from attrs import define as _attrs_define from attrs import field as _attrs_field @@ -6,7 +7,7 @@ from ..types import UNSET, Unset if TYPE_CHECKING: - from ..models.sensor_response_type_0 import SensorResponseType0 + from ..models.sensor_response import SensorResponse T = TypeVar("T", bound="SensorsResponse") @@ -17,28 +18,28 @@ class SensorsResponse: """ Attributes: serial_number (Union[Unset, str]): - sensors (Union[Unset, List[Union['SensorResponseType0', None]]]): + sensors (Union[Unset, list[Union['SensorResponse', None]]]): recorded (Union[None, Unset, str]): battery_percentage (Union[None, Unset, int]): """ serial_number: Union[Unset, str] = UNSET - sensors: Union[Unset, List[Union["SensorResponseType0", None]]] = UNSET + sensors: Union[Unset, list[Union["SensorResponse", None]]] = UNSET recorded: Union[None, Unset, str] = UNSET battery_percentage: Union[None, Unset, int] = UNSET - additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - def to_dict(self) -> Dict[str, Any]: - from ..models.sensor_response_type_0 import SensorResponseType0 + def to_dict(self) -> dict[str, Any]: + from ..models.sensor_response import SensorResponse serial_number = self.serial_number - sensors: Union[Unset, List[Union[Dict[str, Any], None]]] = UNSET + sensors: Union[Unset, list[Union[None, dict[str, Any]]]] = UNSET if not isinstance(self.sensors, Unset): sensors = [] for sensors_item_data in self.sensors: - sensors_item: Union[Dict[str, Any], None] - if isinstance(sensors_item_data, SensorResponseType0): + sensors_item: Union[None, dict[str, Any]] + if isinstance(sensors_item_data, SensorResponse): sensors_item = sensors_item_data.to_dict() else: sensors_item = sensors_item_data @@ -56,7 +57,7 @@ def to_dict(self) -> Dict[str, Any]: else: battery_percentage = self.battery_percentage - field_dict: Dict[str, Any] = {} + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) if serial_number is not UNSET: @@ -71,28 +72,28 @@ def to_dict(self) -> Dict[str, Any]: return field_dict @classmethod - def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: - from ..models.sensor_response_type_0 import SensorResponseType0 + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.sensor_response import SensorResponse - d = src_dict.copy() + d = dict(src_dict) serial_number = d.pop("serialNumber", UNSET) sensors = [] _sensors = d.pop("sensors", UNSET) for sensors_item_data in _sensors or []: - def _parse_sensors_item(data: object) -> Union["SensorResponseType0", None]: + def _parse_sensors_item(data: object) -> Union["SensorResponse", None]: if data is None: return data try: if not isinstance(data, dict): raise TypeError() - componentsschemas_sensor_response_type_0 = SensorResponseType0.from_dict(data) + componentsschemas_sensor_response_type_0 = SensorResponse.from_dict(data) return componentsschemas_sensor_response_type_0 except: # noqa: E722 pass - return cast(Union["SensorResponseType0", None], data) + return cast(Union["SensorResponse", None], data) sensors_item = _parse_sensors_item(sensors_item_data) @@ -127,7 +128,7 @@ def _parse_battery_percentage(data: object) -> Union[None, Unset, int]: return sensors_response @property - def additional_keys(self) -> List[str]: + def additional_keys(self) -> list[str]: return list(self.additional_properties.keys()) def __getitem__(self, key: str) -> Any: diff --git a/api/python/airthings_api_client/py.typed b/api/python/airthings-sdk/airthings_api_client/py.typed similarity index 100% rename from api/python/airthings_api_client/py.typed rename to api/python/airthings-sdk/airthings_api_client/py.typed diff --git a/api/python/airthings-sdk/airthings_api_client/types.py b/api/python/airthings-sdk/airthings_api_client/types.py new file mode 100644 index 0000000..1b96ca4 --- /dev/null +++ b/api/python/airthings-sdk/airthings_api_client/types.py @@ -0,0 +1,54 @@ +"""Contains some shared types for properties""" + +from collections.abc import Mapping, MutableMapping +from http import HTTPStatus +from typing import IO, BinaryIO, Generic, Literal, Optional, TypeVar, Union + +from attrs import define + + +class Unset: + def __bool__(self) -> Literal[False]: + return False + + +UNSET: Unset = Unset() + +# The types that `httpx.Client(files=)` can accept, copied from that library. +FileContent = Union[IO[bytes], bytes, str] +FileTypes = Union[ + # (filename, file (or bytes), content_type) + tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = list[tuple[str, FileTypes]] + + +@define +class File: + """Contains information for file uploads""" + + payload: BinaryIO + file_name: Optional[str] = None + mime_type: Optional[str] = None + + def to_tuple(self) -> FileTypes: + """Return a tuple representation that httpx will accept for multipart/form-data""" + return self.file_name, self.payload, self.mime_type + + +T = TypeVar("T") + + +@define +class Response(Generic[T]): + """A response from an endpoint""" + + status_code: HTTPStatus + content: bytes + headers: MutableMapping[str, str] + parsed: Optional[T] + + +__all__ = ["UNSET", "File", "FileTypes", "RequestFiles", "Response", "Unset"] diff --git a/api/python/airthings-sdk/airthings_sdk/__init__.py b/api/python/airthings-sdk/airthings_sdk/__init__.py new file mode 100644 index 0000000..e5db768 --- /dev/null +++ b/api/python/airthings-sdk/airthings_sdk/__init__.py @@ -0,0 +1,15 @@ +"""A client library for accessing Airthings for Consumer API""" + +from .errors import ApiError, UnexpectedPayloadError, UnexpectedStatusError +from .mapper import Airthings +from .types import AirthingsDevice, AirthingsDeviceType, AirthingsSensor + +__all__ = ( + "Airthings", + "AirthingsDevice", + "AirthingsDeviceType", + "AirthingsSensor", + "ApiError", + "UnexpectedPayloadError", + "UnexpectedStatusError", +) diff --git a/api/python/airthings_sdk/const.py b/api/python/airthings-sdk/airthings_sdk/const.py similarity index 87% rename from api/python/airthings_sdk/const.py rename to api/python/airthings-sdk/airthings_sdk/const.py index b0ec331..225e044 100644 --- a/api/python/airthings_sdk/const.py +++ b/api/python/airthings-sdk/airthings_sdk/const.py @@ -2,3 +2,5 @@ AUTH_URL = "https://accounts-api.airthings.com" API_URL = "https://consumer-api.airthings.com" + +CACHE_TIMEOUT = 3600 diff --git a/api/python/airthings_sdk/errors.py b/api/python/airthings-sdk/airthings_sdk/errors.py similarity index 100% rename from api/python/airthings_sdk/errors.py rename to api/python/airthings-sdk/airthings_sdk/errors.py diff --git a/api/python/airthings_sdk/mapper.py b/api/python/airthings-sdk/airthings_sdk/mapper.py similarity index 52% rename from api/python/airthings_sdk/mapper.py rename to api/python/airthings-sdk/airthings_sdk/mapper.py index 700a2c6..9543a89 100644 --- a/api/python/airthings_sdk/mapper.py +++ b/api/python/airthings-sdk/airthings_sdk/mapper.py @@ -1,11 +1,12 @@ """Module providing an Airthings API SDK.""" import logging -from typing import List, Optional +import time +from typing import Optional from httpx import AsyncClient -from airthings_api_client import Client, AuthenticatedClient +from airthings_api_client import AuthenticatedClient, Client from airthings_api_client.api.accounts import get_accounts_ids from airthings_api_client.api.device import get_devices from airthings_api_client.api.sensor import get_multiple_sensors @@ -17,10 +18,9 @@ from airthings_api_client.models.device_response import DeviceResponse from airthings_api_client.models.get_multiple_sensors_unit import GetMultipleSensorsUnit from airthings_api_client.models.sensors_response import SensorsResponse -from airthings_api_client.types import Unset -from airthings_sdk.const import AUTH_URL, API_URL -from airthings_sdk.errors import UnexpectedStatusError, UnexpectedPayloadError, ApiError -from airthings_sdk.types import AirthingsToken, AirthingsDevice +from airthings_sdk.const import API_URL, AUTH_URL, CACHE_TIMEOUT +from airthings_sdk.errors import ApiError, UnexpectedPayloadError, UnexpectedStatusError +from airthings_sdk.types import AirthingsDevice, AirthingsToken logger = logging.getLogger(__name__) @@ -34,6 +34,10 @@ class Airthings: _unit: GetMultipleSensorsUnit _access_token: AirthingsToken = AirthingsToken() + _accounts: list[str] = [] + _accounts_fetched_at: Optional[int] = None + _cache_timestamp: Optional[int] = None + _auth_api_client: Client = Client( base_url=AUTH_URL, raise_on_unexpected_status=True, @@ -61,15 +65,17 @@ def __init__( if is_metric else GetMultipleSensorsUnit.IMPERIAL ) + self._accounts_fetched_at = None if web_session: self._auth_api_client.set_async_httpx_client(web_session) self._api_client.set_async_httpx_client(web_session) - def verify_auth(self): + def authenticate(self): """Make sure the access token is valid. If not, fetch a new one.""" if self._access_token.is_valid(): + logger.info("Access token is valid, no need to refresh.") return try: @@ -87,60 +93,98 @@ def verify_auth(self): expires_in = auth_response.json().get("expires_in") self._access_token.set_token( - access_token=access_token, expires_in=expires_in + access_token=access_token, + expires_in=expires_in, ) self._api_client.token = self._access_token.value + logger.info("Authenticated successfully ") except LibUnexpectedStatus as e: raise UnexpectedStatusError(e.status_code, e.content) from e - def update_devices(self) -> dict[str, AirthingsDevice]: + async def _update_devices_cache(self): + """Update the devices cache.""" + self._cache_timestamp = int(time.time()) + devices: list[DeviceResponse] = [] + for account in self._accounts: + devices.extend(await self._fetch_all_devices(account_id=account)) + logger.info("Fetched %s devices from account %s.", len(devices), account) + self.devices = { + device.serial_number: AirthingsDevice.from_response(device) + for device in devices + } + logger.info("Updated devices cache with %s devices.", len(self.devices)) + + async def update_data( + self, invalidate_cache: bool = False + ) -> dict[str, AirthingsDevice]: """Update devices and sensors from Airthings API. Return a dict of devices.""" logger.info("Fetching devices and sensors from Airthings API.") - self.verify_auth() - - try: - account_ids = self._fetch_all_accounts_ids() - - res = {} - for account_id in account_ids: - - devices = self._fetch_all_devices(account_id=account_id) + should_refresh = ( + invalidate_cache + or not self.devices + or self._accounts_fetched_at is None + or self._accounts_fetched_at + CACHE_TIMEOUT < int(time.time()) + ) - device_map = {} - for device in devices: - device_map[device.serial_number] = device + fetched_devices = False - sensors = self._fetch_all_device_sensors( - account_id=account_id, unit=self._unit + try: + if should_refresh: + self._accounts = await self._fetch_all_accounts() + self._accounts_fetched_at = int(time.time()) + logger.info("Fetched %s accounts", len(self._accounts)) + + await self._update_devices_cache() + fetched_devices = True + + sensor_responses = [] + + for account_id in self._accounts: + # Fetch sensors for each account + sensor_responses.extend( + await self._fetch_all_sensors( + account_id=account_id, + unit=self._unit, + ) ) - for sensor in sensors: - serial_number = sensor.serial_number - - if isinstance(serial_number, Unset): - continue - - sensor_device = device_map.get(serial_number) - if sensor_device is None: - continue - mapped = AirthingsDevice.from_response(sensor_device, sensor) - res[serial_number] = mapped - - self.devices = res - logger.info("Fetched %s devices and sensors from Airthings API.", len(res)) - return res + for sensor_response in sensor_responses: + device_serial = sensor_response.serial_number + if not isinstance(device_serial, str): + logger.info("Invalid serial number: %s", device_serial) + continue + if device_serial not in self.devices: + if not fetched_devices: + await self._update_devices_cache() + fetched_devices = True + break + + for device in self.devices.values(): + sensors_response = next( + ( + s + for s in sensor_responses + if s.serial_number == device.serial_number + ), + None, + ) + if sensors_response: + device.update_sensors(sensors_response) + return self.devices except LibUnexpectedStatus as e: logger.error( - "Unexpected status code %s received when fetching devices and sensors.", + "Unexpected status code %s received when fetching devices and sensors", e.status_code, ) raise UnexpectedStatusError(e.status_code, e.content) from e - def _fetch_all_accounts_ids(self) -> List[str]: + async def _fetch_all_accounts(self) -> list[str]: """Fetch accounts for the given client""" - response = get_accounts_ids.sync_detailed(client=self._api_client) + self.authenticate() + + response = await get_accounts_ids.asyncio_detailed(client=self._api_client) payload = response.parsed if payload is None: @@ -152,10 +196,14 @@ def _fetch_all_accounts_ids(self) -> List[str]: if isinstance(account.id, str) ] - def _fetch_all_devices(self, account_id: str) -> List[DeviceResponse]: + async def _fetch_all_devices(self, account_id: str) -> list[DeviceResponse]: """Fetch devices for a given account""" - response = get_devices.sync_detailed( - account_id=account_id, client=self._api_client + + self.authenticate() + + response = await get_devices.asyncio_detailed( + account_id=account_id, + client=self._api_client, ) payload = response.parsed @@ -165,14 +213,17 @@ def _fetch_all_devices(self, account_id: str) -> List[DeviceResponse]: return payload.devices or [] - def _fetch_all_device_sensors( + async def _fetch_all_sensors( self, account_id: str, unit: GetMultipleSensorsUnit, page_number: int = 1, - ) -> List[SensorsResponse]: + ) -> list[SensorsResponse]: """Fetch sensors for a given account""" - response = get_multiple_sensors.sync_detailed( + + self.authenticate() + + response = await get_multiple_sensors.asyncio_detailed( account_id=account_id, client=self._api_client, page_number=page_number, @@ -184,10 +235,7 @@ def _fetch_all_device_sensors( if isinstance(payload, Error): raise ApiError(payload.message or "Unknown error") - if ( - payload is None - or isinstance(payload, GetMultipleSensorsResponse200) is False - ): + if payload is None or not isinstance(payload, GetMultipleSensorsResponse200): raise UnexpectedPayloadError(response.content) sensors = payload.results or [] @@ -195,7 +243,7 @@ def _fetch_all_device_sensors( if payload.has_next is not True: return sensors - return sensors + self._fetch_all_device_sensors( + return sensors + await self._fetch_all_sensors( account_id=account_id, page_number=page_number + 1, unit=unit, diff --git a/api/python/airthings-sdk/airthings_sdk/types.py b/api/python/airthings-sdk/airthings_sdk/types.py new file mode 100644 index 0000000..41741d0 --- /dev/null +++ b/api/python/airthings-sdk/airthings_sdk/types.py @@ -0,0 +1,149 @@ +"""Airthings API SDK types.""" + +import logging +import time +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional, cast + +from airthings_api_client.models import ( + DeviceResponse, + SensorResponse, + SensorsResponse, +) +from airthings_api_client.types import Unset + +_LOGGER = logging.getLogger(__name__) + + +class AirthingsDeviceType(str, Enum): + """Airthings device types.""" + + AP_1 = "AP_1" + CORENTIUM_HOME_2 = "RAVEN_RADON" + HUB = "HUB" + VIEW_PLUS = "VIEW_PLUS" + VIEW_POLLUTION = "VIEW_POLLUTION" + VIEW_RADON = "VIEW_RADON" + WAVE = "WAVE" + WAVE_ENHANCE = "WAVE_ENHANCE" + WAVE_MINI = "WAVE_MINI" + WAVE_PLUS = "WAVE_PLUS" + WAVE_RADON = "WAVE_GEN2" + UNKNOWN = "UNKNOWN" + + @classmethod + def from_raw_value(cls, value: str) -> "AirthingsDeviceType": + """Get device type from raw value.""" + for device_type in cls: + if device_type.value == value: + return device_type + _LOGGER.debug("Unknown device type: %s", value) + return AirthingsDeviceType.UNKNOWN + + @property + def product_name(self) -> str: + """Return a display product name for this device type.""" + return { + AirthingsDeviceType.AP_1: "Renew", + AirthingsDeviceType.CORENTIUM_HOME_2: "Corentium Home 2", + AirthingsDeviceType.HUB: "Hub", + AirthingsDeviceType.VIEW_PLUS: "View Plus", + AirthingsDeviceType.VIEW_POLLUTION: "View Pollution", + AirthingsDeviceType.VIEW_RADON: "View Radon", + AirthingsDeviceType.WAVE: "Wave Gen 1", + AirthingsDeviceType.WAVE_ENHANCE: "Wave Enhance", + AirthingsDeviceType.WAVE_MINI: "Wave Mini", + AirthingsDeviceType.WAVE_PLUS: "Wave Plus", + AirthingsDeviceType.WAVE_RADON: "Wave Radon", + }.get(self, "Unknown") + + +@dataclass +class AirthingsSensor: + """Representation of Airthings device sensor.""" + + sensor_type: str + value: int | float + unit: str + + @classmethod + def from_response(cls, sensor_response: SensorResponse | None | Unset): + """Create an AirthingsSensor from a SensorResponse""" + + if sensor_response is None or isinstance(sensor_response, Unset): + return None + + return cls( + sensor_type=cast(str, sensor_response.sensor_type), + value=cast(float | int, sensor_response.value), + unit=cast(str, sensor_response.unit), + ) + + +@dataclass +class AirthingsDevice: + """Representation of an Airthings device""" + + serial_number: str + type: AirthingsDeviceType + name: str + home: Optional[str] + recorded: Optional[str] + sensors: list[AirthingsSensor] = field(default_factory=list) + + @classmethod + def from_response( + cls, + device_response: DeviceResponse, + ) -> "AirthingsDevice": + """Create an AirthingsDevice from a DeviceResponse and a SensorsResponse""" + + return cls( + serial_number=cast(str, device_response.serial_number), + name=cast(str, device_response.name), + type=AirthingsDeviceType.from_raw_value(cast(str, device_response.type_)), + home=cast(str | None, device_response.home), + recorded=None, + sensors=[], + ) + + def update_sensors( + self, + sensors_response: SensorsResponse, + ) -> None: + """Update the sensors of the device from a SensorsResponse""" + mapped = map(AirthingsSensor.from_response, sensors_response.sensors or []) + filtered = list(filter(lambda sensor: sensor is not None, mapped)) + + if sensors_response.battery_percentage is not None: + filtered.append( + AirthingsSensor( + sensor_type="battery", + value=cast(int, sensors_response.battery_percentage), + unit="%", + ) + ) + + self.sensors = filtered + self.recorded = cast(str | None, sensors_response.recorded) + + +class AirthingsToken: + """Representation of an Airthings API token.""" + + value: Optional[str] = None + _expires: Optional[int] = None + + def set_token(self, access_token: str, expires_in: int): + """Set the token and its expiration time.""" + self.value = access_token + self._expires = expires_in + int(time.time()) + + def is_valid(self) -> bool: + """Check if the token is valid.""" + if self.value is None: + return False + if (expires := self._expires) is not None: + return expires > (int(time.time()) + 20) + return False diff --git a/api/python/airthings-sdk/config.yaml b/api/python/airthings-sdk/config.yaml new file mode 100644 index 0000000..fde0b08 --- /dev/null +++ b/api/python/airthings-sdk/config.yaml @@ -0,0 +1,5 @@ +project_name_override: airthings-sdk +package_name_override: airthings_api_client +class_overrides: + SensorResponseType0: + class_name: SensorResponse diff --git a/api/python/airthings-sdk/examples/fetch_devices_and_sensors.py b/api/python/airthings-sdk/examples/fetch_devices_and_sensors.py new file mode 100644 index 0000000..e22e183 --- /dev/null +++ b/api/python/airthings-sdk/examples/fetch_devices_and_sensors.py @@ -0,0 +1,41 @@ +"""Example of fetching devices and sensors from Airthings API.""" + +import asyncio +import logging +import sys + +from airthings_sdk import Airthings + +logging.basicConfig(level=logging.INFO) + + +async def main(): + """Run the example and print devices with their sensors.""" + if len(sys.argv) <= 2: + logging.error("Please add client id and client secret as parameters.") + logging.info("Usage:") + logging.info("python fetch_devices_and_sensors.py ") + sys.exit(1) + client_id = sys.argv[1] + client_secret = sys.argv[2] + + airthings = Airthings( + client_id=client_id, + client_secret=client_secret, + is_metric=True, + ) + + devices = await airthings.update_data() + for device in devices.values(): + print(f"Device: {device.name} ({device.serial_number}), {device.type.name}") + print(" Sensors:") + if not device.sensors: + print(" No sensors available.") + continue + for sensor in device.sensors: + print(f" {sensor.sensor_type}: {sensor.value} {sensor.unit}") + print() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/api/python/airthings-sdk/poetry.lock b/api/python/airthings-sdk/poetry.lock new file mode 100644 index 0000000..13c7d3f --- /dev/null +++ b/api/python/airthings-sdk/poetry.lock @@ -0,0 +1,718 @@ +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "anyio" +version = "4.9.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "astroid" +version = "4.0.4" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.10.0" +groups = ["dev"] +files = [ + {file = "astroid-4.0.4-py3-none-any.whl", hash = "sha256:52f39653876c7dec3e3afd4c2696920e05c83832b9737afc21928f2d2eb7a753"}, + {file = "astroid-4.0.4.tar.gz", hash = "sha256:986fed8bcf79fb82c78b18a53352a0b287a73817d6dbcfba3162da36667c49a0"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "black" +version = "26.1.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168"}, + {file = "black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d"}, + {file = "black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0"}, + {file = "black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24"}, + {file = "black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89"}, + {file = "black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5"}, + {file = "black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68"}, + {file = "black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14"}, + {file = "black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c"}, + {file = "black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4"}, + {file = "black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f"}, + {file = "black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6"}, + {file = "black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a"}, + {file = "black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791"}, + {file = "black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954"}, + {file = "black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304"}, + {file = "black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9"}, + {file = "black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b"}, + {file = "black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b"}, + {file = "black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca"}, + {file = "black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115"}, + {file = "black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79"}, + {file = "black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af"}, + {file = "black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f"}, + {file = "black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0"}, + {file = "black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede"}, + {file = "black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=1.0.0" +platformdirs = ">=2" +pytokens = ">=0.3.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2025.7.14" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2"}, + {file = "certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995"}, +] + +[[package]] +name = "click" +version = "8.3.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dill" +version = "0.4.1" +description = "serialize all of Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, + {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "isort" +version = "6.1.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784"}, + {file = "isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481"}, +] + +[package.extras] +colors = ["colorama"] +plugins = ["setuptools"] + +[[package]] +name = "librt" +version = "0.8.0" +description = "Mypyc runtime library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db63cf3586a24241e89ca1ce0b56baaec9d371a328bd186c529b27c914c9a1ef"}, + {file = "librt-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba9d9e60651615bc614be5e21a82cdb7b1769a029369cf4b4d861e4f19686fb6"}, + {file = "librt-0.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb4b3ad543084ed79f186741470b251b9d269cd8b03556f15a8d1a99a64b7de5"}, + {file = "librt-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d2720335020219197380ccfa5c895f079ac364b4c429e96952cd6509934d8eb"}, + {file = "librt-0.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726305d3e53419d27fc8cdfcd3f9571f0ceae22fa6b5ea1b3662c2e538f833e"}, + {file = "librt-0.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3d107f603b5ee7a79b6aa6f166551b99b32fb4a5303c4dfcb4222fc6a0335e"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41064a0c07b4cc7a81355ccc305cb097d6027002209ffca51306e65ee8293630"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c6e4c10761ddbc0d67d2f6e2753daf99908db85d8b901729bf2bf5eaa60e0567"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ba581acad5ac8f33e2ff1746e8a57e001b47c6721873121bf8bbcf7ba8bd3aa4"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bdab762e2c0b48bab76f1a08acb3f4c77afd2123bedac59446aeaaeed3d086cf"}, + {file = "librt-0.8.0-cp310-cp310-win32.whl", hash = "sha256:6a3146c63220d814c4a2c7d6a1eacc8d5c14aed0ff85115c1dfea868080cd18f"}, + {file = "librt-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:bbebd2bba5c6ae02907df49150e55870fdd7440d727b6192c46b6f754723dde9"}, + {file = "librt-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ce33a9778e294507f3a0e3468eccb6a698b5166df7db85661543eca1cfc5369"}, + {file = "librt-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8070aa3368559de81061ef752770d03ca1f5fc9467d4d512d405bd0483bfffe6"}, + {file = "librt-0.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:20f73d4fecba969efc15cdefd030e382502d56bb6f1fc66b580cce582836c9fa"}, + {file = "librt-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a512c88900bdb1d448882f5623a0b1ad27ba81a9bd75dacfe17080b72272ca1f"}, + {file = "librt-0.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:015e2dde6e096d27c10238bf9f6492ba6c65822dfb69d2bf74c41a8e88b7ddef"}, + {file = "librt-0.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c25a131013eadd3c600686a0c0333eb2896483cbc7f65baa6a7ee761017aef9"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21b14464bee0b604d80a638cf1ee3148d84ca4cc163dcdcecb46060c1b3605e4"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:05a3dd3f116747f7e1a2b475ccdc6fb637fd4987126d109e03013a79d40bf9e6"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fa37f99bff354ff191c6bcdffbc9d7cdd4fc37faccfc9be0ef3a4fd5613977da"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1566dbb9d1eb0987264c9b9460d212e809ba908d2f4a3999383a84d765f2f3f1"}, + {file = "librt-0.8.0-cp311-cp311-win32.whl", hash = "sha256:70defb797c4d5402166787a6b3c66dfb3fa7f93d118c0509ffafa35a392f4258"}, + {file = "librt-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:db953b675079884ffda33d1dca7189fb961b6d372153750beb81880384300817"}, + {file = "librt-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:75d1a8cab20b2043f03f7aab730551e9e440adc034d776f15f6f8d582b0a5ad4"}, + {file = "librt-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17269dd2745dbe8e42475acb28e419ad92dfa38214224b1b01020b8cac70b645"}, + {file = "librt-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4617cef654fca552f00ce5ffdf4f4b68770f18950e4246ce94629b789b92467"}, + {file = "librt-0.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5cb11061a736a9db45e3c1293cfcb1e3caf205912dfa085734ba750f2197ff9a"}, + {file = "librt-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb00bd71b448f16749909b08a0ff16f58b079e2261c2e1000f2bbb2a4f0a45"}, + {file = "librt-0.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95a719a049f0eefaf1952673223cf00d442952273cbd20cf2ed7ec423a0ef58d"}, + {file = "librt-0.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bd32add59b58fba3439d48d6f36ac695830388e3da3e92e4fc26d2d02670d19c"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f764b2424cb04524ff7a486b9c391e93f93dc1bd8305b2136d25e582e99aa2f"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f04ca50e847abc486fa8f4107250566441e693779a5374ba211e96e238f298b9"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9ab3a3475a55b89b87ffd7e6665838e8458e0b596c22e0177e0f961434ec474a"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e36a8da17134ffc29373775d88c04832f9ecfab1880470661813e6c7991ef79"}, + {file = "librt-0.8.0-cp312-cp312-win32.whl", hash = "sha256:4eb5e06ebcc668677ed6389164f52f13f71737fc8be471101fa8b4ce77baeb0c"}, + {file = "librt-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a33335eb59921e77c9acc05d0e654e4e32e45b014a4d61517897c11591094f8"}, + {file = "librt-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:24a01c13a2a9bdad20997a4443ebe6e329df063d1978bbe2ebbf637878a46d1e"}, + {file = "librt-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1"}, + {file = "librt-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf"}, + {file = "librt-0.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8"}, + {file = "librt-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad"}, + {file = "librt-0.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01"}, + {file = "librt-0.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b"}, + {file = "librt-0.8.0-cp313-cp313-win32.whl", hash = "sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935"}, + {file = "librt-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab"}, + {file = "librt-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2"}, + {file = "librt-0.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda"}, + {file = "librt-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556"}, + {file = "librt-0.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06"}, + {file = "librt-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376"}, + {file = "librt-0.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816"}, + {file = "librt-0.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3"}, + {file = "librt-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a"}, + {file = "librt-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7"}, + {file = "librt-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4"}, + {file = "librt-0.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb"}, + {file = "librt-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c"}, + {file = "librt-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447"}, + {file = "librt-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9"}, + {file = "librt-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc"}, + {file = "librt-0.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b705f85311ee76acec5ee70806990a51f0deb519ea0c29c1d1652d79127604d"}, + {file = "librt-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7ce0a8cb67e702dcb06342b2aaaa3da9fb0ddc670417879adfa088b44cf7b3b6"}, + {file = "librt-0.8.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:aaadec87f45a3612b6818d1db5fbfe93630669b7ee5d6bdb6427ae08a1aa2141"}, + {file = "librt-0.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56901f1eec031396f230db71c59a01d450715cbbef9856bf636726994331195d"}, + {file = "librt-0.8.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b055bb3abaf69abed25743d8fc1ab691e4f51a912ee0a6f9a6c84f4bbddb283d"}, + {file = "librt-0.8.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ef3bd856373cf8e7382402731f43bfe978a8613b4039e49e166e1e0dc590216"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e0ffe88ebb5962f8fb0ddcbaaff30f1ea06a79501069310e1e030eafb1ad787"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82e61cd1c563745ad495387c3b65806bfd453badb4adbc019df3389dddee1bf6"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:667e2513cf69bfd1e1ed9a00d6c736d5108714ec071192afb737987955888a25"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b6caff69e25d80c269b1952be8493b4d94ef745f438fa619d7931066bdd26de"}, + {file = "librt-0.8.0-cp39-cp39-win32.whl", hash = "sha256:02a9fe85410cc9bef045e7cb7fd26fdde6669e6d173f99df659aa7f6335961e9"}, + {file = "librt-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:de076eaba208d16efb5962f99539867f8e2c73480988cb513fcf1b5dbb0c9dcf"}, + {file = "librt-0.8.0.tar.gz", hash = "sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy" +version = "1.19.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, +] + +[package.dependencies] +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, +] + +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + +[[package]] +name = "platformdirs" +version = "4.9.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"}, + {file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"}, +] + +[[package]] +name = "pylint" +version = "4.0.4" +description = "python code static checker" +optional = false +python-versions = ">=3.10.0" +groups = ["dev"] +files = [ + {file = "pylint-4.0.4-py3-none-any.whl", hash = "sha256:63e06a37d5922555ee2c20963eb42559918c20bd2b21244e4ef426e7c43b92e0"}, + {file = "pylint-4.0.4.tar.gz", hash = "sha256:d9b71674e19b1c36d79265b5887bf8e55278cbe236c9e95d22dc82cf044fdbd2"}, +] + +[package.dependencies] +astroid = ">=4.0.2,<=4.1.dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version == \"3.11\""}, +] +isort = ">=5,<5.13 || >5.13,<8" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2" +tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytokens" +version = "0.4.1" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, + {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, + {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, + {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, + {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, + {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, + {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, + {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, + {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, + {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, + {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, + {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, + {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, + {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, + {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, + {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, + {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, + {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, + {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, + {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, + {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, + {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, + {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, + {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, + {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, + {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "tomli" +version = "2.4.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680"}, + {file = "tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064"}, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] +markers = {main = "python_version <= \"3.12\""} + +[metadata] +lock-version = "2.1" +python-versions = "^3.10" +content-hash = "8f239e82dc1aafe24ff3691763f1c76b0f04effbd150f9eb3c3566cdb7dc3f1c" diff --git a/api/python/pyproject.toml b/api/python/airthings-sdk/pyproject.toml similarity index 50% rename from api/python/pyproject.toml rename to api/python/airthings-sdk/pyproject.toml index d5b6e23..b14c4b6 100644 --- a/api/python/pyproject.toml +++ b/api/python/airthings-sdk/pyproject.toml @@ -1,31 +1,32 @@ [tool.poetry] name = "airthings-sdk" version = "1.0.0" -description = "A client library for accessing Airthings for Consumer" +description = "A client library for accessing Airthings for Consumer API" authors = [] readme = "README.md" packages = [ - {include = "airthings_api_client"}, - {include = "airthings_sdk"}, + { include = "airthings_api_client" }, + { include = "airthings_sdk" }, ] -include = ["CHANGELOG.md", "api_client/py.typed"] - +include = ["CHANGELOG.md", "airthings_api_client/py.typed"] [tool.poetry.dependencies] -python = "^3.8" -httpx = ">=0.20.0,<0.27.0" -attrs = ">=21.3.0" -python-dateutil = "^2.8.0" +python = "^3.10" +httpx = ">=0.28.1,<0.29.0" +attrs = ">=25,<26" +python-dateutil = "^2.9.0" [tool.poetry.group.dev.dependencies] -pylint = "^3.1.0" -black = "^24.2.0" -mypy = "^1.8.0" +black = "^26.0.0" +mypy = "^1.19.1" +pylint = "^4.0.0" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" [tool.ruff] -select = ["F", "I", "UP"] line-length = 120 + +[tool.ruff.lint] +select = ["F", "I", "UP"] diff --git a/api/python/airthings_api_client/api/__init__.py b/api/python/airthings_api_client/api/__init__.py deleted file mode 100644 index dc035f4..0000000 --- a/api/python/airthings_api_client/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""" Contains methods for accessing the API """ diff --git a/api/python/airthings_api_client/api/accounts/__init__.py b/api/python/airthings_api_client/api/accounts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/python/airthings_api_client/api/device/__init__.py b/api/python/airthings_api_client/api/device/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/python/airthings_api_client/api/health/__init__.py b/api/python/airthings_api_client/api/health/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/python/airthings_api_client/api/sensor/__init__.py b/api/python/airthings_api_client/api/sensor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/python/airthings_api_client/types.py b/api/python/airthings_api_client/types.py deleted file mode 100644 index dbdcc5d..0000000 --- a/api/python/airthings_api_client/types.py +++ /dev/null @@ -1,44 +0,0 @@ -""" Contains some shared types for properties """ -from http import HTTPStatus -from typing import BinaryIO, Generic, Literal, MutableMapping, Optional, Tuple, TypeVar - -from attrs import define - - -class Unset: - def __bool__(self) -> Literal[False]: - return False - - -UNSET: Unset = Unset() - -FileJsonType = Tuple[Optional[str], BinaryIO, Optional[str]] - - -@define -class File: - """Contains information for file uploads""" - - payload: BinaryIO - file_name: Optional[str] = None - mime_type: Optional[str] = None - - def to_tuple(self) -> FileJsonType: - """Return a tuple representation that httpx will accept for multipart/form-data""" - return self.file_name, self.payload, self.mime_type - - -T = TypeVar("T") - - -@define -class Response(Generic[T]): - """A response from an endpoint""" - - status_code: HTTPStatus - content: bytes - headers: MutableMapping[str, str] - parsed: Optional[T] - - -__all__ = ["File", "Response", "FileJsonType", "Unset", "UNSET"] diff --git a/api/python/airthings_sdk/__init__.py b/api/python/airthings_sdk/__init__.py deleted file mode 100644 index 6dd6790..0000000 --- a/api/python/airthings_sdk/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" A client library for accessing Airthings for Consumer API """ - -from .mapper import Airthings -from .types import AirthingsDevice, AirthingsSensor -from .errors import UnexpectedStatusError, ApiError, UnexpectedPayloadError - -__all__ = ( - "Airthings", - "AirthingsDevice", - "AirthingsSensor", - "UnexpectedStatusError", - "ApiError", - "UnexpectedPayloadError", -) diff --git a/api/python/airthings_sdk/types.py b/api/python/airthings_sdk/types.py deleted file mode 100644 index 7a48b8a..0000000 --- a/api/python/airthings_sdk/types.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Airthings API SDK types.""" - -import time -from dataclasses import dataclass, field -from typing import Optional, cast - -from airthings_api_client.models import ( - SensorResponseType0, - DeviceResponse, - SensorsResponse, -) -from airthings_api_client.types import Unset - - -@dataclass -class AirthingsSensor: - """Representation of Airthings device sensor.""" - - sensor_type: str - value: int | float - unit: str - - @classmethod - def from_response(cls, sensor_response: SensorResponseType0 | None | Unset): - """Create an AirthingsSensor from a SensorResponseType0""" - - if sensor_response is None or isinstance(sensor_response, Unset): - return None - - return cls( - sensor_type=cast(str, sensor_response.sensor_type), - value=cast(float | int, sensor_response.value), - unit=cast(str, sensor_response.unit), - ) - - -@dataclass -class AirthingsDevice: - """Representation of an Airthings device""" - - serial_number: str - type: str - name: str - home: Optional[str] - recorded: Optional[str] - sensors: list[AirthingsSensor] = field(default_factory=list) - - @classmethod - def from_response( - cls, device_response: DeviceResponse, sensors_response: SensorsResponse - ) -> "AirthingsDevice": - """Create an AirthingsDevice from a DeviceResponse and a SensorsResponse""" - - mapped = map(AirthingsSensor.from_response, sensors_response.sensors or []) - filtered = list(filter(lambda sensor: sensor is not None, mapped)) - - if sensors_response.battery_percentage is not None: - filtered.append( - AirthingsSensor( - sensor_type="battery", - value=cast(int, sensors_response.battery_percentage), - unit="%", - ) - ) - return cls( - serial_number=cast(str, device_response.serial_number), - name=cast(str, device_response.name), - type=cast(str, device_response.type), - home=cast(str | None, device_response.home), - recorded=cast(str | None, sensors_response.recorded), - sensors=filtered, - ) - - -class AirthingsToken: - """Representation of an Airthings API token.""" - - value: Optional[str] = None - _expires: Optional[int] = None - - def set_token(self, access_token: str, expires_in: int): - """Set the token and its expiration time.""" - self.value = access_token - self._expires = expires_in + int(time.time()) - - def is_valid(self) -> bool: - """Check if the token is valid.""" - return ( - self.value is not None - and self._expires is not None - and self._expires > (int(time.time()) + 20) - ) diff --git a/api/python/examples/fetch_devices_and_sensors.py b/api/python/examples/fetch_devices_and_sensors.py deleted file mode 100644 index 60c705e..0000000 --- a/api/python/examples/fetch_devices_and_sensors.py +++ /dev/null @@ -1,21 +0,0 @@ -""" Example of fetching devices and sensors from Airthings API. """ - -import sys - -from airthings_sdk import Airthings - -if __name__ == "__main__": - if len(sys.argv) <= 2: - print("Please add client id and client secret as parameters.") - print("Usage:") - print("python fetch_devices_and_sensors.py ") - sys.exit(1) - client_id = sys.argv[1] - client_secret = sys.argv[2] - - airthings = Airthings( - client_id=client_id, client_secret=client_secret, is_metric=True - ) - - devices = airthings.update_devices() - print(airthings.devices) diff --git a/api/python/poetry.lock b/api/python/poetry.lock deleted file mode 100644 index cd8c8e7..0000000 --- a/api/python/poetry.lock +++ /dev/null @@ -1,470 +0,0 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. - -[[package]] -name = "anyio" -version = "4.2.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "astroid" -version = "3.2.4" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, - {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2023.11.17" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.2" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] - -[[package]] -name = "httpx" -version = "0.26.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy" -version = "1.14.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, - {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, - {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, - {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, - {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, - {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, - {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, - {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, - {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, - {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, - {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, - {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, - {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, - {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, - {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, - {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -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 = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] - -[[package]] -name = "pylint" -version = "3.2.7" -description = "python code static checker" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, - {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, -] - -[package.dependencies] -astroid = ">=3.2.4,<=3.3.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, -] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tomlkit" -version = "0.12.4" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, - {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, -] - -[[package]] -name = "typing-extensions" -version = "4.9.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "^3.8" -content-hash = "fb93b80365f23338f28d1ee6ad412264de01fe1eb14f4ee4fe316d4bc4b196d5"