From 837118b086c6ba0c8d0c90bb8243280e0b59b112 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:44:35 +0200 Subject: [PATCH 01/13] feat: audit middleware to extract userid --- src/rasenmaeher_api/web/application.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/rasenmaeher_api/web/application.py b/src/rasenmaeher_api/web/application.py index 5002809..e9fb668 100644 --- a/src/rasenmaeher_api/web/application.py +++ b/src/rasenmaeher_api/web/application.py @@ -6,7 +6,7 @@ from contextlib import asynccontextmanager from fastapi import FastAPI -from libpvarki.logging import init_logging +from libpvarki.auditlogging import init_audit, AuditMiddleware import aiohttp from libadvian.tasks import TaskMaster @@ -48,6 +48,13 @@ def get_app_no_init() -> FastAPI: app = FastAPI(docs_url="/api/docs", openapi_url="/api/openapi.json", lifespan=app_lifespan, version=__version__) app.include_router(router=api_router, prefix="/api/v1") app.include_router(router=api_router_v2, prefix="/api/v2") + + # AuditMiddleware extracts user identity from nginx headers (X-ClientCert-DN, + # X-Real-IP, X-Request-ID) and sets ContextVars for the request duration. + # This enables audit logging throughout the request lifecycle. + # Must be added before other middleware to ensure context is available. + app.add_middleware(AuditMiddleware) + # FIXME: figure out WTF mypy wants here, or has FastAPI changed something ? app.add_middleware(DBConnectionMiddleware, config=DBConfig.singleton()) # type: ignore return app @@ -55,12 +62,11 @@ def get_app_no_init() -> FastAPI: def get_app() -> FastAPI: """Returns the FastAPI application.""" - init_logging(RMSettings.singleton().log_level_int) + init_audit(RMSettings.singleton().log_level_int) app = get_app_no_init() LOGGER.info("API init done, setting log verbosity to '{}'.".format(RMSettings.singleton().log_level)) return app - async def report_to_kraftwerk() -> None: """Call the KRAFTWERK announce URL if configured""" conf = RMSettings.singleton() From 59cbc8e13e9fcf42c62d20ac84579f54fa3b345d Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Tue, 23 Dec 2025 00:06:22 +0200 Subject: [PATCH 02/13] feat: implement auditlogging for enrollment views --- .../web/api/enrollment/views.py | 417 ++++++++++++++++-- 1 file changed, 390 insertions(+), 27 deletions(-) diff --git a/src/rasenmaeher_api/web/api/enrollment/views.py b/src/rasenmaeher_api/web/api/enrollment/views.py index e5000e4..82afdb4 100644 --- a/src/rasenmaeher_api/web/api/enrollment/views.py +++ b/src/rasenmaeher_api/web/api/enrollment/views.py @@ -9,6 +9,8 @@ from multikeyjwt import Issuer from libpvarki.schemas.generic import OperationResultResponse +from libpvarki.auditlogging import audit_authentication, audit_iam + from .schema import ( EnrollmentStatusIn, EnrollmentStatusOut, @@ -177,6 +179,7 @@ async def request_enrollment_list(code: Optional[str] = None) -> EnrollmentListO dependencies=[Depends(ValidUser(auto_error=True, require_roles=["admin"]))], ) async def request_enrollment_init( + request: Request, request_in: EnrollmentInitIn = Body( None, examples=[EnrollmentInitIn.Config.schema_extra["examples"]], @@ -185,16 +188,43 @@ async def request_enrollment_init( """ Add new callsign (enrollment) to environment. """ + callsign = request_in.callsign - # TODO ADD POOL NAME CHECK + try: + new_enrollment = await Enrollment.create_for_callsign( + callsign=callsign, pool=None, extra={}, csr=request_in.csr + ) + # Create JWT token for user + claims = {"sub": callsign} + new_jwt = Issuer.singleton().issue(claims) + + LOGGER.audit( + "Enrollment created by admin: %s", + callsign, + extra=audit_iam( + action="enrollment_create", + outcome="success", + target_user=callsign, + admin_action=True, + ), + ) - new_enrollment = await Enrollment.create_for_callsign( - callsign=request_in.callsign, pool=None, extra={}, csr=request_in.csr - ) - # Create JWT token for user - claims = {"sub": request_in.callsign} - new_jwt = Issuer.singleton().issue(claims) - return EnrollmentInitOut(callsign=new_enrollment.callsign, jwt=new_jwt, approvecode=new_enrollment.approvecode) + return EnrollmentInitOut(callsign=new_enrollment.callsign, jwt=new_jwt, approvecode=new_enrollment.approvecode) + + except Exception as exc: + LOGGER.audit( + "Enrollment creation failed: %s", + callsign, + extra=audit_iam( + action="enrollment_create", + outcome="failure", + target_user=callsign, + admin_action=True, + error_code="ENROLLMENT_CREATE_FAILED", + error_message=str(exc), + ), + ) + raise @ENROLLMENT_ROUTER.post( @@ -212,13 +242,54 @@ async def request_enrollment_promote( """ "Promote" callsign/user/enrollment to have 'admin' rights """ + target_callsign = request_in.callsign - obj = await Person.by_callsign(callsign=request_in.callsign) + try: + obj = await Person.by_callsign(callsign=target_callsign) + except NotFound: + LOGGER.audit( + "User promotion failed - user not found: %s", + target_callsign, + extra=audit_iam( + action="user_promote", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="USER_NOT_FOUND", + error_message="Target user does not exist", + ), + ) + raise HTTPException(status_code=404, detail="User not found") role_added = await obj.assign_role(role="admin") if role_added: + LOGGER.audit( + "User promoted to admin: %s", + target_callsign, + extra=audit_iam( + action="user_promote", + outcome="success", + target_user=target_callsign, + admin_action=True, + previous_role="user", + new_role="admin", + ), + ) return OperationResultResponse(success=True, extra="Promote done") + # User already has admin role + LOGGER.audit( + "User promotion skipped - already admin: %s", + target_callsign, + extra=audit_iam( + action="user_promote", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="ALREADY_ADMIN", + error_message="User already has admin privileges", + ), + ) reason = "Given callsign/callsign already has elevated permissions." LOGGER.error("{} : {}".format(request.url, reason)) raise HTTPException(status_code=400, detail=reason) @@ -239,12 +310,53 @@ async def request_enrollment_demote( """ "Demote" callsign/user/enrollment from having 'admin' rights. callsign_hash can be used too. """ + target_callsign = request_in.callsign + + try: + obj = await Person.by_callsign(callsign=target_callsign) + except NotFound: + LOGGER.audit( + "User demotion failed - user not found: %s", + target_callsign, + extra=audit_iam( + action="user_demote", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="USER_NOT_FOUND", + error_message="Target user does not exist", + ), + ) + raise HTTPException(status_code=404, detail="User not found") - obj = await Person.by_callsign(callsign=request_in.callsign) _role_removed = await obj.remove_role(role="admin") if _role_removed: + LOGGER.audit( + "User demoted from admin: %s", + target_callsign, + extra=audit_iam( + action="user_demote", + outcome="success", + target_user=target_callsign, + admin_action=True, + previous_role="admin", + new_role="user", + ), + ) return OperationResultResponse(success=True, extra="Demote done") + LOGGER.audit( + "User demotion skipped - not an admin: %s", + target_callsign, + extra=audit_iam( + action="user_demote", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="NOT_ADMIN", + error_message="User does not have admin privileges to remove", + ), + ) _reason = "Given callsign/callsign_hash doesn't have 'admin' privileges to take away." LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=400, detail=_reason) @@ -265,12 +377,56 @@ async def request_enrollment_lock( """ Lock callsign/user/enrollment so it cannot be used anymore. """ + target_callsign = request_in.callsign - _admin_person = await Person.by_callsign(request.state.mtls_or_jwt.userid) - _usr_enrollment = await Enrollment.by_callsign(callsign=request_in.callsign) - await _usr_enrollment.reject(decider=_admin_person) + try: + _admin_person = await Person.by_callsign(request.state.mtls_or_jwt.userid) + _usr_enrollment = await Enrollment.by_callsign(callsign=target_callsign) + await _usr_enrollment.reject(decider=_admin_person) + + LOGGER.audit( + "Enrollment locked/rejected: %s", + target_callsign, + extra=audit_iam( + action="enrollment_lock", + outcome="success", + target_user=target_callsign, + admin_action=True, + lock_reason=request_in.lock_reason, + ), + ) - return OperationResultResponse(success=True, extra="Lock task done") + return OperationResultResponse(success=True, extra="Lock task done") + + except NotFound: + LOGGER.audit( + "Enrollment lock failed - not found: %s", + target_callsign, + extra=audit_iam( + action="enrollment_lock", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="NOT_FOUND", + error_message="Enrollment not found", + ), + ) + raise HTTPException(status_code=404, detail="Enrollment not found") + + except Exception as exc: + LOGGER.audit( + "Enrollment lock failed: %s", + target_callsign, + extra=audit_iam( + action="enrollment_lock", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="LOCK_FAILED", + error_message=str(exc), + ), + ) + raise @ENROLLMENT_ROUTER.post( @@ -288,13 +444,54 @@ async def post_enrollment_accept( """ Accept callsign_hash (callsign/enrollment) """ + target_callsign = request_in.callsign + + try: + admin_user = await Person.by_callsign(callsign=request.state.mtls_or_jwt.userid) + pending_enrollment = await Enrollment.by_callsign(callsign=target_callsign) + except NotFound: + LOGGER.audit( + "Enrollment approval failed - not found: %s", + target_callsign, + extra=audit_iam( + action="enrollment_approve", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="NOT_FOUND", + error_message="Enrollment or admin user not found", + ), + ) + raise HTTPException(status_code=404, detail="Enrollment not found") - admin_user = await Person.by_callsign(callsign=request.state.mtls_or_jwt.userid) - pending_enrollment = await Enrollment.by_callsign(callsign=request_in.callsign) if request_in.approvecode != pending_enrollment.approvecode: + LOGGER.audit( + "Enrollment approval failed - invalid code: %s", + target_callsign, + extra=audit_iam( + action="enrollment_approve", + outcome="failure", + target_user=target_callsign, + admin_action=True, + error_code="INVALID_APPROVECODE", + error_message="Approval code does not match", + ), + ) raise HTTPException(status_code=403, detail="Invalid approval code for this enrollment") + new_approved_user = await pending_enrollment.approve(approver=admin_user) + LOGGER.audit( + "Enrollment approved: %s", + target_callsign, + extra=audit_iam( + action="enrollment_approve", + outcome="success", + target_user=target_callsign, + admin_action=True, + ), + ) + return OperationResultResponse(success=True, extra=f"Approved {new_approved_user.callsign}") @@ -308,6 +505,18 @@ async def post_invite_code(request: Request) -> EnrollmentInviteCodeCreateOut: Create a new invite code """ pool = await EnrollmentPool.create_for_owner(request.state.person) + + LOGGER.audit( + "Invite code created", + extra=audit_iam( + action="invitecode_create", + outcome="success", + target_resource=pool.invitecode, + target_resource_type="invitecode", + admin_action=True, + ), + ) + return EnrollmentInviteCodeCreateOut(invite_code=pool.invitecode) @@ -322,12 +531,52 @@ async def put_activate_invite_code( """ Activate an invite code """ - obj = await EnrollmentPool.by_invitecode(invitecode=request_in.invite_code) + invite_code = request_in.invite_code + + try: + obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) + except NotFound: + LOGGER.audit( + "Invite code activation failed - not found: %s", + invite_code, + extra=audit_iam( + action="invitecode_activate", + outcome="failure", + target_resource=invite_code, + target_resource_type="invitecode", + error_code="NOT_FOUND", + error_message="Invite code not found", + ), + ) + raise HTTPException(status_code=404, detail="Invite code not found") + _activated_obj = await obj.set_active(state=True) if _activated_obj.active: - return OperationResultResponse(success=True, extra=f"Activated {request_in.invite_code}") - + LOGGER.audit( + "Invite code activated: %s", + invite_code, + extra=audit_iam( + action="invitecode_activate", + outcome="success", + target_resource=invite_code, + target_resource_type="invitecode", + ), + ) + return OperationResultResponse(success=True, extra=f"Activated {invite_code}") + + LOGGER.audit( + "Invite code activation failed: %s", + invite_code, + extra=audit_iam( + action="invitecode_activate", + outcome="failure", + target_resource=invite_code, + target_resource_type="invitecode", + error_code="ACTIVATION_FAILED", + error_message="Unable to activate invite code", + ), + ) _reason = "Error. Unable to activate given invitecode." LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=500, detail=_reason) @@ -344,12 +593,52 @@ async def put_deactivate_invite_code( """ Deactivate an invite code """ - obj = await EnrollmentPool.by_invitecode(invitecode=request_in.invite_code) + invite_code = request_in.invite_code + + try: + obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) + except NotFound: + LOGGER.audit( + "Invite code deactivation failed - not found: %s", + invite_code, + extra=audit_iam( + action="invitecode_deactivate", + outcome="failure", + target_resource=invite_code, + target_resource_type="invitecode", + error_code="NOT_FOUND", + error_message="Invite code not found", + ), + ) + raise HTTPException(status_code=404, detail="Invite code not found") + _deactivated_obj = await obj.set_active(state=False) if _deactivated_obj.active is False: - return OperationResultResponse(success=True, extra=f"Disabled {request_in.invite_code}") - + LOGGER.audit( + "Invite code deactivated: %s", + invite_code, + extra=audit_iam( + action="invitecode_deactivate", + outcome="success", + target_resource=invite_code, + target_resource_type="invitecode", + ), + ) + return OperationResultResponse(success=True, extra=f"Disabled {invite_code}") + + LOGGER.audit( + "Invite code deactivation failed: %s", + invite_code, + extra=audit_iam( + action="invitecode_deactivate", + outcome="failure", + target_resource=invite_code, + target_resource_type="invitecode", + error_code="DEACTIVATION_FAILED", + error_message="Unable to deactivate invite code", + ), + ) _reason = "Error. Unable to deactivate given invitecode." LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=500, detail=_reason) @@ -357,7 +646,6 @@ async def put_deactivate_invite_code( @ENROLLMENT_ROUTER.delete("/invitecode/{invite_code}", response_model=OperationResultResponse) async def delete_invite_code( - # request: Request, invite_code: str, ) -> OperationResultResponse: """ @@ -366,8 +654,35 @@ async def delete_invite_code( try: obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) await obj.delete() + + LOGGER.audit( + "Invite code deleted: %s", + invite_code, + extra=audit_iam( + action="invitecode_delete", + outcome="success", + target_resource=invite_code, + target_resource_type="invitecode", + admin_action=True, + ), + ) + return OperationResultResponse(success=True, extra="Invitecode was deleted") + except NotFound: + LOGGER.audit( + "Invite code deletion failed - not found: %s", + invite_code, + extra=audit_iam( + action="invitecode_delete", + outcome="failure", + target_resource=invite_code, + target_resource_type="invitecode", + admin_action=True, + error_code="NOT_FOUND", + error_message="Invite code not found", + ), + ) return OperationResultResponse(success=False, extra="No such invitecode found") @@ -399,27 +714,75 @@ async def post_enroll_invite_code( """ Enroll with an invite code """ + callsign = request_in.callsign + invite_code = request_in.invite_code # CHECK IF INVITE CODE CAN BE USED - obj = await EnrollmentPool.by_invitecode(invitecode=request_in.invite_code) + try: + obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) + except NotFound: + LOGGER.audit( + "OTP exchange failed - invalid invite code", + extra=audit_authentication( + action="otp_exchange", + outcome="failure", + target_user=callsign, + error_code="INVALID_INVITECODE", + error_message="Invite code not found", + ), + ) + raise HTTPException(status_code=404, detail="Invite code not found") + if obj.active is False: + LOGGER.audit( + "OTP exchange failed - invite code disabled", + extra=audit_authentication( + action="otp_exchange", + outcome="failure", + target_user=callsign, + error_code="INVITECODE_DISABLED", + error_message="Invite code is disabled", + ), + ) _reason = "Error. invitecode disabled." LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=400, detail=_reason) # CHECK THAT THE CALLSIGN CAN BE USED try: - await Enrollment.by_callsign(callsign=request_in.callsign) + await Enrollment.by_callsign(callsign=callsign) + LOGGER.audit( + "OTP exchange failed - callsign already taken: %s", + callsign, + extra=audit_authentication( + action="otp_exchange", + outcome="failure", + target_user=callsign, + error_code="CALLSIGN_TAKEN", + error_message="Callsign is already in use", + ), + ) _reason = "Error. callsign/callsign already taken." LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=400, detail=_reason) except NotFound: pass - enrollment = await obj.create_enrollment(callsign=request_in.callsign, csr=request_in.csr) + enrollment = await obj.create_enrollment(callsign=callsign, csr=request_in.csr) # Create JWT token for user - claims = {"sub": request_in.callsign} + claims = {"sub": callsign} new_jwt = Issuer.singleton().issue(claims) + LOGGER.audit( + "OTP exchange successful - enrollment created: %s", + callsign, + extra=audit_authentication( + action="otp_exchange", + outcome="success", + target_user=callsign, + enrollment_state="pending_approval", + ), + ) + return EnrollmentInitOut(callsign=enrollment.callsign, jwt=new_jwt, approvecode=enrollment.approvecode) From db5e4330f6ff748f0e9e51c52e1c6f57bb48dac9 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Tue, 23 Dec 2025 01:41:51 +0200 Subject: [PATCH 03/13] chore: refer to development libpvarki, fix mistakes in auditlog implementation --- poetry.lock | 44 ++++--- pyproject.toml | 2 +- .../web/api/enrollment/views.py | 124 ++++++++++-------- src/rasenmaeher_api/web/application.py | 1 + 4 files changed, 92 insertions(+), 79 deletions(-) diff --git a/poetry.lock b/poetry.lock index be32e13..6f7901d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1424,15 +1424,13 @@ typing-extensions = ">=4.5.0" [[package]] name = "libadvian" -version = "1.9.0" +version = "1.10.0" description = "Small helpers that do not warrant their own library" optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9,<4.0" groups = ["main"] -files = [ - {file = "libadvian-1.9.0-py3-none-any.whl", hash = "sha256:2461145e0980c9af95480f063d565c01cd68f52c90f19818ec5efbc537d1f702"}, - {file = "libadvian-1.9.0.tar.gz", hash = "sha256:97c78963cb08a70d856be409faa3c9f9bb088787d036dfce68b8e449a199b736"}, -] +files = [] +develop = false [package.extras] all = ["http"] @@ -1440,33 +1438,37 @@ http = ["frozendict", "requests"] logstash = ["http"] vector = ["http"] +[package.source] +type = "git" +url = "https://gitlab.com/advian-oss/python-libadvian.git" +reference = "log_levels" +resolved_reference = "1002a851ba6284551132dbef075c2fa0e1ba110d" + [[package]] name = "libpvarki" -version = "1.9.1" +version = "1.9.2" description = "Common helpers like standard logging init" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "^3.8" groups = ["main"] -files = [ - {file = "libpvarki-1.9.1-py3-none-any.whl", hash = "sha256:de61747063e5f8912b069dbf6fa7bebe0daac71253462265bd670a143f202885"}, - {file = "libpvarki-1.9.1.tar.gz", hash = "sha256:bbfd7bd9b764d15371acc77421b5db9653a709adf652c83f498541d9b7734be4"}, -] +files = [] +develop = false [package.dependencies] -aiodns = ">=3.0,<4.0" +aiodns = "^3.0" aiohttp = ">=3.10.2,<4.0" -brotli = ">=1.0,<2.0" +brotli = "^1.0" cryptography = ">=41.0" -ecs-logging = ">=2.0,<3.0" +ecs-logging = "^2.0" fastapi = ">0.89,<1.0" -libadvian = ">=1.4,<2.0" +libadvian = {git = "https://gitlab.com/advian-oss/python-libadvian.git", branch = "log_levels"} pydantic = ">=1.10,<2.0" -pyopenssl = ">=23.2" [package.source] -type = "legacy" -url = "https://nexus.dev.pvarki.fi/repository/pypilocal/simple" -reference = "nexuslocal" +type = "git" +url = "https://github.com/pvarki/python-libpvarki.git" +reference = "audit-logging-v1" +resolved_reference = "e7d1fbadfd2d9aee1f479a44c367aabf84f68b57" [[package]] name = "librt" @@ -3606,4 +3608,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "770ee3f58d9fdd0eefe599c527a94f09feebd078b4bac4db1809f6fd669a80d5" +content-hash = "381a59a9341001950c0c7e3bce287a5d962be4cf6c0ffe9306feecd5d37d091f" diff --git a/pyproject.toml b/pyproject.toml index af63035..6f22005 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ uvicorn = {version = "^0.20", extras = ["standard"]} gunicorn = "^20.1" pyopenssl = "^23.1" # Can't update to 2.0 before pydantic migration is done -libpvarki = { version="^1.9", source="nexuslocal"} +libpvarki = { git = "https://github.com/pvarki/python-libpvarki.git", branch = "audit-logging-v1" } openapi-readme = "^0.2" python-multipart = "^0.0.6" aiohttp = ">=3.11.10,<4.0" diff --git a/src/rasenmaeher_api/web/api/enrollment/views.py b/src/rasenmaeher_api/web/api/enrollment/views.py index 82afdb4..f888348 100644 --- a/src/rasenmaeher_api/web/api/enrollment/views.py +++ b/src/rasenmaeher_api/web/api/enrollment/views.py @@ -9,7 +9,7 @@ from multikeyjwt import Issuer from libpvarki.schemas.generic import OperationResultResponse -from libpvarki.auditlogging import audit_authentication, audit_iam +from libpvarki.auditlogging import audit_authentication, audit_iam, AUDIT from .schema import ( EnrollmentStatusIn, @@ -179,7 +179,6 @@ async def request_enrollment_list(code: Optional[str] = None) -> EnrollmentListO dependencies=[Depends(ValidUser(auto_error=True, require_roles=["admin"]))], ) async def request_enrollment_init( - request: Request, request_in: EnrollmentInitIn = Body( None, examples=[EnrollmentInitIn.Config.schema_extra["examples"]], @@ -198,7 +197,8 @@ async def request_enrollment_init( claims = {"sub": callsign} new_jwt = Issuer.singleton().issue(claims) - LOGGER.audit( + LOGGER.log( + AUDIT, "Enrollment created by admin: %s", callsign, extra=audit_iam( @@ -212,7 +212,8 @@ async def request_enrollment_init( return EnrollmentInitOut(callsign=new_enrollment.callsign, jwt=new_jwt, approvecode=new_enrollment.approvecode) except Exception as exc: - LOGGER.audit( + LOGGER.log( + AUDIT, "Enrollment creation failed: %s", callsign, extra=audit_iam( @@ -246,8 +247,9 @@ async def request_enrollment_promote( try: obj = await Person.by_callsign(callsign=target_callsign) - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "User promotion failed - user not found: %s", target_callsign, extra=audit_iam( @@ -259,11 +261,12 @@ async def request_enrollment_promote( error_message="Target user does not exist", ), ) - raise HTTPException(status_code=404, detail="User not found") + raise HTTPException(status_code=404, detail="User not found") from exc role_added = await obj.assign_role(role="admin") if role_added: - LOGGER.audit( + LOGGER.log( + AUDIT, "User promoted to admin: %s", target_callsign, extra=audit_iam( @@ -278,7 +281,8 @@ async def request_enrollment_promote( return OperationResultResponse(success=True, extra="Promote done") # User already has admin role - LOGGER.audit( + LOGGER.log( + AUDIT, "User promotion skipped - already admin: %s", target_callsign, extra=audit_iam( @@ -314,8 +318,9 @@ async def request_enrollment_demote( try: obj = await Person.by_callsign(callsign=target_callsign) - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "User demotion failed - user not found: %s", target_callsign, extra=audit_iam( @@ -327,11 +332,12 @@ async def request_enrollment_demote( error_message="Target user does not exist", ), ) - raise HTTPException(status_code=404, detail="User not found") + raise HTTPException(status_code=404, detail="User not found") from exc _role_removed = await obj.remove_role(role="admin") if _role_removed: - LOGGER.audit( + LOGGER.log( + AUDIT, "User demoted from admin: %s", target_callsign, extra=audit_iam( @@ -345,7 +351,8 @@ async def request_enrollment_demote( ) return OperationResultResponse(success=True, extra="Demote done") - LOGGER.audit( + LOGGER.log( + AUDIT, "User demotion skipped - not an admin: %s", target_callsign, extra=audit_iam( @@ -384,7 +391,8 @@ async def request_enrollment_lock( _usr_enrollment = await Enrollment.by_callsign(callsign=target_callsign) await _usr_enrollment.reject(decider=_admin_person) - LOGGER.audit( + LOGGER.log( + AUDIT, "Enrollment locked/rejected: %s", target_callsign, extra=audit_iam( @@ -398,8 +406,9 @@ async def request_enrollment_lock( return OperationResultResponse(success=True, extra="Lock task done") - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "Enrollment lock failed - not found: %s", target_callsign, extra=audit_iam( @@ -411,22 +420,7 @@ async def request_enrollment_lock( error_message="Enrollment not found", ), ) - raise HTTPException(status_code=404, detail="Enrollment not found") - - except Exception as exc: - LOGGER.audit( - "Enrollment lock failed: %s", - target_callsign, - extra=audit_iam( - action="enrollment_lock", - outcome="failure", - target_user=target_callsign, - admin_action=True, - error_code="LOCK_FAILED", - error_message=str(exc), - ), - ) - raise + raise HTTPException(status_code=404, detail="Enrollment not found") from exc @ENROLLMENT_ROUTER.post( @@ -449,8 +443,9 @@ async def post_enrollment_accept( try: admin_user = await Person.by_callsign(callsign=request.state.mtls_or_jwt.userid) pending_enrollment = await Enrollment.by_callsign(callsign=target_callsign) - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "Enrollment approval failed - not found: %s", target_callsign, extra=audit_iam( @@ -462,10 +457,11 @@ async def post_enrollment_accept( error_message="Enrollment or admin user not found", ), ) - raise HTTPException(status_code=404, detail="Enrollment not found") + raise HTTPException(status_code=404, detail="Enrollment not found") from exc if request_in.approvecode != pending_enrollment.approvecode: - LOGGER.audit( + LOGGER.log( + AUDIT, "Enrollment approval failed - invalid code: %s", target_callsign, extra=audit_iam( @@ -481,7 +477,8 @@ async def post_enrollment_accept( new_approved_user = await pending_enrollment.approve(approver=admin_user) - LOGGER.audit( + LOGGER.log( + AUDIT, "Enrollment approved: %s", target_callsign, extra=audit_iam( @@ -506,7 +503,8 @@ async def post_invite_code(request: Request) -> EnrollmentInviteCodeCreateOut: """ pool = await EnrollmentPool.create_for_owner(request.state.person) - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code created", extra=audit_iam( action="invitecode_create", @@ -535,8 +533,9 @@ async def put_activate_invite_code( try: obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "Invite code activation failed - not found: %s", invite_code, extra=audit_iam( @@ -548,12 +547,13 @@ async def put_activate_invite_code( error_message="Invite code not found", ), ) - raise HTTPException(status_code=404, detail="Invite code not found") + raise HTTPException(status_code=404, detail="Invite code not found") from exc _activated_obj = await obj.set_active(state=True) if _activated_obj.active: - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code activated: %s", invite_code, extra=audit_iam( @@ -565,7 +565,8 @@ async def put_activate_invite_code( ) return OperationResultResponse(success=True, extra=f"Activated {invite_code}") - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code activation failed: %s", invite_code, extra=audit_iam( @@ -597,8 +598,9 @@ async def put_deactivate_invite_code( try: obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "Invite code deactivation failed - not found: %s", invite_code, extra=audit_iam( @@ -610,12 +612,13 @@ async def put_deactivate_invite_code( error_message="Invite code not found", ), ) - raise HTTPException(status_code=404, detail="Invite code not found") + raise HTTPException(status_code=404, detail="Invite code not found") from exc _deactivated_obj = await obj.set_active(state=False) if _deactivated_obj.active is False: - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code deactivated: %s", invite_code, extra=audit_iam( @@ -627,7 +630,8 @@ async def put_deactivate_invite_code( ) return OperationResultResponse(success=True, extra=f"Disabled {invite_code}") - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code deactivation failed: %s", invite_code, extra=audit_iam( @@ -655,7 +659,8 @@ async def delete_invite_code( obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) await obj.delete() - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code deleted: %s", invite_code, extra=audit_iam( @@ -670,7 +675,8 @@ async def delete_invite_code( return OperationResultResponse(success=True, extra="Invitecode was deleted") except NotFound: - LOGGER.audit( + LOGGER.log( + AUDIT, "Invite code deletion failed - not found: %s", invite_code, extra=audit_iam( @@ -720,8 +726,9 @@ async def post_enroll_invite_code( # CHECK IF INVITE CODE CAN BE USED try: obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) - except NotFound: - LOGGER.audit( + except NotFound as exc: + LOGGER.log( + AUDIT, "OTP exchange failed - invalid invite code", extra=audit_authentication( action="otp_exchange", @@ -731,10 +738,11 @@ async def post_enroll_invite_code( error_message="Invite code not found", ), ) - raise HTTPException(status_code=404, detail="Invite code not found") + raise HTTPException(status_code=404, detail="Invite code not found") from exc if obj.active is False: - LOGGER.audit( + LOGGER.log( + AUDIT, "OTP exchange failed - invite code disabled", extra=audit_authentication( action="otp_exchange", @@ -751,7 +759,8 @@ async def post_enroll_invite_code( # CHECK THAT THE CALLSIGN CAN BE USED try: await Enrollment.by_callsign(callsign=callsign) - LOGGER.audit( + LOGGER.log( + AUDIT, "OTP exchange failed - callsign already taken: %s", callsign, extra=audit_authentication( @@ -774,7 +783,8 @@ async def post_enroll_invite_code( claims = {"sub": callsign} new_jwt = Issuer.singleton().issue(claims) - LOGGER.audit( + LOGGER.log( + AUDIT, "OTP exchange successful - enrollment created: %s", callsign, extra=audit_authentication( diff --git a/src/rasenmaeher_api/web/application.py b/src/rasenmaeher_api/web/application.py index e9fb668..cac4226 100644 --- a/src/rasenmaeher_api/web/application.py +++ b/src/rasenmaeher_api/web/application.py @@ -67,6 +67,7 @@ def get_app() -> FastAPI: LOGGER.info("API init done, setting log verbosity to '{}'.".format(RMSettings.singleton().log_level)) return app + async def report_to_kraftwerk() -> None: """Call the KRAFTWERK announce URL if configured""" conf = RMSettings.singleton() From 5707848eb58146a994c41494d981073d4ed92da1 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Tue, 23 Dec 2025 01:45:31 +0200 Subject: [PATCH 04/13] chore: bump2version --- .bumpversion.cfg | 2 +- pyproject.toml | 2 +- src/rasenmaeher_api/__init__.py | 2 +- tests/test_rasenmaeher_api.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ae283cb..975f5aa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.10.3 +current_version = 1.10.4 commit = False tag = False diff --git a/pyproject.toml b/pyproject.toml index 6f22005..8c47e9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rasenmaeher_api" -version = "1.10.3" +version = "1.10.4" description = "python-rasenmaeher-api" authors = [ "Aciid <703382+Aciid@users.noreply.github.com>", diff --git a/src/rasenmaeher_api/__init__.py b/src/rasenmaeher_api/__init__.py index 35898c3..42f8c5b 100644 --- a/src/rasenmaeher_api/__init__.py +++ b/src/rasenmaeher_api/__init__.py @@ -1,3 +1,3 @@ """python-rasenmaeher-api""" -__version__ = "1.10.3" # NOTE Use `bump2version --config-file patch` to bump versions correctly +__version__ = "1.10.4" # NOTE Use `bump2version --config-file patch` to bump versions correctly diff --git a/tests/test_rasenmaeher_api.py b/tests/test_rasenmaeher_api.py index f87c9aa..b78270f 100644 --- a/tests/test_rasenmaeher_api.py +++ b/tests/test_rasenmaeher_api.py @@ -16,7 +16,7 @@ def test_version() -> None: """Make sure version matches expected""" - assert __version__ == "1.10.3" + assert __version__ == "1.10.4" @pytest.mark.asyncio(loop_scope="session") From d841d79d61266d53f44d3f2773225cd698808c72 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Thu, 25 Dec 2025 22:52:23 +0200 Subject: [PATCH 05/13] feat: on locking and accepting enrollment, audit trail which invitecode brought them in --- .../web/api/enrollment/views.py | 95 ++++++++++++++----- 1 file changed, 69 insertions(+), 26 deletions(-) diff --git a/src/rasenmaeher_api/web/api/enrollment/views.py b/src/rasenmaeher_api/web/api/enrollment/views.py index f888348..8110aae 100644 --- a/src/rasenmaeher_api/web/api/enrollment/views.py +++ b/src/rasenmaeher_api/web/api/enrollment/views.py @@ -389,23 +389,6 @@ async def request_enrollment_lock( try: _admin_person = await Person.by_callsign(request.state.mtls_or_jwt.userid) _usr_enrollment = await Enrollment.by_callsign(callsign=target_callsign) - await _usr_enrollment.reject(decider=_admin_person) - - LOGGER.log( - AUDIT, - "Enrollment locked/rejected: %s", - target_callsign, - extra=audit_iam( - action="enrollment_lock", - outcome="success", - target_user=target_callsign, - admin_action=True, - lock_reason=request_in.lock_reason, - ), - ) - - return OperationResultResponse(success=True, extra="Lock task done") - except NotFound as exc: LOGGER.log( AUDIT, @@ -422,6 +405,38 @@ async def request_enrollment_lock( ) raise HTTPException(status_code=404, detail="Enrollment not found") from exc + # Get invitecode context if enrollment was created via invite code + invitecode_context: Dict[str, Any] = {} + if _usr_enrollment.pool: + try: + pool = await EnrollmentPool.by_pk(_usr_enrollment.pool) + pool_owner = await Person.by_pk(pool.owner) + invitecode_context = { + "invitecode": pool.invitecode, + "invitecode_owner": pool_owner.callsign, + "invitecode_created": pool.created.isoformat(), + } + except NotFound: + pass # Pool may have been deleted + + await _usr_enrollment.reject(decider=_admin_person) + + LOGGER.log( + AUDIT, + "Enrollment locked/rejected: %s", + target_callsign, + extra=audit_iam( + action="enrollment_lock", + outcome="success", + target_user=target_callsign, + admin_action=True, + lock_reason=request_in.lock_reason, + **invitecode_context, + ), + ) + + return OperationResultResponse(success=True, extra="Lock task done") + @ENROLLMENT_ROUTER.post( "/accept", @@ -459,6 +474,20 @@ async def post_enrollment_accept( ) raise HTTPException(status_code=404, detail="Enrollment not found") from exc + # Get invitecode context if enrollment was created via invite code + invitecode_context: Dict[str, Any] = {} + if pending_enrollment.pool: + try: + pool = await EnrollmentPool.by_pk(pending_enrollment.pool) + pool_owner = await Person.by_pk(pool.owner) + invitecode_context = { + "invitecode": pool.invitecode, + "invitecode_owner": pool_owner.callsign, + "invitecode_created": pool.created.isoformat(), + } + except NotFound: + pass # Pool may have been deleted + if request_in.approvecode != pending_enrollment.approvecode: LOGGER.log( AUDIT, @@ -471,6 +500,7 @@ async def post_enrollment_accept( admin_action=True, error_code="INVALID_APPROVECODE", error_message="Approval code does not match", + **invitecode_context, ), ) raise HTTPException(status_code=403, detail="Invalid approval code for this enrollment") @@ -486,6 +516,7 @@ async def post_enrollment_accept( outcome="success", target_user=target_callsign, admin_action=True, + **invitecode_context, ), ) @@ -729,9 +760,9 @@ async def post_enroll_invite_code( except NotFound as exc: LOGGER.log( AUDIT, - "OTP exchange failed - invalid invite code", + "Enrollment initialization failed - invalid invite code", extra=audit_authentication( - action="otp_exchange", + action="enroll_initialize", outcome="failure", target_user=callsign, error_code="INVALID_INVITECODE", @@ -740,19 +771,25 @@ async def post_enroll_invite_code( ) raise HTTPException(status_code=404, detail="Invite code not found") from exc + # Fetch invite code owner for audit trail + invitecode_owner = await Person.by_pk(obj.owner) + if obj.active is False: LOGGER.log( AUDIT, - "OTP exchange failed - invite code disabled", + "Enrollment initialization failed - invite code disabled", extra=audit_authentication( - action="otp_exchange", + action="enroll_initialize", outcome="failure", target_user=callsign, error_code="INVITECODE_DISABLED", error_message="Invite code is disabled", + invitecode=invite_code, + invitecode_owner=invitecode_owner.callsign, + invitecode_created=obj.created.isoformat(), ), ) - _reason = "Error. invitecode disabled." + _reason = "Error. Invitecode disabled." LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=400, detail=_reason) @@ -761,14 +798,17 @@ async def post_enroll_invite_code( await Enrollment.by_callsign(callsign=callsign) LOGGER.log( AUDIT, - "OTP exchange failed - callsign already taken: %s", + "Enrollment initialization failed - callsign already taken: %s", callsign, extra=audit_authentication( - action="otp_exchange", + action="enroll_initialize", outcome="failure", target_user=callsign, error_code="CALLSIGN_TAKEN", error_message="Callsign is already in use", + invitecode=invite_code, + invitecode_owner=invitecode_owner.callsign, + invitecode_created=obj.created.isoformat(), ), ) _reason = "Error. callsign/callsign already taken." @@ -785,13 +825,16 @@ async def post_enroll_invite_code( LOGGER.log( AUDIT, - "OTP exchange successful - enrollment created: %s", + "Enrollment initialization successful - enrollment created: %s", callsign, extra=audit_authentication( - action="otp_exchange", + action="enroll_initialize", outcome="success", target_user=callsign, enrollment_state="pending_approval", + invitecode=invite_code, + invitecode_owner=invitecode_owner.callsign, + invitecode_created=obj.created.isoformat(), ), ) From 6ed59e42e83d8c7f18a958055a550511852a2490 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 15:53:17 +0200 Subject: [PATCH 06/13] chore: make sure we point to same libadvian MR as libpvarki --- poetry.lock | 728 ++++++++++++++++++++++++------------------------- pyproject.toml | 4 +- 2 files changed, 358 insertions(+), 374 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6f7901d..fcbd992 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,18 +2,18 @@ [[package]] name = "aiodns" -version = "3.6.0" +version = "3.6.1" description = "Simple DNS resolver for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiodns-3.6.0-py3-none-any.whl", hash = "sha256:9fa49f812648374d046436a3b96491afb141320cd0b501df83880f034d0b5b85"}, - {file = "aiodns-3.6.0.tar.gz", hash = "sha256:9b0ef54339e6687cdbd39a7d73d7de2467cb5c115281da28f2598f058633dac8"}, + {file = "aiodns-3.6.1-py3-none-any.whl", hash = "sha256:46233ccad25f2037903828c5d05b64590eaa756e51d12b4a5616e2defcbc98c7"}, + {file = "aiodns-3.6.1.tar.gz", hash = "sha256:b0e9ce98718a5b8f7ca8cd16fc393163374bc2412236b91f6c851d066e3324b6"}, ] [package.dependencies] -pycares = ">=4.9.0" +pycares = ">=4.9.0,<5" [[package]] name = "aiohappyeyeballs" @@ -294,38 +294,39 @@ yaml = ["PyYAML"] [[package]] name = "black" -version = "25.11.0" +version = "25.12.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, - {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, - {file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"}, - {file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"}, - {file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"}, - {file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"}, - {file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"}, - {file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"}, - {file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"}, - {file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"}, - {file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"}, - {file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"}, - {file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"}, - {file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"}, - {file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"}, - {file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"}, - {file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"}, - {file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"}, - {file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"}, - {file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"}, - {file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"}, - {file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"}, - {file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"}, - {file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"}, - {file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"}, - {file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"}, + {file = "black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8"}, + {file = "black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a"}, + {file = "black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea"}, + {file = "black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f"}, + {file = "black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da"}, + {file = "black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a"}, + {file = "black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be"}, + {file = "black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b"}, + {file = "black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5"}, + {file = "black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655"}, + {file = "black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a"}, + {file = "black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783"}, + {file = "black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59"}, + {file = "black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892"}, + {file = "black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43"}, + {file = "black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5"}, + {file = "black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f"}, + {file = "black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf"}, + {file = "black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d"}, + {file = "black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce"}, + {file = "black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5"}, + {file = "black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f"}, + {file = "black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f"}, + {file = "black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83"}, + {file = "black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b"}, + {file = "black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828"}, + {file = "black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7"}, ] [package.dependencies] @@ -738,104 +739,104 @@ markers = {dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "coverage" -version = "7.12.0" +version = "7.13.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b"}, - {file = "coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73"}, - {file = "coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553"}, - {file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d"}, - {file = "coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef"}, - {file = "coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022"}, - {file = "coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f"}, - {file = "coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245"}, - {file = "coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c"}, - {file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984"}, - {file = "coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6"}, - {file = "coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4"}, - {file = "coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc"}, - {file = "coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647"}, - {file = "coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f"}, - {file = "coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b"}, - {file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937"}, - {file = "coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa"}, - {file = "coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a"}, - {file = "coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c"}, - {file = "coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941"}, - {file = "coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d"}, - {file = "coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508"}, - {file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc"}, - {file = "coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8"}, - {file = "coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07"}, - {file = "coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc"}, - {file = "coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87"}, - {file = "coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12"}, - {file = "coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2"}, - {file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455"}, - {file = "coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d"}, - {file = "coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c"}, - {file = "coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d"}, - {file = "coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92"}, - {file = "coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c"}, - {file = "coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e"}, - {file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17"}, - {file = "coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933"}, - {file = "coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe"}, - {file = "coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d"}, - {file = "coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d"}, - {file = "coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339"}, - {file = "coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1"}, - {file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b"}, - {file = "coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a"}, - {file = "coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291"}, - {file = "coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384"}, - {file = "coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a"}, - {file = "coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, + {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, + {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, + {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, + {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, + {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, + {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, + {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, + {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, + {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, + {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, + {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, + {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, + {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, + {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, + {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, + {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, + {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, + {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, + {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, + {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, + {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, + {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, ] [package.extras] @@ -967,14 +968,14 @@ develop = ["elastic-apm", "mock", "pytest", "pytest-cov", "structlog"] [[package]] name = "fastapi" -version = "0.124.0" +version = "0.125.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "fastapi-0.124.0-py3-none-any.whl", hash = "sha256:91596bdc6dde303c318f06e8d2bc75eafb341fc793a0c9c92c0bc1db1ac52480"}, - {file = "fastapi-0.124.0.tar.gz", hash = "sha256:260cd178ad75e6d259991f2fd9b0fee924b224850079df576a3ba604ce58f4e6"}, + {file = "fastapi-0.125.0-py3-none-any.whl", hash = "sha256:2570ec4f3aecf5cca8f0428aed2398b774fcdfee6c2116f86e80513f2f86a7a1"}, + {file = "fastapi-0.125.0.tar.gz", hash = "sha256:16b532691a33e2c5dee1dac32feb31dc6eb41a3dd4ff29a95f9487cb21c054c0"}, ] [package.dependencies] @@ -990,14 +991,14 @@ standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[stand [[package]] name = "filelock" -version = "3.20.0" +version = "3.20.1" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, - {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, + {file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"}, + {file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"}, ] [[package]] @@ -1472,88 +1473,89 @@ resolved_reference = "e7d1fbadfd2d9aee1f479a44c367aabf84f68b57" [[package]] name = "librt" -version = "0.7.3" +version = "0.7.5" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev"] -files = [ - {file = "librt-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2682162855a708e3270eba4b92026b93f8257c3e65278b456c77631faf0f4f7a"}, - {file = "librt-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:440c788f707c061d237c1e83edf6164ff19f5c0f823a3bf054e88804ebf971ec"}, - {file = "librt-0.7.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399938edbd3d78339f797d685142dd8a623dfaded023cf451033c85955e4838a"}, - {file = "librt-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1975eda520957c6e0eb52d12968dd3609ffb7eef05d4223d097893d6daf1d8a7"}, - {file = "librt-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9da128d0edf990cf0d2ca011b02cd6f639e79286774bd5b0351245cbb5a6e51"}, - {file = "librt-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19acfde38cb532a560b98f473adc741c941b7a9bc90f7294bc273d08becb58b"}, - {file = "librt-0.7.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b4f57f7a0c65821c5441d98c47ff7c01d359b1e12328219709bdd97fdd37f90"}, - {file = "librt-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:256793988bff98040de23c57cf36e1f4c2f2dc3dcd17537cdac031d3b681db71"}, - {file = "librt-0.7.3-cp310-cp310-win32.whl", hash = "sha256:fcb72249ac4ea81a7baefcbff74df7029c3cb1cf01a711113fa052d563639c9c"}, - {file = "librt-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4887c29cadbdc50640179e3861c276325ff2986791e6044f73136e6e798ff806"}, - {file = "librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af"}, - {file = "librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5"}, - {file = "librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7"}, - {file = "librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445"}, - {file = "librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7"}, - {file = "librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b"}, - {file = "librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720"}, - {file = "librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a"}, - {file = "librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2"}, - {file = "librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e"}, - {file = "librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0"}, - {file = "librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3"}, - {file = "librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18"}, - {file = "librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60"}, - {file = "librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed"}, - {file = "librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e"}, - {file = "librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b"}, - {file = "librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f"}, - {file = "librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c"}, - {file = "librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b"}, - {file = "librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a"}, - {file = "librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc"}, - {file = "librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed"}, - {file = "librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc"}, - {file = "librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e"}, - {file = "librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5"}, - {file = "librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055"}, - {file = "librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292"}, - {file = "librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910"}, - {file = "librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093"}, - {file = "librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe"}, - {file = "librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0"}, - {file = "librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a"}, - {file = "librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93"}, - {file = "librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e"}, - {file = "librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207"}, - {file = "librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f"}, - {file = "librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678"}, - {file = "librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218"}, - {file = "librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620"}, - {file = "librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e"}, - {file = "librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3"}, - {file = "librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010"}, - {file = "librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e"}, - {file = "librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a"}, - {file = "librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a"}, - {file = "librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f"}, - {file = "librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e"}, - {file = "librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e"}, - {file = "librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70"}, - {file = "librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712"}, - {file = "librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42"}, - {file = "librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd"}, - {file = "librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f"}, - {file = "librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b"}, - {file = "librt-0.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd8551aa21df6c60baa2624fd086ae7486bdde00c44097b32e1d1b1966e365e0"}, - {file = "librt-0.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6eb9295c730e26b849ed1f4022735f36863eb46b14b6e10604c1c39b8b5efaea"}, - {file = "librt-0.7.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3edbf257c40d21a42615e9e332a6b10a8bacaaf58250aed8552a14a70efd0d65"}, - {file = "librt-0.7.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b29e97273bd6999e2bfe9fe3531b1f4f64effd28327bced048a33e49b99674a"}, - {file = "librt-0.7.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e40520c37926166c24d0c2e0f3bc3a5f46646c34bdf7b4ea9747c297d6ee809"}, - {file = "librt-0.7.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6bdd9adfca615903578d2060ee8a6eb1c24eaf54919ff0ddc820118e5718931b"}, - {file = "librt-0.7.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f57aca20e637750a2c18d979f7096e2c2033cc40cf7ed201494318de1182f135"}, - {file = "librt-0.7.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cad9971881e4fec00d96af7eaf4b63aa7a595696fc221808b0d3ce7ca9743258"}, - {file = "librt-0.7.3-cp39-cp39-win32.whl", hash = "sha256:170cdb8436188347af17bf9cccf3249ba581c933ed56d926497119d4cf730cec"}, - {file = "librt-0.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:b278a9248a4e3260fee3db7613772ca9ab6763a129d6d6f29555e2f9b168216d"}, - {file = "librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798"}, +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.7.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81056e01bba1394f1d92904ec61a4078f66df785316275edbaf51d90da8c6e26"}, + {file = "librt-0.7.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7c72c8756eeb3aefb1b9e3dac7c37a4a25db63640cac0ab6fc18e91a0edf05a"}, + {file = "librt-0.7.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddc4a16207f88f9597b397fc1f60781266d13b13de922ff61c206547a29e4bbd"}, + {file = "librt-0.7.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63055d3dda433ebb314c9f1819942f16a19203c454508fdb2d167613f7017169"}, + {file = "librt-0.7.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f85f9b5db87b0f52e53c68ad2a0c5a53e00afa439bd54a1723742a2b1021276"}, + {file = "librt-0.7.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c566a4672564c5d54d8ab65cdaae5a87ee14c1564c1a2ddc7a9f5811c750f023"}, + {file = "librt-0.7.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fee15c2a190ef389f14928135c6fb2d25cd3fdb7887bfd9a7b444bbdc8c06b96"}, + {file = "librt-0.7.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:584cb3e605ec45ba350962cec853e17be0a25a772f21f09f1e422f7044ae2a7d"}, + {file = "librt-0.7.5-cp310-cp310-win32.whl", hash = "sha256:9c08527055fbb03c641c15bbc5b79dd2942fb6a3bd8dabf141dd7e97eeea4904"}, + {file = "librt-0.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:dd810f2d39c526c42ea205e0addad5dc08ef853c625387806a29d07f9d150d9b"}, + {file = "librt-0.7.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f952e1a78c480edee8fb43aa2bf2e84dcd46c917d44f8065b883079d3893e8fc"}, + {file = "librt-0.7.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75965c1f4efb7234ff52a58b729d245a21e87e4b6a26a0ec08052f02b16274e4"}, + {file = "librt-0.7.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:732e0aa0385b59a1b2545159e781c792cc58ce9c134249233a7c7250a44684c4"}, + {file = "librt-0.7.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdde31759bd8888f3ef0eebda80394a48961328a17c264dce8cc35f4b9cde35d"}, + {file = "librt-0.7.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3146d52465b3b6397d25d513f428cb421c18df65b7378667bb5f1e3cc45805"}, + {file = "librt-0.7.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29c8d2fae11d4379ea207ba7fc69d43237e42cf8a9f90ec6e05993687e6d648b"}, + {file = "librt-0.7.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb41f04046b4f22b1e7ba5ef513402cd2e3477ec610e5f92d38fe2bba383d419"}, + {file = "librt-0.7.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8bb7883c1e94ceb87c2bf81385266f032da09cd040e804cc002f2c9d6b842e2f"}, + {file = "librt-0.7.5-cp311-cp311-win32.whl", hash = "sha256:84d4a6b9efd6124f728558a18e79e7cc5c5d4efc09b2b846c910de7e564f5bad"}, + {file = "librt-0.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab4b0d3bee6f6ff7017e18e576ac7e41a06697d8dea4b8f3ab9e0c8e1300c409"}, + {file = "librt-0.7.5-cp311-cp311-win_arm64.whl", hash = "sha256:730be847daad773a3c898943cf67fb9845a3961d06fb79672ceb0a8cd8624cfa"}, + {file = "librt-0.7.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba1077c562a046208a2dc6366227b3eeae8f2c2ab4b41eaf4fd2fa28cece4203"}, + {file = "librt-0.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:654fdc971c76348a73af5240d8e2529265b9a7ba6321e38dd5bae7b0d4ab3abe"}, + {file = "librt-0.7.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6b7b58913d475911f6f33e8082f19dd9b120c4f4a5c911d07e395d67b81c6982"}, + {file = "librt-0.7.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e0fd344bad57026a8f4ccfaf406486c2fc991838050c2fef156170edc3b775"}, + {file = "librt-0.7.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46aa91813c267c3f60db75d56419b42c0c0b9748ec2c568a0e3588e543fb4233"}, + {file = "librt-0.7.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ddc0ab9dbc5f9ceaf2bf7a367bf01f2697660e908f6534800e88f43590b271db"}, + {file = "librt-0.7.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7a488908a470451338607650f1c064175094aedebf4a4fa37890682e30ce0b57"}, + {file = "librt-0.7.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e47fc52602ffc374e69bf1b76536dc99f7f6dd876bd786c8213eaa3598be030a"}, + {file = "librt-0.7.5-cp312-cp312-win32.whl", hash = "sha256:cda8b025875946ffff5a9a7590bf9acde3eb02cb6200f06a2d3e691ef3d9955b"}, + {file = "librt-0.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:b591c094afd0ffda820e931148c9e48dc31a556dc5b2b9b3cc552fa710d858e4"}, + {file = "librt-0.7.5-cp312-cp312-win_arm64.whl", hash = "sha256:532ddc6a8a6ca341b1cd7f4d999043e4c71a212b26fe9fd2e7f1e8bb4e873544"}, + {file = "librt-0.7.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b1795c4b2789b458fa290059062c2f5a297ddb28c31e704d27e161386469691a"}, + {file = "librt-0.7.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2fcbf2e135c11f721193aa5f42ba112bb1046afafbffd407cbc81d8d735c74d0"}, + {file = "librt-0.7.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c039bbf79a9a2498404d1ae7e29a6c175e63678d7a54013a97397c40aee026c5"}, + {file = "librt-0.7.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3919c9407faeeee35430ae135e3a78acd4ecaaaa73767529e2c15ca1d73ba325"}, + {file = "librt-0.7.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26b46620e1e0e45af510d9848ea0915e7040605dd2ae94ebefb6c962cbb6f7ec"}, + {file = "librt-0.7.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9bbb8facc5375476d392990dd6a71f97e4cb42e2ac66f32e860f6e47299d5e89"}, + {file = "librt-0.7.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e9e9c988b5ffde7be02180f864cbd17c0b0c1231c235748912ab2afa05789c25"}, + {file = "librt-0.7.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:edf6b465306215b19dbe6c3fb63cf374a8f3e1ad77f3b4c16544b83033bbb67b"}, + {file = "librt-0.7.5-cp313-cp313-win32.whl", hash = "sha256:060bde69c3604f694bd8ae21a780fe8be46bb3dbb863642e8dfc75c931ca8eee"}, + {file = "librt-0.7.5-cp313-cp313-win_amd64.whl", hash = "sha256:a82d5a0ee43aeae2116d7292c77cc8038f4841830ade8aa922e098933b468b9e"}, + {file = "librt-0.7.5-cp313-cp313-win_arm64.whl", hash = "sha256:3c98a8d0ac9e2a7cb8ff8c53e5d6e8d82bfb2839abf144fdeaaa832f2a12aa45"}, + {file = "librt-0.7.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9937574e6d842f359b8585903d04f5b4ab62277a091a93e02058158074dc52f2"}, + {file = "librt-0.7.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5cd3afd71e9bc146203b6c8141921e738364158d4aa7cdb9a874e2505163770f"}, + {file = "librt-0.7.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cffa3ef0af29687455161cb446eff059bf27607f95163d6a37e27bcb37180f6"}, + {file = "librt-0.7.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82f3f088482e2229387eadf8215c03f7726d56f69cce8c0c40f0795aebc9b361"}, + {file = "librt-0.7.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7aa33153a5bb0bac783d2c57885889b1162823384e8313d47800a0e10d0070e"}, + {file = "librt-0.7.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:265729b551a2dd329cc47b323a182fb7961af42abf21e913c9dd7d3331b2f3c2"}, + {file = "librt-0.7.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:168e04663e126416ba712114050f413ac306759a1791d87b7c11d4428ba75760"}, + {file = "librt-0.7.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:553dc58987d1d853adda8aeadf4db8e29749f0b11877afcc429a9ad892818ae2"}, + {file = "librt-0.7.5-cp314-cp314-win32.whl", hash = "sha256:263f4fae9eba277513357c871275b18d14de93fd49bf5e43dc60a97b81ad5eb8"}, + {file = "librt-0.7.5-cp314-cp314-win_amd64.whl", hash = "sha256:85f485b7471571e99fab4f44eeb327dc0e1f814ada575f3fa85e698417d8a54e"}, + {file = "librt-0.7.5-cp314-cp314-win_arm64.whl", hash = "sha256:49c596cd18e90e58b7caa4d7ca7606049c1802125fcff96b8af73fa5c3870e4d"}, + {file = "librt-0.7.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:54d2aef0b0f5056f130981ad45081b278602ff3657fe16c88529f5058038e802"}, + {file = "librt-0.7.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0b4791202296ad51ac09a3ff58eb49d9da8e3a4009167a6d76ac418a974e5fd4"}, + {file = "librt-0.7.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e860909fea75baef941ee6436e0453612505883b9d0d87924d4fda27865b9a2"}, + {file = "librt-0.7.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f02c4337bf271c4f06637f5ff254fad2238c0b8e32a3a480ebb2fc5e26f754a5"}, + {file = "librt-0.7.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f51ffe59f4556243d3cc82d827bde74765f594fa3ceb80ec4de0c13ccd3416"}, + {file = "librt-0.7.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b7f080ba30601dfa3e3deed3160352273e1b9bc92e652f51103c3e9298f7899"}, + {file = "librt-0.7.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb565b4219abc8ea2402e61c7ba648a62903831059ed3564fa1245cc245d58d7"}, + {file = "librt-0.7.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a3cfb15961e7333ea6ef033dc574af75153b5c230d5ad25fbcd55198f21e0cf"}, + {file = "librt-0.7.5-cp314-cp314t-win32.whl", hash = "sha256:118716de5ad6726332db1801bc90fa6d94194cd2e07c1a7822cebf12c496714d"}, + {file = "librt-0.7.5-cp314-cp314t-win_amd64.whl", hash = "sha256:3dd58f7ce20360c6ce0c04f7bd9081c7f9c19fc6129a3c705d0c5a35439f201d"}, + {file = "librt-0.7.5-cp314-cp314t-win_arm64.whl", hash = "sha256:08153ea537609d11f774d2bfe84af39d50d5c9ca3a4d061d946e0c9d8bce04a1"}, + {file = "librt-0.7.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:df2e210400b28e50994477ebf82f055698c79797b6ee47a1669d383ca33263e1"}, + {file = "librt-0.7.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2cc7d187e8c6e9b7bdbefa9697ce897a704ea7a7ce844f2b4e0e2aa07ae51d3"}, + {file = "librt-0.7.5-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39183abee670bc37b85f11e86c44a9cad1ed6efa48b580083e89ecee13dd9717"}, + {file = "librt-0.7.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191cbd42660446d67cf7a95ac7bfa60f49b8b3b0417c64f216284a1d86fc9335"}, + {file = "librt-0.7.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea1b60b86595a5dc1f57b44a801a1c4d8209c0a69518391d349973a4491408e6"}, + {file = "librt-0.7.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:af69d9e159575e877c7546d1ee817b4ae089aa221dd1117e20c24ad8dc8659c7"}, + {file = "librt-0.7.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0e2bf8f91093fac43e3eaebacf777f12fd539dce9ec5af3efc6d8424e96ccd49"}, + {file = "librt-0.7.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8dcae24de1bc9da93aa689cb6313c70e776d7cea2fcf26b9b6160fedfe6bd9af"}, + {file = "librt-0.7.5-cp39-cp39-win32.whl", hash = "sha256:cdb001a1a0e4f41e613bca2c0fc147fc8a7396f53fc94201cbfd8ec7cd69ca4b"}, + {file = "librt-0.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:a9eacbf983319b26b5f340a2e0cd47ac1ee4725a7f3a72fd0f15063c934b69d6"}, + {file = "librt-0.7.5.tar.gz", hash = "sha256:de4221a1181fa9c8c4b5f35506ed6f298948f44003d84d2a8b9885d7e01e6cfa"}, ] [[package]] @@ -1785,54 +1787,54 @@ fastapi = ["fastapi (>0.89,<1.0)"] [[package]] name = "mypy" -version = "1.19.0" +version = "1.19.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8"}, - {file = "mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39"}, - {file = "mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab"}, - {file = "mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e"}, - {file = "mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3"}, - {file = "mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134"}, - {file = "mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106"}, - {file = "mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7"}, - {file = "mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7"}, - {file = "mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b"}, - {file = "mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7"}, - {file = "mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e"}, - {file = "mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d"}, - {file = "mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760"}, - {file = "mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6"}, - {file = "mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2"}, - {file = "mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431"}, - {file = "mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018"}, - {file = "mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e"}, - {file = "mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d"}, - {file = "mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba"}, - {file = "mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364"}, - {file = "mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee"}, - {file = "mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53"}, - {file = "mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d"}, - {file = "mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18"}, - {file = "mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7"}, - {file = "mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f"}, - {file = "mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835"}, - {file = "mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1"}, - {file = "mypy-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0dde5cb375cb94deff0d4b548b993bec52859d1651e073d63a1386d392a95495"}, - {file = "mypy-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1cf9c59398db1c68a134b0b5354a09a1e124523f00bacd68e553b8bd16ff3299"}, - {file = "mypy-1.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3210d87b30e6af9c8faed61be2642fcbe60ef77cec64fa1ef810a630a4cf671c"}, - {file = "mypy-1.19.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2c1101ab41d01303103ab6ef82cbbfedb81c1a060c868fa7cc013d573d37ab5"}, - {file = "mypy-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ea4fd21bb48f0da49e6d3b37ef6bd7e8228b9fe41bbf4d80d9364d11adbd43c"}, - {file = "mypy-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:16f76ff3f3fd8137aadf593cb4607d82634fca675e8211ad75c43d86033ee6c6"}, - {file = "mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9"}, - {file = "mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] -librt = ">=0.6.2" +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=0.9.0" typing_extensions = ">=4.6.0" @@ -1858,14 +1860,14 @@ files = [ [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, + {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, + {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, ] [[package]] @@ -2376,62 +2378,49 @@ files = [ [[package]] name = "pydantic" -version = "1.10.24" +version = "1.10.26" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "pydantic-1.10.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c"}, - {file = "pydantic-1.10.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683"}, - {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d"}, - {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b"}, - {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36"}, - {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37"}, - {file = "pydantic-1.10.24-cp310-cp310-win_amd64.whl", hash = "sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666"}, - {file = "pydantic-1.10.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702"}, - {file = "pydantic-1.10.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b"}, - {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6"}, - {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3"}, - {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d"}, - {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993"}, - {file = "pydantic-1.10.24-cp311-cp311-win_amd64.whl", hash = "sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a"}, - {file = "pydantic-1.10.24-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624"}, - {file = "pydantic-1.10.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6"}, - {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813"}, - {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de"}, - {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a"}, - {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1"}, - {file = "pydantic-1.10.24-cp312-cp312-win_amd64.whl", hash = "sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c"}, - {file = "pydantic-1.10.24-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c"}, - {file = "pydantic-1.10.24-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def"}, - {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574"}, - {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69"}, - {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8"}, - {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00"}, - {file = "pydantic-1.10.24-cp313-cp313-win_amd64.whl", hash = "sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6"}, - {file = "pydantic-1.10.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70"}, - {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0"}, - {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47"}, - {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f"}, - {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2"}, - {file = "pydantic-1.10.24-cp37-cp37m-win_amd64.whl", hash = "sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579"}, - {file = "pydantic-1.10.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621"}, - {file = "pydantic-1.10.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c"}, - {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c"}, - {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd"}, - {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667"}, - {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52"}, - {file = "pydantic-1.10.24-cp38-cp38-win_amd64.whl", hash = "sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49"}, - {file = "pydantic-1.10.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2"}, - {file = "pydantic-1.10.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f"}, - {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94"}, - {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4"}, - {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb"}, - {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816"}, - {file = "pydantic-1.10.24-cp39-cp39-win_amd64.whl", hash = "sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01"}, - {file = "pydantic-1.10.24-py3-none-any.whl", hash = "sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193"}, - {file = "pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53"}, + {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, + {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, + {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, + {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, + {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, + {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, + {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, + {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, + {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, + {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, + {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, + {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, + {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, + {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, + {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, + {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, + {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, + {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, + {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, + {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, + {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, + {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, + {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, + {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, + {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, + {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, + {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, + {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, + {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, + {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, + {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, + {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, + {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, + {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, + {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, + {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, + {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, ] [package.dependencies] @@ -2897,69 +2886,64 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.44" +version = "2.0.45" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "SQLAlchemy-2.0.44-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:471733aabb2e4848d609141a9e9d56a427c0a038f4abf65dd19d7a21fd563632"}, - {file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48bf7d383a35e668b984c805470518b635d48b95a3c57cb03f37eaa3551b5f9f"}, - {file = "SQLAlchemy-2.0.44-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bf4bb6b3d6228fcf3a71b50231199fb94d2dd2611b66d33be0578ea3e6c2726"}, - {file = "SQLAlchemy-2.0.44-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:e998cf7c29473bd077704cea3577d23123094311f59bdc4af551923b168332b1"}, - {file = "SQLAlchemy-2.0.44-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ebac3f0b5732014a126b43c2b7567f2f0e0afea7d9119a3378bde46d3dcad88e"}, - {file = "SQLAlchemy-2.0.44-cp37-cp37m-win32.whl", hash = "sha256:3255d821ee91bdf824795e936642bbf43a4c7cedf5d1aed8d24524e66843aa74"}, - {file = "SQLAlchemy-2.0.44-cp37-cp37m-win_amd64.whl", hash = "sha256:78e6c137ba35476adb5432103ae1534f2f5295605201d946a4198a0dea4b38e7"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c77f3080674fc529b1bd99489378c7f63fcb4ba7f8322b79732e0258f0ea3ce"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26ef74ba842d61635b0152763d057c8d48215d5be9bb8b7604116a059e9985"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a172b31785e2f00780eccab00bc240ccdbfdb8345f1e6063175b3ff12ad1b0"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9480c0740aabd8cb29c329b422fb65358049840b34aba0adf63162371d2a96e"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17835885016b9e4d0135720160db3095dc78c583e7b902b6be799fb21035e749"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cbe4f85f50c656d753890f39468fcd8190c5f08282caf19219f684225bfd5fd2"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-win32.whl", hash = "sha256:2fcc4901a86ed81dc76703f3b93ff881e08761c63263c46991081fd7f034b165"}, - {file = "sqlalchemy-2.0.44-cp310-cp310-win_amd64.whl", hash = "sha256:9919e77403a483ab81e3423151e8ffc9dd992c20d2603bf17e4a8161111e55f5"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3"}, - {file = "sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4"}, - {file = "sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73"}, - {file = "sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2fc44e5965ea46909a416fff0af48a219faefd5773ab79e5f8a5fcd5d62b2667"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dc8b3850d2a601ca2320d081874033684e246d28e1c5e89db0864077cfc8f5a9"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d733dec0614bb8f4bcb7c8af88172b974f685a31dc3a65cca0527e3120de5606"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22be14009339b8bc16d6b9dc8780bacaba3402aa7581658e246114abbd2236e3"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:357bade0e46064f88f2c3a99808233e67b0051cdddf82992379559322dfeb183"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4848395d932e93c1595e59a8672aa7400e8922c39bb9b0668ed99ac6fa867822"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-win32.whl", hash = "sha256:2f19644f27c76f07e10603580a47278abb2a70311136a7f8fd27dc2e096b9013"}, - {file = "sqlalchemy-2.0.44-cp38-cp38-win_amd64.whl", hash = "sha256:1df4763760d1de0dfc8192cc96d8aa293eb1a44f8f7a5fbe74caf1b551905c5e"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7027414f2b88992877573ab780c19ecb54d3a536bef3397933573d6b5068be4"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fe166c7d00912e8c10d3a9a0ce105569a31a3d0db1a6e82c4e0f4bf16d5eca9"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3caef1ff89b1caefc28f0368b3bde21a7e3e630c2eddac16abd9e47bd27cc36a"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc2856d24afa44295735e72f3c75d6ee7fdd4336d8d3a8f3d44de7aa6b766df2"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:11bac86b0deada30b6b5f93382712ff0e911fe8d31cb9bf46e6b149ae175eff0"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d18cd0e9a0f37c9f4088e50e3839fcb69a380a0ec957408e0b57cff08ee0a26"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-win32.whl", hash = "sha256:9e9018544ab07614d591a26c1bd4293ddf40752cc435caf69196740516af7100"}, - {file = "sqlalchemy-2.0.44-cp39-cp39-win_amd64.whl", hash = "sha256:8e0e4e66fd80f277a8c3de016a81a554e76ccf6b8d881ee0b53200305a8433f6"}, - {file = "sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05"}, - {file = "sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c64772786d9eee72d4d3784c28f0a636af5b0a29f3fe26ff11f55efe90c0bd85"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae64ebf7657395824a19bca98ab10eb9a3ecb026bf09524014f1bb81cb598d4"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f02325709d1b1a1489f23a39b318e175a171497374149eae74d612634b234c0"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2c3684fca8a05f0ac1d9a21c1f4a266983a7ea9180efb80ffeb03861ecd01a0"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040f6f0545b3b7da6b9317fc3e922c9a98fc7243b2a1b39f78390fc0942f7826"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-win32.whl", hash = "sha256:830d434d609fe7bfa47c425c445a8b37929f140a7a44cdaf77f6d34df3a7296a"}, + {file = "sqlalchemy-2.0.45-cp310-cp310-win_amd64.whl", hash = "sha256:0209d9753671b0da74da2cfbb9ecf9c02f72a759e4b018b3ab35f244c91842c7"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177"}, + {file = "sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2"}, + {file = "sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f"}, + {file = "sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d"}, + {file = "sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4"}, + {file = "sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6"}, + {file = "sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953"}, + {file = "sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1"}, + {file = "sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf"}, + {file = "sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e"}, + {file = "sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b"}, + {file = "sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8"}, + {file = "sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a"}, + {file = "sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee"}, + {file = "sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6"}, + {file = "sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a"}, + {file = "sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774"}, + {file = "sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce"}, + {file = "sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33"}, + {file = "sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74"}, + {file = "sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f"}, + {file = "sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177"}, + {file = "sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b"}, + {file = "sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5964f832431b7cdfaaa22a660b4c7eb1dfcd6ed41375f67fd3e3440fd95cb3cc"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee580ab50e748208754ae8980cec79ec205983d8cf8b3f7c39067f3d9f2c8e22"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13e27397a7810163440c6bfed6b3fe46f1bfb2486eb540315a819abd2c004128"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ed3635353e55d28e7f4a95c8eda98a5cdc0a0b40b528433fbd41a9ae88f55b3d"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:db6834900338fb13a9123307f0c2cbb1f890a8656fcd5e5448ae3ad5bbe8d312"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-win32.whl", hash = "sha256:1d8b4a7a8c9b537509d56d5cd10ecdcfbb95912d72480c8861524efecc6a3fff"}, + {file = "sqlalchemy-2.0.45-cp38-cp38-win_amd64.whl", hash = "sha256:ebd300afd2b62679203435f596b2601adafe546cb7282d5a0cd3ed99e423720f"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d29b2b99d527dbc66dd87c3c3248a5dd789d974a507f4653c969999fc7c1191b"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59a8b8bd9c6bedf81ad07c8bd5543eedca55fe9b8780b2b628d495ba55f8db1e"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd93c6f5d65f254ceabe97548c709e073d6da9883343adaa51bf1a913ce93f8e"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d0beadc2535157070c9c17ecf25ecec31e13c229a8f69196d7590bde8082bf1"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e057f928ffe9c9b246a55b469c133b98a426297e1772ad24ce9f0c47d123bd5b"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-win32.whl", hash = "sha256:c1c2091b1489435ff85728fafeb990f073e64f6f5e81d5cd53059773e8521eb6"}, + {file = "sqlalchemy-2.0.45-cp39-cp39-win_amd64.whl", hash = "sha256:56ead1f8dfb91a54a28cd1d072c74b3d635bcffbd25e50786533b822d4f2cde2"}, + {file = "sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0"}, + {file = "sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88"}, ] [package.dependencies] @@ -2993,14 +2977,14 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlmodel" -version = "0.0.27" +version = "0.0.29" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49"}, - {file = "sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620"}, + {file = "sqlmodel-0.0.29-py3-none-any.whl", hash = "sha256:56e4b3e9ab5825f6cbdd40bc46f90f01c0f73c73f44843a03d494c0845a948cd"}, + {file = "sqlmodel-0.0.29.tar.gz", hash = "sha256:cdebdeda7addb3d934c7a70812a23aa8bdff5550bfffa023db72dec1ba5e45d7"}, ] [package.dependencies] @@ -3119,26 +3103,26 @@ files = [ [[package]] name = "tzdata" -version = "2025.2" +version = "2025.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main"] files = [ - {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, - {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, + {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, + {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, ] [[package]] name = "urllib3" -version = "2.6.0" +version = "2.6.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"}, - {file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"}, + {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"}, + {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"}, ] [package.extras] @@ -3608,4 +3592,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "381a59a9341001950c0c7e3bce287a5d962be4cf6c0ffe9306feecd5d37d091f" +content-hash = "681e5fa478321c63d30b019ec7ba44270fde4039122498081f996034cc106a55" diff --git a/pyproject.toml b/pyproject.toml index 8c47e9a..b438d7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ priority = "explicit" [tool.poetry.dependencies] python = ">=3.11,<4.0" -libadvian = "^1.6" +libadvian = { git = "https://gitlab.com/advian-oss/python-libadvian.git", branch = "log_levels" } click = "^8.0" fastapi = ">=0.89,<1.0" # caret behaviour on 0.x is to lock to 0.x.* # FIXME: Migrate to v2, see https://docs.pydantic.dev/2.3/migration/#basesettings-has-moved-to-pydantic-settings @@ -98,7 +98,7 @@ brotli = "^1.0" cchardet = { version="^2.1", python="<=3.10"} filelock = "^3.12" python-keycloak = "^4.2.0" -sqlmodel = ">=0.0.22,<1.0" +sqlmodel = "==0.0.29" psycopg2-binary = "^2.9" [tool.poetry.group.dev.dependencies] From f31b70c0eae445a11503ef9f83a5d1a7581a5154 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:24:51 +0200 Subject: [PATCH 07/13] feat: auditlogging to firstuser endpoints --- .../web/api/firstuser/views.py | 97 ++++++++++++++++++- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/src/rasenmaeher_api/web/api/firstuser/views.py b/src/rasenmaeher_api/web/api/firstuser/views.py index 0d28eec..3cd24d9 100644 --- a/src/rasenmaeher_api/web/api/firstuser/views.py +++ b/src/rasenmaeher_api/web/api/firstuser/views.py @@ -2,10 +2,12 @@ import asyncio import logging + from fastapi import APIRouter, Request, Body, Depends, HTTPException from multikeyjwt.middleware import JWTBearer, JWTPayload from libadvian.tasks import TaskMaster +from libpvarki.auditlogging import audit_authentication, audit_iam, audit_authorization, AUDIT from rasenmaeher_api.web.api.firstuser.schema import ( FirstuserCheckCodeIn, @@ -36,20 +38,59 @@ async def get_check_code( try: res = await LoginCode.by_code(code=params.temp_admin_code) except NotFound: + LOGGER.log( + AUDIT, + "First user code check failed - input is not a first user otp code", + extra=audit_authentication( + action="firstuser_code_check", + outcome="failure", + error_code="WRONG_CODE", + error_message="Input is not a first user otp code", + ), + ) return FirstuserCheckCodeOut(code_ok=False) # This error should already be raised in LoginCode if not res: + LOGGER.log( + AUDIT, + "First user code check failed - backend error", + extra=audit_authentication( + action="firstuser_code_check", + outcome="failure", + error_code="BACKEND_ERROR", + error_message="Undefined backend error", + ), + ) _reason = "Error. Undefined backend error q_ssjsfjwe1" LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=500, detail=_reason) # Code already used err. if res.used_on is not None: + LOGGER.log( + AUDIT, + "First user code check failed - code already used", + extra=audit_authentication( + action="firstuser_code_check", + outcome="failure", + error_code="CODE_ALREADY_USED", + error_message="Temporary admin code has already been used", + ), + ) _reason = "Code already used" LOGGER.error("{} : {}".format(request.url, _reason)) raise HTTPException(status_code=403, detail=_reason) + LOGGER.log( + AUDIT, + "First user otp code check successful", + extra=audit_authentication( + action="firstuser_code_check", + outcome="success", + ), + ) + return FirstuserCheckCodeOut(code_ok=True) @@ -66,7 +107,21 @@ async def post_admin_add( """ Add callsign aka username/identity. This callsign is also elevated to have managing permissions. """ + callsign = request_in.callsign + if not jwt.get("anon_admin_session", False): + LOGGER.log( + AUDIT, + "First admin creation failed - unauthorized JWT: %s", + callsign, + extra=audit_authorization( + action="firstuser_create", + outcome="failure", + target_user=callsign, + error_code="MISSING_CLAIM", + error_message="JWT missing anon_admin_session claim", + ), + ) LOGGER.error("Requesting JWT must have admin session claim") raise HTTPException(status_code=403, detail="Forbidden") @@ -76,11 +131,19 @@ async def post_admin_add( if not anon_admin_added: anon_user = await Person.create_with_cert(callsign="anon_admin", extra={}) await anon_user.assign_role(role="anon_admin") + LOGGER.log( + AUDIT, + "Anonymous admin bootstrap user created", + extra=audit_iam( + action="anon_admin_create", + outcome="success", + target_user="anon_admin", + bootstrap_operation=True, + ), + ) # Create new admin user enrollment - enrollment = await Enrollment.create_for_callsign( - callsign=request_in.callsign, pool=None, extra={}, csr=request_in.csr - ) + enrollment = await Enrollment.create_for_callsign(callsign=callsign, pool=None, extra={}, csr=request_in.csr) # Get the anon_admin 'user' that will be used to approve the new admin user # and approve the user @@ -98,14 +161,40 @@ async def tms_wait() -> None: await asyncio.wait_for(tms_wait(), timeout=3.0) except asyncio.TimeoutError: LOGGER.warning("Timed out while waiting for background tasks, continuing anyway") + role_add_success = await new_admin.assign_role(role="admin") if role_add_success is False: + LOGGER.log( + AUDIT, + "First admin creation failed - user already admin: %s. This should not happen!", + callsign, + extra=audit_iam( + action="firstuser_create", + outcome="failure", + target_user=callsign, + error_code="ALREADY_ADMIN", + error_message="User already has admin role - this should not happen!", + ), + ) reason = "Error. User already admin. This shouldn't happen..." LOGGER.error("{} : {}".format(request.url, reason)) raise HTTPException(status_code=400, detail=reason) # Create JWT token for new admin user - code = await LoginCode.create_for_claims(claims={"sub": request_in.callsign}) + code = await LoginCode.create_for_claims(claims={"sub": callsign}) + + LOGGER.log( + AUDIT, + "First admin user created successfully: %s", + callsign, + extra=audit_iam( + action="firstuser_create", + outcome="success", + target_user=callsign, + new_role="admin", + bootstrap_operation=True, + ), + ) return FirstuserAddAdminOut(admin_added=True, jwt_exchange_code=code) From 52cb5ba66305befbdbfff457a1c28b7ceed29844 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 17:42:32 +0200 Subject: [PATCH 08/13] feat: auditlogging to tilauspalvelu token generation endpoints --- src/rasenmaeher_api/web/api/tokens/views.py | 159 +++++++++++++++++++- 1 file changed, 155 insertions(+), 4 deletions(-) diff --git a/src/rasenmaeher_api/web/api/tokens/views.py b/src/rasenmaeher_api/web/api/tokens/views.py index f961c39..b93d999 100644 --- a/src/rasenmaeher_api/web/api/tokens/views.py +++ b/src/rasenmaeher_api/web/api/tokens/views.py @@ -1,4 +1,4 @@ -"""Views dealing with login tokens issues by/via TILAUSPALVELU""" +"""Views dealing with login tokens issued by/via TILAUSPALVELU.""" import logging @@ -6,6 +6,14 @@ from multikeyjwt import Verifier, Issuer from multikeyjwt.middleware import JWTBearer, JWTPayload +from libpvarki.auditlogging import ( + audit_authentication, + audit_authorization, + audit_session, + audit_iam, + audit_anomaly, + AUDIT, +) from .schema import JWTExchangeRequestResponse, LoginCodeCreateRequest, LoginCodeRequestResponse from ....db import SeenToken, LoginCode @@ -21,15 +29,67 @@ async def exchange_token(req: JWTExchangeRequestResponse) -> JWTExchangeRequestR LOGGER.debug("Called") payload = Verifier.singleton().decode(req.jwt) if not payload: + LOGGER.log( + AUDIT, + "JWT exchange failed - no valid token", + extra=audit_authentication( + action="jwt_exchange", + outcome="failure", + error_code="INVALID_TOKEN", + error_message="No acceptable token found in request", + ), + ) LOGGER.info("No acceptable token found") raise HTTPException(status_code=403, detail="Not authenticated") + + token_subject = payload.get("sub", "unknown") + if "nonce" not in payload: + LOGGER.log( + AUDIT, + "JWT exchange failed - missing nonce: %s", + token_subject, + extra=audit_anomaly( + action="jwt_exchange_no_nonce", + error_code="MISSING_NONCE", + error_message="JWT missing required nonce field - possible crafted token", + target_user=token_subject, + ), + ) LOGGER.error("No nonce given, this is not allowed") raise HTTPException(status_code=403, detail="Not authenticated") - # This will throw 403 compatible exception for reuser - await SeenToken.use_token(payload["nonce"]) + + # This will throw 403 compatible exception for reuse + try: + await SeenToken.use_token(payload["nonce"]) + except HTTPException: + LOGGER.log( + AUDIT, + "JWT exchange failed - token replay attempt: %s", + token_subject, + extra=audit_anomaly( + action="jwt_exchange_replay", + error_code="TOKEN_REUSED", + error_message="Attempt to reuse single-use JWT - replay attack detected", + target_user=token_subject, + ), + ) + raise + # We don't strictly *need* to check this but it makes unusable claim sets much more obvious earlier if not payload.get("anon_admin_session", False): + LOGGER.log( + AUDIT, + "JWT exchange failed - wrong token type: %s", + token_subject, + extra=audit_authorization( + action="jwt_exchange", + outcome="failure", + target_user=token_subject, + error_code="WRONG_TOKEN_TYPE", + error_message="Token lacks anon_admin_session claim", + ), + ) LOGGER.error("This token cannot be exchanged for anonymous admin session") raise HTTPException(status_code=403, detail="Not authenticated") @@ -37,6 +97,18 @@ async def exchange_token(req: JWTExchangeRequestResponse) -> JWTExchangeRequestR new_jwt = Issuer.singleton().issue( {key: val for key, val in payload.items() if key not in TOKEN_COPY_EXCLUDE_FIELDS} ) + + LOGGER.log( + AUDIT, + "JWT exchange successful: %s", + token_subject, + extra=audit_authentication( + action="jwt_exchange", + outcome="success", + target_user=token_subject, + ), + ) + resp = JWTExchangeRequestResponse(jwt=new_jwt) LOGGER.debug("returning {}".format(resp)) return resp @@ -47,7 +119,22 @@ async def refresh_token(jwt: JWTPayload = Depends(JWTBearer(auto_error=True))) - """Refresh your JWT""" # Copy all claims to fresh token LOGGER.debug("Called") + + token_subject = jwt.get("sub", "unknown") + new_jwt = Issuer.singleton().issue({key: val for key, val in jwt.items() if key not in TOKEN_COPY_EXCLUDE_FIELDS}) + + LOGGER.log( + AUDIT, + "JWT refreshed: %s", + token_subject, + extra=audit_session( + action="jwt_refresh", + outcome="success", + target_user=token_subject, + ), + ) + resp = JWTExchangeRequestResponse(jwt=new_jwt) LOGGER.debug("returning {}".format(resp)) return resp @@ -59,10 +146,41 @@ async def create_code( ) -> LoginCodeRequestResponse: """Generate an alphanumeric code that can be exchanged for JWT with the given claims""" LOGGER.debug("Called") + + initiator = jwt.get("sub", "unknown") + if not jwt.get("anon_admin_session", False): + LOGGER.log( + AUDIT, + "Login code generation blocked - unauthorized: %s", + initiator, + extra=audit_authorization( + action="logincode_generate", + outcome="failure", + error_code="MISSING_CLAIM", + error_message="JWT missing anon_admin_session claim", + ), + ) LOGGER.error("Requesting JWT must have admin session claim") raise HTTPException(status_code=403, detail="Forbidden") + code = await LoginCode.create_for_claims(req.claims) + + target_user = req.claims.get("sub", "unspecified") + + LOGGER.log( + AUDIT, + "Login code generated for: %s", + target_user, + extra=audit_iam( + action="logincode_generate", + outcome="success", + target_user=target_user, + target_resource=code, + target_resource_type="logincode", + ), + ) + resp = LoginCodeRequestResponse(code=code) LOGGER.debug("returning {}".format(resp)) return resp @@ -73,10 +191,43 @@ async def exchange_code(req: LoginCodeRequestResponse) -> JWTExchangeRequestResp """Exchange code for JWT""" LOGGER.debug("Called") if not req.code: + LOGGER.log( + AUDIT, + "Code exchange failed - no code provided", + extra=audit_authentication( + action="logincode_exchange", + outcome="failure", + error_code="NO_CODE", + error_message="No login code provided in request", + ), + ) LOGGER.info("No code given") raise HTTPException(status_code=403, detail="Not authenticated") - new_jwt = await LoginCode.use_code(req.code) + try: + new_jwt = await LoginCode.use_code(req.code) + except HTTPException: + LOGGER.log( + AUDIT, + "Code exchange failed - invalid or used code", + extra=audit_authentication( + action="logincode_exchange", + outcome="failure", + error_code="INVALID_CODE", + error_message="Login code not found or already used", + ), + ) + raise + + LOGGER.log( + AUDIT, + "Code exchange successful", + extra=audit_authentication( + action="logincode_exchange", + outcome="success", + ), + ) + resp = JWTExchangeRequestResponse(jwt=new_jwt) LOGGER.debug("returning {}".format(resp)) return resp From 3ac57ce4baec1e886773e4c330de14183eafc654 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:05:42 +0200 Subject: [PATCH 09/13] feat: auditlogging to people user deletion endpoint --- src/rasenmaeher_api/web/api/people/skibiid.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/rasenmaeher_api/web/api/people/skibiid.py diff --git a/src/rasenmaeher_api/web/api/people/skibiid.py b/src/rasenmaeher_api/web/api/people/skibiid.py new file mode 100644 index 0000000..e69de29 From 16612a2885cad839c56712784b47f3ad45bbb302 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 19:35:45 +0200 Subject: [PATCH 10/13] Revert "feat: auditlogging to people user deletion endpoint" This reverts commit 3ac57ce4baec1e886773e4c330de14183eafc654. --- src/rasenmaeher_api/web/api/people/skibiid.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/rasenmaeher_api/web/api/people/skibiid.py diff --git a/src/rasenmaeher_api/web/api/people/skibiid.py b/src/rasenmaeher_api/web/api/people/skibiid.py deleted file mode 100644 index e69de29..0000000 From dfcd1032e4b3f84cf4b29d92c5d2e05c1526daa2 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 19:44:32 +0200 Subject: [PATCH 11/13] feat: auditlogging to people (delete user) endpoint --- src/rasenmaeher_api/web/api/people/views.py | 61 ++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/rasenmaeher_api/web/api/people/views.py b/src/rasenmaeher_api/web/api/people/views.py index 1d25834..566b8fd 100644 --- a/src/rasenmaeher_api/web/api/people/views.py +++ b/src/rasenmaeher_api/web/api/people/views.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends from libpvarki.schemas.generic import OperationResultResponse +from libpvarki.auditlogging import audit_iam, AUDIT from .schema import ( CallSignPerson, @@ -14,7 +15,7 @@ from ..middleware.mtls import MTLSorJWT from ..middleware.user import ValidUser from ....db import Person -from ....db.errors import BackendError +from ....db.errors import BackendError, NotFound LOGGER = logging.getLogger(__name__) @@ -108,10 +109,66 @@ async def request_people_list_byrole(role: str) -> PeopleListOut: ) async def delete_person(callsign: str) -> OperationResultResponse: """delete==revoke a callsign""" - person = await Person.by_callsign(callsign) + try: + person = await Person.by_callsign(callsign) + except NotFound: + LOGGER.log( + AUDIT, + "User revocation failed - user not found: %s", + callsign, + extra=audit_iam( + action="user_revoke", + outcome="failure", + target_user=callsign, + admin_action=True, + error_code="USER_NOT_FOUND", + error_message="User does not exist", + ), + ) + return OperationResultResponse(success=False, error="User not found") + try: deleted = await person.delete() + if deleted: + LOGGER.log( + AUDIT, + "User revoked: %s", + callsign, + extra=audit_iam( + action="user_revoke", + outcome="success", + target_user=callsign, + admin_action=True, + ), + ) + else: + LOGGER.log( + AUDIT, + "User revocation returned false: %s", + callsign, + extra=audit_iam( + action="user_revoke", + outcome="failure", + target_user=callsign, + admin_action=True, + error_code="DELETE_RETURNED_FALSE", + error_message="Delete operation returned false", + ), + ) return OperationResultResponse(success=deleted) except BackendError as exc: + LOGGER.log( + AUDIT, + "User revocation failed - backend error: %s", + callsign, + extra=audit_iam( + action="user_revoke", + outcome="failure", + target_user=callsign, + admin_action=True, + error_code="BACKEND_ERROR", + error_message=str(exc), + ), + ) LOGGER.error("Backend failure: {}".format(exc)) return OperationResultResponse(success=False, error=str(exc)) From 0b7b2339840470e7da6e56bbdaf920a5337e2123 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Sat, 27 Dec 2025 19:47:52 +0200 Subject: [PATCH 12/13] feat: track which codes are checked, whether they are firstadmincodes --- src/rasenmaeher_api/web/api/firstuser/views.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/rasenmaeher_api/web/api/firstuser/views.py b/src/rasenmaeher_api/web/api/firstuser/views.py index 3cd24d9..472bc74 100644 --- a/src/rasenmaeher_api/web/api/firstuser/views.py +++ b/src/rasenmaeher_api/web/api/firstuser/views.py @@ -40,12 +40,14 @@ async def get_check_code( except NotFound: LOGGER.log( AUDIT, - "First user code check failed - input is not a first user otp code", + "First user code check - code is not a first admin otp code: %s", + params.temp_admin_code, extra=audit_authentication( action="firstuser_code_check", outcome="failure", - error_code="WRONG_CODE", - error_message="Input is not a first user otp code", + error_code="CODE_NOT_FIRST_ADMIN_OTP", + error_message="Code is not a first admin otp", + attempted_code=params.temp_admin_code, ), ) return FirstuserCheckCodeOut(code_ok=False) @@ -60,6 +62,7 @@ async def get_check_code( outcome="failure", error_code="BACKEND_ERROR", error_message="Undefined backend error", + attempted_code=params.temp_admin_code, ), ) _reason = "Error. Undefined backend error q_ssjsfjwe1" @@ -70,12 +73,14 @@ async def get_check_code( if res.used_on is not None: LOGGER.log( AUDIT, - "First user code check failed - code already used", + "First user code check failed - code already used: %s", + params.temp_admin_code, extra=audit_authentication( action="firstuser_code_check", outcome="failure", error_code="CODE_ALREADY_USED", error_message="Temporary admin code has already been used", + attempted_code=params.temp_admin_code, ), ) _reason = "Code already used" @@ -84,10 +89,12 @@ async def get_check_code( LOGGER.log( AUDIT, - "First user otp code check successful", + "First user OTP code check successful: %s", + params.temp_admin_code, extra=audit_authentication( action="firstuser_code_check", outcome="success", + validated_code=params.temp_admin_code, ), ) From f0e134fe1d767915c20927ad2fdf47329ed1f0d5 Mon Sep 17 00:00:00 2001 From: Benjam <53127823+benjamsf@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:54:20 +0200 Subject: [PATCH 13/13] feat: from libpvarki use code_fingerprint to avoid logging working invitecodes --- .pre-commit-config.yaml | 2 +- poetry.lock | 10 +-- pyproject.toml | 2 +- .../web/api/enrollment/views.py | 72 +++++++++---------- .../web/api/firstuser/views.py | 32 ++++----- 5 files changed, 52 insertions(+), 66 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30da704..1042f27 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,7 +67,7 @@ repos: - id: detect-secrets language: system exclude: "poetry\\.lock|tests/data/.*" - # args: ['--baseline', '.secrets.baseline'] + args: ['--baseline', '.secrets.baseline'] - repo: https://github.com/rhysd/actionlint rev: "v1.7.8" hooks: diff --git a/poetry.lock b/poetry.lock index fcbd992..d31e526 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1442,7 +1442,7 @@ vector = ["http"] [package.source] type = "git" url = "https://gitlab.com/advian-oss/python-libadvian.git" -reference = "log_levels" +reference = "1002a851ba6284551132dbef075c2fa0e1ba110d" resolved_reference = "1002a851ba6284551132dbef075c2fa0e1ba110d" [[package]] @@ -1450,7 +1450,7 @@ name = "libpvarki" version = "1.9.2" description = "Common helpers like standard logging init" optional = false -python-versions = "^3.8" +python-versions = "^3.9" groups = ["main"] files = [] develop = false @@ -1462,14 +1462,14 @@ brotli = "^1.0" cryptography = ">=41.0" ecs-logging = "^2.0" fastapi = ">0.89,<1.0" -libadvian = {git = "https://gitlab.com/advian-oss/python-libadvian.git", branch = "log_levels"} +libadvian = {git = "https://gitlab.com/advian-oss/python-libadvian.git", rev = "1002a851ba6284551132dbef075c2fa0e1ba110d"} pydantic = ">=1.10,<2.0" [package.source] type = "git" url = "https://github.com/pvarki/python-libpvarki.git" reference = "audit-logging-v1" -resolved_reference = "e7d1fbadfd2d9aee1f479a44c367aabf84f68b57" +resolved_reference = "55e45136ee345e38a546444db903ff2746de6441" [[package]] name = "librt" @@ -3592,4 +3592,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "681e5fa478321c63d30b019ec7ba44270fde4039122498081f996034cc106a55" +content-hash = "497118b56181c0982f56d989f4322dcd081e340856280c4537baa6948291a925" diff --git a/pyproject.toml b/pyproject.toml index b438d7f..632d5ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ priority = "explicit" [tool.poetry.dependencies] python = ">=3.11,<4.0" -libadvian = { git = "https://gitlab.com/advian-oss/python-libadvian.git", branch = "log_levels" } +libadvian = { git = "https://gitlab.com/advian-oss/python-libadvian.git", rev = "1002a851ba6284551132dbef075c2fa0e1ba110d" } click = "^8.0" fastapi = ">=0.89,<1.0" # caret behaviour on 0.x is to lock to 0.x.* # FIXME: Migrate to v2, see https://docs.pydantic.dev/2.3/migration/#basesettings-has-moved-to-pydantic-settings diff --git a/src/rasenmaeher_api/web/api/enrollment/views.py b/src/rasenmaeher_api/web/api/enrollment/views.py index 8110aae..bb15750 100644 --- a/src/rasenmaeher_api/web/api/enrollment/views.py +++ b/src/rasenmaeher_api/web/api/enrollment/views.py @@ -4,12 +4,11 @@ import logging import uuid - from fastapi import APIRouter, Request, Body, Depends, HTTPException from multikeyjwt import Issuer from libpvarki.schemas.generic import OperationResultResponse -from libpvarki.auditlogging import audit_authentication, audit_iam, AUDIT +from libpvarki.auditlogging import audit_authentication, code_fingerprint, audit_iam, AUDIT from .schema import ( EnrollmentStatusIn, @@ -199,7 +198,7 @@ async def request_enrollment_init( LOGGER.log( AUDIT, - "Enrollment created by admin: %s", + "New enrollment initiated by invitecode, invitecode creator is the admin %s", callsign, extra=audit_iam( action="enrollment_create", @@ -214,7 +213,7 @@ async def request_enrollment_init( except Exception as exc: LOGGER.log( AUDIT, - "Enrollment creation failed: %s", + "New enrollment initiation failed, originating invitecode creator is the admin %s", callsign, extra=audit_iam( action="enrollment_create", @@ -412,7 +411,7 @@ async def request_enrollment_lock( pool = await EnrollmentPool.by_pk(_usr_enrollment.pool) pool_owner = await Person.by_pk(pool.owner) invitecode_context = { - "invitecode": pool.invitecode, + "invitecode_id": str(pool.pk), "invitecode_owner": pool_owner.callsign, "invitecode_created": pool.created.isoformat(), } @@ -481,7 +480,7 @@ async def post_enrollment_accept( pool = await EnrollmentPool.by_pk(pending_enrollment.pool) pool_owner = await Person.by_pk(pool.owner) invitecode_context = { - "invitecode": pool.invitecode, + "invitecode_id": str(pool.pk), "invitecode_owner": pool_owner.callsign, "invitecode_created": pool.created.isoformat(), } @@ -491,7 +490,7 @@ async def post_enrollment_accept( if request_in.approvecode != pending_enrollment.approvecode: LOGGER.log( AUDIT, - "Enrollment approval failed - invalid code: %s", + "Enrollment approval failed - invalid approvecode: %s", target_callsign, extra=audit_iam( action="enrollment_approve", @@ -509,7 +508,7 @@ async def post_enrollment_accept( LOGGER.log( AUDIT, - "Enrollment approved: %s", + "Enrollment approved for user: %s", target_callsign, extra=audit_iam( action="enrollment_approve", @@ -540,7 +539,7 @@ async def post_invite_code(request: Request) -> EnrollmentInviteCodeCreateOut: extra=audit_iam( action="invitecode_create", outcome="success", - target_resource=pool.invitecode, + target_resource=str(pool.pk), target_resource_type="invitecode", admin_action=True, ), @@ -567,13 +566,12 @@ async def put_activate_invite_code( except NotFound as exc: LOGGER.log( AUDIT, - "Invite code activation failed - not found: %s", - invite_code, + "Invite code activation failed - not found", extra=audit_iam( action="invitecode_activate", outcome="failure", - target_resource=invite_code, target_resource_type="invitecode", + input_fingerprint=code_fingerprint(invite_code), error_code="NOT_FOUND", error_message="Invite code not found", ), @@ -585,25 +583,23 @@ async def put_activate_invite_code( if _activated_obj.active: LOGGER.log( AUDIT, - "Invite code activated: %s", - invite_code, + "Invite code activated", extra=audit_iam( action="invitecode_activate", outcome="success", - target_resource=invite_code, + target_resource=str(obj.pk), target_resource_type="invitecode", ), ) - return OperationResultResponse(success=True, extra=f"Activated {invite_code}") + return OperationResultResponse(success=True, extra=f"Activated invitecode {obj.pk}") LOGGER.log( AUDIT, - "Invite code activation failed: %s", - invite_code, + "Invite code activation failed", extra=audit_iam( action="invitecode_activate", outcome="failure", - target_resource=invite_code, + target_resource=str(obj.pk), target_resource_type="invitecode", error_code="ACTIVATION_FAILED", error_message="Unable to activate invite code", @@ -632,13 +628,12 @@ async def put_deactivate_invite_code( except NotFound as exc: LOGGER.log( AUDIT, - "Invite code deactivation failed - not found: %s", - invite_code, + "Invite code deactivation failed - not found", extra=audit_iam( action="invitecode_deactivate", outcome="failure", - target_resource=invite_code, target_resource_type="invitecode", + input_fingerprint=code_fingerprint(invite_code), error_code="NOT_FOUND", error_message="Invite code not found", ), @@ -650,25 +645,23 @@ async def put_deactivate_invite_code( if _deactivated_obj.active is False: LOGGER.log( AUDIT, - "Invite code deactivated: %s", - invite_code, + "Invite code deactivated", extra=audit_iam( action="invitecode_deactivate", outcome="success", - target_resource=invite_code, + target_resource=str(obj.pk), target_resource_type="invitecode", ), ) - return OperationResultResponse(success=True, extra=f"Disabled {invite_code}") + return OperationResultResponse(success=True, extra=f"Disabled invitecode {obj.pk}") LOGGER.log( AUDIT, - "Invite code deactivation failed: %s", - invite_code, + "Invite code deactivation failed", extra=audit_iam( action="invitecode_deactivate", outcome="failure", - target_resource=invite_code, + target_resource=str(obj.pk), target_resource_type="invitecode", error_code="DEACTIVATION_FAILED", error_message="Unable to deactivate invite code", @@ -688,16 +681,16 @@ async def delete_invite_code( """ try: obj = await EnrollmentPool.by_invitecode(invitecode=invite_code) + pool_pk = str(obj.pk) await obj.delete() LOGGER.log( AUDIT, - "Invite code deleted: %s", - invite_code, + "Invite code deleted", extra=audit_iam( action="invitecode_delete", outcome="success", - target_resource=invite_code, + target_resource=pool_pk, target_resource_type="invitecode", admin_action=True, ), @@ -708,13 +701,12 @@ async def delete_invite_code( except NotFound: LOGGER.log( AUDIT, - "Invite code deletion failed - not found: %s", - invite_code, + "Invite code deletion failed - not found", extra=audit_iam( action="invitecode_delete", outcome="failure", - target_resource=invite_code, target_resource_type="invitecode", + input_fingerprint=code_fingerprint(invite_code), admin_action=True, error_code="NOT_FOUND", error_message="Invite code not found", @@ -767,6 +759,7 @@ async def post_enroll_invite_code( target_user=callsign, error_code="INVALID_INVITECODE", error_message="Invite code not found", + input_fingerprint=code_fingerprint(invite_code), ), ) raise HTTPException(status_code=404, detail="Invite code not found") from exc @@ -784,7 +777,7 @@ async def post_enroll_invite_code( target_user=callsign, error_code="INVITECODE_DISABLED", error_message="Invite code is disabled", - invitecode=invite_code, + invitecode_id=str(obj.pk), invitecode_owner=invitecode_owner.callsign, invitecode_created=obj.created.isoformat(), ), @@ -806,7 +799,7 @@ async def post_enroll_invite_code( target_user=callsign, error_code="CALLSIGN_TAKEN", error_message="Callsign is already in use", - invitecode=invite_code, + invitecode_id=str(obj.pk), invitecode_owner=invitecode_owner.callsign, invitecode_created=obj.created.isoformat(), ), @@ -825,14 +818,13 @@ async def post_enroll_invite_code( LOGGER.log( AUDIT, - "Enrollment initialization successful - enrollment created: %s", - callsign, + "Enrollment initialization successful - enrollment created", extra=audit_authentication( action="enroll_initialize", outcome="success", target_user=callsign, enrollment_state="pending_approval", - invitecode=invite_code, + invitecode_id=str(obj.pk), invitecode_owner=invitecode_owner.callsign, invitecode_created=obj.created.isoformat(), ), diff --git a/src/rasenmaeher_api/web/api/firstuser/views.py b/src/rasenmaeher_api/web/api/firstuser/views.py index 472bc74..88ae39f 100644 --- a/src/rasenmaeher_api/web/api/firstuser/views.py +++ b/src/rasenmaeher_api/web/api/firstuser/views.py @@ -7,7 +7,7 @@ from multikeyjwt.middleware import JWTBearer, JWTPayload from libadvian.tasks import TaskMaster -from libpvarki.auditlogging import audit_authentication, audit_iam, audit_authorization, AUDIT +from libpvarki.auditlogging import code_fingerprint, audit_authentication, audit_iam, audit_authorization, AUDIT from rasenmaeher_api.web.api.firstuser.schema import ( FirstuserCheckCodeIn, @@ -25,7 +25,6 @@ LOGGER = logging.getLogger(__name__) -# /check-code @router.get("/check-code", response_model=FirstuserCheckCodeOut) async def get_check_code( request: Request, @@ -35,19 +34,20 @@ async def get_check_code( /check-code?temp_admin_code=xxxx, Checks if the given code can be used or not in this /firstuser api route... """ + code_fp = code_fingerprint(params.temp_admin_code) + try: res = await LoginCode.by_code(code=params.temp_admin_code) except NotFound: LOGGER.log( AUDIT, - "First user code check - code is not a first admin otp code: %s", - params.temp_admin_code, + "First user code check - code is not a first admin otp code", extra=audit_authentication( action="firstuser_code_check", outcome="failure", error_code="CODE_NOT_FIRST_ADMIN_OTP", error_message="Code is not a first admin otp", - attempted_code=params.temp_admin_code, + input_fingerprint=code_fp, ), ) return FirstuserCheckCodeOut(code_ok=False) @@ -62,7 +62,7 @@ async def get_check_code( outcome="failure", error_code="BACKEND_ERROR", error_message="Undefined backend error", - attempted_code=params.temp_admin_code, + input_fingerprint=code_fp, ), ) _reason = "Error. Undefined backend error q_ssjsfjwe1" @@ -73,14 +73,13 @@ async def get_check_code( if res.used_on is not None: LOGGER.log( AUDIT, - "First user code check failed - code already used: %s", - params.temp_admin_code, + "First user code check failed - code already used", extra=audit_authentication( action="firstuser_code_check", outcome="failure", error_code="CODE_ALREADY_USED", error_message="Temporary admin code has already been used", - attempted_code=params.temp_admin_code, + logincode_id=str(res.pk), ), ) _reason = "Code already used" @@ -89,19 +88,17 @@ async def get_check_code( LOGGER.log( AUDIT, - "First user OTP code check successful: %s", - params.temp_admin_code, + "First user OTP code check successful", extra=audit_authentication( action="firstuser_code_check", outcome="success", - validated_code=params.temp_admin_code, + logincode_id=str(res.pk), ), ) return FirstuserCheckCodeOut(code_ok=True) -# /add-admin @router.post("/add-admin", response_model=FirstuserAddAdminOut) async def post_admin_add( request: Request, @@ -119,8 +116,7 @@ async def post_admin_add( if not jwt.get("anon_admin_session", False): LOGGER.log( AUDIT, - "First admin creation failed - unauthorized JWT: %s", - callsign, + "First admin creation failed - unauthorized JWT", extra=audit_authorization( action="firstuser_create", outcome="failure", @@ -174,8 +170,7 @@ async def tms_wait() -> None: if role_add_success is False: LOGGER.log( AUDIT, - "First admin creation failed - user already admin: %s. This should not happen!", - callsign, + "First admin creation failed - user already admin (this should not happen)", extra=audit_iam( action="firstuser_create", outcome="failure", @@ -193,8 +188,7 @@ async def tms_wait() -> None: LOGGER.log( AUDIT, - "First admin user created successfully: %s", - callsign, + "First admin user created successfully", extra=audit_iam( action="firstuser_create", outcome="success",