diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..6086111 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,11 @@ +## 2025-05-15 - [EventBridge] High-Performance JSON Parsing with model_validate_json +**Learning:** For external API responses, Pydantic's `model_validate_json(response.content)` is significantly faster than `model_validate(response.json())`. This is because `model_validate_json` leverages Pydantic's high-performance Rust-based JSON parser directly on raw bytes, bypassing the intermediate Python dictionary creation by `response.json()`. +**Action:** Use `model_validate_json` when processing raw JSON payloads from external HTTP calls or events to minimize Lambda execution time. + +## 2025-05-15 - [EventBridge] Connection Pooling with requests.Session +**Learning:** Reusing a `requests.Session` in AWS Lambda allows for TCP/TLS connection pooling across warm starts. This can reduce latency by 50-200ms per request by avoiding the overhead of establishing a new connection for every invocation. +**Action:** Always instantiate `requests.Session()` at the class level or module level for persistent outbound HTTP connections. + +## 2025-05-15 - [GraphQL] Rejected: TypeAdapter for List Validation +**Learning:** While `pydantic.TypeAdapter(list[Model])` provides a theoretical ~65% performance improvement over list comprehensions by leveraging Rust-based batch processing, it may be rejected if the perceived value is low relative to the original implementation's simplicity, especially in template code. +**Action:** Prioritize optimizations that have a dramatic and undeniable impact on core latency or resource consumption. diff --git a/templates/eventbridge/handler.py b/templates/eventbridge/handler.py index 8a61d68..9e59fe3 100644 --- a/templates/eventbridge/handler.py +++ b/templates/eventbridge/handler.py @@ -4,7 +4,7 @@ from aws_lambda_powertools.utilities.parser import event_parser from aws_lambda_powertools.utilities.parser.models import EventBridgeModel from aws_lambda_powertools.utilities.typing import LambdaContext -from requests import get +from requests import Session from templates.eventbridge.models import ApiResponse from templates.eventbridge.settings import Settings @@ -16,6 +16,7 @@ metrics = Metrics(namespace=settings.metrics_namespace, service=settings.service_name) secrets_provider = SecretsProvider() repository = Repository(settings.table_name) +session = Session() # Use a single session for connection pooling and performance class Handler: @@ -27,14 +28,12 @@ def __init__(self, secrets_provider: SecretsProvider, repository: Repository) -> def handle(self, event: EventBridgeModel) -> ApiResponse: try: token = self._secrets_provider.get(settings.secret_name) - response = get( + response = session.get( settings.api_url, headers={"Authorization": f"Bearer {token}"}, timeout=settings.api_timeout_seconds, ) response.raise_for_status() - # Optimize by using Pydantic's Rust-based JSON parser directly on the raw bytes. - # This avoids redundant dictionary creation and improves performance. api_response = ApiResponse.model_validate_json(response.content) self._repository.put_item(api_response.model_dump(by_alias=True, exclude_none=True)) metrics.add_metric(name="ApiCallSuccess", unit=MetricUnit.Count, value=1) diff --git a/tests/eventbridge/test_handler.py b/tests/eventbridge/test_handler.py index 51c3145..ee7a5eb 100644 --- a/tests/eventbridge/test_handler.py +++ b/tests/eventbridge/test_handler.py @@ -64,7 +64,7 @@ def test_successful_invocation(mocker, lambda_context) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") mock_metrics = mocker.patch.object(handler_module, "metrics") @@ -91,7 +91,7 @@ def test_secret_loading_failure(mocker, lambda_context) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mocker.patch.object(handler_module, "get") + mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") mock_metrics = mocker.patch.object(handler_module, "metrics") @@ -111,7 +111,7 @@ def test_api_non_2xx_response(mocker, lambda_context) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") mock_metrics = mocker.patch.object(handler_module, "metrics") @@ -134,7 +134,7 @@ def test_api_network_exception(mocker, lambda_context) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") mock_metrics = mocker.patch.object(handler_module, "metrics") @@ -155,7 +155,7 @@ def test_invalid_eventbridge_event(mocker, lambda_context) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -174,7 +174,7 @@ def test_dynamodb_write_failure(mocker, lambda_context) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") mock_metrics = mocker.patch.object(handler_module, "metrics") diff --git a/tests/eventbridge/test_properties.py b/tests/eventbridge/test_properties.py index 42544b6..bb0a49b 100644 --- a/tests/eventbridge/test_properties.py +++ b/tests/eventbridge/test_properties.py @@ -52,7 +52,7 @@ def test_valid_event_shapes(mocker, source, detail_type, detail) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -102,7 +102,7 @@ def test_invalid_event_prevents_api_call(mocker, missing_key) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -140,7 +140,7 @@ def test_secret_exception_propagates(mocker, exc) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -180,7 +180,7 @@ def test_bearer_token_header(mocker, token) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -236,7 +236,7 @@ def test_api_failure_propagates(mocker, status_code) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -346,7 +346,7 @@ def test_successful_response_persisted(mocker, status) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets @@ -390,7 +390,7 @@ def test_dynamodb_write_failure_propagates(mocker, exc) -> None: import templates.eventbridge.handler as handler_module mock_secrets = mocker.patch.object(handler_module, "secrets_provider") - mock_get = mocker.patch.object(handler_module, "get") + mock_get = mocker.patch.object(handler_module.session, "get") mock_repo = mocker.patch.object(handler_module, "repository") handler_module.handler._secrets_provider = mock_secrets diff --git a/uv.lock b/uv.lock index d5d1b49..07a1034 100644 --- a/uv.lock +++ b/uv.lock @@ -13,11 +13,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -117,43 +117,43 @@ wheels = [ [[package]] name = "boto3" -version = "1.43.23" +version = "1.43.24" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/7e/18f6d87625930231708681ccfba20c2c6ade8d977c37d388992c0589efdd/boto3-1.43.23.tar.gz", hash = "sha256:5d26498702ffd021dc0d57d0eefcc7101cd995ea0ed08c057c9b631efccbaa48", size = 113242, upload-time = "2026-06-04T19:39:37.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/8f/94dfa39ec618ecb2fe5b5b79428c95100e3ae3c1aa5083c283dd3cfb5ecd/boto3-1.43.24.tar.gz", hash = "sha256:ba5afa266bf7265e0c1a454fcfd48bffe5939cb16ed223bebc669c3dc8ee0bc8", size = 113154, upload-time = "2026-06-05T19:30:01.635Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/1e/27b67ee4d10cc755aa9a35d45aa476d7bb2366c4dea91b0db94fa0fe27bd/boto3-1.43.23-py3-none-any.whl", hash = "sha256:8afc058924ef8a5c62467fe2e1e2e0304c22018587a044714da89f9c602ba856", size = 140536, upload-time = "2026-06-04T19:39:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/59/b7/e66c9b37b96153aa371fe48d24194151293f6577dd3eaa1fc146c281456d/boto3-1.43.24-py3-none-any.whl", hash = "sha256:b18ef745274ef548a9660d733d985d4a971b16bd8a6af88165ea9d0e40913b86", size = 140536, upload-time = "2026-06-05T19:29:58.968Z" }, ] [[package]] name = "botocore" -version = "1.43.23" +version = "1.43.24" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/79/9c3313d8be64ebff5a100d73777d5c6249229ceee57e269a0830b2f7d3a3/botocore-1.43.23.tar.gz", hash = "sha256:a6737c598750f330bfa8ef2be2d9fa84b5d2d643b6bbb0d22e129e03b7535df1", size = 15464775, upload-time = "2026-06-04T19:39:26.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/67/55d0611b341482bc9649d16df765f849a1862184ac3709356decf632279f/botocore-1.43.24.tar.gz", hash = "sha256:0c02f2b40e99419d496ece0ea2dcdedb5c45998c16fd1674276c7dbb30767a16", size = 15471690, upload-time = "2026-06-05T19:29:33.731Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/0e/63e4720c6fe6dab278faf9f09b59c377e49a9f9a1afaec2c69dc2e7b9de2/botocore-1.43.23-py3-none-any.whl", hash = "sha256:69ff3d951cb644d1d84db646663c7eb919dc9c0c47e5768e947c8a71121b3d77", size = 15147962, upload-time = "2026-06-04T19:39:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b7/360b5afe74c4d7cff871ea6e8f335e2e11de2945c9deb1eea6438f49faa2/botocore-1.43.24-py3-none-any.whl", hash = "sha256:42903b4bfafd8f15a735ed940473f28e4ba21b2ea67a9b9aaa11dfa7fcb19fd5", size = 15155182, upload-time = "2026-06-05T19:29:29.457Z" }, ] [[package]] name = "cattrs" -version = "25.3.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/00/2432bb2d445b39b5407f0a90e01b9a271475eea7caf913d7a86bcb956385/cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a", size = 509321, upload-time = "2025-10-07T12:26:08.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/2b/a40e1488fdfa02d3f9a653a61a5935ea08b3c2225ee818db6a76c7ba9695/cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff", size = 70738, upload-time = "2025-10-07T12:26:06.603Z" }, + { url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" }, ] [[package]] @@ -377,20 +377,20 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/b2/d6fc3f2347f43dada79e5ff118493e8109c98400a0e29a1d5264a3aa479b/distlib-0.4.1.tar.gz", hash = "sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b", size = 610526, upload-time = "2026-06-02T11:17:40.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97", size = 469216, upload-time = "2026-06-02T11:17:38.779Z" }, ] [[package]] name = "filelock" -version = "3.29.0" +version = "3.29.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, ] [[package]] @@ -416,14 +416,14 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.155.1" +version = "6.155.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/ef/4a94c12429986a90076057513e084bf32106a9bdc62c8e29f58673dd85a2/hypothesis-6.155.1.tar.gz", hash = "sha256:07c102031612b98d7c1be15ca3608c43e1234d9d07e3a190a53fa01536700196", size = 477300, upload-time = "2026-05-29T23:12:57.515Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/04/64032a1dccd2233615c8a3f701bbb563558575ed017496a24b6d81762c91/hypothesis-6.155.2.tar.gz", hash = "sha256:ae36880287c9c5defe9f199d3d2b67d9947a4da2a46e6c57373cbdf2345b20e1", size = 477765, upload-time = "2026-06-05T16:32:23.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/6e/8c9cf32201238617454303b1605dfa667d90cd1ef51226f92d9c2b3b8f7c/hypothesis-6.155.1-py3-none-any.whl", hash = "sha256:2753f469df3ba3c483b08e0c37dbcbc41d8316ebb921abcc07493ee9c8a7d187", size = 543715, upload-time = "2026-05-29T23:12:54.77Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6e/e735f27ac1a530a4cd0a31cd970ec495a3a11830fdc5d281cc292593b330/hypothesis-6.155.2-py3-none-any.whl", hash = "sha256:c85ce6dcd630a90ce501f1d1dd1bc84b97f5649ca8a27e134c8cbf5aa480b1a5", size = 544213, upload-time = "2026-06-05T16:32:21.15Z" }, ] [[package]] @@ -437,11 +437,11 @@ wheels = [ [[package]] name = "idna" -version = "3.16" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -646,21 +646,21 @@ python = [ [[package]] name = "mkdocstrings-python" -version = "2.0.3" +version = "2.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffelib" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/b4/5fed370d8ebd96e4e399460a7146ae989263f16588b05a6facd6dbd51e60/mkdocstrings_python-2.0.4.tar.gz", hash = "sha256:58c73c5d358e64e9b1673447663f4a2f8a8941e392e225fc0a0c893758cc452f", size = 199219, upload-time = "2026-06-05T08:13:01.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e3/00ec594aef5f55522e6d373bc2ac53e53a8f5e9ae32f2d6854b0de4270f3/mkdocstrings_python-2.0.4-py3-none-any.whl", hash = "sha256:fd87c173e1e719a85997b6d4f852cdc55f36710e0ed08da3a7bd9abe79c9db00", size = 104790, upload-time = "2026-06-05T08:13:00.393Z" }, ] [[package]] name = "moto" -version = "5.2.1" +version = "5.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -671,9 +671,9 @@ dependencies = [ { name = "werkzeug" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/e9/c38202162db2e76623176be9f1dbc9aa41228ffa91ee8da2d3986082c3e3/moto-5.2.1.tar.gz", hash = "sha256:ccb2f3e1dfa82e50e054bda98b0be708d244d2668364dcc1d45e8d3de6091bde", size = 8634437, upload-time = "2026-05-10T19:11:57.286Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/63/d944f387582cc53f53febbff2b3fa36a6d2ed7c1feef8990bf646cfa9cba/moto-5.2.2.tar.gz", hash = "sha256:aac8023a429e125e91c91f8f4730a67b54f518cda587352f7e67252fe3168f75", size = 8678761, upload-time = "2026-06-06T18:57:54.931Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/79/8085b7c1ecd48d0535c3c8444a1d8df2926e457dce8e55fabc332a382c9c/moto-5.2.1-py3-none-any.whl", hash = "sha256:19d2fbd6e613aa5b4e364c52cd5d3cea371643a0f4210689a703227bd2924c5c", size = 6671379, upload-time = "2026-05-10T19:11:53.543Z" }, + { url = "https://files.pythonhosted.org/packages/c1/45/13cff46f4f617a6e97e1d497d75abd913e250bb4c823a4985668c6e593e4/moto-5.2.2-py3-none-any.whl", hash = "sha256:3817f1e39721ca833579b921e53e3b68547ace6a34d848c9486fbb5905808de9", size = 6698689, upload-time = "2026-06-06T18:57:51.435Z" }, ] [[package]] @@ -714,11 +714,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, ] [[package]] @@ -911,15 +911,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.3.1" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/60/e88788207d81e46362cfbef0d4aaf4c0f49efc3c12d4c3fa3f542c34ebec/python_discovery-1.3.1.tar.gz", hash = "sha256:62f6db28064c9613e7ca76cb3f00c38c839a07c31c00dfe7ed0986493d2150a6", size = 68011, upload-time = "2026-05-12T20:53:36.336Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl", hash = "sha256:ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c", size = 33185, upload-time = "2026-05-12T20:53:34.969Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" }, ] [[package]] @@ -1159,7 +1159,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.3.3" +version = "21.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -1167,9 +1167,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/ba/1f6e8c957e4932be060dcdc482d339c12e0216351478add3645cdaa53c05/virtualenv-21.3.3.tar.gz", hash = "sha256:f5bda277e553b1c2b3c1a8debfc30496e1288cc93ce6b7b71b3280047e317328", size = 7613784, upload-time = "2026-05-13T18:01:30.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl", hash = "sha256:7d5987d8369e098e41406efb780a3d4ca79280097293899e351a6407ee153ab3", size = 7594554, upload-time = "2026-05-13T18:01:27.815Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" }, ] [[package]]