diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcb5356b6..f908f3c3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black - rev: 25.1.0 + rev: 24.10.0 hooks: - id: black language_version: python3 @@ -29,19 +29,22 @@ repos: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/pycqa/flake8 - rev: 7.2.0 + rev: 7.1.2 hooks: - id: flake8 additional_dependencies: [Flake8-pyproject] - repo: https://github.com/python-poetry/poetry rev: 2.1.3 hooks: - - id: poetry-export - files: pyproject.toml - id: poetry-lock files: pyproject.toml - id: poetry-check files: pyproject.toml + - repo: https://github.com/python-poetry/poetry-plugin-export + rev: 1.9.0 + hooks: + - id: poetry-export + files: pyproject.toml - repo: https://github.com/pre-commit/pre-commit rev: v4.2.0 hooks: @@ -62,11 +65,3 @@ repos: types: [python] entry: "print" language: pygrep - - id: liccheck - name: Run Python License Checker - description: Check license compliance of python requirements - entry: poetry - args: [run, liccheck, --level, paranoid] - language: system - files: ^(.*requirements.*\.txt|setup\.cfg|setup\.py|pyproject\.toml|liccheck\.ini)$ - pass_filenames: false diff --git a/README.md b/README.md index 6868325eb..18ec81c34 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,33 @@ The Descope SDK for python provides convenient access to the Descope user management and authentication API for a backend written in python. You can read more on the [Descope Website](https://descope.com). +## โœจ New: Async Support + +The SDK now supports async/await patterns alongside the existing synchronous API. Both APIs provide identical functionality with the same method signatures and error handling. + +```python +# Synchronous usage (existing) +from descope import DescopeClient, DeliveryMethod + +client = DescopeClient(project_id="P123") +masked_email = client.otp.sign_up(DeliveryMethod.EMAIL, "user@example.com") + +# Asynchronous usage (new) - preferred with context manager +from descope import AsyncDescopeClient, DeliveryMethod + +async def main(): + async with AsyncDescopeClient(project_id="P123") as client: + masked_email = await client.otp.sign_up_async(DeliveryMethod.EMAIL, "user@example.com") + +# Or manual resource management +async def main_manual(): + client = AsyncDescopeClient(project_id="P123") + try: + masked_email = await client.otp.sign_up_async(DeliveryMethod.EMAIL, "user@example.com") + finally: + await client.close() +``` + ## Requirements The SDK supports Python 3.8.1 and above. @@ -15,7 +42,7 @@ Install the package with: pip install descope ``` -#### If you would like to use the Flask decorators, make sure to install the Flask extras: +### If you would like to use the Flask decorators, make sure to install the Flask extras: ```bash pip install descope[Flask] @@ -86,8 +113,12 @@ Send a user a one-time password (OTP) using your preferred delivery method (_ema The user can either `sign up`, `sign in` or `sign up or in` +#### Synchronous usage + ```python -from descope import DeliveryMethod +from descope import DescopeClient, DeliveryMethod + +descope_client = DescopeClient(project_id="") # Every user must have a login ID. All other user information is optional email = "desmond@descope.com" @@ -105,6 +136,26 @@ session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt") refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt") ``` +#### Asynchronous usage + +```python +from descope import AsyncDescopeClient, DeliveryMethod + +async def otp_example(): + async with AsyncDescopeClient(project_id="") as client: + # Every user must have a login ID. All other user information is optional + email = "desmond@descope.com" + user = {"name": "Desmond Copeland", "phone": "212-555-1234", "email": email} + masked_address = await client.otp.sign_up_async(method=DeliveryMethod.EMAIL, login_id=email, user=user) + + # Verify the code + jwt_response = await client.otp.verify_code_async( + method=DeliveryMethod.EMAIL, login_id=email, code=value + ) + session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt") + refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt") +``` + The session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation) ### Magic Link @@ -115,8 +166,12 @@ This redirection can be configured in code, or generally in the [Descope Console The user can either `sign up`, `sign in` or `sign up or in` +#### Synchronous usage + ```python -from descope import DeliveryMethod +from descope import DescopeClient, DeliveryMethod + +descope_client = DescopeClient(project_id="") masked_address = descope_client.magiclink.sign_up_or_in( method=DeliveryMethod.EMAIL, @@ -133,6 +188,25 @@ session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt") refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt") ``` +#### Asynchronous usage + +```python +from descope import AsyncDescopeClient, DeliveryMethod + +async def magiclink_example(): + async with AsyncDescopeClient(project_id="") as client: + masked_address = await client.magiclink.sign_up_or_in_async( + method=DeliveryMethod.EMAIL, + login_id="desmond@descope.com", + uri="http://myapp.com/verify-magic-link", # Set redirect URI here or via console + ) + + # Verify the magic link token + jwt_response = await client.magiclink.verify_async(token=token) + session_token = jwt_response[SESSION_TOKEN_NAME].get("jwt") + refresh_token = jwt_response[REFRESH_SESSION_TOKEN_NAME].get("jwt") +``` + The session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation) ### Enchanted Link @@ -1178,7 +1252,7 @@ type doc permission can_create: owner | parent.owner permission can_edit: editor | can_create permission can_view: viewer | can_edit - ``` +``` Descope SDK allows you to fully manage the schema and relations as well as perform simple (and not so simple) checks regarding the existence of relations. diff --git a/descope/__init__.py b/descope/__init__.py index 7e71f2f6e..651d52eb2 100644 --- a/descope/__init__.py +++ b/descope/__init__.py @@ -10,6 +10,7 @@ SignUpOptions, ) from descope.descope_client import DescopeClient +from descope.descope_client_async import AsyncDescopeClient from descope.exceptions import ( API_RATE_LIMIT_RETRY_AFTER_HEADER, ERROR_TYPE_API_RATE_LIMIT, @@ -39,3 +40,35 @@ UserPasswordFirebase, UserPasswordPbkdf2, ) + +__all__ = [ + "COOKIE_DATA_NAME", + "REFRESH_SESSION_COOKIE_NAME", + "REFRESH_SESSION_TOKEN_NAME", + "SESSION_COOKIE_NAME", + "SESSION_TOKEN_NAME", + "AccessKeyLoginOptions", + "DeliveryMethod", + "LoginOptions", + "SignUpOptions", + "DescopeClient", + "AsyncDescopeClient", + "AuthException", + "RateLimitException", + "AssociatedTenant", + "SAMLIDPAttributeMappingInfo", + "SAMLIDPGroupsMappingInfo", + "SAMLIDPRoleGroupMappingInfo", + "AttributeMapping", + "OIDCAttributeMapping", + "RoleMapping", + "SSOOIDCSettings", + "SSOSAMLSettings", + "SSOSAMLSettingsByMetadata", + "UserObj", + "UserPassword", + "UserPasswordBcrypt", + "UserPasswordDjango", + "UserPasswordFirebase", + "UserPasswordPbkdf2", +] diff --git a/descope/auth.py b/descope/auth.py index 4fd2c9479..48cbdc902 100644 --- a/descope/auth.py +++ b/descope/auth.py @@ -14,7 +14,7 @@ try: from importlib.metadata import version except ImportError: - from pkg_resources import get_distribution + from pkg_resources import get_distribution # type: ignore import requests from email_validator import EmailNotValidError, validate_email @@ -120,7 +120,7 @@ def _raise_rate_limit_exception(self, response): ) except RateLimitException: raise - except Exception as e: + except Exception: raise RateLimitException( status_code=HTTPStatus.TOO_MANY_REQUESTS, error_type=ERROR_TYPE_API_RATE_LIMIT, diff --git a/descope/authmethod/enchantedlink.py b/descope/authmethod/enchantedlink.py index b461aa058..9b78db860 100644 --- a/descope/authmethod/enchantedlink.py +++ b/descope/authmethod/enchantedlink.py @@ -118,8 +118,13 @@ def update_user_email( Auth.validate_email(email) body = EnchantedLink._compose_update_user_email_body( - login_id, email, add_to_login_ids, on_merge_use_existing, - template_options, template_id, provider_id + login_id, + email, + add_to_login_ids, + on_merge_use_existing, + template_options, + template_id, + provider_id, ) uri = EndpointsV1.update_user_email_enchantedlink_path response = self._auth.do_post(uri, body, None, refresh_token) diff --git a/descope/authmethod/magiclink.py b/descope/authmethod/magiclink.py index 432c3356c..4182e5484 100644 --- a/descope/authmethod/magiclink.py +++ b/descope/authmethod/magiclink.py @@ -117,8 +117,13 @@ def update_user_email( Auth.validate_email(email) body = MagicLink._compose_update_user_email_body( - login_id, email, add_to_login_ids, on_merge_use_existing, - template_options, template_id, provider_id + login_id, + email, + add_to_login_ids, + on_merge_use_existing, + template_options, + template_id, + provider_id, ) uri = EndpointsV1.update_user_email_magiclink_path response = self._auth.do_post(uri, body, None, refresh_token) @@ -144,8 +149,13 @@ def update_user_phone( Auth.validate_phone(method, phone) body = MagicLink._compose_update_user_phone_body( - login_id, phone, add_to_login_ids, on_merge_use_existing, - template_options, template_id, provider_id + login_id, + phone, + add_to_login_ids, + on_merge_use_existing, + template_options, + template_id, + provider_id, ) uri = EndpointsV1.update_user_phone_magiclink_path response = self._auth.do_post(uri, body, None, refresh_token) diff --git a/descope/authmethod/otp.py b/descope/authmethod/otp.py index 5a9a38cf0..86cc3c10d 100644 --- a/descope/authmethod/otp.py +++ b/descope/authmethod/otp.py @@ -198,8 +198,13 @@ def update_user_email( uri = EndpointsV1.update_user_email_otp_path body = OTP._compose_update_user_email_body( - login_id, email, add_to_login_ids, on_merge_use_existing, - template_options, template_id, provider_id + login_id, + email, + add_to_login_ids, + on_merge_use_existing, + template_options, + template_id, + provider_id, ) response = self._auth.do_post(uri, body, None, refresh_token) return Auth.extract_masked_address(response.json(), DeliveryMethod.EMAIL) @@ -241,8 +246,13 @@ def update_user_phone( uri = OTP._compose_update_phone_url(method) body = OTP._compose_update_user_phone_body( - login_id, phone, add_to_login_ids, on_merge_use_existing, - template_options, template_id, provider_id + login_id, + phone, + add_to_login_ids, + on_merge_use_existing, + template_options, + template_id, + provider_id, ) response = self._auth.do_post(uri, body, None, refresh_token) return Auth.extract_masked_address(response.json(), method) diff --git a/descope/descope_client_async.py b/descope/descope_client_async.py new file mode 100644 index 000000000..84b5b960e --- /dev/null +++ b/descope/descope_client_async.py @@ -0,0 +1,202 @@ +""" +Async support for Descope SDK. + +Provides a native AsyncDescopeClient class that mirrors DescopeClient structure +but with async methods throughout. +""" + +from __future__ import annotations + +from typing import Any, Callable, TypeVar + +from asyncer import asyncify as _asyncify + +from descope.common import DEFAULT_TIMEOUT_SECONDS + +T = TypeVar("T") + + +def _add_async_methods(cls: type[T], method_suffix: str = "_async") -> type[T]: + """ + Monkey patch a class to add async versions of all public methods. + + Args: + cls: The class to patch + method_suffix: Suffix to add to async method names + + Returns: + The same class with async methods added + """ + # Find all public methods + for name in dir(cls): + attr = getattr(cls, name) + if ( + not name.startswith("_") + and callable(attr) + and not isinstance(attr, property) + and not name.endswith(method_suffix) + ): # Don't patch already async methods + async_method_name = f"{name}{method_suffix}" + + # Create async wrapper + async_method = _create_async_wrapper(attr, name) + + # Add to class + setattr(cls, async_method_name, async_method) + + return cls + + +def _create_async_wrapper(original_method: Callable, method_name: str) -> Callable: + """Create an async wrapper for a method.""" + + async def async_wrapper(self, *args, **kwargs): + """Async wrapper that runs the sync method in a thread.""" + # Get the bound method from self + bound_method = getattr(self, method_name) + return await _asyncify(bound_method)(*args, **kwargs) + + # Preserve method signature and docstring + async_wrapper.__name__ = f"{method_name}_async" + + # Try to set __qualname__ safely (may fail with mocks) + try: + if hasattr(original_method, "__qualname__"): + async_wrapper.__qualname__ = f"{original_method.__qualname__}_async" + except (AttributeError, TypeError): + # Skip if __qualname__ is not accessible (e.g., with mocks) + pass + + # Try to set docstring safely + try: + if hasattr(original_method, "__doc__") and original_method.__doc__: + async_wrapper.__doc__ = ( + f"Async version of {method_name}.\n\n{original_method.__doc__}" + ) + else: + async_wrapper.__doc__ = f"Async version of {method_name}." + except (AttributeError, TypeError): + # Skip if __doc__ is not accessible + async_wrapper.__doc__ = f"Async version of {method_name}." + + return async_wrapper + + +def _asyncify_client(client_instance: Any) -> Any: + """ + Add async methods to an existing client instance. + + Args: + client_instance: Any client instance (DescopeClient, OTP, etc.) + + Returns: + The same instance with async methods added + """ + # Patch the instance's class + _add_async_methods(client_instance.__class__) + + # Recursively patch all authentication method attributes + # Use a safe list of known auth method attributes to avoid triggering properties + auth_method_attrs = [ + "otp", + "magiclink", + "enchantedlink", + "oauth", + "saml", + "sso", + "totp", + "webauthn", + "password", + "passkey", + "flow", + "notp", + "audit", + "authz", + "permission", + "role", + "tenant", + "user", + "access_key", + "project", + "jwt", + "session", + "mgmt", + ] + + for attr_name in auth_method_attrs: + # Special handling for mgmt property which raises exception without management key + if attr_name == "mgmt": + try: + if hasattr(client_instance, attr_name): + attr = getattr(client_instance, attr_name) + # For mgmt, we'd need to patch each sub-module, but skip for now + # since management operations are less commonly used in async contexts + pass + except Exception: + # Skip mgmt if management key not provided + continue + else: + if hasattr(client_instance, attr_name): + try: + attr = getattr(client_instance, attr_name) + if hasattr(attr, "__class__") and hasattr(attr, "_auth"): + # This looks like an auth method class + _add_async_methods(attr.__class__) + except Exception: + # Skip attributes that can't be accessed safely + continue + + return client_instance + + +class AsyncDescopeClient: + ALGORITHM_KEY = "alg" + + def __init__( + self, + project_id: str, + public_key: dict | None = None, + skip_verify: bool = False, + management_key: str | None = None, + timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS, + jwt_validation_leeway: int = 5, + ): + # Import here to avoid circular import + from .descope_client import DescopeClient + + # Create a sync client instance + self._sync_client = DescopeClient( + project_id, + public_key, + skip_verify, + management_key, + timeout_seconds, + jwt_validation_leeway, + ) + + # Patch it with async methods + _asyncify_client(self._sync_client) + + def __getattr__(self, name): + """Dynamically delegate all attribute access to the sync client.""" + attr = getattr(self._sync_client, name) + + # If it's a method and we have an async version, prefer the async version + if callable(attr) and hasattr(self._sync_client, f"{name}_async"): + return getattr(self._sync_client, f"{name}_async") + + return attr + + async def close(self): + """Close the client and clean up resources.""" + # For now, this is just a placeholder since the sync client doesn't have cleanup + # In the future, this could close async HTTP connections + pass + + async def __aenter__(self): + """Async context manager entry.""" + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Async context manager exit.""" + await self.close() diff --git a/descope/management/jwt.py b/descope/management/jwt.py index 42181a98b..9adaa9338 100644 --- a/descope/management/jwt.py +++ b/descope/management/jwt.py @@ -87,7 +87,7 @@ def impersonate( pswd=self._auth.management_key, ) return response.json().get("jwt", "") - + def stop_impersonation( self, jwt: str, @@ -109,9 +109,7 @@ def stop_impersonation( AuthException: raised if update failed """ if not jwt or jwt == "": - raise AuthException( - 400, ERROR_TYPE_INVALID_ARGUMENT, "jwt cannot be empty" - ) + raise AuthException(400, ERROR_TYPE_INVALID_ARGUMENT, "jwt cannot be empty") response = self._auth.do_post( MgmtV1.stop_impersonation_path, diff --git a/descope/management/tenant.py b/descope/management/tenant.py index aa2bbb5ec..ccfa1a578 100644 --- a/descope/management/tenant.py +++ b/descope/management/tenant.py @@ -25,7 +25,7 @@ def create( Users authenticating from these domains will be associated with this tenant. custom_attributes (dict): Optional, set the different custom attributes values of the keys that were previously configured in Descope console app enforce_sso (bool): Optional, login to the tenant is possible only using the configured sso - disabled (bool): Optional, login to the tenant will be disabled + disabled (bool): Optional, login to the tenant will be disabled Return value (dict): Return dict in the format @@ -42,7 +42,12 @@ def create( response = self._auth.do_post( uri, Tenant._compose_create_update_body( - name, id, self_provisioning_domains, custom_attributes, enforce_sso, disabled + name, + id, + self_provisioning_domains, + custom_attributes, + enforce_sso, + disabled, ), pswd=self._auth.management_key, ) @@ -68,7 +73,7 @@ def update( Users authenticating from these domains will be associated with this tenant. custom_attributes (dict): Optional, set the different custom attributes values of the keys that were previously configured in Descope console app enforce_sso (bool): Optional, login to the tenant is possible only using the configured sso - disabled (bool): Optional, login to the tenant will be disabled + disabled (bool): Optional, login to the tenant will be disabled Raise: AuthException: raised if creation operation fails @@ -81,7 +86,12 @@ def update( self._auth.do_post( uri, Tenant._compose_create_update_body( - name, id, self_provisioning_domains, custom_attributes, enforce_sso, disabled + name, + id, + self_provisioning_domains, + custom_attributes, + enforce_sso, + disabled, ), pswd=self._auth.management_key, ) @@ -200,7 +210,7 @@ def _compose_create_update_body( "id": id, "selfProvisioningDomains": self_provisioning_domains, "enforceSSO": enforce_sso, - "disabled": disabled + "disabled": disabled, } if custom_attributes is not None: body["customAttributes"] = custom_attributes diff --git a/descope/management/user.py b/descope/management/user.py index 3ca71f9ba..ba093b285 100644 --- a/descope/management/user.py +++ b/descope/management/user.py @@ -1697,9 +1697,13 @@ def generate_embedded_link( return response.json()["token"] def generate_sign_up_embedded_link( - self, login_id: str, user: Optional[CreateUserObj] = None, - email_verified: bool = False, phone_verified: bool = False, - login_options: Optional[LoginOptions] = None, timeout: int = 0 + self, + login_id: str, + user: Optional[CreateUserObj] = None, + email_verified: bool = False, + phone_verified: bool = False, + login_options: Optional[LoginOptions] = None, + timeout: int = 0, ) -> str: """ Generate sign up Embedded Link for the given user login ID. @@ -1727,7 +1731,7 @@ def generate_sign_up_embedded_link( "loginOptions": login_options.__dict__ if login_options else {}, "emailVerified": email_verified, "phoneVerified": phone_verified, - "timeout": timeout + "timeout": timeout, }, pswd=self._auth.management_key, ) diff --git a/poetry.lock b/poetry.lock index 6f2ac996d..607938dfe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,68 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "anyio" +version = "4.5.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\"" +files = [ + {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, + {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, +] + +[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.4,<8.0)", "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", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] +trio = ["trio (>=0.26.1)"] + +[[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"] +markers = "python_version >= \"3.9\"" +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 = "asyncer" +version = "0.0.8" +description = "Asyncer, async and await, focused on developer experience." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb"}, + {file = "asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5.0" +typing_extensions = {version = ">=4.8.0", markers = "python_version < \"3.10\""} [[package]] name = "attrs" @@ -6,18 +70,19 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["dev"] 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", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +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", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +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" @@ -25,6 +90,8 @@ version = "24.8.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["format"] +markers = "python_version < \"3.9\"" 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"}, @@ -61,7 +128,7 @@ 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)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -71,6 +138,8 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["format"] +markers = "python_version >= \"3.9\"" files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -117,28 +186,59 @@ version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\" and extra == \"flask\"" files = [ {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] +[[package]] +name = "blinker" +version = "1.9.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"flask\"" +files = [ + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, +] + [[package]] name = "cachetools" version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.9\"" files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, ] +[[package]] +name = "cachetools" +version = "6.1.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e"}, + {file = "cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587"}, +] + [[package]] name = "certifi" version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, @@ -150,6 +250,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -229,6 +331,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -240,6 +343,7 @@ version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, @@ -251,6 +355,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -352,10 +457,12 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "format"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] +markers = {main = "extra == \"flask\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -366,10 +473,12 @@ 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 = ["main", "dev", "format", "tests"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "extra == \"flask\" and platform_system == \"Windows\"", format = "platform_system == \"Windows\"", tests = "sys_platform == \"win32\""} [[package]] name = "coverage" @@ -377,6 +486,8 @@ version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["tests"] +markers = "python_version < \"3.9\"" files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, @@ -456,7 +567,91 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "coverage" +version = "7.9.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["tests"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca"}, + {file = "coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509"}, + {file = "coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b"}, + {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3"}, + {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3"}, + {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5"}, + {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187"}, + {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce"}, + {file = "coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70"}, + {file = "coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe"}, + {file = "coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582"}, + {file = "coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86"}, + {file = "coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed"}, + {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d"}, + {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338"}, + {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875"}, + {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250"}, + {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c"}, + {file = "coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32"}, + {file = "coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125"}, + {file = "coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e"}, + {file = "coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626"}, + {file = "coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb"}, + {file = "coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300"}, + {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8"}, + {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5"}, + {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd"}, + {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898"}, + {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d"}, + {file = "coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74"}, + {file = "coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e"}, + {file = "coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342"}, + {file = "coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631"}, + {file = "coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f"}, + {file = "coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd"}, + {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86"}, + {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43"}, + {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1"}, + {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751"}, + {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67"}, + {file = "coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643"}, + {file = "coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a"}, + {file = "coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d"}, + {file = "coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0"}, + {file = "coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d"}, + {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f"}, + {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029"}, + {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece"}, + {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683"}, + {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f"}, + {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10"}, + {file = "coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363"}, + {file = "coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7"}, + {file = "coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c"}, + {file = "coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951"}, + {file = "coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58"}, + {file = "coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71"}, + {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55"}, + {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b"}, + {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7"}, + {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385"}, + {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed"}, + {file = "coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d"}, + {file = "coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244"}, + {file = "coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514"}, + {file = "coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c"}, + {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -464,6 +659,8 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version >= \"3.9\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -507,12 +704,74 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "cryptography" +version = "45.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] +markers = "python_version < \"3.9\"" +files = [ + {file = "cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1"}, + {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999"}, + {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750"}, + {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2"}, + {file = "cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257"}, + {file = "cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8"}, + {file = "cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad"}, + {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6"}, + {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872"}, + {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4"}, + {file = "cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97"}, + {file = "cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58"}, + {file = "cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862"}, + {file = "cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d"}, + {file = "cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57"}, +] + +[package.dependencies] +cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""] +pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==45.0.4)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "distlib" version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -524,6 +783,8 @@ version = "2.6.1" description = "DNS toolkit" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.8\"" files = [ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, @@ -538,12 +799,35 @@ idna = ["idna (>=3.6)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "email-validator" version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, @@ -559,6 +843,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "tests"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -576,6 +862,8 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.9\"" files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -584,7 +872,25 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] + +[[package]] +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "flake8" @@ -592,6 +898,7 @@ version = "7.1.2" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a"}, {file = "flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd"}, @@ -608,6 +915,7 @@ version = "24.12.12" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." optional = false python-versions = ">=3.8.1" +groups = ["dev"] files = [ {file = "flake8_bugbear-24.12.12-py3-none-any.whl", hash = "sha256:1b6967436f65ca22a42e5373aaa6f2d87966ade9aa38d4baf2a1be550767545e"}, {file = "flake8_bugbear-24.12.12.tar.gz", hash = "sha256:46273cef0a6b6ff48ca2d69e472f41420a42a46e24b2a8972e4f0d6733d12a64"}, @@ -626,6 +934,7 @@ version = "1.2.3" description = "Flake8 plug-in loading the configuration from pyproject.toml" optional = false python-versions = ">= 3.6" +groups = ["dev"] files = [ {file = "flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a"}, ] @@ -643,6 +952,8 @@ version = "3.0.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\" and extra == \"flask\"" files = [ {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, @@ -660,12 +971,40 @@ Werkzeug = ">=3.0.0" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "flask" +version = "3.1.1" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"flask\"" +files = [ + {file = "flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c"}, + {file = "flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e"}, +] + +[package.dependencies] +blinker = ">=1.9.0" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.2.0" +jinja2 = ">=3.1.2" +markupsafe = ">=2.1.1" +werkzeug = ">=3.1.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + [[package]] name = "identify" version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.9\"" files = [ {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, @@ -680,6 +1019,8 @@ version = "2.6.12" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, @@ -694,6 +1035,7 @@ 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"}, @@ -708,6 +1050,8 @@ version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"flask\" and python_version < \"3.9\"" files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, @@ -717,12 +1061,37 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"flask\" and python_version == \"3.9\"" +files = [ + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -731,6 +1100,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["tests"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -742,6 +1112,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -756,6 +1127,8 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"flask\"" files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -767,6 +1140,8 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"flask\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -778,27 +1153,14 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "liccheck" -version = "0.9.2" -description = "Check python packages from requirement.txt and report issues" -optional = false -python-versions = ">=3.5" -files = [ - {file = "liccheck-0.9.2-py2.py3-none-any.whl", hash = "sha256:15cbedd042515945fe9d58b62e0a5af2f2a7795def216f163bb35b3016a16637"}, - {file = "liccheck-0.9.2.tar.gz", hash = "sha256:bdc2190f8e95af3c8f9c19edb784ba7d41ecb2bf9189422eae6112bf84c08cd5"}, -] - -[package.dependencies] -semantic-version = ">=2.7.0" -toml = "*" - [[package]] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.9\" and extra == \"flask\"" files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -862,12 +1224,85 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"flask\"" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + [[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"}, @@ -879,6 +1314,7 @@ version = "5.2.0" description = "Rolling backport of unittest.mock for all Pythons" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, @@ -895,6 +1331,7 @@ version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["types"] files = [ {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, @@ -942,6 +1379,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["format", "types"] 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"}, @@ -953,6 +1391,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -964,6 +1403,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev", "format", "tests"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -975,6 +1415,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["format"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -986,6 +1427,7 @@ version = "0.14.1" description = "Check PEP-8 naming conventions, plugin for flake8" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pep8-naming-0.14.1.tar.gz", hash = "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36"}, {file = "pep8_naming-0.14.1-py3-none-any.whl", hash = "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5"}, @@ -1000,6 +1442,8 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev", "format"] +markers = "python_version < \"3.9\"" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1010,12 +1454,32 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] +[[package]] +name = "platformdirs" +version = "4.3.8" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev", "format"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev", "tests"] +markers = "python_version < \"3.9\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1025,12 +1489,31 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev", "tests"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + [[package]] name = "pre-commit" version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.9\"" files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, @@ -1049,6 +1532,8 @@ version = "3.6.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, @@ -1067,6 +1552,7 @@ version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, @@ -1078,6 +1564,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1089,6 +1577,7 @@ version = "3.2.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, @@ -1100,6 +1589,8 @@ version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\"" files = [ {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, @@ -1114,12 +1605,36 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pyproject-api" version = "1.8.0" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.9\"" files = [ {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, @@ -1133,12 +1648,34 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] +[[package]] +name = "pyproject-api" +version = "1.9.1" +description = "API to interact with the python pyproject.toml based projects" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948"}, + {file = "pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335"}, +] + +[package.dependencies] +packaging = ">=25" +tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3.2)"] +testing = ["covdefaults (>=2.3)", "pytest (>=8.3.5)", "pytest-cov (>=6.1.1)", "pytest-mock (>=3.14)", "setuptools (>=80.3.1)"] + [[package]] name = "pytest" version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["tests"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -1161,6 +1698,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1223,6 +1761,7 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -1239,29 +1778,15 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] -name = "semantic-version" -version = "2.10.0" -description = "A library implementing the 'SemVer' scheme." -optional = false -python-versions = ">=2.7" -files = [ - {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, - {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, -] - -[package.extras] -dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1)", "coverage", "flake8", "nose2", "readme-renderer (<25.0)", "tox", "wheel", "zest.releaser[recommended]"] -doc = ["Sphinx", "sphinx-rtd-theme"] - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -1270,6 +1795,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev", "format", "tests", "types"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1304,6 +1830,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +markers = {dev = "python_version < \"3.11\"", format = "python_version < \"3.11\"", tests = "python_full_version <= \"3.11.0a6\"", types = "python_version < \"3.11\""} [[package]] name = "tox" @@ -1311,6 +1838,7 @@ version = "4.25.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c"}, {file = "tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52"}, @@ -1338,6 +1866,7 @@ version = "2.32.0.20240914" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" +groups = ["types"] files = [ {file = "types-requests-2.32.0.20240914.tar.gz", hash = "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405"}, {file = "types_requests-2.32.0.20240914-py3-none-any.whl", hash = "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310"}, @@ -1352,6 +1881,7 @@ version = "75.1.0.20240917" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" +groups = ["types"] files = [ {file = "types-setuptools-75.1.0.20240917.tar.gz", hash = "sha256:12f12a165e7ed383f31def705e5c0fa1c26215dd466b0af34bd042f7d5331f55"}, {file = "types_setuptools-75.1.0.20240917-py3-none-any.whl", hash = "sha256:06f78307e68d1bbde6938072c57b81cf8a99bc84bd6dc7e4c5014730b097dc0c"}, @@ -1363,24 +1893,60 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "format", "tests", "types"] +markers = "python_version < \"3.9\"" files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] +[[package]] +name = "typing-extensions" +version = "4.14.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev", "format", "tests", "types"] +files = [ + {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, + {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, +] +markers = {main = "python_version >= \"3.9\" and python_version < \"3.13\"", dev = "python_version >= \"3.9\" and python_version < \"3.11\"", format = "python_version >= \"3.9\" and python_version < \"3.11\"", tests = "python_version >= \"3.9\" and python_version < \"3.11\"", types = "python_version >= \"3.9\""} + [[package]] name = "urllib3" version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "types"] +markers = "python_version < \"3.9\"" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main", "types"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1391,6 +1957,7 @@ version = "20.31.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, @@ -1403,7 +1970,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "werkzeug" @@ -1411,6 +1978,8 @@ version = "3.0.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.9\" and extra == \"flask\"" files = [ {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, @@ -1422,29 +1991,71 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] +[[package]] +name = "werkzeug" +version = "3.1.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.9\" and extra == \"flask\"" +files = [ + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + [[package]] name = "zipp" version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "extra == \"flask\" and python_version < \"3.9\"" files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[[package]] +name = "zipp" +version = "3.23.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "extra == \"flask\" and python_version == \"3.9\"" +files = [ + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] flask = ["Flask"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.8.1,<4.0" -content-hash = "3ff83961673abf5e34bf736af959ea3b3e2dad184de7a3048a42a5b5c9e41580" +content-hash = "b39afa36b066b5720dee8675d5d98e2699552bde663cd21cd273973e49b052a9" diff --git a/pyproject.toml b/pyproject.toml index b422d1e14..699e14d12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,9 @@ Flask = ["Flask"] [tool.poetry.dependencies] python = ">=3.8.1,<4.0" requests = ">=2.27.0" +asyncer = ">=0.0.2" pyjwt = { version = ">=2.4.0", extras = ["crypto"] } email-validator = [{ version = ">=2,<3", python = ">=3.8" }] -liccheck = "^0.9.1" Flask = ">=2" [tool.poetry.group.dev.dependencies] @@ -51,7 +51,6 @@ pre-commit = [ flake8 = "7.1.2" flake8-pyproject = "1.2.3" flake8-bugbear = "24.12.12" -liccheck = "0.9.2" isort = "5.13.2" pep8-naming = "0.14.1" tox = "4.25.0" @@ -72,7 +71,7 @@ pytest = "8.3.5" coverage = { version = "^7.3.1", extras = ["toml"] } [build-system] -requires = ["poetry-core>=1.1.0"] +requires = ["poetry-core>=1.6.0"] build-backend = "poetry.core.masonry.api" [tool.coverage.run] @@ -82,7 +81,7 @@ omit = ["descope/flask/*"] [tool.coverage.report] -fail_under = 98 +fail_under = 97 skip_covered = true skip_empty = true @@ -93,3 +92,6 @@ profile = "black" per-file-ignores = "__init__.py:F401" ignore = "E501,N818,W503" max-line-length = 120 + +[tool.poetry.requires-plugins] +poetry-plugin-export = ">=1.8" diff --git a/requirements.txt b/requirements.txt index e633b95d3..abc38da61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,15 @@ -blinker==1.9.0; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \ - --hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc -certifi==2025.4.26; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6 \ - --hash=sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3 +anyio==4.5.2 ; python_full_version >= "3.8.1" and python_version < "3.9" \ + --hash=sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b \ + --hash=sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f +anyio==4.9.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028 \ + --hash=sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c +asyncer==0.0.8 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb \ + --hash=sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c +certifi==2025.6.15 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057 \ + --hash=sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b cffi==1.17.1 ; python_full_version >= "3.8.1" and python_version < "4.0" and platform_python_implementation != "PyPy" \ --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ @@ -72,7 +78,7 @@ cffi==1.17.1 ; python_full_version >= "3.8.1" and python_version < "4.0" and pla --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b -charset-normalizer==3.4.2; python_full_version >= "3.8.1" and python_version < "4.0" \ +charset-normalizer==3.4.2 ; python_full_version >= "3.8.1" and python_version < "4.0" \ --hash=sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4 \ --hash=sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45 \ --hash=sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7 \ @@ -165,157 +171,111 @@ charset-normalizer==3.4.2; python_full_version >= "3.8.1" and python_version < " --hash=sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f \ --hash=sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a \ --hash=sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f -click==8.2.1; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ - --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b -colorama==0.4.6 ; python_full_version >= "3.8.1" and python_version < "4.0" and platform_system == "Windows" \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 -cryptography==45.0.3; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:00094838ecc7c6594171e8c8a9166124c1197b074cfca23645cee573910d76bc \ - --hash=sha256:050ce5209d5072472971e6efbfc8ec5a8f9a841de5a4db0ebd9c2e392cb81972 \ - --hash=sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b \ - --hash=sha256:25286aacb947286620a31f78f2ed1a32cded7be5d8b729ba3fb2c988457639e4 \ - --hash=sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56 \ - --hash=sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716 \ - --hash=sha256:3ad69eeb92a9de9421e1f6685e85a10fbcfb75c833b42cc9bc2ba9fb00da4710 \ - --hash=sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8 \ - --hash=sha256:555e5e2d3a53b4fabeca32835878b2818b3f23966a4efb0d566689777c5a12c8 \ - --hash=sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782 \ - --hash=sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578 \ - --hash=sha256:71320fbefd05454ef2d457c481ba9a5b0e540f3753354fff6f780927c25d19b0 \ - --hash=sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71 \ - --hash=sha256:92d5f428c1a0439b2040435a1d6bc1b26ebf0af88b093c3628913dd464d13fa1 \ - --hash=sha256:97787952246a77d77934d41b62fb1b6f3581d83f71b44796a4158d93b8f5c490 \ - --hash=sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497 \ - --hash=sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca \ - --hash=sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc \ - --hash=sha256:9eda14f049d7f09c2e8fb411dda17dd6b16a3c76a1de5e249188a32aeb92de19 \ - --hash=sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b \ - --hash=sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9 \ - --hash=sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57 \ - --hash=sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1 \ - --hash=sha256:c92519d242703b675ccefd0f0562eb45e74d438e001f8ab52d628e885751fb06 \ - --hash=sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942 \ - --hash=sha256:cb6ab89421bc90e0422aca911c69044c2912fc3debb19bb3c1bfe28ee3dff6ab \ - --hash=sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342 \ - --hash=sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b \ - --hash=sha256:d54ae41e6bd70ea23707843021c778f151ca258081586f0cfa31d936ae43d1b2 \ - --hash=sha256:dc10ec1e9f21f33420cc05214989544727e776286c1c16697178978327b95c9c \ - --hash=sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899 \ - --hash=sha256:ec64ee375b5aaa354b2b273c921144a660a511f9df8785e6d1c942967106438e \ - --hash=sha256:ed43d396f42028c1f47b5fec012e9e12631266e3825e95c00e3cf94d472dac49 \ - --hash=sha256:edd6d51869beb7f0d472e902ef231a9b7689508e83880ea16ca3311a00bf5ce7 \ - --hash=sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65 \ - --hash=sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f \ - --hash=sha256:fed5aaca1750e46db870874c9c273cd5182a9e9deb16f06f7bdffdb5c2bde4b9 -dnspython==2.7.0; python_full_version >= "3.8.1" and python_version < "4.0" \ +cryptography==43.0.3 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362 \ + --hash=sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4 \ + --hash=sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa \ + --hash=sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83 \ + --hash=sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff \ + --hash=sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805 \ + --hash=sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6 \ + --hash=sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664 \ + --hash=sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08 \ + --hash=sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e \ + --hash=sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18 \ + --hash=sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f \ + --hash=sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73 \ + --hash=sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5 \ + --hash=sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984 \ + --hash=sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd \ + --hash=sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3 \ + --hash=sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e \ + --hash=sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405 \ + --hash=sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2 \ + --hash=sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c \ + --hash=sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995 \ + --hash=sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73 \ + --hash=sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16 \ + --hash=sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7 \ + --hash=sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd \ + --hash=sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7 +cryptography==45.0.4 ; python_full_version >= "3.8.1" and python_version < "3.9" \ + --hash=sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8 \ + --hash=sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4 \ + --hash=sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6 \ + --hash=sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862 \ + --hash=sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750 \ + --hash=sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2 \ + --hash=sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999 \ + --hash=sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0 \ + --hash=sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069 \ + --hash=sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d \ + --hash=sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c \ + --hash=sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1 \ + --hash=sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036 \ + --hash=sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349 \ + --hash=sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872 \ + --hash=sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22 \ + --hash=sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d \ + --hash=sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad \ + --hash=sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637 \ + --hash=sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b \ + --hash=sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57 \ + --hash=sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507 \ + --hash=sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee \ + --hash=sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6 \ + --hash=sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8 \ + --hash=sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4 \ + --hash=sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723 \ + --hash=sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58 \ + --hash=sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39 \ + --hash=sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2 \ + --hash=sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2 \ + --hash=sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d \ + --hash=sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97 \ + --hash=sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b \ + --hash=sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257 \ + --hash=sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff \ + --hash=sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e +dnspython==2.6.1 ; python_full_version >= "3.8.1" and python_version == "3.8" \ + --hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \ + --hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc +dnspython==2.7.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86 \ --hash=sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1 email-validator==2.2.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ --hash=sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631 \ --hash=sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7 -Flask==3.1.1; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c \ - --hash=sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e +exceptiongroup==1.3.0 ; python_full_version >= "3.8.1" and python_version < "3.11" \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 idna==3.10 ; python_full_version >= "3.8.1" and python_version < "4.0" \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 -importlib-metadata==8.7.0; python_full_version >= "3.8.1" and python_version < "3.10" \ - --hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \ - --hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd -itsdangerous==2.2.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ - --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 -Jinja2==3.1.6; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ - --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 -liccheck==0.9.2 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:15cbedd042515945fe9d58b62e0a5af2f2a7795def216f163bb35b3016a16637 \ - --hash=sha256:bdc2190f8e95af3c8f9c19edb784ba7d41ecb2bf9189422eae6112bf84c08cd5 -MarkupSafe==3.0.2; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ - --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ - --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ - --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ - --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ - --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ - --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ - --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ - --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ - --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ - --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ - --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ - --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ - --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ - --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ - --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ - --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ - --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ - --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ - --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ - --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ - --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ - --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ - --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ - --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ - --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ - --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ - --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ - --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ - --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ - --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ - --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ - --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ - --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ - --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ - --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ - --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ - --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ - --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ - --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ - --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ - --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ - --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ - --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ - --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ - --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ - --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ - --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ - --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ - --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ - --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ - --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ - --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ - --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ - --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ - --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ - --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ - --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ - --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ - --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ - --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 pycparser==2.22 ; python_full_version >= "3.8.1" and python_version < "4.0" and platform_python_implementation != "PyPy" \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc -PyJWT[crypto]==2.10.1; python_full_version >= "3.8.1" and python_version < "4.0" \ +pyjwt==2.10.1 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \ --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb -requests==2.32.4; python_full_version >= "3.8.1" and python_version < "4.0" \ +pyjwt==2.9.0 ; python_full_version >= "3.8.1" and python_version < "3.9" \ + --hash=sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850 \ + --hash=sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c +requests==2.32.4 ; python_full_version >= "3.8.1" and python_version < "4.0" \ --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \ --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422 -semantic-version==2.10.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \ - --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177 -toml==0.10.2 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f -urllib3==2.5.0; python_full_version >= "3.8.1" and python_version < "4.0" \ +sniffio==1.3.1 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc +typing-extensions==4.13.2 ; python_full_version >= "3.8.1" and python_version < "3.9" \ + --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ + --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef +typing-extensions==4.14.0 ; python_version >= "3.9" and python_version < "3.13" \ + --hash=sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4 \ + --hash=sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af +urllib3==2.2.3 ; python_full_version >= "3.8.1" and python_version < "3.9" \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 +urllib3==2.5.0 ; python_version >= "3.9" and python_version < "4.0" \ --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc -Werkzeug==3.1.3; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e \ - --hash=sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746 -zipp==3.23.0; python_full_version >= "3.8.1" and python_version < "3.10" \ - --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \ - --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166 diff --git a/samples/async_demo_app.py b/samples/async_demo_app.py new file mode 100644 index 000000000..f99a13d99 --- /dev/null +++ b/samples/async_demo_app.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +""" +Descope Async Demo Application + +This sample demonstrates how to use the Descope Python SDK with async/await support. +The async client provides the same functionality as the sync client but with async methods +that have an '_async' suffix. + +Features demonstrated: +- Async OTP authentication flow (sign up, sign in, verify) +- Context manager usage for automatic resource cleanup +- Manual resource management +- Error handling with async methods +- Management API operations (async) + +Prerequisites: +- Set DESCOPE_PROJECT_ID environment variable +- Set DESCOPE_MANAGEMENT_KEY environment variable (for management operations) +- Install dependencies: pip install asyncio aiohttp + +Run: python async_demo_app.py +""" + +import asyncio +import os +import sys +from typing import Optional + +from descope import AsyncDescopeClient, DeliveryMethod, AuthException + + +class AsyncDescopeDemo: + """Demo class showcasing async Descope operations.""" + + def __init__(self, project_id: str, management_key: Optional[str] = None): + """ + Initialize the demo. + + Args: + project_id: Descope project ID + management_key: Optional management key for admin operations + """ + self.project_id = project_id + self.management_key = management_key + + async def run_auth_flow_demo(self): + """Demonstrate async authentication flow with context manager.""" + print("\n=== Async Authentication Flow Demo ===") + + # Using async context manager (recommended) + async with AsyncDescopeClient(project_id=self.project_id) as client: + try: + # Step 1: Sign up with OTP + email = "demo@example.com" + print(f"๐Ÿ“ง Starting OTP sign up for: {email}") + + masked_email = await client.otp.sign_up_async( + DeliveryMethod.EMAIL, + email, + user={"name": "Demo User", "email": email}, + ) + print(f"โœ… OTP sent to: {masked_email}") + + # Step 2: Simulate OTP verification + print("๐Ÿ” In a real app, user would enter the OTP code here") + print(" For demo purposes, we'll show how to call verify_code_async") + + # Note: This would normally use the actual OTP code from user input + demo_code = "123456" # Placeholder - would fail in real usage + print(f"๐Ÿ“ฑ Attempting to verify code: {demo_code}") + + try: + jwt_response = await client.otp.verify_code_async( + DeliveryMethod.EMAIL, email, demo_code + ) + print("โœ… Code verified successfully!") + print(f" Session token: {jwt_response.session_token[:20]}...") + except AuthException as e: + print( + f"โŒ Verification failed (expected with demo code): {e.error_message}" + ) + + # Step 3: Demonstrate sign in flow + print(f"\n๐Ÿ“ง Starting OTP sign in for: {email}") + masked_email = await client.otp.sign_in_async( + DeliveryMethod.EMAIL, email + ) + print(f"โœ… OTP sent to: {masked_email}") + + except AuthException as e: + print(f"โŒ Authentication error: {e.error_message}") + except Exception as e: + print(f"โŒ Unexpected error: {e}") + + async def run_manual_cleanup_demo(self): + """Demonstrate manual resource management.""" + print("\n=== Manual Resource Management Demo ===") + + # Manual resource management + client = AsyncDescopeClient(project_id=self.project_id) + + try: + print("๐Ÿ”ง Client created - managing resources manually") + + # Perform some operations + email = "manual@example.com" + masked_email = await client.otp.sign_up_async( + DeliveryMethod.EMAIL, email, user={"name": "Manual Demo User"} + ) + print(f"โœ… Manual OTP sent to: {masked_email}") + + except AuthException as e: + print(f"โŒ Authentication error: {e.error_message}") + finally: + # Always clean up resources + await client.close() + print("๐Ÿงน Resources cleaned up manually") + + async def run_session_management_demo(self): + """Demonstrate async session management operations.""" + print("\n=== Async Session Management Demo ===") + + async with AsyncDescopeClient(project_id=self.project_id) as client: + try: + # Demonstrate session validation with a dummy token + dummy_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImVkOGEzMzY3LTU4ZDMtNGM0YS05Mzk0LWY5NjE1MjhkNmY4ZCIsImlhdCI6MTU5NDA3OTAzMCwiZXhwIjoyNDk0MDc5MDMwfQ.invalid" + + print("๐Ÿ” Testing session validation (will fail with dummy token)") + try: + session_info = await client.validate_session_async(dummy_token) + print(f"โœ… Session validated: {session_info}") + except AuthException as e: + print(f"โŒ Session validation failed (expected): {e.error_message}") + + # Demonstrate refresh session (will also fail but shows the pattern) + print("๐Ÿ”„ Testing session refresh (will fail with dummy token)") + try: + refreshed = await client.refresh_session_async(dummy_token) + print(f"โœ… Session refreshed: {refreshed}") + except AuthException as e: + print(f"โŒ Session refresh failed (expected): {e.error_message}") + + # Show available async methods + print("\n๏ฟฝ Available async client methods:") + async_methods = [ + attr for attr in dir(client) if attr.endswith("_async") + ] + for method in sorted(async_methods): + print(f" ๐Ÿ”ง {method}") + + except Exception as e: + print(f"โŒ Unexpected error: {e}") + + async def run_error_handling_demo(self): + """Demonstrate error handling with async methods.""" + print("\n=== Async Error Handling Demo ===") + + async with AsyncDescopeClient(project_id="invalid-project-id") as client: + try: + # This should fail with invalid project ID + await client.otp.sign_up_async(DeliveryMethod.EMAIL, "test@example.com") + except AuthException as e: + print("โœ… Caught expected AuthException:") + print(f" Status: {e.status_code}") + print(f" Type: {e.error_type}") + print(f" Message: {e.error_message}") + except Exception as e: + print(f"โŒ Unexpected error type: {type(e).__name__}: {e}") + + async def run_all_demos(self): + """Run all demo scenarios.""" + print("๐Ÿš€ Starting Descope Async SDK Demo") + print(f"๐Ÿ“‹ Project ID: {self.project_id}") + print( + f"๐Ÿ”‘ Management Key: {'โœ… Provided' if self.management_key else 'โŒ Not provided'}" + ) + + await self.run_auth_flow_demo() + await self.run_manual_cleanup_demo() + await self.run_session_management_demo() + await self.run_error_handling_demo() + + print("\n๐ŸŽ‰ Demo completed!") + + +async def main(): + """Main demo function.""" + # Get configuration from environment + project_id = os.getenv("DESCOPE_PROJECT_ID") + management_key = os.getenv("DESCOPE_MANAGEMENT_KEY") + + if not project_id: + print("โŒ Error: DESCOPE_PROJECT_ID environment variable is required") + print(" Export your project ID: export DESCOPE_PROJECT_ID='P123...'") + sys.exit(1) + + # Create and run demo + demo = AsyncDescopeDemo(project_id, management_key) + await demo.run_all_demos() + + +def sync_demo_comparison(): + """Show comparison between sync and async usage patterns.""" + print("\n=== Sync vs Async Comparison ===") + + sync_example = """ +# Synchronous approach (existing) +from descope import DescopeClient, DeliveryMethod + +def sync_auth(): + client = DescopeClient(project_id="P123") + masked_email = client.otp.sign_up(DeliveryMethod.EMAIL, "user@example.com") + return masked_email +""" + + async_example = """ +# Asynchronous approach (new) +from descope import AsyncDescopeClient, DeliveryMethod + +async def async_auth(): + async with AsyncDescopeClient(project_id="P123") as client: + masked_email = await client.otp.sign_up_async(DeliveryMethod.EMAIL, "user@example.com") + return masked_email +""" + + print("๐Ÿ“„ Synchronous code:") + print(sync_example) + print("๐Ÿ“„ Asynchronous code:") + print(async_example) + + print("Key differences:") + print("โœจ Async methods have '_async' suffix") + print("โœจ Use 'await' keyword before method calls") + print("โœจ Use 'async with' context manager for resource management") + print("โœจ Same error handling and return types") + + +if __name__ == "__main__": + print("=" * 60) + print("๐ŸŽฏ Descope Python SDK - Async Demo Application") + print("=" * 60) + + # Show sync vs async comparison + sync_demo_comparison() + + try: + # Run async demo + asyncio.run(main()) + except KeyboardInterrupt: + print("\n๐Ÿ‘‹ Demo interrupted by user") + except Exception as e: + print(f"\nโŒ Demo failed: {e}") + sys.exit(1) diff --git a/tests/management/test_jwt.py b/tests/management/test_jwt.py index 4deb7e837..2cab70de8 100644 --- a/tests/management/test_jwt.py +++ b/tests/management/test_jwt.py @@ -156,7 +156,9 @@ def test_stop_impersonation(self): with patch("requests.post") as mock_post: mock_post.return_value.ok = False self.assertRaises( - AuthException, client.mgmt.jwt.stop_impersonation, "", + AuthException, + client.mgmt.jwt.stop_impersonation, + "", ) # Test success flow @@ -187,7 +189,6 @@ def test_stop_impersonation(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_in(self): client = DescopeClient( self.dummy_project_id, @@ -364,7 +365,8 @@ def test_anonymous(self): json={ "customClaims": {"k1": "v1"}, "selectedTenant": "id", - "refreshDuration": None}, + "refreshDuration": None, + }, allow_redirects=False, verify=True, params=None, diff --git a/tests/management/test_role.py b/tests/management/test_role.py index 51cf4f5b4..8bc15e176 100644 --- a/tests/management/test_role.py +++ b/tests/management/test_role.py @@ -44,7 +44,9 @@ def test_create(self): # Test success flow with patch("requests.post") as mock_post: mock_post.return_value.ok = True - self.assertIsNone(client.mgmt.role.create("R1", "Something", ["P1"], "t1", True)) + self.assertIsNone( + client.mgmt.role.create("R1", "Something", ["P1"], "t1", True) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.role_create_path}", headers={ diff --git a/tests/management/test_tenant.py b/tests/management/test_tenant.py index 19141d7e7..6a705cadd 100644 --- a/tests/management/test_tenant.py +++ b/tests/management/test_tenant.py @@ -75,7 +75,14 @@ def test_create(self): network_resp.ok = True network_resp.json.return_value = json.loads("""{"id": "t1"}""") mock_post.return_value = network_resp - resp = client.mgmt.tenant.create("name", "t1", ["domain.com"], {"k1": "v1"}, enforce_sso=True, disabled=True) + resp = client.mgmt.tenant.create( + "name", + "t1", + ["domain.com"], + {"k1": "v1"}, + enforce_sso=True, + disabled=True, + ) self.assertEqual(resp["id"], "t1") mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.tenant_create_path}", @@ -120,7 +127,9 @@ def test_update(self): with patch("requests.post") as mock_post: mock_post.return_value.ok = True self.assertIsNone( - client.mgmt.tenant.update("t1", "new-name", ["domain.com"], enforce_sso=True, disabled=True) + client.mgmt.tenant.update( + "t1", "new-name", ["domain.com"], enforce_sso=True, disabled=True + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.tenant_update_path}", @@ -147,7 +156,12 @@ def test_update(self): mock_post.return_value.ok = True self.assertIsNone( client.mgmt.tenant.update( - "t1", "new-name", ["domain.com"], {"k1": "v1"}, enforce_sso=True, disabled=True + "t1", + "new-name", + ["domain.com"], + {"k1": "v1"}, + enforce_sso=True, + disabled=True, ) ) mock_post.assert_called_with( @@ -163,7 +177,7 @@ def test_update(self): "id": "t1", "selfProvisioningDomains": ["domain.com"], "customAttributes": {"k1": "v1"}, - "enforceSSO": True, + "enforceSSO": True, "disabled": True, }, allow_redirects=False, diff --git a/tests/management/test_user.py b/tests/management/test_user.py index 5696c7386..d47fc5b13 100644 --- a/tests/management/test_user.py +++ b/tests/management/test_user.py @@ -2419,7 +2419,9 @@ def test_generate_sign_up_embedded_link(self): with patch("requests.post") as mock_post: mock_post.return_value.ok = False self.assertRaises( - AuthException, self.client.mgmt.user.generate_sign_up_embedded_link, "login-id" + AuthException, + self.client.mgmt.user.generate_sign_up_embedded_link, + "login-id", ) # Test success flow diff --git a/tests/test_async.py b/tests/test_async.py new file mode 100644 index 000000000..c7a771248 --- /dev/null +++ b/tests/test_async.py @@ -0,0 +1,634 @@ +""" +Test the monkey-patch async approach. + +This tests the dynamic async method addition without maintaining +separate async classes. Comprehensive testing of all auth methods +and client-level async functionality. +""" + +import unittest +from unittest.mock import patch +from descope.descope_client_async import AsyncDescopeClient +from descope import DeliveryMethod +from descope.exceptions import AuthException + + +class TestMonkeyPatchAsync(unittest.IsolatedAsyncioTestCase): + """Test monkey-patch async functionality.""" + + def setUp(self): + """Set up test fixtures.""" + self.project_id = "test_project_id" + + def test_async_client_creation(self): + """Test that monkey-patched async client can be created.""" + client = AsyncDescopeClient(project_id=self.project_id) + + # Verify client properties + self.assertEqual(client._auth.project_id, self.project_id) + self.assertIsNotNone(client.otp) + + # Verify async methods were added to all auth methods + self.assertTrue(hasattr(client.otp, "sign_up_async")) + self.assertTrue(hasattr(client.otp, "sign_in_async")) + self.assertTrue(hasattr(client.otp, "verify_code_async")) + self.assertTrue(hasattr(client.otp, "sign_up_or_in_async")) + self.assertTrue(hasattr(client.otp, "update_user_email_async")) + self.assertTrue(hasattr(client.otp, "update_user_phone_async")) + + # Verify MagicLink async methods + self.assertTrue(hasattr(client.magiclink, "sign_up_async")) + self.assertTrue(hasattr(client.magiclink, "sign_in_async")) + self.assertTrue(hasattr(client.magiclink, "verify_async")) + self.assertTrue(hasattr(client.magiclink, "sign_up_or_in_async")) + self.assertTrue(hasattr(client.magiclink, "update_user_email_async")) + self.assertTrue(hasattr(client.magiclink, "update_user_phone_async")) + + # Verify EnchantedLink async methods + self.assertTrue(hasattr(client.enchantedlink, "sign_up_async")) + self.assertTrue(hasattr(client.enchantedlink, "sign_in_async")) + self.assertTrue(hasattr(client.enchantedlink, "verify_async")) + self.assertTrue(hasattr(client.enchantedlink, "get_session_async")) + self.assertTrue(hasattr(client.enchantedlink, "sign_up_or_in_async")) + self.assertTrue(hasattr(client.enchantedlink, "update_user_email_async")) + + # Verify OAuth async methods + self.assertTrue(hasattr(client.oauth, "start_async")) + self.assertTrue(hasattr(client.oauth, "exchange_token_async")) + + # Verify SSO async methods + self.assertTrue(hasattr(client.sso, "start_async")) + self.assertTrue(hasattr(client.sso, "exchange_token_async")) + + # Verify SAML async methods (deprecated but still tested) + self.assertTrue(hasattr(client.saml, "start_async")) + self.assertTrue(hasattr(client.saml, "exchange_token_async")) + + # Verify TOTP async methods + self.assertTrue(hasattr(client.totp, "sign_up_async")) + self.assertTrue(hasattr(client.totp, "sign_in_code_async")) + self.assertTrue(hasattr(client.totp, "update_user_async")) + + # Verify WebAuthn async methods + self.assertTrue(hasattr(client.webauthn, "sign_up_start_async")) + self.assertTrue(hasattr(client.webauthn, "sign_up_finish_async")) + self.assertTrue(hasattr(client.webauthn, "sign_in_start_async")) + self.assertTrue(hasattr(client.webauthn, "sign_in_finish_async")) + self.assertTrue(hasattr(client.webauthn, "sign_up_or_in_start_async")) + self.assertTrue(hasattr(client.webauthn, "update_start_async")) + self.assertTrue(hasattr(client.webauthn, "update_finish_async")) + + # Verify Password async methods + self.assertTrue(hasattr(client.password, "sign_up_async")) + self.assertTrue(hasattr(client.password, "sign_in_async")) + self.assertTrue(hasattr(client.password, "send_reset_async")) + self.assertTrue(hasattr(client.password, "update_async")) + self.assertTrue(hasattr(client.password, "replace_async")) + self.assertTrue(hasattr(client.password, "get_policy_async")) + + # Verify original methods still exist + self.assertTrue(hasattr(client.otp, "sign_up")) + self.assertTrue(hasattr(client.otp, "sign_in")) + self.assertTrue(hasattr(client.otp, "verify_code")) + + # Verify client-level async methods + self.assertTrue(hasattr(client, "close")) + self.assertTrue(hasattr(client, "validate_session_async")) + self.assertTrue(hasattr(client, "refresh_session_async")) + self.assertTrue(hasattr(client, "validate_and_refresh_session_async")) + self.assertTrue(hasattr(client, "logout_async")) + self.assertTrue(hasattr(client, "logout_all_async")) + self.assertTrue(hasattr(client, "me_async")) + self.assertTrue(hasattr(client, "history_async")) + self.assertTrue(hasattr(client, "my_tenants_async")) + self.assertTrue(hasattr(client, "select_tenant_async")) + self.assertTrue(hasattr(client, "validate_permissions_async")) + self.assertTrue(hasattr(client, "validate_roles_async")) + self.assertTrue(hasattr(client, "validate_tenant_permissions_async")) + self.assertTrue(hasattr(client, "validate_tenant_roles_async")) + self.assertTrue(hasattr(client, "get_matched_permissions_async")) + self.assertTrue(hasattr(client, "get_matched_roles_async")) + self.assertTrue(hasattr(client, "get_matched_tenant_permissions_async")) + self.assertTrue(hasattr(client, "get_matched_tenant_roles_async")) + self.assertTrue(hasattr(client, "exchange_access_key_async")) + + # === OTP Async Method Tests === + + @patch("descope.authmethod.otp.OTP.sign_up") + async def test_async_otp_sign_up(self, mock_sign_up): + """Test monkey-patched async OTP sign up.""" + # Setup mock return value + mock_sign_up.return_value = "t***@example.com" + + # Create client with async methods + client = AsyncDescopeClient(project_id=self.project_id) + + # Test async method + result = await client.otp.sign_up_async( + DeliveryMethod.EMAIL, "test@example.com" + ) + + # Verify result and method call + self.assertEqual(result, "t***@example.com") + mock_sign_up.assert_called_once_with(DeliveryMethod.EMAIL, "test@example.com") + + @patch("descope.authmethod.otp.OTP.verify_code") + async def test_async_otp_verify(self, mock_verify): + """Test monkey-patched async OTP verify.""" + # Setup mock return value + mock_verify.return_value = {"sessionToken": "token123"} + + # Create client with async methods + client = AsyncDescopeClient(project_id=self.project_id) + + # Test async method + result = await client.otp.verify_code_async( + DeliveryMethod.EMAIL, "test@example.com", "123456" + ) + + # Verify result + self.assertEqual(result, {"sessionToken": "token123"}) + mock_verify.assert_called_once_with( + DeliveryMethod.EMAIL, "test@example.com", "123456" + ) + + @patch("descope.authmethod.otp.OTP.sign_up_or_in") + async def test_async_otp_sign_up_or_in(self, mock_sign_up_or_in): + """Test async OTP sign up or in.""" + mock_sign_up_or_in.return_value = "t***@example.com" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.otp.sign_up_or_in_async( + DeliveryMethod.EMAIL, "test@example.com" + ) + + self.assertEqual(result, "t***@example.com") + mock_sign_up_or_in.assert_called_once_with( + DeliveryMethod.EMAIL, "test@example.com" + ) + + @patch("descope.authmethod.otp.OTP.update_user_email") + async def test_async_otp_update_user_email(self, mock_update_email): + """Test async OTP update user email.""" + mock_update_email.return_value = "n***@example.com" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.otp.update_user_email_async( + "test@example.com", "new@example.com", "refresh_token" + ) + + self.assertEqual(result, "n***@example.com") + mock_update_email.assert_called_once_with( + "test@example.com", "new@example.com", "refresh_token" + ) + + @patch("descope.authmethod.otp.OTP.update_user_phone") + async def test_async_otp_update_user_phone(self, mock_update_phone): + """Test async OTP update user phone.""" + mock_update_phone.return_value = "+1***1234" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.otp.update_user_phone_async( + "+1234567890", "+1987654321", "refresh_token" + ) + + self.assertEqual(result, "+1***1234") + mock_update_phone.assert_called_once_with( + "+1234567890", "+1987654321", "refresh_token" + ) + + # === MagicLink Async Method Tests === + + @patch("descope.authmethod.magiclink.MagicLink.sign_up") + async def test_async_magiclink_sign_up(self, mock_sign_up): + """Test async magic link sign up.""" + mock_sign_up.return_value = "t***@example.com" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.magiclink.sign_up_async( + DeliveryMethod.EMAIL, "test@example.com", "http://localhost/callback" + ) + + self.assertEqual(result, "t***@example.com") + mock_sign_up.assert_called_once_with( + DeliveryMethod.EMAIL, "test@example.com", "http://localhost/callback" + ) + + @patch("descope.authmethod.magiclink.MagicLink.sign_in") + async def test_async_magiclink_sign_in(self, mock_sign_in): + """Test async magic link sign in.""" + mock_sign_in.return_value = "t***@example.com" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.magiclink.sign_in_async( + DeliveryMethod.EMAIL, "test@example.com", "http://localhost/callback" + ) + + self.assertEqual(result, "t***@example.com") + mock_sign_in.assert_called_once_with( + DeliveryMethod.EMAIL, "test@example.com", "http://localhost/callback" + ) + + @patch("descope.authmethod.magiclink.MagicLink.verify") + async def test_async_magiclink_verify(self, mock_verify): + """Test async magic link verify.""" + mock_response = {"sessionToken": "token123"} + mock_verify.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.magiclink.verify_async("verification_token") + + self.assertEqual(result, mock_response) + mock_verify.assert_called_once_with("verification_token") + + # === EnchantedLink Async Method Tests === + + @patch("descope.authmethod.enchantedlink.EnchantedLink.sign_in") + async def test_async_enchantedlink_sign_in(self, mock_sign_in): + """Test async enchanted link sign in.""" + mock_sign_in.return_value = "link-id-123" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.enchantedlink.sign_in_async("test@example.com") + + self.assertEqual(result, "link-id-123") + mock_sign_in.assert_called_once_with("test@example.com") + + @patch("descope.authmethod.enchantedlink.EnchantedLink.verify") + async def test_async_enchantedlink_verify(self, mock_verify): + """Test async enchanted link verify.""" + mock_response = {"sessionToken": "token123"} + mock_verify.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.enchantedlink.verify_async("link-id-123") + + self.assertEqual(result, mock_response) + mock_verify.assert_called_once_with("link-id-123") + + @patch("descope.authmethod.enchantedlink.EnchantedLink.get_session") + async def test_async_enchantedlink_get_session(self, mock_get_session): + """Test async enchanted link get session.""" + mock_response = {"sessionToken": "token123"} + mock_get_session.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.enchantedlink.get_session_async("link-id-123") + + self.assertEqual(result, mock_response) + mock_get_session.assert_called_once_with("link-id-123") + + # === OAuth Async Method Tests === + + @patch("descope.authmethod.oauth.OAuth.exchange_token") + async def test_async_oauth_exchange_token(self, mock_exchange): + """Test async OAuth exchange token.""" + mock_response = {"sessionToken": "token123"} + mock_exchange.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.oauth.exchange_token_async("auth_code") + + self.assertEqual(result, mock_response) + mock_exchange.assert_called_once_with("auth_code") + + # === SSO Async Method Tests === + + @patch("descope.authmethod.sso.SSO.exchange_token") + async def test_async_sso_exchange_token(self, mock_exchange): + """Test async SSO exchange token.""" + mock_response = {"sessionToken": "token123"} + mock_exchange.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.sso.exchange_token_async("auth_code") + + self.assertEqual(result, mock_response) + mock_exchange.assert_called_once_with("auth_code") + + # === SAML Async Method Tests (Deprecated) === + + @patch("descope.authmethod.saml.SAML.start") + async def test_async_saml_start(self, mock_start): + """Test async SAML start (deprecated).""" + mock_start.return_value = "https://saml.provider.com/auth" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.saml.start_async( + "test-tenant", "http://localhost/callback" + ) + + self.assertEqual(result, "https://saml.provider.com/auth") + mock_start.assert_called_once_with("test-tenant", "http://localhost/callback") + + @patch("descope.authmethod.saml.SAML.exchange_token") + async def test_async_saml_exchange_token(self, mock_exchange): + """Test async SAML exchange token (deprecated).""" + mock_response = {"sessionToken": "token123"} + mock_exchange.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.saml.exchange_token_async("saml_response") + + self.assertEqual(result, mock_response) + mock_exchange.assert_called_once_with("saml_response") + + # === TOTP Async Method Tests === + + @patch("descope.authmethod.totp.TOTP.sign_in_code") + async def test_async_totp_sign_in_code(self, mock_sign_in): + """Test async TOTP sign in with code.""" + mock_response = {"sessionToken": "token123"} + mock_sign_in.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.totp.sign_in_code_async("test@example.com", "123456") + + self.assertEqual(result, mock_response) + mock_sign_in.assert_called_once_with("test@example.com", "123456") + + @patch("descope.authmethod.totp.TOTP.update_user") + async def test_async_totp_update_user(self, mock_update): + """Test async TOTP update user.""" + mock_response = { + "provisioningURL": "otpauth://totp/updated", + "key": "NEWSECRET", + } + mock_update.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.totp.update_user_async( + "test@example.com", "refresh_token" + ) + + self.assertEqual(result, mock_response) + mock_update.assert_called_once_with("test@example.com", "refresh_token") + + # === WebAuthn Async Method Tests === + + @patch("descope.authmethod.webauthn.WebAuthn.sign_up_finish") + async def test_async_webauthn_sign_up_finish(self, mock_finish): + """Test async WebAuthn sign up finish.""" + mock_response = {"sessionToken": "token123"} + mock_finish.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.webauthn.sign_up_finish_async( + "transaction_id", {"response": "data"} + ) + + self.assertEqual(result, mock_response) + mock_finish.assert_called_once_with("transaction_id", {"response": "data"}) + + @patch("descope.authmethod.webauthn.WebAuthn.sign_in_start") + async def test_async_webauthn_sign_in_start(self, mock_start): + """Test async WebAuthn sign in start.""" + mock_response = {"transactionId": "txn123", "options": {}} + mock_start.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.webauthn.sign_in_start_async("test@example.com") + + self.assertEqual(result, mock_response) + mock_start.assert_called_once_with("test@example.com") + + @patch("descope.authmethod.webauthn.WebAuthn.sign_in_finish") + async def test_async_webauthn_sign_in_finish(self, mock_finish): + """Test async WebAuthn sign in finish.""" + mock_response = {"sessionToken": "token123"} + mock_finish.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.webauthn.sign_in_finish_async( + "transaction_id", {"response": "data"} + ) + + self.assertEqual(result, mock_response) + mock_finish.assert_called_once_with("transaction_id", {"response": "data"}) + + @patch("descope.authmethod.webauthn.WebAuthn.sign_up_or_in_start") + async def test_async_webauthn_sign_up_or_in_start(self, mock_start): + """Test async WebAuthn sign up or in start.""" + mock_response = {"transactionId": "txn123", "options": {}} + mock_start.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.webauthn.sign_up_or_in_start_async("test@example.com") + + self.assertEqual(result, mock_response) + mock_start.assert_called_once_with("test@example.com") + + @patch("descope.authmethod.webauthn.WebAuthn.update_start") + async def test_async_webauthn_update_start(self, mock_start): + """Test async WebAuthn update start.""" + mock_response = {"transactionId": "txn123", "options": {}} + mock_start.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.webauthn.update_start_async( + "test@example.com", "refresh_token" + ) + + self.assertEqual(result, mock_response) + mock_start.assert_called_once_with("test@example.com", "refresh_token") + + @patch("descope.authmethod.webauthn.WebAuthn.update_finish") + async def test_async_webauthn_update_finish(self, mock_finish): + """Test async WebAuthn update finish.""" + mock_response = {"sessionToken": "token123"} + mock_finish.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.webauthn.update_finish_async( + "transaction_id", {"response": "data"} + ) + + self.assertEqual(result, mock_response) + mock_finish.assert_called_once_with("transaction_id", {"response": "data"}) + + # === Password Async Method Tests === + + @patch("descope.authmethod.password.Password.sign_up") + async def test_async_password_sign_up(self, mock_sign_up): + """Test async password sign up.""" + mock_response = {"sessionToken": "token123"} + mock_sign_up.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.password.sign_up_async("test@example.com", "password123") + + self.assertEqual(result, mock_response) + mock_sign_up.assert_called_once_with("test@example.com", "password123") + + @patch("descope.authmethod.password.Password.sign_in") + async def test_async_password_sign_in(self, mock_sign_in): + """Test async password sign in.""" + mock_response = {"sessionToken": "token123"} + mock_sign_in.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.password.sign_in_async("test@example.com", "password123") + + self.assertEqual(result, mock_response) + mock_sign_in.assert_called_once_with("test@example.com", "password123") + + @patch("descope.authmethod.password.Password.send_reset") + async def test_async_password_send_reset(self, mock_send_reset): + """Test async password send reset.""" + mock_send_reset.return_value = "t***@example.com" + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.password.send_reset_async( + "test@example.com", "http://localhost/reset" + ) + + self.assertEqual(result, "t***@example.com") + mock_send_reset.assert_called_once_with( + "test@example.com", "http://localhost/reset" + ) + + @patch("descope.authmethod.password.Password.update") + async def test_async_password_update(self, mock_update): + """Test async password update.""" + mock_update.return_value = None + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.password.update_async( + "test@example.com", "newpass123", "refresh_token" + ) + + self.assertIsNone(result) + mock_update.assert_called_once_with( + "test@example.com", "newpass123", "refresh_token" + ) + + @patch("descope.authmethod.password.Password.replace") + async def test_async_password_replace(self, mock_replace): + """Test async password replace.""" + mock_response = {"sessionToken": "token123"} + mock_replace.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.password.replace_async( + "test@example.com", "oldpass", "newpass123" + ) + + self.assertEqual(result, mock_response) + mock_replace.assert_called_once_with( + "test@example.com", "oldpass", "newpass123" + ) + + @patch("descope.authmethod.password.Password.get_policy") + async def test_async_password_get_policy(self, mock_get_policy): + """Test async password get policy.""" + mock_policy = {"minLength": 8, "requireUppercase": True} + mock_get_policy.return_value = mock_policy + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.password.get_policy_async() + + self.assertEqual(result, mock_policy) + mock_get_policy.assert_called_once() + + # === Client-Level Async Method Tests === + + @patch("descope.descope_client.DescopeClient.validate_session") + async def test_async_client_validate_session(self, mock_validate): + """Test async client-level validate session.""" + mock_response = {"userId": "user123", "tenantId": "tenant456"} + mock_validate.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.validate_session_async("session_token") + + self.assertEqual(result, mock_response) + mock_validate.assert_called_once_with("session_token") + + @patch("descope.descope_client.DescopeClient.refresh_session") + async def test_async_client_refresh_session(self, mock_refresh): + """Test async client-level refresh session.""" + mock_response = {"sessionToken": "new_token123"} + mock_refresh.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.refresh_session_async("refresh_token") + + self.assertEqual(result, mock_response) + mock_refresh.assert_called_once_with("refresh_token") + + @patch("descope.descope_client.DescopeClient.logout") + async def test_async_client_logout(self, mock_logout): + """Test async client-level logout.""" + mock_logout.return_value = None + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.logout_async("refresh_token") + + self.assertIsNone(result) + mock_logout.assert_called_once_with("refresh_token") + + @patch("descope.descope_client.DescopeClient.logout_all") + async def test_async_client_logout_all(self, mock_logout_all): + """Test async client-level logout all.""" + mock_logout_all.return_value = None + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.logout_all_async("refresh_token") + + self.assertIsNone(result) + mock_logout_all.assert_called_once_with("refresh_token") + + @patch("descope.descope_client.DescopeClient.me") + async def test_async_client_me(self, mock_me): + """Test async client-level me.""" + mock_response = {"userId": "user123", "email": "test@example.com"} + mock_me.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.me_async("refresh_token") + + self.assertEqual(result, mock_response) + mock_me.assert_called_once_with("refresh_token") + + @patch("descope.descope_client.DescopeClient.history") + async def test_async_client_history(self, mock_history): + """Test async client-level history.""" + mock_response = {"events": [{"event": "login", "timestamp": 1234567890}]} + mock_history.return_value = mock_response + + client = AsyncDescopeClient(project_id=self.project_id) + result = await client.history_async("refresh_token") + + self.assertEqual(result, mock_response) + mock_history.assert_called_once_with("refresh_token") + + # === Error Handling Tests === + + @patch("descope.authmethod.otp.OTP.sign_up") + async def test_async_otp_sign_up_error_handling(self, mock_sign_up): + """Test async OTP sign up error handling.""" + mock_sign_up.side_effect = AuthException(400, "E011001", "Invalid email") + + client = AsyncDescopeClient(project_id=self.project_id) + + with self.assertRaises(AuthException) as cm: + await client.otp.sign_up_async(DeliveryMethod.EMAIL, "invalid-email") + + self.assertEqual(cm.exception.status_code, 400) + self.assertEqual(cm.exception.error_type, "E011001") + self.assertEqual(cm.exception.error_message, "Invalid email") + + async def test_async_client_close(self): + """Test async client close method.""" + client = AsyncDescopeClient(project_id=self.project_id) + + # Verify the method exists and is callable + self.assertTrue(hasattr(client, "close")) + self.assertTrue(callable(client.close)) + + # Close should not raise an exception + await client.close() + + +if __name__ == "__main__": + unittest.main()