diff --git a/.github/workflows/check-license.yml b/.github/workflows/check-license.yml index 133817b2a..e5e885c89 100644 --- a/.github/workflows/check-license.yml +++ b/.github/workflows/check-license.yml @@ -2,19 +2,20 @@ name: License Header Check on: pull_request: - branches: - - develop push: branches: - develop +permissions: + contents: read + jobs: check-license-header: name: Check License Header runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - - uses: apache/skywalking-eyes/header@main + - uses: apache/skywalking-eyes/header@55373684d1b70e5f8fd9fc8ec114a89ad11a56a3 with: mode: check diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d594bf6e..5b523cb62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,14 +1,14 @@ name: CI on: + pull_request: push: branches: - develop - main - pull_request: - branches: - - develop +permissions: + contents: read jobs: ci: @@ -20,19 +20,19 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v5 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Set up JDK 17 - uses: actions/setup-java@v5 + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e with: java-version: 17 distribution: 'corretto' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 - name: Install uv and set the Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 with: python-version: ${{ matrix.python-version }} activate-environment: true diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml index 7c1c34ab1..d3c6ffedf 100644 --- a/.github/workflows/update-gradle-wrapper.yml +++ b/.github/workflows/update-gradle-wrapper.yml @@ -7,14 +7,21 @@ on: # Run at midnight (UTC) every wednesday - cron: "0 0 * * 3" +permissions: + contents: read + jobs: update-gradle-wrapper: runs-on: ubuntu-latest + permissions: + # allow job to open a pull request with changes + pull-requests: write + steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Update Gradle Wrapper - uses: gradle-update/update-gradle-wrapper-action@v2 + uses: gradle-update/update-gradle-wrapper-action@512b1875f3b6270828abfe77b247d5895a2da1e5 with: paths: codegen/** diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java index 90aa2e0fe..1e7b67ac1 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java @@ -22,7 +22,7 @@ private AwsPythonDependency() {} */ public static final PythonDependency SMITHY_AWS_CORE = new PythonDependency( "smithy_aws_core", - "~=0.2.0", + "~=0.3.0", PythonDependency.Type.DEPENDENCY, false); } diff --git a/codegen/build.gradle.kts b/codegen/build.gradle.kts index c8f736a5d..cec136127 100644 --- a/codegen/build.gradle.kts +++ b/codegen/build.gradle.kts @@ -15,5 +15,5 @@ allprojects { group = "software.amazon.smithy.python" - version = "0.1.0" + version = "0.2.0" } diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java index 95c6e0d5a..a189712bf 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java @@ -207,22 +207,9 @@ private void writeSharedOperationInit(PythonWriter writer, OperationShape operat if config.protocol is None or config.transport is None: raise ExpectationNotMetError("protocol and transport MUST be set on the config to make calls.") - # Resolve retry strategy from config - if isinstance(config.retry_strategy, RetryStrategy): - retry_strategy = config.retry_strategy - elif isinstance(config.retry_strategy, RetryStrategyOptions): - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=config.retry_strategy - ) - elif config.retry_strategy is None: - retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( - options=RetryStrategyOptions() - ) - else: - raise TypeError( - f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " - f"got {type(config.retry_strategy).__name__}" - ) + retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy( + retry_strategy=config.retry_strategy + ) pipeline = RequestPipeline( protocol=config.protocol, diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java index 71fde8b10..220c80df2 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java @@ -22,7 +22,7 @@ public final class SmithyPythonDependency { */ public static final PythonDependency SMITHY_CORE = new PythonDependency( "smithy_core", - "~=0.2.0", + "~=0.3.0", Type.DEPENDENCY, false); @@ -78,7 +78,7 @@ public final class SmithyPythonDependency { */ public static final PythonDependency SMITHY_AWS_CORE = new PythonDependency( "smithy_aws_core", - "~=0.2.0", + "~=0.3.0", Type.DEPENDENCY, false); @@ -87,7 +87,7 @@ public final class SmithyPythonDependency { */ public static final PythonDependency PYTEST = new PythonDependency( "pytest", - ">=7.2.0,<8.0.0", + ">=9.0.1,<10.0.0", Type.TEST_DEPENDENCY, false); @@ -96,7 +96,7 @@ public final class SmithyPythonDependency { */ public static final PythonDependency PYTEST_ASYNCIO = new PythonDependency( "pytest-asyncio", - ">=0.20.3,<0.21.0", + ">=1.3.0,<1.4.0", Type.TEST_DEPENDENCY, false); diff --git a/codegen/gradle/libs.versions.toml b/codegen/gradle/libs.versions.toml index 4ccc1b007..0eebde41c 100644 --- a/codegen/gradle/libs.versions.toml +++ b/codegen/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] junit5 = "6.0.1" -smithy = "1.64.0" +smithy = "1.65.0" test-logger-plugin = "4.0.0" spotbugs = "6.0.22" spotless = "8.1.0" smithy-gradle-plugins = "1.3.0" -dep-analysis = "3.4.1" +dep-analysis = "3.5.1" jsoup = "1.21.2" commonmark = "0.17.0" diff --git a/packages/aws-sdk-signers/src/aws_sdk_signers/_io.py b/packages/aws-sdk-signers/src/aws_sdk_signers/_io.py index ba50f3c4d..9a88ea756 100644 --- a/packages/aws-sdk-signers/src/aws_sdk_signers/_io.py +++ b/packages/aws-sdk-signers/src/aws_sdk_signers/_io.py @@ -1,8 +1,8 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from asyncio import iscoroutinefunction from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable +from inspect import iscoroutinefunction from io import BytesIO from typing import ( Self, diff --git a/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py b/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py index 29ba02894..f74179be5 100644 --- a/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py +++ b/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py @@ -6,11 +6,11 @@ import hmac import io import warnings -from asyncio import iscoroutinefunction from binascii import hexlify from collections.abc import AsyncIterable, Iterable from copy import deepcopy from hashlib import sha256 +from inspect import iscoroutinefunction from typing import TYPE_CHECKING, Required, TypedDict, TypeGuard from urllib.parse import parse_qsl, quote diff --git a/packages/smithy-aws-core/.changes/0.3.0.json b/packages/smithy-aws-core/.changes/0.3.0.json new file mode 100644 index 000000000..8a075baeb --- /dev/null +++ b/packages/smithy-aws-core/.changes/0.3.0.json @@ -0,0 +1,8 @@ +{ + "changes": [ + { + "type": "dependency", + "description": "Bump `smithy-core` from `~=0.2.0` to `~=0.3.0`." + } + ] +} diff --git a/packages/smithy-aws-core/CHANGELOG.md b/packages/smithy-aws-core/CHANGELOG.md index 90a18a3d5..5727dae72 100644 --- a/packages/smithy-aws-core/CHANGELOG.md +++ b/packages/smithy-aws-core/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.3.0 + +### Dependencies +* Bump `smithy-core` from `~=0.2.0` to `~=0.3.0`. + ## v0.2.0 ### Dependencies diff --git a/packages/smithy-aws-core/pyproject.toml b/packages/smithy-aws-core/pyproject.toml index c9c2defe5..7dbd5ffbc 100644 --- a/packages/smithy-aws-core/pyproject.toml +++ b/packages/smithy-aws-core/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ "Topic :: Software Development :: Libraries" ] dependencies = [ - "smithy-core~=0.2.0", + "smithy-core~=0.3.0", "smithy-http~=0.3.0", "aws-sdk-signers~=0.1.0" ] diff --git a/packages/smithy-aws-core/src/smithy_aws_core/__init__.py b/packages/smithy-aws-core/src/smithy_aws_core/__init__.py index d4291a169..fa1387347 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/__init__.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/__init__.py @@ -1,4 +1,4 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -__version__ = "0.2.0" +__version__ = "0.3.0" diff --git a/packages/smithy-aws-event-stream/.changes/0.2.1.json b/packages/smithy-aws-event-stream/.changes/0.2.1.json new file mode 100644 index 000000000..cfaba4aa1 --- /dev/null +++ b/packages/smithy-aws-event-stream/.changes/0.2.1.json @@ -0,0 +1,8 @@ +{ + "changes": [ + { + "type": "dependency", + "description": "Removed strict pinning on `smithy-core` in favor of client managed versions." + } + ] +} \ No newline at end of file diff --git a/packages/smithy-aws-event-stream/CHANGELOG.md b/packages/smithy-aws-event-stream/CHANGELOG.md index 50724dc71..25709de38 100644 --- a/packages/smithy-aws-event-stream/CHANGELOG.md +++ b/packages/smithy-aws-event-stream/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.2.1 + +### Dependencies +* Removed strict pinning on `smithy-core` in favor of client managed versions. + ## v0.2.0 ### Dependencies diff --git a/packages/smithy-aws-event-stream/pyproject.toml b/packages/smithy-aws-event-stream/pyproject.toml index 081b7761b..25a219268 100644 --- a/packages/smithy-aws-event-stream/pyproject.toml +++ b/packages/smithy-aws-event-stream/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ "Topic :: Software Development :: Libraries" ] dependencies = [ - "smithy-core~=0.2.0", + "smithy-core", ] [project.urls] diff --git a/packages/smithy-aws-event-stream/src/smithy_aws_event_stream/__init__.py b/packages/smithy-aws-event-stream/src/smithy_aws_event_stream/__init__.py index c392c786c..068e5f449 100644 --- a/packages/smithy-aws-event-stream/src/smithy_aws_event_stream/__init__.py +++ b/packages/smithy-aws-event-stream/src/smithy_aws_event_stream/__init__.py @@ -1,4 +1,4 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/packages/smithy-core/.changes/0.3.0.json b/packages/smithy-core/.changes/0.3.0.json new file mode 100644 index 000000000..06eb6613d --- /dev/null +++ b/packages/smithy-core/.changes/0.3.0.json @@ -0,0 +1,12 @@ +{ + "changes": [ + { + "type": "enhancement", + "description": "Improved default error message for instances of ClientTimeoutError." + }, + { + "type": "breaking", + "description": "Refactored `resolve_retry_strategy` to avoid code duplication per operation." + } + ] +} diff --git a/packages/smithy-core/CHANGELOG.md b/packages/smithy-core/CHANGELOG.md index 29d49d545..5af0e57f0 100644 --- a/packages/smithy-core/CHANGELOG.md +++ b/packages/smithy-core/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v0.3.0 + +### Breaking Changes +* Refactored `resolve_retry_strategy` to avoid code duplication per operation. + +### Enhancements +* Improved default error message for instances of ClientTimeoutError. + ## v0.2.0 ### Features diff --git a/packages/smithy-core/src/smithy_core/__init__.py b/packages/smithy-core/src/smithy_core/__init__.py index f6c4e1583..860e7402f 100644 --- a/packages/smithy-core/src/smithy_core/__init__.py +++ b/packages/smithy-core/src/smithy_core/__init__.py @@ -8,7 +8,7 @@ from . import interfaces, rfc3986 from .exceptions import SmithyError -__version__ = "0.2.0" +__version__ = "0.3.0" class HostType(Enum): diff --git a/packages/smithy-core/src/smithy_core/aio/client.py b/packages/smithy-core/src/smithy_core/aio/client.py index 01f18097b..6060727b4 100644 --- a/packages/smithy-core/src/smithy_core/aio/client.py +++ b/packages/smithy-core/src/smithy_core/aio/client.py @@ -469,9 +469,7 @@ async def _handle_attempt[I: SerializeableShape, O: DeserializeableShape]( ) except Exception as e: if isinstance(e, self.transport.TIMEOUT_EXCEPTIONS): - raise ClientTimeoutError( - message=f"Client timeout occurred: {e}" - ) from e + raise ClientTimeoutError(message="A timeout error occurred.") from e raise _LOGGER.debug("Received response: %s", transport_response) diff --git a/packages/smithy-core/src/smithy_core/aio/types.py b/packages/smithy-core/src/smithy_core/aio/types.py index d175e7e25..3b84317cd 100644 --- a/packages/smithy-core/src/smithy_core/aio/types.py +++ b/packages/smithy-core/src/smithy_core/aio/types.py @@ -1,9 +1,9 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import asyncio -from asyncio import iscoroutinefunction from collections import deque from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable +from inspect import iscoroutinefunction from io import BytesIO from typing import Any, Self, cast diff --git a/packages/smithy-core/src/smithy_core/interfaces/__init__.py b/packages/smithy-core/src/smithy_core/interfaces/__init__.py index 39be88d23..558c4c15e 100644 --- a/packages/smithy-core/src/smithy_core/interfaces/__init__.py +++ b/packages/smithy-core/src/smithy_core/interfaces/__init__.py @@ -1,7 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from asyncio import iscoroutinefunction from collections.abc import ItemsView, Iterator, KeysView, ValuesView +from inspect import iscoroutinefunction from typing import TYPE_CHECKING, Any, Protocol, TypeGuard, overload, runtime_checkable if TYPE_CHECKING: diff --git a/packages/smithy-core/src/smithy_core/retries.py b/packages/smithy-core/src/smithy_core/retries.py index ce990e6b4..67c63c1a3 100644 --- a/packages/smithy-core/src/smithy_core/retries.py +++ b/packages/smithy-core/src/smithy_core/retries.py @@ -34,13 +34,24 @@ class RetryStrategyResolver: """ async def resolve_retry_strategy( - self, *, options: RetryStrategyOptions + self, *, retry_strategy: RetryStrategy | RetryStrategyOptions | None ) -> RetryStrategy: """Resolve a retry strategy from the provided options, using cache when possible. - :param options: The retry strategy options to use for creating the strategy. + :param retry_strategy: An explicitly configured retry strategy or options for creating one. """ - return self._create_retry_strategy(options.retry_mode, options.max_attempts) + if isinstance(retry_strategy, RetryStrategy): + return retry_strategy + elif retry_strategy is None: + retry_strategy = RetryStrategyOptions() + elif not isinstance(retry_strategy, RetryStrategyOptions): # type: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"retry_strategy must be RetryStrategy, RetryStrategyOptions, or None, " + f"got {type(retry_strategy).__name__}" + ) + return self._create_retry_strategy( + retry_strategy.retry_mode, retry_strategy.max_attempts + ) @lru_cache def _create_retry_strategy( diff --git a/packages/smithy-core/tests/functional/__init__.py b/packages/smithy-core/tests/functional/__init__.py new file mode 100644 index 000000000..04f8b7b76 --- /dev/null +++ b/packages/smithy-core/tests/functional/__init__.py @@ -0,0 +1,2 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/packages/smithy-core/tests/functional/test_retries.py b/packages/smithy-core/tests/functional/test_retries.py new file mode 100644 index 000000000..9a72b491b --- /dev/null +++ b/packages/smithy-core/tests/functional/test_retries.py @@ -0,0 +1,153 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from asyncio import gather, sleep + +import pytest +from smithy_core.exceptions import CallError, ClientTimeoutError, RetryError +from smithy_core.interfaces import retries as retries_interface +from smithy_core.retries import ( + ExponentialBackoffJitterType, + ExponentialRetryBackoffStrategy, + StandardRetryQuota, + StandardRetryStrategy, +) + + +# TODO: Refactor this to use a smithy-testing generated client +async def retry_operation( + strategy: retries_interface.RetryStrategy, + responses: list[int | Exception], +) -> tuple[str, int]: + token = strategy.acquire_initial_retry_token() + response_iter = iter(responses) + + while True: + if token.retry_delay: + await sleep(token.retry_delay) + + response = next(response_iter) + attempt = token.retry_count + 1 + + # Success case + if response == 200: + strategy.record_success(token=token) + return "success", attempt + + # Error case - either status code or exception + if isinstance(response, Exception): + error = response + else: + error = CallError( + fault="server" if response >= 500 else "client", + message=f"HTTP {response}", + is_retry_safe=response >= 500, + ) + + try: + token = strategy.refresh_retry_token_for_retry( + token_to_renew=token, error=error + ) + except RetryError: + raise error + + +async def test_standard_retry_eventually_succeeds(): + quota = StandardRetryQuota(initial_capacity=500) + strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota) + + result, attempts = await retry_operation(strategy, [500, 500, 200]) + + assert result == "success" + assert attempts == 3 + assert quota.available_capacity == 495 + + +async def test_standard_retry_fails_due_to_max_attempts(): + quota = StandardRetryQuota(initial_capacity=500) + strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota) + + with pytest.raises(CallError, match="502"): + await retry_operation(strategy, [502, 502, 502]) + + assert quota.available_capacity == 490 + + +async def test_retry_quota_exhausted_after_single_retry(): + quota = StandardRetryQuota(initial_capacity=5) + strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota) + + with pytest.raises(CallError, match="502"): + await retry_operation(strategy, [500, 502]) + + assert quota.available_capacity == 0 + + +async def test_retry_quota_prevents_retries_when_quota_zero(): + quota = StandardRetryQuota(initial_capacity=0) + strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota) + + with pytest.raises(CallError, match="500"): + await retry_operation(strategy, [500]) + + assert quota.available_capacity == 0 + + +async def test_retry_quota_stops_retries_when_exhausted(): + quota = StandardRetryQuota(initial_capacity=10) + strategy = StandardRetryStrategy(max_attempts=5, retry_quota=quota) + + with pytest.raises(CallError, match="503"): + await retry_operation(strategy, [500, 502, 503]) + + assert quota.available_capacity == 0 + + +async def test_retry_quota_recovers_after_successful_responses(): + quota = StandardRetryQuota(initial_capacity=15) + strategy = StandardRetryStrategy(max_attempts=5, retry_quota=quota) + + # First operation: 2 retries then success + await retry_operation(strategy, [500, 502, 200]) + assert quota.available_capacity == 10 + + # Second operation: 1 retry then success + await retry_operation(strategy, [500, 200]) + assert quota.available_capacity == 10 + + +async def test_retry_quota_shared_across_concurrent_operations(): + quota = StandardRetryQuota(initial_capacity=500) + backoff = ExponentialRetryBackoffStrategy( + backoff_scale_value=1, + max_backoff=10, + jitter_type=ExponentialBackoffJitterType.FULL, + ) + strategy = StandardRetryStrategy( + max_attempts=5, + retry_quota=quota, + backoff_strategy=backoff, + ) + + result1, result2 = await gather( + retry_operation(strategy, [500, 500, 200]), + retry_operation(strategy, [500, 200]), + ) + + assert result1 == ("success", 3) + assert result2 == ("success", 2) + assert quota.available_capacity == 495 + + +async def test_retry_quota_handles_timeout_errors(): + quota = StandardRetryQuota(initial_capacity=500) + strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota) + + timeout1 = ClientTimeoutError() + timeout2 = ClientTimeoutError() + + result, attempts = await retry_operation(strategy, [timeout1, timeout2, 200]) + + assert result == "success" + assert attempts == 3 + assert quota.available_capacity == 490 diff --git a/packages/smithy-core/tests/unit/test_retries.py b/packages/smithy-core/tests/unit/test_retries.py index 18f9e380c..c36c5b758 100644 --- a/packages/smithy-core/tests/unit/test_retries.py +++ b/packages/smithy-core/tests/unit/test_retries.py @@ -220,34 +220,58 @@ def test_retry_quota_acquire_timeout_error( assert retry_quota.available_capacity == 0 -async def test_caching_retry_strategy_default_resolution() -> None: +async def test_retry_strategy_resolver_none_returns_default() -> None: resolver = RetryStrategyResolver() - options = RetryStrategyOptions() - strategy = await resolver.resolve_retry_strategy(options=options) + strategy = await resolver.resolve_retry_strategy(retry_strategy=None) assert isinstance(strategy, StandardRetryStrategy) assert strategy.max_attempts == 3 -async def test_caching_retry_strategy_resolver_creates_strategies_by_options() -> None: +async def test_retry_strategy_resolver_creates_different_strategies() -> None: resolver = RetryStrategyResolver() options1 = RetryStrategyOptions(max_attempts=3) options2 = RetryStrategyOptions(max_attempts=5) - strategy1 = await resolver.resolve_retry_strategy(options=options1) - strategy2 = await resolver.resolve_retry_strategy(options=options2) + strategy1 = await resolver.resolve_retry_strategy(retry_strategy=options1) + strategy2 = await resolver.resolve_retry_strategy(retry_strategy=options2) assert strategy1.max_attempts == 3 assert strategy2.max_attempts == 5 + assert strategy1 is not strategy2 -async def test_caching_retry_strategy_resolver_caches_strategies() -> None: +async def test_retry_strategy_resolver_caches_strategies() -> None: resolver = RetryStrategyResolver() + strategy1 = await resolver.resolve_retry_strategy(retry_strategy=None) + strategy2 = await resolver.resolve_retry_strategy(retry_strategy=None) options = RetryStrategyOptions(max_attempts=5) - strategy1 = await resolver.resolve_retry_strategy(options=options) - strategy2 = await resolver.resolve_retry_strategy(options=options) + strategy3 = await resolver.resolve_retry_strategy(retry_strategy=options) + strategy4 = await resolver.resolve_retry_strategy(retry_strategy=options) assert strategy1 is strategy2 + assert strategy3 is strategy4 + assert strategy1 is not strategy3 + + +async def test_retry_strategy_resolver_returns_existing_strategy() -> None: + resolver = RetryStrategyResolver() + provided_strategy = SimpleRetryStrategy(max_attempts=7) + + strategy = await resolver.resolve_retry_strategy(retry_strategy=provided_strategy) + + assert strategy is provided_strategy + assert strategy.max_attempts == 7 + + +async def test_retry_strategy_resolver_rejects_invalid_type() -> None: + resolver = RetryStrategyResolver() + + with pytest.raises( + TypeError, + match="retry_strategy must be RetryStrategy, RetryStrategyOptions, or None", + ): + await resolver.resolve_retry_strategy(retry_strategy="invalid") # type: ignore diff --git a/packages/smithy-http/.changes/0.3.1.json b/packages/smithy-http/.changes/0.3.1.json new file mode 100644 index 000000000..a5b6c090b --- /dev/null +++ b/packages/smithy-http/.changes/0.3.1.json @@ -0,0 +1,12 @@ +{ + "changes": [ + { + "type": "bugfix", + "description": "Fixed empty error messaging for CRT-based timeout errors." + }, + { + "type": "dependency", + "description": "Removed strict pinning on `smithy-core` in favor of client managed versions." + } + ] +} diff --git a/packages/smithy-http/CHANGELOG.md b/packages/smithy-http/CHANGELOG.md index e999a0232..bc0e08daf 100644 --- a/packages/smithy-http/CHANGELOG.md +++ b/packages/smithy-http/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v0.3.1 + +### Bug fixes +* Fixed empty error messaging for CRT-based timeout errors. + +### Dependencies +* Removed strict pinning on `smithy-core` in favor of client managed versions. + ## v0.3.0 ### Features diff --git a/packages/smithy-http/pyproject.toml b/packages/smithy-http/pyproject.toml index 18a5ce0a7..f9b609a60 100644 --- a/packages/smithy-http/pyproject.toml +++ b/packages/smithy-http/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ "Topic :: Software Development :: Libraries" ] dependencies = [ - "smithy-core~=0.2.0", + "smithy-core", ] [project.urls] diff --git a/packages/smithy-http/src/smithy_http/__init__.py b/packages/smithy-http/src/smithy_http/__init__.py index f877b9f44..5f6aea018 100644 --- a/packages/smithy-http/src/smithy_http/__init__.py +++ b/packages/smithy-http/src/smithy_http/__init__.py @@ -6,7 +6,7 @@ from . import interfaces from .interfaces import FieldPosition -__version__ = "0.3.0" +__version__ = "0.3.1" class Field(interfaces.Field): diff --git a/packages/smithy-http/src/smithy_http/aio/crt.py b/packages/smithy-http/src/smithy_http/aio/crt.py index db13162e1..5e5cc861c 100644 --- a/packages/smithy-http/src/smithy_http/aio/crt.py +++ b/packages/smithy-http/src/smithy_http/aio/crt.py @@ -187,7 +187,7 @@ async def send( return await self._await_response(crt_stream) except AwsCrtError as e: if e.name in self._TIMEOUT_ERROR_NAMES: - raise _CRTTimeoutError() from e + raise _CRTTimeoutError(f"CRT {e.name}: {e.message}") from e raise async def _await_response( diff --git a/packages/smithy-http/src/smithy_http/serializers.py b/packages/smithy-http/src/smithy_http/serializers.py index acac7ac75..11a5f18ea 100644 --- a/packages/smithy-http/src/smithy_http/serializers.py +++ b/packages/smithy-http/src/smithy_http/serializers.py @@ -1,11 +1,11 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from asyncio import iscoroutinefunction from base64 import b64encode from collections.abc import Callable, Iterator, Sized from contextlib import contextmanager from datetime import datetime from decimal import Decimal +from inspect import iscoroutinefunction from io import BytesIO from typing import TYPE_CHECKING from urllib.parse import quote as urlquote diff --git a/packages/smithy-http/tests/unit/test_serializers.py b/packages/smithy-http/tests/unit/test_serializers.py index 83e291b21..7f52da070 100644 --- a/packages/smithy-http/tests/unit/test_serializers.py +++ b/packages/smithy-http/tests/unit/test_serializers.py @@ -1,10 +1,10 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import datetime -from asyncio import iscoroutinefunction from dataclasses import dataclass, field from datetime import UTC from decimal import Decimal +from inspect import iscoroutinefunction from io import BytesIO from typing import Any, ClassVar, Protocol, Self diff --git a/packages/smithy-json/.changes/0.2.1.json b/packages/smithy-json/.changes/0.2.1.json new file mode 100644 index 000000000..cfaba4aa1 --- /dev/null +++ b/packages/smithy-json/.changes/0.2.1.json @@ -0,0 +1,8 @@ +{ + "changes": [ + { + "type": "dependency", + "description": "Removed strict pinning on `smithy-core` in favor of client managed versions." + } + ] +} \ No newline at end of file diff --git a/packages/smithy-json/CHANGELOG.md b/packages/smithy-json/CHANGELOG.md index d1f01ed00..924ce1878 100644 --- a/packages/smithy-json/CHANGELOG.md +++ b/packages/smithy-json/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.2.1 + +### Dependencies +* Removed strict pinning on `smithy-core` in favor of client managed versions. + ## v0.2.0 ### Dependencies diff --git a/packages/smithy-json/pyproject.toml b/packages/smithy-json/pyproject.toml index d6ebe1452..b4c113e87 100644 --- a/packages/smithy-json/pyproject.toml +++ b/packages/smithy-json/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ ] dependencies = [ "ijson>=3.3.0", - "smithy-core~=0.2.0", + "smithy-core", ] [project.urls] diff --git a/packages/smithy-json/src/smithy_json/__init__.py b/packages/smithy-json/src/smithy_json/__init__.py index a16b4e4b4..25c23bc57 100644 --- a/packages/smithy-json/src/smithy_json/__init__.py +++ b/packages/smithy-json/src/smithy_json/__init__.py @@ -13,7 +13,7 @@ from ._private.serializers import JSONShapeSerializer as _JSONShapeSerializer from .settings import JSONSettings -__version__ = "0.2.0" +__version__ = "0.2.1" __all__ = ("JSONCodec", "JSONDocument", "JSONSettings") diff --git a/scripts/changelog/new-entry.py b/scripts/changelog/new-entry.py index f21cb748a..aec8b511f 100755 --- a/scripts/changelog/new-entry.py +++ b/scripts/changelog/new-entry.py @@ -7,7 +7,7 @@ import argparse import json -from datetime import datetime +import uuid from pathlib import Path PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent.parent @@ -33,8 +33,8 @@ def create_change_entry( next_release_dir.mkdir(exist_ok=True) # Generate unique filename - timestamp = datetime.now().strftime("%Y%m%d%H%M%S") - filename = f"{package_name}-{change_type}-{timestamp}.json" + unique_id = uuid.uuid4().hex + filename = f"{package_name}-{change_type}-{unique_id}.json" entry_data = { "type": change_type, diff --git a/uv.lock b/uv.lock index 3bf8fb831..efa62699c 100644 --- a/uv.lock +++ b/uv.lock @@ -166,53 +166,88 @@ wheels = [ [[package]] name = "coverage" -version = "7.8.0" +version = "7.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, + { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, + { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941", size = 217725, upload-time = "2025-11-18T13:32:49.22Z" }, + { url = "https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a", size = 218098, upload-time = "2025-11-18T13:32:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9c/b846bbc774ff81091a12a10203e70562c91ae71badda00c5ae5b613527b1/coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d", size = 249093, upload-time = "2025-11-18T13:32:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211", size = 251686, upload-time = "2025-11-18T13:32:54.862Z" }, + { url = "https://files.pythonhosted.org/packages/cc/75/b095bd4b39d49c3be4bffbb3135fea18a99a431c52dd7513637c0762fecb/coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d", size = 252930, upload-time = "2025-11-18T13:32:56.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f3/466f63015c7c80550bead3093aacabf5380c1220a2a93c35d374cae8f762/coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c", size = 249296, upload-time = "2025-11-18T13:32:58.074Z" }, + { url = "https://files.pythonhosted.org/packages/27/86/eba2209bf2b7e28c68698fc13437519a295b2d228ba9e0ec91673e09fa92/coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9", size = 251068, upload-time = "2025-11-18T13:32:59.646Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/ca8ae7dbba962a3351f18940b359b94c6bafdd7757945fdc79ec9e452dc7/coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0", size = 249034, upload-time = "2025-11-18T13:33:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/39136149325cad92d420b023b5fd900dabdd1c3a0d1d5f148ef4a8cedef5/coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508", size = 248853, upload-time = "2025-11-18T13:33:02.935Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b6/76e1add8b87ef60e00643b0b7f8f7bb73d4bf5249a3be19ebefc5793dd25/coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc", size = 250619, upload-time = "2025-11-18T13:33:04.336Z" }, + { url = "https://files.pythonhosted.org/packages/95/87/924c6dc64f9203f7a3c1832a6a0eee5a8335dbe5f1bdadcc278d6f1b4d74/coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8", size = 220261, upload-time = "2025-11-18T13:33:06.493Z" }, + { url = "https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07", size = 221072, upload-time = "2025-11-18T13:33:07.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/49/5c9dc46205fef31b1b226a6e16513193715290584317fd4df91cdaf28b22/coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc", size = 219702, upload-time = "2025-11-18T13:33:09.631Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/f87922641c7198667994dd472a91e1d9b829c95d6c29529ceb52132436ad/coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87", size = 218420, upload-time = "2025-11-18T13:33:11.153Z" }, + { url = "https://files.pythonhosted.org/packages/85/dd/1cc13b2395ef15dbb27d7370a2509b4aee77890a464fb35d72d428f84871/coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6", size = 218773, upload-time = "2025-11-18T13:33:12.569Z" }, + { url = "https://files.pythonhosted.org/packages/74/40/35773cc4bb1e9d4658d4fb669eb4195b3151bef3bbd6f866aba5cd5dac82/coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7", size = 260078, upload-time = "2025-11-18T13:33:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/231bb1a6ffc2905e396557585ebc6bdc559e7c66708376d245a1f1d330fc/coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560", size = 262144, upload-time = "2025-11-18T13:33:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/32f4aa9f3bf0b56f3971001b56508352c7753915345d45fab4296a986f01/coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12", size = 264574, upload-time = "2025-11-18T13:33:17.354Z" }, + { url = "https://files.pythonhosted.org/packages/68/7c/00489fcbc2245d13ab12189b977e0cf06ff3351cb98bc6beba8bd68c5902/coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296", size = 259298, upload-time = "2025-11-18T13:33:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/f0760d65d56c3bea95b449e02570d4abd2549dc784bf39a2d4721a2d8ceb/coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507", size = 262150, upload-time = "2025-11-18T13:33:20.644Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/9a9314df00f9326d78c1e5a910f520d599205907432d90d1c1b7a97aa4b1/coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d", size = 259763, upload-time = "2025-11-18T13:33:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/10/34/01a0aceed13fbdf925876b9a15d50862eb8845454301fe3cdd1df08b2182/coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2", size = 258653, upload-time = "2025-11-18T13:33:24.239Z" }, + { url = "https://files.pythonhosted.org/packages/8d/04/81d8fd64928acf1574bbb0181f66901c6c1c6279c8ccf5f84259d2c68ae9/coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455", size = 260856, upload-time = "2025-11-18T13:33:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/f2/76/fa2a37bfaeaf1f766a2d2360a25a5297d4fb567098112f6517475eee120b/coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d", size = 220936, upload-time = "2025-11-18T13:33:28.165Z" }, + { url = "https://files.pythonhosted.org/packages/f9/52/60f64d932d555102611c366afb0eb434b34266b1d9266fc2fe18ab641c47/coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c", size = 222001, upload-time = "2025-11-18T13:33:29.656Z" }, + { url = "https://files.pythonhosted.org/packages/77/df/c303164154a5a3aea7472bf323b7c857fed93b26618ed9fc5c2955566bb0/coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d", size = 220273, upload-time = "2025-11-18T13:33:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/fc12db0883478d6e12bbd62d481210f0c8daf036102aa11434a0c5755825/coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92", size = 217777, upload-time = "2025-11-18T13:33:32.86Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c1/ce3e525d223350c6ec16b9be8a057623f54226ef7f4c2fee361ebb6a02b8/coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360", size = 218100, upload-time = "2025-11-18T13:33:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/15/87/113757441504aee3808cb422990ed7c8bcc2d53a6779c66c5adef0942939/coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac", size = 249151, upload-time = "2025-11-18T13:33:36.135Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/9529d9bd44049b6b05bb319c03a3a7e4b0a8a802d28fa348ad407e10706d/coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d", size = 251667, upload-time = "2025-11-18T13:33:37.996Z" }, + { url = "https://files.pythonhosted.org/packages/11/bb/567e751c41e9c03dc29d3ce74b8c89a1e3396313e34f255a2a2e8b9ebb56/coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c", size = 253003, upload-time = "2025-11-18T13:33:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b3/c2cce2d8526a02fb9e9ca14a263ca6fc074449b33a6afa4892838c903528/coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434", size = 249185, upload-time = "2025-11-18T13:33:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a7/967f93bb66e82c9113c66a8d0b65ecf72fc865adfba5a145f50c7af7e58d/coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc", size = 251025, upload-time = "2025-11-18T13:33:43.634Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/f2f6f56337bc1af465d5b2dc1ee7ee2141b8b9272f3bf6213fcbc309a836/coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc", size = 248979, upload-time = "2025-11-18T13:33:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7a/bf4209f45a4aec09d10a01a57313a46c0e0e8f4c55ff2965467d41a92036/coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e", size = 248800, upload-time = "2025-11-18T13:33:47.546Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b7/1e01b8696fb0521810f60c5bbebf699100d6754183e6cc0679bf2ed76531/coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17", size = 250460, upload-time = "2025-11-18T13:33:49.537Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/84324fb9cb46c024760e706353d9b771a81b398d117d8c1fe010391c186f/coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933", size = 220533, upload-time = "2025-11-18T13:33:51.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/71/1033629deb8460a8f97f83e6ac4ca3b93952e2b6f826056684df8275e015/coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe", size = 221348, upload-time = "2025-11-18T13:33:52.776Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5f/ac8107a902f623b0c251abdb749be282dc2ab61854a8a4fcf49e276fce2f/coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d", size = 219922, upload-time = "2025-11-18T13:33:54.316Z" }, + { url = "https://files.pythonhosted.org/packages/79/6e/f27af2d4da367f16077d21ef6fe796c874408219fa6dd3f3efe7751bd910/coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d", size = 218511, upload-time = "2025-11-18T13:33:56.343Z" }, + { url = "https://files.pythonhosted.org/packages/67/dd/65fd874aa460c30da78f9d259400d8e6a4ef457d61ab052fd248f0050558/coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03", size = 218771, upload-time = "2025-11-18T13:33:57.966Z" }, + { url = "https://files.pythonhosted.org/packages/55/e0/7c6b71d327d8068cb79c05f8f45bf1b6145f7a0de23bbebe63578fe5240a/coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9", size = 260151, upload-time = "2025-11-18T13:33:59.597Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/4697457d58285b7200de6b46d606ea71066c6e674571a946a6ea908fb588/coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6", size = 262257, upload-time = "2025-11-18T13:34:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/2f/33/acbc6e447aee4ceba88c15528dbe04a35fb4d67b59d393d2e0d6f1e242c1/coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339", size = 264671, upload-time = "2025-11-18T13:34:02.795Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e2822a795c1ed44d569980097be839c5e734d4c0c1119ef8e0a073496a30/coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e", size = 259231, upload-time = "2025-11-18T13:34:04.397Z" }, + { url = "https://files.pythonhosted.org/packages/72/c5/a7ec5395bb4a49c9b7ad97e63f0c92f6bf4a9e006b1393555a02dae75f16/coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13", size = 262137, upload-time = "2025-11-18T13:34:06.068Z" }, + { url = "https://files.pythonhosted.org/packages/67/0c/02c08858b764129f4ecb8e316684272972e60777ae986f3865b10940bdd6/coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f", size = 259745, upload-time = "2025-11-18T13:34:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/04/4fd32b7084505f3829a8fe45c1a74a7a728cb251aaadbe3bec04abcef06d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1", size = 258570, upload-time = "2025-11-18T13:34:09.676Z" }, + { url = "https://files.pythonhosted.org/packages/48/35/2365e37c90df4f5342c4fa202223744119fe31264ee2924f09f074ea9b6d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b", size = 260899, upload-time = "2025-11-18T13:34:11.259Z" }, + { url = "https://files.pythonhosted.org/packages/05/56/26ab0464ca733fa325e8e71455c58c1c374ce30f7c04cebb88eabb037b18/coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a", size = 221313, upload-time = "2025-11-18T13:34:12.863Z" }, + { url = "https://files.pythonhosted.org/packages/da/1c/017a3e1113ed34d998b27d2c6dba08a9e7cb97d362f0ec988fcd873dcf81/coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291", size = 222423, upload-time = "2025-11-18T13:34:15.14Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/bcc504fdd5169301b52568802bb1b9cdde2e27a01d39fbb3b4b508ab7c2c/coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384", size = 220459, upload-time = "2025-11-18T13:34:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, ] [[package]] name = "freezegun" -version = "1.5.1" +version = "1.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/ef/722b8d71ddf4d48f25f6d78aa2533d505bf3eec000a7cacb8ccc8de61f2f/freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9", size = 33697, upload-time = "2024-05-11T17:32:53.911Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/dd/23e2f4e357f8fd3bdff613c1fe4466d21bfb00a6177f238079b17f7b1c84/freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a", size = 35914, upload-time = "2025-08-09T10:39:08.338Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/0b/0d7fee5919bccc1fdc1c2a7528b98f65c6f69b223a3fd8f809918c142c36/freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1", size = 17569, upload-time = "2024-05-11T17:32:51.715Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2e/b41d8a1a917d6581fc27a35d05561037b048e47df50f27f8ac9c7e27a710/freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2", size = 19266, upload-time = "2025-08-09T10:39:06.636Z" }, ] [[package]] @@ -506,6 +541,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pyright" version = "1.1.406" @@ -521,42 +565,45 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.5" +version = "9.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, ] [[package]] name = "pytest-asyncio" -version = "0.26.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload-time = "2025-03-25T06:22:28.883Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload-time = "2025-03-25T06:22:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] name = "pytest-cov" -version = "6.1.1" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, + { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]]