diff --git a/app/api/wps.py b/app/api/wps.py index 4108a39..d40f858 100644 --- a/app/api/wps.py +++ b/app/api/wps.py @@ -9,7 +9,7 @@ from typing import Annotated from fastapi import APIRouter, File, Form, Request, UploadFile -from fastapi.responses import StreamingResponse +from fastapi.responses import Response, StreamingResponse from app.core.drawings import DrawingsService, DrawingsServiceDep from app.schemas.drawings import DrawingsCreateResponse, DrawingsUpdateResponse @@ -38,6 +38,14 @@ File(description="The KMZ file to upload. Only KMZ files are accepted."), ] +AdminIdForm = Annotated[ + uuid.UUID, + Form( + description="Admin identifier required to authorize the operation", + examples=["00000000-0000-0000-0000-000000000000"], + ), +] + @router.post( "/drawings", @@ -104,13 +112,7 @@ async def get_drawing( async def update_drawing( # noqa: PLR0913, PLR0917 request: Request, drawing_id: uuid.UUID, - admin_id: Annotated[ - uuid.UUID, - Form( - description="Admin identifier required to update the drawing", - examples=["00000000-0000-0000-0000-000000000000"], - ), - ], + admin_id: AdminIdForm, file: KmzFile, sha256: Sha256Form, drawings: DrawingsServiceDep, @@ -124,3 +126,26 @@ async def update_drawing( # noqa: PLR0913, PLR0917 timestamps. """ return await drawings.update_drawing(drawing_id, admin_id, file, request, sha256) + + +@router.delete( + "/drawings/{drawing_id}", + status_code=204, + responses={ + 403: {"model": ErrorResponse}, + 404: {"model": ErrorResponse}, + 500: {"model": ErrorResponse}, + }, +) +async def delete_drawing( + drawing_id: uuid.UUID, + admin_id: AdminIdForm, + drawings: DrawingsServiceDep, +) -> Response: + """Delete a KMZ drawing. + + The admin_id must match the stored drawing metadata, otherwise the request + is rejected with 403. The deletion is permanent. + """ + await drawings.delete_drawing(drawing_id, admin_id) + return Response(status_code=204) diff --git a/app/core/drawings.py b/app/core/drawings.py index 0f1ece9..fa60f86 100644 --- a/app/core/drawings.py +++ b/app/core/drawings.py @@ -272,6 +272,37 @@ async def get_drawing(self, drawing_id: uuid.UUID) -> tuple[AsyncIterator[bytes] return self._s3.get_drawing(s3_key), s3_key + async def delete_drawing(self, drawing_id: uuid.UUID, admin_id: uuid.UUID) -> None: + """Delete a KMZ drawing from S3. + + Verifies the drawing exists and the admin_id matches the stored metadata + before deleting the object. The deletion is permanent, the only way to + recover it is through S3 versioning. + + Args: + drawing_id: The UUID of the drawing to delete. + admin_id: Admin identifier that must match the stored drawing. + + Raises: + DrawingNotFoundError: If no drawing exists with the given identifier. + AdminIdMismatchError: If the admin_id does not match the stored one. + S3Error: If the S3 delete operation fails. + + """ + s3_key = self.build_s3_key(drawing_id) + + # Head the object first so a missing drawing surfaces as a 404 before + # the delete is attempted + existing = await self._s3.head_drawing(s3_key) + + if existing.get("admin-id") != str(admin_id): + logger.warning("admin_id mismatch for drawing %s", drawing_id) + raise AdminIdMismatchError + + await self._s3.delete_drawing(s3_key) + + logger.info("Drawing deleted: id=%s", drawing_id) + async def get_drawings_service( s3: S3ServiceDep, diff --git a/app/core/s3.py b/app/core/s3.py index 84989f7..3dbd201 100644 --- a/app/core/s3.py +++ b/app/core/s3.py @@ -117,6 +117,22 @@ async def head_drawing(self, key: str) -> dict[str, str]: else: return metadata + async def delete_drawing(self, key: str) -> None: + """Delete a KMZ file from S3. + + Args: + key: S3 object key + + Raises: + S3Error: If the S3 delete fails + + """ + try: + await self._client.delete_object(Bucket=self._bucket, Key=key) + except botocore.exceptions.BotoCoreError as e: + logger.exception("S3 delete failed for key %s", key) + raise S3Error(f"S3 delete failed for key {key}: {e}") from e + async def check_bucket(self) -> bool: """Check whether the configured S3 bucket is accessible. diff --git a/app/tests/test_drawings_endpoints.py b/app/tests/test_drawings_endpoints.py index ad589f6..d3022b1 100644 --- a/app/tests/test_drawings_endpoints.py +++ b/app/tests/test_drawings_endpoints.py @@ -311,3 +311,72 @@ def test_create_drawing_body_size_within_limit(client: TestClient, valid_kmz_byt data={"sha256": _sha256(valid_kmz_bytes)}, ) assert response.status_code == 201 + + +def test_delete_drawing_success(client: TestClient, valid_kmz_bytes: bytes): + """DELETE an existing drawing with the correct admin_id returns 204 and removes it.""" + create_resp = client.post( + "/api/wps/v1/drawings", + files={"file": ("test.kmz", valid_kmz_bytes, "application/vnd.google-earth.kmz")}, + data={"sha256": _sha256(valid_kmz_bytes)}, + ) + assert create_resp.status_code == 201 + drawing_id = create_resp.json()["id"] + admin_id = create_resp.json()["admin_id"] + + response = client.request( + "DELETE", + f"/api/wps/v1/drawings/{drawing_id}", + data={"admin_id": admin_id}, + ) + assert response.status_code == 204 + + # The drawing must no longer be retrievable + get_resp = client.get(f"/api/wps/v1/drawings/{drawing_id}") + assert get_resp.status_code == 404 + + +def test_delete_drawing_wrong_admin_id(client: TestClient, valid_kmz_bytes: bytes): + """DELETE with a mismatched admin_id returns 403 Forbidden.""" + create_resp = client.post( + "/api/wps/v1/drawings", + files={"file": ("test.kmz", valid_kmz_bytes, "application/vnd.google-earth.kmz")}, + data={"sha256": _sha256(valid_kmz_bytes)}, + ) + assert create_resp.status_code == 201 + drawing_id = create_resp.json()["id"] + + response = client.request( + "DELETE", + f"/api/wps/v1/drawings/{drawing_id}", + data={"admin_id": str(uuid.uuid4())}, + ) + assert response.status_code == 403 + assert "detail" in response.json() + + +def test_delete_drawing_not_found(client: TestClient): + """DELETE a non-existent drawing returns 404 Not Found.""" + response = client.request( + "DELETE", + "/api/wps/v1/drawings/00000000-0000-0000-0000-000000000000", + data={"admin_id": str(uuid.uuid4())}, + ) + assert response.status_code == 404 + assert "detail" in response.json() + + +def test_delete_drawing_missing_admin_id(client: TestClient): + """DELETE without the admin_id form field returns 422 Unprocessable Entity.""" + response = client.request("DELETE", "/api/wps/v1/drawings/00000000-0000-0000-0000-000000000000") + assert response.status_code == 422 + + +def test_delete_drawing_invalid_uuid(client: TestClient): + """DELETE with a malformed UUID returns 422 Unprocessable Entity.""" + response = client.request( + "DELETE", + "/api/wps/v1/drawings/not-a-valid-uuid", + data={"admin_id": str(uuid.uuid4())}, + ) + assert response.status_code == 422 diff --git a/app/tests/test_s3_service.py b/app/tests/test_s3_service.py index 26a69ce..9a64c39 100644 --- a/app/tests/test_s3_service.py +++ b/app/tests/test_s3_service.py @@ -213,3 +213,39 @@ async def test_check_bucket_failure(settings) -> None: svc = S3Service(client=client, bucket=settings.aws_s3_bucket_name) assert await svc.check_bucket() is False + + +@pytest.mark.asyncio +async def test_delete_drawing_success(settings) -> None: + """Upload then delete, verify the object is gone.""" + session = aioboto3.Session() + async with session.client("s3", endpoint_url=settings.aws_endpoint_url) as client: # type: ignore # noqa: PGH003 + svc = S3Service(client=client, bucket=settings.aws_s3_bucket_name) + key = "drawings/delete-test.kmz" + data = b"content to delete" + metadata = { + "sha256": "jkl012", + "admin-id": "11111111-1111-1111-1111-111111111111", + "created-at": "2026-01-01T00:00:00+00:00", + "modified-at": "2026-01-01T00:00:00+00:00", + } + + await svc.upload_drawing( + key, io.BytesIO(data), "application/vnd.google-earth.kmz", metadata + ) + + await svc.delete_drawing(key) + + with pytest.raises(DrawingNotFoundError): + await svc.head_drawing(key) + + +@pytest.mark.asyncio +async def test_delete_drawing_botocore_error(settings) -> None: + """A BotoCoreError during delete should raise S3Error.""" + client = MagicMock() + client.delete_object = AsyncMock(side_effect=botocore.exceptions.BotoCoreError()) + svc = S3Service(client=client, bucket=settings.aws_s3_bucket_name) + + with pytest.raises(S3Error): + await svc.delete_drawing("drawings/x.kmz") diff --git a/pyproject.toml b/pyproject.toml index f09055b..9bcebdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,9 @@ dependencies = [ # https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation#readme "httptools~=0.8.0", "opentelemetry-exporter-otlp~=1.41", - "opentelemetry-instrumentation-asgi==0.62b1", # asgi is used by fastapi - "opentelemetry-instrumentation-botocore==0.62b1", - "opentelemetry-instrumentation-fastapi==0.62b1", + "opentelemetry-instrumentation-asgi==0.65b0", # asgi is used by fastapi + "opentelemetry-instrumentation-botocore==0.65b0", + "opentelemetry-instrumentation-fastapi==0.65b0", "opentelemetry-sdk~=1.41", "orjson~=3.11", "pydantic-settings~=2.13", diff --git a/scripts/smoke_test_drawings_api.sh b/scripts/smoke_test_drawings_api.sh index 0211443..5155898 100755 --- a/scripts/smoke_test_drawings_api.sh +++ b/scripts/smoke_test_drawings_api.sh @@ -325,6 +325,32 @@ else fail " Downloaded content does NOT match updated file" fi +# --- DELETE /api/wps/v1/drawings/{id} --- +echo -e "\n${BOLD}=== DELETE /api/wps/v1/drawings/{id} ===${NC}" + +HTTP=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE \ + -F "admin_id=$WRONG_ADMIN_ID" \ + "$BASE_URL/api/wps/v1/drawings/$DRAWING_ID") +assert_status "Reject wrong admin_id" 403 "$HTTP" + +HTTP=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE \ + -F "admin_id=$ADMIN_ID" \ + "$BASE_URL/api/wps/v1/drawings/00000000-0000-0000-0000-000000000000") +assert_status "Delete non-existent drawing → 404" 404 "$HTTP" + +HTTP=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE \ + "$BASE_URL/api/wps/v1/drawings/$DRAWING_ID") +assert_status "Delete without admin_id form field → 422" 422 "$HTTP" + +HTTP=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE \ + -F "admin_id=$ADMIN_ID" \ + "$BASE_URL/api/wps/v1/drawings/$DRAWING_ID") +assert_status "Delete existing drawing → 204" 204 "$HTTP" + +HTTP=$(curl -s -o /dev/null -w '%{http_code}' \ + "$BASE_URL/api/wps/v1/drawings/$DRAWING_ID") +assert_status "Deleted drawing no longer retrievable → 404" 404 "$HTTP" + # --- OpenAPI spec --- echo -e "\n${BOLD}=== OpenAPI Spec ===${NC}" diff --git a/scripts/test_s3_local.py b/scripts/test_s3_local.py index 189090a..6e8414f 100644 --- a/scripts/test_s3_local.py +++ b/scripts/test_s3_local.py @@ -29,7 +29,7 @@ TEST_FILE = "France.kmz" -async def main() -> None: +async def main() -> None: # noqa: PLR0915 import aioboto3 # noqa: PLC0415 session = aioboto3.Session() @@ -94,6 +94,19 @@ async def main() -> None: except DrawingNotFoundError: print("OK (DrawingNotFoundError)") + # Delete + print("6. delete_drawing ...", end=" ", flush=True) + await svc.delete_drawing(key) + print("OK") + + # Head after delete (should be gone) + print("7. head_drawing (after delete) ...", end=" ", flush=True) + try: + await svc.head_drawing(key) + print("FAIL (expected DrawingNotFoundError)") + except DrawingNotFoundError: + print("OK (DrawingNotFoundError)") + print() print("All checks passed ✓") diff --git a/uv.lock b/uv.lock index 2390b6e..4cc4ae7 100644 --- a/uv.lock +++ b/uv.lock @@ -1038,18 +1038,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" @@ -1395,45 +1383,44 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/fc/b7564cbef36601aef0d6c9bc01f7badb64be8e862c2e1c3c5c3b43b53e4f/opentelemetry_api-1.41.1.tar.gz", hash = "sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621", size = 71416, upload-time = "2026-04-24T13:15:38.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/59/3e7118ed140f76b0982ba4321bdaed1997a0473f9720de2d10788a577033/opentelemetry_api-1.41.1-py3-none-any.whl", hash = "sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f", size = 69007, upload-time = "2026-04-24T13:15:15.662Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" }, ] [[package]] name = "opentelemetry-exporter-otlp" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/84/d55baf8e1a222f40282956083e67de9fa92d5fa451108df4839505fa2a24/opentelemetry_exporter_otlp-1.41.1.tar.gz", hash = "sha256:299a2f0541ca175df186f5ac58fd5db177ba1e9b72b0826049062f750d55b47f", size = 6152, upload-time = "2026-04-24T13:15:40.006Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/45/7af37fe54e5d3e66e7dcd7ba8b8aeee73f202bfac909cc94b8c4e428f9ac/opentelemetry_exporter_otlp-1.44.0.tar.gz", hash = "sha256:af1cde7c33ea8ed624bf04ac49a885730fe44c1f1ad698656e592c38f70ce106", size = 6090, upload-time = "2026-07-16T15:25:34.585Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/d5/ea4aa7dfc458fd537bd9519ea0e7226eef2a6212dfe952694984167daaba/opentelemetry_exporter_otlp-1.41.1-py3-none-any.whl", hash = "sha256:db276c5a80c02b063994e80950d00ca1bfddcf6520f608335b7dc2db0c0eb9c6", size = 7025, upload-time = "2026-04-24T13:15:17.839Z" }, + { url = "https://files.pythonhosted.org/packages/18/c3/7b466a9463944e70b37b744072a0c1b88a425dade3fff0631adec66c9bcc/opentelemetry_exporter_otlp-1.44.0-py3-none-any.whl", hash = "sha256:4a498fa8d8fd8be9e8e2d175fe5524a3fe581ccffadd8509db86526a5fb97051", size = 6727, upload-time = "2026-07-16T15:25:14.445Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/fa/f9e3bd3c4d692b3ce9a2880a167d1f79681a1bea11f00d5bf76adc03e6ea/opentelemetry_exporter_otlp_proto_common-1.41.1.tar.gz", hash = "sha256:0e253156ea9c36b0bd3d2440c5c9ba7dd1f3fb64ba7a08fc85fbac536b56e1fb", size = 20409, upload-time = "2026-04-24T13:15:40.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/09/4d717852c1cf3f854b76c7110a5d00883bc3c99288b9b0dbcbeb9e306eb6/opentelemetry_exporter_otlp_proto_common-1.44.0.tar.gz", hash = "sha256:dc87a5a5bc58f149a56d1547e4691588fa12994cdc3bc039a694ccb3375862ac", size = 20202, upload-time = "2026-07-16T15:25:37.658Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/48/bce76d3ea772b609757e9bc844e02ab408a6446609bf74fb562062ba6b71/opentelemetry_exporter_otlp_proto_common-1.41.1-py3-none-any.whl", hash = "sha256:10da74dad6a49344b9b7b21b6182e3060373a235fde1528616d5f01f92e66aa9", size = 18366, upload-time = "2026-04-24T13:15:18.917Z" }, + { url = "https://files.pythonhosted.org/packages/5e/71/65fd9d54c10b860f87c045ccee1264cab7011268895d3528818a29c1172a/opentelemetry_exporter_otlp_proto_common-1.44.0-py3-none-any.whl", hash = "sha256:9a9fe61bba73d802904bc989f1d6b4a7b1ee40f06c40e98d6f85af65aaebb694", size = 17045, upload-time = "2026-07-16T15:25:18.201Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, @@ -1444,14 +1431,14 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/9b/e4503060b8695579dbaad187dc8cef4554188de68748c88060599b77489e/opentelemetry_exporter_otlp_proto_grpc-1.41.1.tar.gz", hash = "sha256:b05df8fa1333dc9a3fda36b676b96b5095ab6016d3f0c3296d430d629ba1443b", size = 25755, upload-time = "2026-04-24T13:15:41.93Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/47/80d9e9d468dc5de3af5096f5ccdb065fa4dd1470f74495cc53e59e397f47/opentelemetry_exporter_otlp_proto_grpc-1.44.0.tar.gz", hash = "sha256:40d1ae9e03fcc36de3cbac610cc99f35894938bff9cfd90fc4ec68bd85448463", size = 27225, upload-time = "2026-07-16T15:25:38.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/f2/c54f33c92443d087703e57e52e55f22f111373a5c4c4aa349ea60efe512e/opentelemetry_exporter_otlp_proto_grpc-1.41.1-py3-none-any.whl", hash = "sha256:537926dcef951136992479af1d9cd88f25e33d56c530e9f020ed57774dca2f94", size = 20297, upload-time = "2026-04-24T13:15:20.212Z" }, + { url = "https://files.pythonhosted.org/packages/54/29/6ae42ba32b153ae0a44ae125f0caff2188bbe62d99c82d1768da30864e72/opentelemetry_exporter_otlp_proto_grpc-1.44.0-py3-none-any.whl", hash = "sha256:6a1a645ea182a2f59440c51fa8301d309f3324a8f9d65f8395584b064b67ee4e", size = 19624, upload-time = "2026-07-16T15:25:19.096Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, @@ -1462,14 +1449,14 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/33/5b/9d3c7f70cca10136ba82a81e738dee626c8e7fc61c6887ea9a58bf34c606/opentelemetry_exporter_otlp_proto_http-1.41.1.tar.gz", hash = "sha256:4747a9604c8550ab38c6fd6180e2fcb80de3267060bef2c306bad3cb443302bc", size = 24139, upload-time = "2026-04-24T13:15:42.977Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/87/95e2a5aaa795b4e2260d74e16df2d5541deb2ea9de010bcd615f4dee2654/opentelemetry_exporter_otlp_proto_http-1.44.0.tar.gz", hash = "sha256:c633d7270ad6b57cd4cfbe8b0007a9e2e7c0cb50bd6c50fe2a7b245f721a09d8", size = 25806, upload-time = "2026-07-16T15:25:39.162Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/4d/ef07ff2fc630849f2080ae0ae73a61f67257905b7ac79066640bfa0c5739/opentelemetry_exporter_otlp_proto_http-1.41.1-py3-none-any.whl", hash = "sha256:1a21e8f49c7a946d935551e90947d6c3eb39236723c6624401da0f33d68edcb4", size = 22673, upload-time = "2026-04-24T13:15:21.313Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d0/fdeb1a98d8d3a6205f5f297c51b4a9bfe65126ab60339669bbe3dd54c2e2/opentelemetry_exporter_otlp_proto_http-1.44.0-py3-none-any.whl", hash = "sha256:838592fce774c1c8bb7b9a0a7facbfa82e17be5a8a4e94cef10cb84ae026bae3", size = 21850, upload-time = "2026-07-16T15:25:20.006Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.62b1" +version = "0.65b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1477,14 +1464,14 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/cb/0523b92c112a6cc70be43724343dc45225d3af134419844d7879a07755d4/opentelemetry_instrumentation-0.62b1.tar.gz", hash = "sha256:90e92a905ba4f84db06ac3aec96701df6c079b2d66e9379f8739f0a1bdcc7f45", size = 34043, upload-time = "2026-04-24T13:22:31.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/91/3c58961cb0360cd60509064734f0be4275383c8681d73c580a40ca83ddce/opentelemetry_instrumentation-0.65b0.tar.gz", hash = "sha256:071d9d9eced9bd6460444ec3b0c77229870ed05a881c22c84fdede58e4eed09b", size = 42689, upload-time = "2026-07-16T15:25:50.275Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/0f/45adbaea1f81b847cffdcee4f4b5f89297e42facf7fac78c7aaac4c38e75/opentelemetry_instrumentation-0.62b1-py3-none-any.whl", hash = "sha256:976fc6e640f2006599e97429c949e622c108d0c17c2059347d1e6c93c707f257", size = 34163, upload-time = "2026-04-24T13:21:31.722Z" }, + { url = "https://files.pythonhosted.org/packages/40/7b/85eab1215f72adf0e68d3dc4a679b9bff993fa679ff34cd8dd378e2659fd/opentelemetry_instrumentation-0.65b0-py3-none-any.whl", hash = "sha256:ea967a72b9939b5fcfdad572753b4306c59dcb99e3f382d95dae04286805e137", size = 36717, upload-time = "2026-07-16T15:24:51.424Z" }, ] [[package]] name = "opentelemetry-instrumentation-asgi" -version = "0.62b1" +version = "0.65b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, @@ -1493,14 +1480,14 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/43/b2f0703ff46718ff7b17d7fbf8e9d7f20e26a23c7c325092dd762d09cf9d/opentelemetry_instrumentation_asgi-0.62b1.tar.gz", hash = "sha256:7cf5f5d5c493bbb1edd2bd6d51fa879d964e94048904017258a32ffa47329310", size = 26781, upload-time = "2026-04-24T13:22:37.158Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/83/8e8e83b7ac285281687c7be2fd305213ccccbb8c0a2dd4fb45a8ccaf12c7/opentelemetry_instrumentation_asgi-0.65b0.tar.gz", hash = "sha256:892bca67c56522ffa85a8a83cf934d7b50b3be2132e45cbee705825f0a5ba426", size = 26140, upload-time = "2026-07-16T15:25:54.544Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/41/968c1fe12fb90abffca6620e65d4af91451c02ecca8f74a17a62cac490de/opentelemetry_instrumentation_asgi-0.62b1-py3-none-any.whl", hash = "sha256:b7f89be48528512619bd54fa2459f72afb1695ba71d7024d382ad96d467e7fa8", size = 17011, upload-time = "2026-04-24T13:21:38.006Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9c/376962840b619d2d55fe8ee2285f8c70971c090e5fff614516fc654a6f3a/opentelemetry_instrumentation_asgi-0.65b0-py3-none-any.whl", hash = "sha256:3a845a8ebd1c4ef0d8263401e6545f5b219b2feee612090d50f578a87e71fd65", size = 15903, upload-time = "2026-07-16T15:24:57.198Z" }, ] [[package]] name = "opentelemetry-instrumentation-botocore" -version = "0.62b1" +version = "0.65b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1509,14 +1496,14 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/b8/47f6b6eb37f790f0de4339d6ca05b5222877c8327c1f0a967e6b443494d9/opentelemetry_instrumentation_botocore-0.62b1.tar.gz", hash = "sha256:2191ebfbb9cd6354ef1de6c0dd127c1cc847fabb7c9c25b8d000e766ce1b5671", size = 125970, upload-time = "2026-04-24T13:22:43.735Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/03/a0c6a0f34ba3cacda527317e7f2a5a1fcfd21c9911eee3e6fc2be63d0a93/opentelemetry_instrumentation_botocore-0.65b0.tar.gz", hash = "sha256:64987326ac98a47a85821606a981f5058864de0e98b43c8de892eb0fe625b04e", size = 125099, upload-time = "2026-07-16T15:25:59.335Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/45/a2d8655d4f2b2c135943ca99fde9698685ef33f4229daf1263873f34bd0e/opentelemetry_instrumentation_botocore-0.62b1-py3-none-any.whl", hash = "sha256:7768cc2650b979649fe3475238baa7779e4194e25997d164b7eca276db559d43", size = 40517, upload-time = "2026-04-24T13:21:44.429Z" }, + { url = "https://files.pythonhosted.org/packages/3f/59/0b51f55d9fb96949674efe895d330506b7b8c1b13a6401f0eec49d1196af/opentelemetry_instrumentation_botocore-0.65b0-py3-none-any.whl", hash = "sha256:a5e80b9423f8369c9bbc27a64b31f02cc44218633028ad8d1ace0a2a7fc8f4cd", size = 36242, upload-time = "2026-07-16T15:25:03.377Z" }, ] [[package]] name = "opentelemetry-instrumentation-fastapi" -version = "0.62b1" +version = "0.65b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -1525,9 +1512,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/38/91780475a25370b6d483afbaed3e1e170459d6351c5f7c08d66b65e2172e/opentelemetry_instrumentation_fastapi-0.62b1.tar.gz", hash = "sha256:b377d4ba32868fb1ff0f64da3fcdd3aa154d698fc83d65f5d380ea21bf31ee19", size = 25054, upload-time = "2026-04-24T13:22:50.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/23/b057f8196d06efdc1b50e3ff11fbc499a7d96b35c87f217eb7885542f4ea/opentelemetry_instrumentation_fastapi-0.65b0.tar.gz", hash = "sha256:10a3a95486036230413a58fe4fdf4a83fa6bba46918407e527476994bd92bd97", size = 26236, upload-time = "2026-07-16T15:26:05.954Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/6f/602e4081d3fe82731aff7e3e9c2f1662d85701841d6dc25f16a1874e11cd/opentelemetry_instrumentation_fastapi-0.62b1-py3-none-any.whl", hash = "sha256:93fa9cc4f315819aee5f4fceb6196c1e5b0fbd789c5520c631de228bd3e5285b", size = 13484, upload-time = "2026-04-24T13:21:54.538Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b0/c9b0300d33349ecc3dfd2362516eaffc44877e90970e6a52178ff953fec3/opentelemetry_instrumentation_fastapi-0.65b0-py3-none-any.whl", hash = "sha256:cda2610a0ec1b22d19886f33e4d861e9f5dbb886aeaa3a1263b47aff82c36943", size = 13261, upload-time = "2026-07-16T15:25:12.429Z" }, ] [[package]] @@ -1544,50 +1531,50 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/e8/633c6d8a9c8840338b105907e55c32d3da1983abab5e52f899f72a82c3d1/opentelemetry_proto-1.41.1.tar.gz", hash = "sha256:4b9d2eb631237ea43b80e16c073af438554e32bc7e9e3f8ca4a9582f900020e5", size = 45670, upload-time = "2026-04-24T13:15:49.768Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/01/40ac4ae9a149263cc52c2cee200ddd80cb6d8db1a4610abf8eabce0fe771/opentelemetry_proto-1.44.0.tar.gz", hash = "sha256:c547a79c2f8c0c515d31509154682e5921c7cfd5ca67b70e1f9266e2c3e103f3", size = 46488, upload-time = "2026-07-16T15:25:45.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/1e/5cd77035e3e82070e2265a63a760f715aacd3cb16dddc7efee913f297fcc/opentelemetry_proto-1.41.1-py3-none-any.whl", hash = "sha256:0496713b804d127a4147e32849fbaf5683fac8ee98550e8e7679cd706c289720", size = 72076, upload-time = "2026-04-24T13:15:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7c/8be563d68e93bbefa5c8affb82ddcff91b3ad858ce49957ba7b16fd3e0ab/opentelemetry_proto-1.44.0-py3-none-any.whl", hash = "sha256:898b155a0e1557afd867478fb6158e8122a46329ca0bb8dc53cc55e98f017f56", size = 72483, upload-time = "2026-07-16T15:25:28.429Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.41.1" +version = "1.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/d0/54ee30dab82fb0acda23d144502771ff76ef8728459c83c3e89ef9fb1825/opentelemetry_sdk-1.41.1.tar.gz", hash = "sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6", size = 230180, upload-time = "2026-04-24T13:15:50.991Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/77/a6592cbc7c8d9bcc9d6757a9df45e04a7c585e3e6e7a13456da522b21109/opentelemetry_sdk-1.44.0.tar.gz", hash = "sha256:cebe7f65dc12f26ead75c6064de12fd2a9052e5060c0272d402cfa203aae123b", size = 208624, upload-time = "2026-07-16T15:25:46.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/e7/a1420b698aad018e1cf60fdbaaccbe49021fb415e2a0d81c242f4c518f54/opentelemetry_sdk-1.41.1-py3-none-any.whl", hash = "sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d", size = 180213, upload-time = "2026-04-24T13:15:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/e7/23/ff077e61886ee020a17ce9c8b6fa11c601c8d8345b09ea24f605445df62a/opentelemetry_sdk-1.44.0-py3-none-any.whl", hash = "sha256:df081c4c6bcfdb1211e3e86140376792643128a25f8d72d1d27675936e7e96ad", size = 137221, upload-time = "2026-07-16T15:25:29.534Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.62b1" +version = "0.65b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/de/911ac9e309052aca1b20b2d5549d3db45d1011e1a610e552c6ccdd1b64f8/opentelemetry_semantic_conventions-0.62b1.tar.gz", hash = "sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802", size = 145750, upload-time = "2026-04-24T13:15:52.236Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/73/0cbdebcb4cf545fdd328da14f5137e37d0770c3f26185e478b0d15d94f50/opentelemetry_semantic_conventions-0.65b0.tar.gz", hash = "sha256:f9b2b81e9d5b64f11bc952075e7e9c7fb0aab075c7fd1c46d597f1b919852d60", size = 148774, upload-time = "2026-07-16T15:25:46.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/a6/83dc2ab6fa397ee66fba04fe2e74bdf7be3b3870005359ceb7689103c058/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl", hash = "sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c", size = 231620, upload-time = "2026-04-24T13:15:35.454Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0e/49df70d9b81fb5cbae4bbf2a49d865b09bcbcbc4eb53f5851b1027738d78/opentelemetry_semantic_conventions-0.65b0-py3-none-any.whl", hash = "sha256:1cacde7b0ad306f84c5ef08c3dbe1bbaf20165bba6f8bff43b670e555a086bcb", size = 204645, upload-time = "2026-07-16T15:25:30.688Z" }, ] [[package]] name = "opentelemetry-util-http" -version = "0.62b1" +version = "0.65b0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/1b/aa71b63e18d30a8384036b9937f40f7618f8030a7aa213155fb54f6f2b47/opentelemetry_util_http-0.62b1.tar.gz", hash = "sha256:adf6facbb89aef8f8bc566e2f04624942ba08a7b678b3479a91051a8f4dc70a3", size = 11393, upload-time = "2026-04-24T13:23:12.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/a9/d7525a59fdd240e69b5af4a6338e78fafa1b4203394122cbd6701fb5f84a/opentelemetry_util_http-0.65b0.tar.gz", hash = "sha256:84f82d826978bba416ab453460ff6a7391cdc3534c93a786595e4068680016b7", size = 11243, upload-time = "2026-07-16T15:26:27.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/a9d9d32161c1ced61346267db4c9702da54f81ec5dc88214bc65c23f4e9d/opentelemetry_util_http-0.62b1-py3-none-any.whl", hash = "sha256:c57e8a6c19fc422c288e6074e882f506f85030b69b7376182f74f9257b9261f0", size = 9295, upload-time = "2026-04-24T13:22:28.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/3f/ab8d29df207ce5f470a07fa96ebb48af4e95b7fab7e7635311b9a32f2fab/opentelemetry_util_http-0.65b0-py3-none-any.whl", hash = "sha256:7553b606f963097cb190536dc30556cce85090692e471a422fff30ca29b04348", size = 8245, upload-time = "2026-07-16T15:25:46.482Z" }, ] [[package]] @@ -2338,9 +2325,9 @@ requires-dist = [ { name = "fastapi", extras = ["standard"], specifier = "~=0.135" }, { name = "httptools", specifier = "~=0.8.0" }, { name = "opentelemetry-exporter-otlp", specifier = "~=1.41" }, - { name = "opentelemetry-instrumentation-asgi", specifier = "==0.62b1" }, - { name = "opentelemetry-instrumentation-botocore", specifier = "==0.62b1" }, - { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.62b1" }, + { name = "opentelemetry-instrumentation-asgi", specifier = "==0.65b0" }, + { name = "opentelemetry-instrumentation-botocore", specifier = "==0.65b0" }, + { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.65b0" }, { name = "opentelemetry-sdk", specifier = "~=1.41" }, { name = "orjson", specifier = "~=3.11" }, { name = "pydantic-settings", specifier = "~=2.13" }, @@ -2779,12 +2766,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/f4/ed5c402ac8fde4403ed3366c2716bfddc8a6677ebd59f3d62772cc7fe468/yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688", size = 97222, upload-time = "2026-07-20T02:07:41.55Z" }, { url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" }, ] - -[[package]] -name = "zipp" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, -]