diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ed62d10ba..8363b88652 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,6 @@ repos: hooks: - id: gitleaks-pre-commit name: gitleaks git (staged only) - entry: echo "aloha" + entry: echo "Aloha" language: system pass_filenames: false diff --git a/api/entrypoint.py b/api/entrypoint.py index 7269073587..d004962b25 100644 --- a/api/entrypoint.py +++ b/api/entrypoint.py @@ -8,94 +8,116 @@ get_middleware as get_supertokens_middleware, ) -from oss.src.utils.logging import get_module_logger -from oss.src.routers import ( - admin_router, - app_router, - environment_router, - evaluators_router, - testset_router, - user_profile, - variants_router, - bases_router, - configs_router, - health_router, - permissions_router, - projects_router, - api_key_router, - organization_router, - workspace_router, - container_router, -) -from oss.src.apis.fastapi.middleware.request import request_id_middleware from oss.src.utils.common import is_ee +from oss.src.utils.logging import get_module_logger +from oss.src.utils.helpers import warn_deprecated_env_vars, validate_required_env_vars + from oss.src.open_api import open_api_tags_metadata + from oss.databases.postgres.migrations.core.utils import ( - check_for_new_migrations as check_for_new_entities_migratons, + check_for_new_migrations as check_for_new_core_migrations, ) from oss.databases.postgres.migrations.tracing.utils import ( check_for_new_migrations as check_for_new_tracing_migrations, ) -from oss.src.utils.helpers import warn_deprecated_env_vars, validate_required_env_vars -from oss.src.dbs.postgres.secrets.dao import SecretsDAO -from oss.src.core.secrets.services import VaultService -from oss.src.apis.fastapi.vault.router import VaultRouter + from oss.src.services.auth_helper import authentication_middleware from oss.src.services.analytics_service import analytics_middleware -from oss.src.dbs.postgres.observability.dao import ObservabilityDAO -from oss.src.core.observability.service import ObservabilityService -from oss.src.apis.fastapi.observability.router import ObservabilityRouter -from oss.src.dbs.postgres.tracing.dao import TracingDAO -from oss.src.dbs.postgres.git.dao import GitDAO -from oss.src.dbs.postgres.blobs.dao import BlobsDAO - -from oss.src.apis.fastapi.workflows.router import WorkflowsRouter -from oss.src.core.workflows.service import WorkflowsService +# DBEs +from oss.src.dbs.postgres.queries.dbes import ( + QueryArtifactDBE, + QueryVariantDBE, + QueryRevisionDBE, +) +from oss.src.dbs.postgres.testcases.dbes import ( + TestcaseBlobDBE, +) +from oss.src.dbs.postgres.testsets.dbes import ( + TestsetArtifactDBE, + TestsetVariantDBE, + TestsetRevisionDBE, +) from oss.src.dbs.postgres.workflows.dbes import ( WorkflowArtifactDBE, WorkflowVariantDBE, WorkflowRevisionDBE, ) -from oss.src.dbs.postgres.git.dao import GitDAO -from oss.src.core.workflows.service import WorkflowsService -from oss.src.apis.fastapi.workflows.router import WorkflowsRouter -from oss.src.apis.fastapi.evaluators.router import SimpleEvaluatorsRouter +# DAOs +from oss.src.dbs.postgres.secrets.dao import SecretsDAO +from oss.src.dbs.postgres.observability.dao import ObservabilityDAO +from oss.src.dbs.postgres.tracing.dao import TracingDAO +from oss.src.dbs.postgres.blobs.dao import BlobsDAO +from oss.src.dbs.postgres.git.dao import GitDAO +from oss.src.dbs.postgres.evaluations.dao import EvaluationsDAO -from oss.src.apis.fastapi.tracing.router import TracingRouter +# Services +from oss.src.core.secrets.services import VaultService +from oss.src.core.observability.service import ObservabilityService from oss.src.core.tracing.service import TracingService +from oss.src.core.invocations.service import InvocationsService +from oss.src.core.annotations.service import AnnotationsService +from oss.src.core.testcases.service import TestcasesService +from oss.src.core.testsets.service import TestsetsService +from oss.src.core.testsets.service import SimpleTestsetsService +from oss.src.core.queries.service import QueriesService +from oss.src.core.queries.service import SimpleQueriesService +from oss.src.core.applications.service import LegacyApplicationsService +from oss.src.core.workflows.service import WorkflowsService +from oss.src.core.evaluators.service import EvaluatorsService +from oss.src.core.evaluators.service import SimpleEvaluatorsService +from oss.src.core.evaluations.service import EvaluationsService +from oss.src.core.evaluations.service import SimpleEvaluationsService +# Routers +from oss.src.apis.fastapi.vault.router import VaultRouter +from oss.src.apis.fastapi.observability.router import ObservabilityRouter +from oss.src.apis.fastapi.tracing.router import TracingRouter +from oss.src.apis.fastapi.invocations.router import InvocationsRouter from oss.src.apis.fastapi.annotations.router import AnnotationsRouter +from oss.src.apis.fastapi.testcases.router import TestcasesRouter +from oss.src.apis.fastapi.testsets.router import TestsetsRouter +from oss.src.apis.fastapi.testsets.router import SimpleTestsetsRouter +from oss.src.apis.fastapi.queries.router import QueriesRouter +from oss.src.apis.fastapi.queries.router import SimpleQueriesRouter +from oss.src.apis.fastapi.applications.router import LegacyApplicationsRouter +from oss.src.apis.fastapi.workflows.router import WorkflowsRouter +from oss.src.apis.fastapi.evaluators.router import EvaluatorsRouter +from oss.src.apis.fastapi.evaluators.router import SimpleEvaluatorsRouter +from oss.src.apis.fastapi.evaluations.router import EvaluationsRouter +from oss.src.apis.fastapi.evaluations.router import SimpleEvaluationsRouter -from oss.src.apis.fastapi.testsets.router import SimpleTestsetsRouter -from oss.src.core.testsets.service import TestsetsService -from oss.src.core.testcases.service import TestcasesService -from oss.src.dbs.postgres.testcases.dbes import TestcaseBlobDBE -from oss.src.dbs.postgres.testsets.dbes import ( - TestsetArtifactDBE, - TestsetVariantDBE, - TestsetRevisionDBE, +from oss.src.routers import ( + admin_router, + app_router, + environment_router, + evaluators_router, + testset_router, + user_profile, + variants_router, + bases_router, + configs_router, + health_router, + permissions_router, + projects_router, + api_key_router, + organization_router, + workspace_router, + container_router, ) +import agenta as ag -from oss.src.apis.fastapi.evaluations.router import EvaluationsRouter -from oss.src.core.evaluations.service import EvaluationsService -from oss.src.dbs.postgres.evaluations.dao import EvaluationsDAO - +ag.init() -origins = [ - "http://localhost:3000", - "http://localhost:3001", - "http://0.0.0.0:3000", - "http://0.0.0.0:3001", - "https://docs.agenta.ai", -] +ee = None +if is_ee(): + import ee.src.main as ee # type: ignore -celery_app = Celery("agenta_app") -celery_app.config_from_object("oss.src.celery_config") +log = get_module_logger(__name__) @signals.setup_logging.connect @@ -103,11 +125,8 @@ def on_celery_setup_logging(**kwargs): pass # effectively no-op, preventing celery from reconfiguring logging -log = get_module_logger(__name__) - - @asynccontextmanager -async def lifespan(application: FastAPI, cache=True): +async def lifespan(*args, **kwargs): """ Lifespan initializes the database engine and load the default llm services. @@ -116,7 +135,7 @@ async def lifespan(application: FastAPI, cache=True): cache: A boolean value that indicates whether to use the cached data or not. """ - await check_for_new_entities_migratons() + await check_for_new_core_migrations() await check_for_new_tracing_migrations() warn_deprecated_env_vars() @@ -125,71 +144,145 @@ async def lifespan(application: FastAPI, cache=True): yield +celery_app = Celery("agenta_app") + +celery_app.config_from_object("oss.src.celery_config") + app = FastAPI( lifespan=lifespan, openapi_tags=open_api_tags_metadata, root_path="/api", ) +# MIDDLEWARE ------------------------------------------------------------------- app.middleware("http")(authentication_middleware) -app.middleware("http")(analytics_middleware) -app.middleware("http")(request_id_middleware) -if is_ee(): - import ee.src.main as ee - - app = ee.extend_main(app) +app.middleware("http")(analytics_middleware) -app.add_middleware(get_supertokens_middleware()) -allow_headers = ["Content-Type"] + get_all_supertokens_cors_headers() +app.add_middleware( + get_supertokens_middleware(), +) app.add_middleware( CORSMiddleware, - allow_origins=origins, + allow_origins=[ + "http://localhost:3000", + "http://localhost:3001", + "http://0.0.0.0:3000", + "http://0.0.0.0:3001", + "https://docs.agenta.ai", + ], allow_origin_regex=r"https://.*\.vercel\.app", allow_credentials=True, allow_methods=["*"], - allow_headers=allow_headers, + allow_headers=["Content-Type"] + get_all_supertokens_cors_headers(), ) -app.include_router(admin_router.router, prefix="/admin", tags=["Admin"]) -app.include_router(health_router.router, prefix="/health") -app.include_router( - permissions_router.router, prefix="/permissions", tags=["Access Control"] +if ee and is_ee(): + app = ee.extend_main(app) + +# DAOS ------------------------------------------------------------------------- + +secrets_dao = SecretsDAO() + +observability_dao = ObservabilityDAO() + +tracing_dao = TracingDAO() + +testcases_dao = BlobsDAO( + BlobDBE=TestcaseBlobDBE, ) -app.include_router(projects_router.router, prefix="/projects", tags=["Scopes"]) -app.include_router(user_profile.router, prefix="/profile") -app.include_router(app_router.router, prefix="/apps", tags=["Apps"]) -app.include_router(variants_router.router, prefix="/variants", tags=["Variants"]) -app.include_router(container_router.router, prefix="/containers", tags=["Containers"]) -app.include_router(evaluators_router.router, prefix="/evaluators", tags=["Evaluators"]) -app.include_router(testset_router.router, prefix="/testsets", tags=["Testsets"]) -app.include_router( - environment_router.router, prefix="/environments", tags=["Environments"] +testsets_dao = GitDAO( + ArtifactDBE=TestsetArtifactDBE, + VariantDBE=TestsetVariantDBE, + RevisionDBE=TestsetRevisionDBE, ) -app.include_router(bases_router.router, prefix="/bases", tags=["Bases"]) -app.include_router(configs_router.router, prefix="/configs", tags=["Configs"]) -app.include_router(api_key_router.router, prefix="/keys", tags=["Api Keys"]) -app.include_router( - organization_router.router, prefix="/organizations", tags=["Organization"] + +queries_dao = GitDAO( + ArtifactDBE=QueryArtifactDBE, + VariantDBE=QueryVariantDBE, + RevisionDBE=QueryRevisionDBE, ) -app.include_router(workspace_router.router, prefix="/workspaces", tags=["Workspace"]) + +workflows_dao = GitDAO( + ArtifactDBE=WorkflowArtifactDBE, + VariantDBE=WorkflowVariantDBE, + RevisionDBE=WorkflowRevisionDBE, +) + +evaluations_dao = EvaluationsDAO() + +# SERVICES --------------------------------------------------------------------- vault_service = VaultService( - secrets_dao=SecretsDAO(), + secrets_dao=secrets_dao, ) -vault_router = VaultRouter( - vault_service=vault_service, +observability_service = ObservabilityService( + observability_dao=observability_dao, ) tracing_service = TracingService( - tracing_dao=TracingDAO(), + tracing_dao=tracing_dao, ) -observability_service = ObservabilityService( - observability_dao=ObservabilityDAO(), +testcases_service = TestcasesService( + testcases_dao=testcases_dao, +) + +testsets_service = TestsetsService( + testsets_dao=testsets_dao, + testcases_service=testcases_service, +) + +simple_testsets_service = SimpleTestsetsService( + testsets_service=testsets_service, +) + +queries_service = QueriesService( + queries_dao=queries_dao, +) + +simple_queries_service = SimpleQueriesService( + queries_service=queries_service, +) + +legacy_applications_service = LegacyApplicationsService() + +workflows_service = WorkflowsService( + workflows_dao=workflows_dao, +) + +evaluators_service = EvaluatorsService( + workflows_service=workflows_service, +) + +simple_evaluators_service = SimpleEvaluatorsService( + evaluators_service=evaluators_service, +) + +evaluations_service = EvaluationsService( + evaluations_dao=evaluations_dao, + tracing_service=tracing_service, + queries_service=queries_service, + testsets_service=testsets_service, + evaluators_service=evaluators_service, +) + +simple_evaluations_service = SimpleEvaluationsService( + queries_service=queries_service, + testsets_service=testsets_service, + evaluators_service=evaluators_service, + evaluations_service=evaluations_service, + simple_testsets_service=simple_testsets_service, + simple_evaluators_service=simple_evaluators_service, +) + +# ROUTERS ---------------------------------------------------------------------- + +secrets = VaultRouter( + vault_service=vault_service, ) observability = ObservabilityRouter( @@ -197,61 +290,93 @@ async def lifespan(application: FastAPI, cache=True): tracing_service=tracing_service, ) - tracing = TracingRouter( tracing_service=tracing_service, ) -workflow_service = WorkflowsService( - workflows_dao=GitDAO( - ArtifactDBE=WorkflowArtifactDBE, - VariantDBE=WorkflowVariantDBE, - RevisionDBE=WorkflowRevisionDBE, - ), - tracing_router=tracing.router, +testcases = TestcasesRouter( + testcases_service=testcases_service, ) -simple_evaluators = SimpleEvaluatorsRouter( - workflows_service=workflow_service, +testsets = TestsetsRouter( + testsets_service=testsets_service, ) +simple_testsets = SimpleTestsetsRouter( + simple_testsets_service=simple_testsets_service, +) -annotations = AnnotationsRouter( - tracing_service=tracing_service, - workflows_service=workflow_service, +queries = QueriesRouter( + queries_service=queries_service, ) -workflows = WorkflowsRouter( - workflows_service=workflow_service, +simple_queries = SimpleQueriesRouter( + simple_queries_service=simple_queries_service, ) -testcases_service = TestcasesService( - blobs_dao=BlobsDAO( - BlobDBE=TestcaseBlobDBE, - ) +legacy_applications = LegacyApplicationsRouter( + legacy_applications_service=legacy_applications_service, ) -testsets_service = TestsetsService( - testsets_dao=GitDAO( - ArtifactDBE=TestsetArtifactDBE, - VariantDBE=TestsetVariantDBE, - RevisionDBE=TestsetRevisionDBE, - ), - testcases_service=testcases_service, +workflows = WorkflowsRouter( + workflows_service=workflows_service, ) -simple_testsets = SimpleTestsetsRouter( - testsets_service=testsets_service, +evaluators = EvaluatorsRouter( + evaluators_service=evaluators_service, ) -evaluations_service = EvaluationsService( - evaluations_dao=EvaluationsDAO(), +simple_evaluators = SimpleEvaluatorsRouter( + simple_evaluators_service=simple_evaluators_service, ) evaluations = EvaluationsRouter( evaluations_service=evaluations_service, + queries_service=queries_service, +) + +simple_evaluations = SimpleEvaluationsRouter( + simple_evaluations_service=simple_evaluations_service, +) + +invocations_service = InvocationsService( + legacy_applications_service=legacy_applications_service, + tracing_router=tracing, +) + +annotations_service = AnnotationsService( + tracing_router=tracing, + evaluators_service=evaluators_service, + simple_evaluators_service=simple_evaluators_service, ) +invocations = InvocationsRouter( + invocations_service=invocations_service, +) + +annotations = AnnotationsRouter( + annotations_service=annotations_service, +) + +# MOUNTING ROUTERS TO APP ROUTES ----------------------------------------------- + +app.include_router( + secrets.router, + prefix="/vault/v1", + tags=["Secrets"], +) + +app.include_router( + router=observability.otlp, + prefix="/otlp", + tags=["Observability"], +) + +app.include_router( + router=observability.router, + prefix="/observability/v1", + tags=["Observability"], +) app.include_router( router=tracing.router, @@ -260,21 +385,27 @@ async def lifespan(application: FastAPI, cache=True): ) app.include_router( - router=simple_evaluators.router, - prefix="/preview/simple/evaluators", - tags=["Evals"], + router=invocations.router, + prefix="/preview/invocations", + tags=["Invocations"], ) app.include_router( router=annotations.router, prefix="/preview/annotations", - tags=["Evals"], + tags=["Annotations"], ) app.include_router( - router=workflows.router, - prefix="/preview/workflows", - tags=["Workflows"], + router=testcases.router, + prefix="/preview/testcases", + tags=["Testcases"], +) + +app.include_router( + router=testsets.router, + prefix="/preview/testsets", + tags=["Testsets"], ) app.include_router( @@ -283,6 +414,48 @@ async def lifespan(application: FastAPI, cache=True): tags=["Testsets"], ) +app.include_router( + router=queries.router, + prefix="/preview/queries", + tags=["Queries"], +) + +app.include_router( + router=simple_queries.router, + prefix="/preview/simple/queries", + tags=["Queries"], +) + +app.include_router( + router=legacy_applications.router, + prefix="/preview/legacy/applications", + tags=["Applications"], +) + +app.include_router( + router=workflows.router, + prefix="/preview/workflows", + tags=["Workflows"], +) + +app.include_router( + router=evaluators.router, + prefix="/preview/evaluators", + tags=["Evaluators"], +) + +app.include_router( + router=simple_evaluators.router, + prefix="/preview/simple/evaluators", + tags=["Evaluators"], +) + +app.include_router( + router=evaluations.admin_router, + prefix="/admin/evaluations", + tags=["Evaluations", "Admin"], +) + app.include_router( router=evaluations.router, prefix="/preview/evaluations", @@ -290,22 +463,108 @@ async def lifespan(application: FastAPI, cache=True): ) app.include_router( - router=observability.otlp, - prefix="/otlp", - tags=["Observability"], + router=simple_evaluations.router, + prefix="/preview/simple/evaluations", + tags=["Evaluations"], ) + app.include_router( - router=observability.router, - prefix="/observability/v1", - tags=["Observability"], + admin_router.router, + prefix="/admin", + tags=["Admin"], +) + +app.include_router( + health_router.router, + prefix="/health", ) + app.include_router( - vault_router.router, - prefix="/vault/v1", - tags=["Vault"], + permissions_router.router, + prefix="/permissions", + tags=["Access Control"], ) -if is_ee(): - import ee.src.main as ee +app.include_router( + projects_router.router, + prefix="/projects", + tags=["Scopes"], +) + +app.include_router( + user_profile.router, + prefix="/profile", +) + +app.include_router( + app_router.router, + prefix="/apps", + tags=["Apps"], +) + +app.include_router( + variants_router.router, + prefix="/variants", + tags=["Variants"], +) + +app.include_router( + container_router.router, + prefix="/containers", + tags=["Containers"], +) +app.include_router( + evaluators_router.router, + prefix="/evaluators", + tags=["Evaluators"], +) + +app.include_router( + testset_router.router, + prefix="/testsets", + tags=["Testsets"], +) + +app.include_router( + environment_router.router, + prefix="/environments", + tags=["Environments"], +) + +app.include_router( + bases_router.router, + prefix="/bases", + tags=["Bases"], +) + +app.include_router( + configs_router.router, + prefix="/configs", + tags=["Configs"], +) + +app.include_router( + api_key_router.router, + prefix="/keys", + tags=["Api Keys"], +) + +app.include_router( + organization_router.router, + prefix="/organizations", + tags=["Organization"], +) + +app.include_router( + workspace_router.router, + prefix="/workspaces", + tags=["Workspace"], +) + +# ------------------------------------------------------------------------------ + +if ee and is_ee(): app = ee.extend_app_schema(app) + + ee.load_tasks() diff --git a/api/oss/databases/postgres/migrations/core/data_migrations/evaluators.py b/api/oss/databases/postgres/migrations/core/data_migrations/evaluators.py new file mode 100644 index 0000000000..f70c0bb683 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/data_migrations/evaluators.py @@ -0,0 +1,195 @@ +import uuid +import asyncio +import traceback +from uuid import UUID +from typing import Optional + +import click +from sqlalchemy.future import select +from sqlalchemy import func +from sqlalchemy.ext.asyncio import AsyncConnection, create_async_engine + +from oss.src.models.db_models import ProjectDB as ProjectDBE +from oss.src.dbs.postgres.workflows.dbes import ( + WorkflowArtifactDBE, + WorkflowVariantDBE, + WorkflowRevisionDBE, +) +from oss.src.dbs.postgres.git.dao import GitDAO +from oss.src.core.evaluators.service import SimpleEvaluatorsService, EvaluatorsService +from oss.src.models.deprecated_models import ( + DeprecatedAutoEvaluatorConfigDBwProject as DeprecatedEvaluatorConfigDBwProject, + DeprecatedOrganizationDB, +) +from oss.src.core.workflows.service import WorkflowsService +from oss.src.core.tracing.service import TracingService +from oss.src.apis.fastapi.tracing.router import TracingRouter +from oss.src.dbs.postgres.tracing.dao import TracingDAO + + +# Define constants +DEFAULT_BATCH_SIZE = 200 + +# Initialize plug-ins for migration +tracing_service = TracingService( + tracing_dao=TracingDAO(), +) +tracing = TracingRouter( + tracing_service=tracing_service, +) +evaluators_service = EvaluatorsService( + workflows_service=WorkflowsService( + workflows_dao=GitDAO( + ArtifactDBE=WorkflowArtifactDBE, + VariantDBE=WorkflowVariantDBE, + RevisionDBE=WorkflowRevisionDBE, + ), + ) +) +simple_evaluators_service = SimpleEvaluatorsService( + evaluators_service=evaluators_service, +) + + +async def _fetch_project_owner( + *, + project_id: uuid.UUID, + connection: AsyncConnection, +) -> Optional[uuid.UUID]: + """Fetch the owner user ID for a given project.""" + organization_owner_query = ( + select(DeprecatedOrganizationDB.owner) + .select_from(ProjectDBE) + .join( + DeprecatedOrganizationDB, + ProjectDBE.organization_id == DeprecatedOrganizationDB.id, + ) + .where(ProjectDBE.id == project_id) + ) + result = await connection.execute(organization_owner_query) + owner = result.scalar_one_or_none() + return UUID(owner) if owner is not None else None + + +async def migration_old_evaluator_configs_to_new_evaluator_configs( + connection: AsyncConnection, +): + """Migrate old evaluator configurations to new workflow-based system.""" + try: + offset = 0 + total_migrated = 0 + skipped_records = 0 + + # Count total rows with a non-null project_id + total_query = ( + select(func.count()) + .select_from(DeprecatedEvaluatorConfigDBwProject) + .filter(DeprecatedEvaluatorConfigDBwProject.project_id.isnot(None)) + ) + result = await connection.execute(total_query) + total_rows = result.scalar() + total_evaluators = total_rows or 0 + + click.echo( + click.style( + f"Total rows in evaluator_configs with project_id: {total_evaluators}", + fg="yellow", + ) + ) + + while offset < total_evaluators: + # STEP 1: Fetch evaluator configurations with non-null project_id + result = await connection.execute( + select(DeprecatedEvaluatorConfigDBwProject) + .filter(DeprecatedEvaluatorConfigDBwProject.project_id.isnot(None)) + .offset(offset) + .limit(DEFAULT_BATCH_SIZE) + ) + evaluator_configs_rows = result.fetchall() + + if not evaluator_configs_rows: + break + + # Process and transfer records to evaluator workflows + for old_evaluator in evaluator_configs_rows: + try: + # STEP 2: Get owner from project_id + owner = await _fetch_project_owner( + project_id=old_evaluator.project_id, # type: ignore + connection=connection, + ) + if not owner: + skipped_records += 1 + click.echo( + click.style( + f"Skipping record with ID {old_evaluator.id} due to missing owner in workspace member table", + fg="yellow", + ) + ) + continue + + # STEP 3: Migrate records using transfer_* util function + new_evaluator = await simple_evaluators_service.transfer( + project_id=old_evaluator.project_id, + user_id=owner, + evaluator_id=old_evaluator.id, + ) + if not new_evaluator: + skipped_records += 1 + click.echo( + click.style( + f"Skipping record with ID {old_evaluator.id} due to old evaluator not existing in database table", + fg="yellow", + ) + ) + continue + + except Exception as e: + click.echo( + click.style( + f"Failed to migrate evaluator {old_evaluator.id}: {str(e)}", + fg="red", + ) + ) + click.echo(click.style(traceback.format_exc(), fg="red")) + skipped_records += 1 + continue + + # Update progress tracking for current batch + batch_migrated = len(evaluator_configs_rows) + offset += DEFAULT_BATCH_SIZE + total_migrated += batch_migrated + + click.echo( + click.style( + f"Processed {batch_migrated} records in this batch.", + fg="yellow", + ) + ) + + # Update progress tracking for all batches + remaining_records = total_evaluators - total_migrated + click.echo(click.style(f"Total migrated: {total_migrated}", fg="yellow")) + click.echo(click.style(f"Skipped records: {skipped_records}", fg="yellow")) + click.echo( + click.style(f"Records left to migrate: {remaining_records}", fg="yellow") + ) + + except Exception as e: + click.echo(f"Error occurred: {e}") + click.echo(click.style(traceback.format_exc(), fg="red")) + + +def run_migration(sqlalchemy_url: str): + import concurrent.futures + + async def _start(): + connection = create_async_engine(url=sqlalchemy_url) + async with connection.connect() as connection: + await migration_old_evaluator_configs_to_new_evaluator_configs( + connection=connection + ) + + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(asyncio.run, _start()) + future.result() diff --git a/api/oss/databases/postgres/migrations/core/data_migrations/secrets.py b/api/oss/databases/postgres/migrations/core/data_migrations/secrets.py index 052f5a7d87..6970f6c4e1 100644 --- a/api/oss/databases/postgres/migrations/core/data_migrations/secrets.py +++ b/api/oss/databases/postgres/migrations/core/data_migrations/secrets.py @@ -38,11 +38,11 @@ def rename_and_update_secrets_data_schema(session: Connection): while True: with set_data_encryption_key(data_encryption_key=encryption_key): # Fetch a batch of records using keyset pagination (ID-based) - query = select(SecretsDBE).order_by(SecretsDBE.id).limit(BATCH_SIZE) + stmt = select(SecretsDBE).order_by(SecretsDBE.id).limit(BATCH_SIZE) if last_processed_id: - query = query.where(SecretsDBE.id > last_processed_id) + stmt = stmt.where(SecretsDBE.id > last_processed_id) - secrets_dbes = session.execute(query).fetchall() + secrets_dbes = session.execute(stmt).fetchall() if not secrets_dbes: break # No more records to process @@ -124,11 +124,11 @@ def revert_rename_and_update_secrets_data_schema(session: Connection): while True: with set_data_encryption_key(data_encryption_key=encryption_key): # Fetch a batch of records using keyset pagination - query = select(SecretsDBE).order_by(SecretsDBE.id).limit(BATCH_SIZE) + stmt = select(SecretsDBE).order_by(SecretsDBE.id).limit(BATCH_SIZE) if last_processed_id: - query = query.where(SecretsDBE.id > last_processed_id) + stmt = stmt.where(SecretsDBE.id > last_processed_id) - secrets_dbes = session.execute(query).fetchall() + secrets_dbes = session.execute(stmt).fetchall() if not secrets_dbes: break # No more records to process diff --git a/api/oss/databases/postgres/migrations/core/data_migrations/testsets.py b/api/oss/databases/postgres/migrations/core/data_migrations/testsets.py new file mode 100644 index 0000000000..da657b7e03 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/data_migrations/testsets.py @@ -0,0 +1,194 @@ +import uuid +import asyncio +import traceback +from uuid import UUID +from typing import Optional + +import click +from sqlalchemy.future import select +from sqlalchemy import func +from sqlalchemy.ext.asyncio import AsyncConnection, create_async_engine + +from oss.src.models.db_models import ProjectDB as ProjectDBE +from oss.src.dbs.postgres.testcases.dbes import ( + TestcaseBlobDBE, +) +from oss.src.dbs.postgres.blobs.dao import BlobsDAO +from oss.src.dbs.postgres.testsets.dbes import ( + TestsetArtifactDBE, + TestsetVariantDBE, + TestsetRevisionDBE, +) +from oss.src.dbs.postgres.git.dao import GitDAO +from oss.src.core.testcases.service import TestcasesService +from oss.src.core.testsets.service import TestsetsService, SimpleTestsetsService +from oss.src.models.deprecated_models import ( + DeprecatedTestSetDB, + DeprecatedOrganizationDB, +) + + +# Define constants +DEFAULT_BATCH_SIZE = 200 + +# Initialize plug-ins for migration +testcases_dao = BlobsDAO( + BlobDBE=TestcaseBlobDBE, +) +testsets_dao = GitDAO( + ArtifactDBE=TestsetArtifactDBE, + VariantDBE=TestsetVariantDBE, + RevisionDBE=TestsetRevisionDBE, +) +testcases_service = TestcasesService( + testcases_dao=testcases_dao, +) +testsets_service = TestsetsService( + testsets_dao=testsets_dao, + testcases_service=testcases_service, +) +simple_testsets_service = SimpleTestsetsService( + testsets_service=testsets_service, +) + + +async def _fetch_project_owner( + *, + project_id: uuid.UUID, + connection: AsyncConnection, +) -> Optional[uuid.UUID]: + """Fetch the owner user ID for a given project.""" + organization_owner_query = ( + select(DeprecatedOrganizationDB.owner) + .select_from(ProjectDBE) + .join( + DeprecatedOrganizationDB, + ProjectDBE.organization_id == DeprecatedOrganizationDB.id, + ) + .where(ProjectDBE.id == project_id) + ) + result = await connection.execute(organization_owner_query) + owner = result.scalar_one_or_none() + return UUID(owner) if owner is not None else None + + +async def migration_old_testsets_to_new_testsets( + connection: AsyncConnection, +): + """Migrate old testsets to new testsets system.""" + try: + offset = 0 + total_migrated = 0 + skipped_records = 0 + + # Count total rows with a non-null project_id + total_query = ( + select(func.count()) + .select_from(DeprecatedTestSetDB) + .filter(DeprecatedTestSetDB.project_id.isnot(None)) + ) + result = await connection.execute(total_query) + total_rows = result.scalar() + total_testsets = total_rows or 0 + + click.echo( + click.style( + f"Total rows in testsets with project_id: {total_testsets}", + fg="yellow", + ) + ) + + while offset < total_testsets: + # STEP 1: Fetch evaluator configurations with non-null project_id + result = await connection.execute( + select(DeprecatedTestSetDB) + .filter(DeprecatedTestSetDB.project_id.isnot(None)) + .offset(offset) + .limit(DEFAULT_BATCH_SIZE) + ) + testsets_rows = result.fetchall() + + if not testsets_rows: + break + + # Process and transfer records to testset workflows + for testset in testsets_rows: + try: + # STEP 2: Get owner from project_id + owner = await _fetch_project_owner( + project_id=testset.project_id, # type: ignore + connection=connection, + ) + if not owner: + skipped_records += 1 + click.echo( + click.style( + f"Skipping record with ID {testset.id} due to missing owner in workspace member table", + fg="yellow", + ) + ) + continue + + # STEP 3: Migrate records using transfer_* util function + new_testset = await simple_testsets_service.transfer( + project_id=testset.project_id, + user_id=owner, + testset_id=testset.id, + ) + if not new_testset: + skipped_records += 1 + click.echo( + click.style( + f"Skipping record with ID {testset.id} due to old testset not existing in database table", + fg="yellow", + ) + ) + continue + + except Exception as e: + click.echo( + click.style( + f"Failed to migrate testset {testset.id}: {str(e)}", + fg="red", + ) + ) + click.echo(click.style(traceback.format_exc(), fg="red")) + skipped_records += 1 + continue + + # Update progress tracking for current batch + batch_migrated = len(testsets_rows) + offset += DEFAULT_BATCH_SIZE + total_migrated += batch_migrated + + click.echo( + click.style( + f"Processed {batch_migrated} records in this batch.", + fg="yellow", + ) + ) + + # Update progress tracking for all batches + remaining_records = total_testsets - total_migrated + click.echo(click.style(f"Total migrated: {total_migrated}", fg="yellow")) + click.echo(click.style(f"Skipped records: {skipped_records}", fg="yellow")) + click.echo( + click.style(f"Records left to migrate: {remaining_records}", fg="yellow") + ) + + except Exception as e: + click.echo(f"Error occurred: {e}") + click.echo(click.style(traceback.format_exc(), fg="red")) + + +def run_migration(sqlalchemy_url: str): + import concurrent.futures + + async def _start(): + connection = create_async_engine(url=sqlalchemy_url) + async with connection.connect() as connection: + await migration_old_testsets_to_new_testsets(connection=connection) + + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(asyncio.run, _start()) + future.result() diff --git a/api/oss/databases/postgres/migrations/core/temp/80910d2fa9a4_migrate_old_testsets_to_new_.py b/api/oss/databases/postgres/migrations/core/temp/80910d2fa9a4_migrate_old_testsets_to_new_.py new file mode 100644 index 0000000000..a22dad5b2e --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/temp/80910d2fa9a4_migrate_old_testsets_to_new_.py @@ -0,0 +1,32 @@ +"""migrate old testsets to new testsets data structure + +Revision ID: 80910d2fa9a4 +Revises: ... +Create Date: 2025-07-25 07:35:57.319449 + +""" + +from typing import Sequence, Union + +from alembic import context +from oss.databases.postgres.migrations.core.data_migrations.testsets import ( + run_migration, +) + +# revision identifiers, used by Alembic. +revision: str = "80910d2fa9a4" +down_revision: Union[str, None] = "..." +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + run_migration(sqlalchemy_url=context.config.get_main_option("sqlalchemy.url")) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/api/oss/databases/postgres/migrations/core/temp/bd7937ee784d_migrate_old_evaluators_to_new_.py b/api/oss/databases/postgres/migrations/core/temp/bd7937ee784d_migrate_old_evaluators_to_new_.py new file mode 100644 index 0000000000..e9d905297c --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/temp/bd7937ee784d_migrate_old_evaluators_to_new_.py @@ -0,0 +1,32 @@ +"""migrate old evaluators to new evaluators data structure + +Revision ID: bd7937ee784d +Revises: ... +Create Date: 2025-07-25 07:35:57.319449 + +""" + +from typing import Sequence, Union + +from alembic import context +from oss.databases.postgres.migrations.core.data_migrations.evaluators import ( + run_migration, +) + +# revision identifiers, used by Alembic. +revision: str = "bd7937ee784d" +down_revision: Union[str, None] = "..." +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + run_migration(sqlalchemy_url=context.config.get_main_option("sqlalchemy.url")) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/api/oss/databases/postgres/migrations/core/versions/0f086ebc2f83_extend_app_type.py b/api/oss/databases/postgres/migrations/core/versions/0f086ebc2f83_extend_app_type.py index 9b2552911a..c112db0d1a 100644 --- a/api/oss/databases/postgres/migrations/core/versions/0f086ebc2f83_extend_app_type.py +++ b/api/oss/databases/postgres/migrations/core/versions/0f086ebc2f83_extend_app_type.py @@ -1,4 +1,5 @@ """Extend app_type + Revision ID: 0f086ebc2f83 Revises: 0f086ebc2f82 Create Date: 2025-01-08 10:24:00 diff --git a/api/oss/databases/postgres/migrations/core/versions/30dcf07de96a_add_tables_for_queries.py b/api/oss/databases/postgres/migrations/core/versions/30dcf07de96a_add_tables_for_queries.py new file mode 100644 index 0000000000..fb5ccf5b83 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/30dcf07de96a_add_tables_for_queries.py @@ -0,0 +1,403 @@ +"""add tables for queries (artifacts, variants, & revisions) + +Revision ID: 30dcf07de96a +Revises: 37e1d1e8b181 +Create Date: 2025-07-30 14:55:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "30dcf07de96a" +down_revision: Union[str, None] = "37e1d1e8b181" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # - ARTIFACTS -------------------------------------------------------------- + + op.create_table( + "query_artifacts", + sa.Column( + "project_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "slug", + sa.String(), + nullable=False, + ), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + sa.Column( + "deleted_at", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + sa.Column( + "created_by_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "updated_by_id", + sa.UUID(), + nullable=True, + ), + sa.Column( + "deleted_by_id", + sa.UUID(), + nullable=True, + ), + sa.Column( + "flags", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "tags", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "meta", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "name", + sa.String(), + nullable=True, + ), + sa.Column( + "description", + sa.String(), + nullable=True, + ), + sa.PrimaryKeyConstraint( + "project_id", + "id", + ), + sa.UniqueConstraint( + "project_id", + "slug", + ), + sa.ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + sa.Index( + "ix_query_artifacts_project_id_slug", + "project_id", + "slug", + ), + ) + + # -------------------------------------------------------------------------- + + # - VARIANTS --------------------------------------------------------------- + + op.create_table( + "query_variants", + sa.Column( + "project_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "artifact_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "slug", + sa.String(), + nullable=False, + ), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + sa.Column( + "deleted_at", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + sa.Column( + "created_by_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "updated_by_id", + sa.UUID(), + nullable=True, + ), + sa.Column( + "deleted_by_id", + sa.UUID(), + nullable=True, + ), + sa.Column( + "flags", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "tags", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "meta", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "name", + sa.String(), + nullable=True, + ), + sa.Column( + "description", + sa.String(), + nullable=True, + ), + sa.PrimaryKeyConstraint( + "project_id", + "id", + ), + sa.UniqueConstraint( + "project_id", + "slug", + ), + sa.ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["project_id", "artifact_id"], + ["query_artifacts.project_id", "query_artifacts.id"], + ondelete="CASCADE", + ), + sa.Index( + "ix_query_variants_project_id_slug", + "project_id", + "slug", + ), + sa.Index( + "ix_query_variants_project_id_artifact_id", + "project_id", + "artifact_id", + ), + ) + + # -------------------------------------------------------------------------- + + # - REVISIONS -------------------------------------------------------------- + + op.create_table( + "query_revisions", + sa.Column( + "project_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "artifact_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "variant_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "slug", + sa.String(), + nullable=False, + ), + sa.Column( + "version", + sa.String(), + nullable=True, + ), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + sa.Column( + "deleted_at", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + sa.Column( + "created_by_id", + sa.UUID(), + nullable=False, + ), + sa.Column( + "updated_by_id", + sa.UUID(), + nullable=True, + ), + sa.Column( + "deleted_by_id", + sa.UUID(), + nullable=True, + ), + sa.Column( + "flags", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "tags", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "meta", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.Column( + "name", + sa.String(), + nullable=True, + ), + sa.Column( + "description", + sa.String(), + nullable=True, + ), + sa.Column( + "message", + sa.String(), + nullable=True, + ), + sa.Column( + "author", + sa.UUID(), + nullable=False, + ), + sa.Column( + "date", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column( + "data", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + sa.PrimaryKeyConstraint( + "project_id", + "id", + ), + sa.UniqueConstraint( + "project_id", + "slug", + ), + sa.ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["project_id", "artifact_id"], + ["query_artifacts.project_id", "query_artifacts.id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["project_id", "variant_id"], + ["query_variants.project_id", "query_variants.id"], + ondelete="CASCADE", + ), + sa.Index( + "ix_query_revisions_project_id_slug", + "project_id", + "slug", + ), + sa.Index( + "ix_query_revisions_project_id_artifact_id", + "project_id", + "artifact_id", + ), + sa.Index( + "ix_query_revisions_project_id_variant_id", + "project_id", + "variant_id", + ), + ) + + # -------------------------------------------------------------------------- + + +def downgrade() -> None: + # - REVISIONS -------------------------------------------------------------- + + op.drop_table("query_revisions") + + # -------------------------------------------------------------------------- + + # - VARIANTS --------------------------------------------------------------- + + op.drop_table("query_variants") + + # -------------------------------------------------------------------------- + + # - ARTIFACTS -------------------------------------------------------------- + + op.drop_table("query_artifacts") + + # -------------------------------------------------------------------------- diff --git a/api/oss/databases/postgres/migrations/core/versions/37e1d1e8b181_renamed_evaluator_configs_table.py b/api/oss/databases/postgres/migrations/core/versions/37e1d1e8b181_renamed_evaluator_configs_table.py new file mode 100644 index 0000000000..f7e154aecc --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/37e1d1e8b181_renamed_evaluator_configs_table.py @@ -0,0 +1,31 @@ +"""rename evaluator configs table + +Revision ID: 37e1d1e8b181 +Revises: aa1b2c3d4e5f +Create Date: 2025-07-31 10:17:45.613091 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "37e1d1e8b181" +down_revision: Union[str, None] = "aa1b2c3d4e5f" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.rename_table("evaluators_configs", "auto_evaluator_configs") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.rename_table("auto_evaluator_configs", "evaluators_configs") + # ### end Alembic commands ### diff --git a/api/oss/databases/postgres/migrations/core/versions/3b5f5652f611_populate_runs_references.py b/api/oss/databases/postgres/migrations/core/versions/3b5f5652f611_populate_runs_references.py new file mode 100644 index 0000000000..bb43067ccb --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/3b5f5652f611_populate_runs_references.py @@ -0,0 +1,77 @@ +"""Populate runs references + +Revision ID: 3b5f5652f611 +Revises: b3f15a7140ab +Create Date: 2025-10-07 12:00:00 +""" + +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa +import json + +# revision identifiers, used by Alembic. +revision: str = "3b5f5652f611" +down_revision: Union[str, None] = "b3f15a7140ab" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + conn = op.get_bind() + + rows = conn.execute( + sa.text('SELECT id, data, "references" FROM evaluation_runs') + ).fetchall() + + for run_id, data, existing_refs in rows: + if existing_refs not in (None, [], {}): + continue + if not data or "steps" not in data: + continue + + refs_out = [] + seen = set() + + for step in data.get("steps", []): + refs = step.get("references", {}) + if not isinstance(refs, dict): + continue + + for key, ref in refs.items(): + if not isinstance(ref, dict): + continue + + entry = {"key": key} + + if ref.get("id") is not None: + entry["id"] = ref["id"] + if ref.get("slug") is not None: + entry["slug"] = ref["slug"] + if ref.get("version") is not None: + entry["version"] = ref["version"] + + dedup_key = ( + entry.get("id"), + entry["key"], + entry.get("slug"), + entry.get("version"), + ) + if dedup_key in seen: + continue + seen.add(dedup_key) + + refs_out.append(entry) + + if refs_out: + conn.execute( + sa.text( + 'UPDATE evaluation_runs SET "references" = :refs WHERE id = :id' + ), + {"refs": json.dumps(refs_out), "id": run_id}, + ) + + +def downgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text('UPDATE evaluation_runs SET "references" = NULL')) diff --git a/api/oss/databases/postgres/migrations/core/versions/5a71b3f140ab_fix_all_preview_schemas.py b/api/oss/databases/postgres/migrations/core/versions/5a71b3f140ab_fix_all_preview_schemas.py new file mode 100644 index 0000000000..62d244d1e1 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/5a71b3f140ab_fix_all_preview_schemas.py @@ -0,0 +1,426 @@ +"""fix all preview schemas + +Revision ID: 5a71b3f140ab +Revises: 8089ee7692d1 +Create Date: 2025-09-03 14:28:06.362553 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision: str = "5a71b3f140ab" +down_revision: Union[str, None] = "8089ee7692d1" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # EVALUATION RUNS ---------------------------------------------------------- + + op.add_column( + "evaluation_runs", + sa.Column( + "references", + postgresql.JSONB(none_as_null=True, astext_type=sa.Text()), + nullable=True, + ), + ) + + op.create_index( + "ix_evaluation_runs_references", + "evaluation_runs", + ["references"], + unique=False, + postgresql_using="gin", + postgresql_ops={"references": "jsonb_path_ops"}, + ) + op.create_index( + "ix_evaluation_runs_flags", + "evaluation_runs", + ["flags"], + unique=False, + postgresql_using="gin", + ) + op.create_index( + "ix_evaluation_runs_tags", + "evaluation_runs", + ["tags"], + unique=False, + postgresql_using="gin", + ) + + # EVALUATION SCENARIOS ----------------------------------------------------- + + op.add_column( + "evaluation_scenarios", + sa.Column( + "interval", + postgresql.INTEGER(), + nullable=True, + ), + ) + + op.create_index( + "ix_evaluation_scenarios_timestamp_interval", + "evaluation_scenarios", + ["timestamp", "interval"], + unique=False, + ) + op.create_index( + "ix_evaluation_scenarios_flags", + "evaluation_scenarios", + ["flags"], + unique=False, + postgresql_using="gin", + ) + op.create_index( + "ix_evaluation_scenarios_tags", + "evaluation_scenarios", + ["tags"], + unique=False, + postgresql_using="gin", + ) + + # EVALUATION RESULTS ------------------------------------------------------- + + op.alter_column( + "evaluation_steps", + "timestamp", + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=True, + ) + op.add_column( + "evaluation_steps", + sa.Column( + "interval", + postgresql.INTEGER(), + nullable=True, + ), + ) + + op.create_unique_constraint( + "uq_evaluation_steps_project_run_scenario_step_repeat", + "evaluation_steps", + ["project_id", "run_id", "scenario_id", "step_key", "repeat_idx"], + ) + + op.create_index( + "ix_evaluation_steps_tags", + "evaluation_steps", + ["tags"], + unique=False, + postgresql_using="gin", + ) + op.create_index( + "ix_evaluation_steps_flags", + "evaluation_steps", + ["flags"], + unique=False, + postgresql_using="gin", + ) + op.create_index( + "ix_evaluation_steps_timestamp_interval", + "evaluation_steps", + ["timestamp", "interval"], + unique=False, + ) + op.create_index( + "ix_evaluation_steps_repeat_idx", + "evaluation_steps", + ["repeat_idx"], + unique=False, + ) + op.create_index( + "ix_evaluation_steps_step_key", + "evaluation_steps", + ["step_key"], + unique=False, + ) + + op.rename_table("evaluation_steps", "evaluation_results") + + op.execute( + "ALTER TABLE evaluation_results RENAME CONSTRAINT " + "uq_evaluation_steps_project_run_scenario_step_repeat TO " + "uq_evaluation_results_project_run_scenario_step_repeat" + ) + + op.execute( + "ALTER INDEX ix_evaluation_steps_project_id RENAME TO ix_evaluation_results_project_id" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_run_id RENAME TO ix_evaluation_results_run_id" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_scenario_id RENAME TO ix_evaluation_results_scenario_id" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_step_key RENAME TO ix_evaluation_results_step_key" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_repeat_idx RENAME TO ix_evaluation_results_repeat_idx" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_timestamp_interval RENAME TO ix_evaluation_results_timestamp_interval" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_flags RENAME TO ix_evaluation_results_flags" + ) + op.execute( + "ALTER INDEX ix_evaluation_steps_tags RENAME TO ix_evaluation_results_tags" + ) + + # EVALUATION METRICS ------------------------------------------------------- + + op.add_column( + "evaluation_metrics", + sa.Column( + "interval", + postgresql.INTEGER(), + nullable=True, + ), + ) + + op.drop_constraint( + op.f("evaluation_metrics_project_id_run_id_scenario_id_key"), + "evaluation_metrics", + type_="unique", + ) + + op.create_unique_constraint( + "uq_evaluation_metrics_project_run_scenario_timestamp_interval", + "evaluation_metrics", + ["project_id", "run_id", "scenario_id", "timestamp", "interval"], + ) + + op.create_index( + "ix_evaluation_metrics_timestamp_interval", + "evaluation_metrics", + ["timestamp", "interval"], + unique=False, + ) + op.create_index( + "ix_evaluation_metrics_flags", + "evaluation_metrics", + ["flags"], + unique=False, + postgresql_using="gin", + ) + op.create_index( + "ix_evaluation_metrics_tags", + "evaluation_metrics", + ["tags"], + unique=False, + postgresql_using="gin", + ) + + # EVALUATION QUEUES -------------------------------------------------------- + + op.add_column( + "evaluation_queues", + sa.Column( + "name", + sa.String(), + nullable=True, + ), + ) + op.add_column( + "evaluation_queues", + sa.Column( + "description", + sa.String(), + nullable=True, + ), + ) + op.add_column( + "evaluation_queues", + sa.Column( + "status", + sa.VARCHAR(), + nullable=False, + server_default=sa.text("'pending'::varchar"), + ), + ) + + op.create_index( + "ix_evaluation_queues_flags", + "evaluation_queues", + ["flags"], + unique=False, + postgresql_using="gin", + ) + op.create_index( + "ix_evaluation_queues_tags", + "evaluation_queues", + ["tags"], + unique=False, + postgresql_using="gin", + ) + + # -------------------------------------------------------------------------- + + +def downgrade() -> None: + # EVALUATION QUEUES -------------------------------------------------------- + + op.drop_index( + "ix_evaluation_queues_tags", + table_name="evaluation_queues", + ) + op.drop_index( + "ix_evaluation_queues_flags", + table_name="evaluation_queues", + ) + + op.drop_column( + "evaluation_queues", + "status", + ) + op.drop_column( + "evaluation_queues", + "description", + ) + op.drop_column( + "evaluation_queues", + "name", + ) + + # EVALUATION METRICS ------------------------------------------------------- + + op.drop_index( + "ix_evaluation_metrics_tags", + table_name="evaluation_metrics", + ) + op.drop_index( + "ix_evaluation_metrics_flags", + table_name="evaluation_metrics", + ) + op.drop_index( + "ix_evaluation_metrics_timestamp_interval", + table_name="evaluation_metrics", + ) + + op.drop_constraint( + "uq_evaluation_metrics_project_run_scenario_timestamp_interval", + "evaluation_metrics", + type_="unique", + ) + + op.create_unique_constraint( + op.f("evaluation_metrics_project_id_run_id_scenario_id_key"), + "evaluation_metrics", + ["project_id", "run_id", "scenario_id"], + postgresql_nulls_not_distinct=False, + ) + + op.drop_column("evaluation_metrics", "interval") + + # EVALUATION RESULTS ------------------------------------------------------- + + op.execute( + "ALTER INDEX ix_evaluation_results_tags RENAME TO ix_evaluation_steps_tags" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_flags RENAME TO ix_evaluation_steps_flags" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_timestamp_interval RENAME TO ix_evaluation_steps_timestamp_interval" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_repeat_idx RENAME TO ix_evaluation_steps_repeat_idx" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_step_key RENAME TO ix_evaluation_steps_step_key" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_scenario_id RENAME TO ix_evaluation_steps_scenario_id" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_run_id RENAME TO ix_evaluation_steps_run_id" + ) + op.execute( + "ALTER INDEX ix_evaluation_results_project_id RENAME TO ix_evaluation_steps_project_id" + ) + + op.execute( + "ALTER TABLE evaluation_results RENAME CONSTRAINT uq_evaluation_results_project_run_scenario_step_repeat " + "TO uq_evaluation_steps_project_run_scenario_step_repeat" + ) + + op.rename_table("evaluation_results", "evaluation_steps") + + op.drop_index( + "ix_evaluation_steps_tags", + table_name="evaluation_steps", + ) + op.drop_index( + "ix_evaluation_steps_flags", + table_name="evaluation_steps", + ) + op.drop_index( + "ix_evaluation_steps_timestamp_interval", + table_name="evaluation_steps", + ) + op.drop_index( + "ix_evaluation_steps_repeat_idx", + table_name="evaluation_steps", + ) + op.drop_index( + "ix_evaluation_steps_step_key", + table_name="evaluation_steps", + ) + + op.drop_constraint( + "uq_evaluation_steps_project_run_scenario_step_repeat", + "evaluation_steps", + type_="unique", + ) + + op.alter_column( + "evaluation_steps", + "timestamp", + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=False, + ) + + op.drop_column("evaluation_steps", "interval") + + # EVALUATION SCENARIOS ----------------------------------------------------- + + op.drop_index( + "ix_evaluation_scenarios_tags", + table_name="evaluation_scenarios", + ) + op.drop_index( + "ix_evaluation_scenarios_flags", + table_name="evaluation_scenarios", + ) + op.drop_index( + "ix_evaluation_scenarios_timestamp_interval", + table_name="evaluation_scenarios", + ) + + op.drop_column("evaluation_scenarios", "interval") + + # EVALUATION RUNS ---------------------------------------------------------- + + op.drop_index( + "ix_evaluation_runs_tags", + table_name="evaluation_runs", + ) + op.drop_index( + "ix_evaluation_runs_flags", + table_name="evaluation_runs", + ) + op.drop_index( + "ix_evaluation_runs_references", + table_name="evaluation_runs", + ) + + op.drop_column("evaluation_runs", "references") + + # -------------------------------------------------------------------------- diff --git a/api/oss/databases/postgres/migrations/core/versions/8089ee7692d1_cleanup_preview_entities.py b/api/oss/databases/postgres/migrations/core/versions/8089ee7692d1_cleanup_preview_entities.py new file mode 100644 index 0000000000..36e9e4edd4 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/8089ee7692d1_cleanup_preview_entities.py @@ -0,0 +1,168 @@ +"""clean up preview entities + +Revision ID: 8089ee7692d1 +Revises: fa07e07350bf +Create Date: 2025-08-20 16:00:00.00000000 + +""" + +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "8089ee7692d1" +down_revision: Union[str, None] = "fa07e07350bf" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +TABLES_WITH_DATA_MIGRATION = [ + "evaluation_runs", + "evaluation_metrics", + "evaluation_queues", + "testcase_blobs", + "testset_revisions", + "query_revisions", + "workflow_revisions", +] + +TABLES_WITH_META_MIGRATION = [ + "evaluation_runs", + "evaluation_scenarios", + "evaluation_steps", + "evaluation_metrics", + "evaluation_queues", + "testcase_blobs", + "testset_artifacts", + "testset_variants", + "testset_revisions", + "query_artifacts", + "query_variants", + "query_revisions", + "workflow_artifacts", + "workflow_variants", + "workflow_revisions", +] + + +def upgrade() -> None: + # Convert jsonb -> json for data columns + for table in TABLES_WITH_DATA_MIGRATION: + op.alter_column( + table_name=table, + column_name="data", + type_=sa.JSON(), + postgresql_using="data::json", + ) + + # Convert jsonb -> json for meta columns + for table in TABLES_WITH_META_MIGRATION: + op.alter_column( + table_name=table, + column_name="meta", + type_=sa.JSON(), + postgresql_using="meta::json", + ) + + # Add new timestamp column + op.add_column( + "evaluation_scenarios", + sa.Column( + "timestamp", + sa.TIMESTAMP(timezone=True), + nullable=True, + ), + ) + + # Add repeat_idx and drop old repeat_id + retry_id + op.add_column( + "evaluation_steps", + sa.Column( + "repeat_idx", + sa.Integer(), + nullable=True, + ), + ) + op.drop_column( + "evaluation_steps", + "repeat_id", + ) + op.drop_column( + "evaluation_steps", + "retry_id", + ) + + # Rename key -> step_key + op.alter_column( + "evaluation_steps", + "key", + new_column_name="step_key", + existing_type=sa.String(), # adjust if needed + existing_nullable=False, + ) + + op.drop_column( + "evaluation_metrics", + "interval", + ) + + +def downgrade() -> None: + op.add_column( + "evaluation_metrics", + sa.Column( + "interval", + sa.Integer(), + nullable=True, + ), + ) + + # Rename step_key back to key + op.alter_column( + "evaluation_steps", + "step_key", + new_column_name="key", + existing_type=sa.String(), # adjust if needed + existing_nullable=False, + ) + + # Recreate repeat_id and retry_id columns + op.add_column( + "evaluation_steps", + sa.Column("repeat_id", sa.UUID(), nullable=False), + ) + op.add_column( + "evaluation_steps", + sa.Column("retry_id", sa.UUID(), nullable=False), + ) + + # Drop repeat_idx column + op.drop_column( + "evaluation_steps", + "repeat_idx", + ) + + # Drop timestamp column + op.drop_column( + "evaluation_scenarios", + "timestamp", + ) + + # Convert meta columns back to jsonb + for table in TABLES_WITH_META_MIGRATION: + op.alter_column( + table_name=table, + column_name="meta", + type_=sa.dialects.postgresql.JSONB(), + postgresql_using="meta::jsonb", + ) + + # Convert data columns back to jsonb + for table in TABLES_WITH_DATA_MIGRATION: + op.alter_column( + table_name=table, + column_name="data", + type_=sa.dialects.postgresql.JSONB(), + postgresql_using="data::jsonb", + ) diff --git a/api/oss/databases/postgres/migrations/core/versions/91d3b4a8c27f_fix_ag_config.py b/api/oss/databases/postgres/migrations/core/versions/91d3b4a8c27f_fix_ag_config.py index 50efe3b02e..147cd042f8 100644 --- a/api/oss/databases/postgres/migrations/core/versions/91d3b4a8c27f_fix_ag_config.py +++ b/api/oss/databases/postgres/migrations/core/versions/91d3b4a8c27f_fix_ag_config.py @@ -1,4 +1,5 @@ """Fix ag_config + Revision ID: 91d3b4a8c27f Revises: 7cc66fc40298 Create Date: 2025-04-24 11:00:00 diff --git a/api/oss/databases/postgres/migrations/core/versions/b3f15a7140ab_add_version_to_eval_entities.py b/api/oss/databases/postgres/migrations/core/versions/b3f15a7140ab_add_version_to_eval_entities.py new file mode 100644 index 0000000000..f6a9d6a9af --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/b3f15a7140ab_add_version_to_eval_entities.py @@ -0,0 +1,107 @@ +"""Add version to evaluation entities + +Revision ID: b3f15a7140ab +Revises: 5a71b3f140ab +Create Date: 2025-10-03 14:30:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision: str = "b3f15a7140ab" +down_revision: Union[str, None] = "5a71b3f140ab" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # BASED ON + # version = Column( + # String, + # nullable=True, + # ) + + # EVALUATION RUNS ---------------------------------------------------------- + + op.add_column( + "evaluation_runs", + sa.Column( + "version", + sa.String(), + nullable=True, + ), + ) + + # EVALUATION SCENARIOS ----------------------------------------------------- + + op.add_column( + "evaluation_scenarios", + sa.Column( + "version", + sa.String(), + nullable=True, + ), + ) + + # EVALUATION RESULTS ------------------------------------------------------- + + op.add_column( + "evaluation_results", + sa.Column( + "version", + sa.String(), + nullable=True, + ), + ) + + # EVALUATION METRICS ------------------------------------------------------- + + op.add_column( + "evaluation_metrics", + sa.Column( + "version", + sa.String(), + nullable=True, + ), + ) + + # EVALUATION QUEUES -------------------------------------------------------- + + op.add_column( + "evaluation_queues", + sa.Column( + "version", + sa.String(), + nullable=True, + ), + ) + + # -------------------------------------------------------------------------- + + +def downgrade() -> None: + # EVALUATION QUEUES -------------------------------------------------------- + + op.drop_column("evaluation_queues", "version") + + # EVALUATION METRICS ------------------------------------------------------- + + op.drop_column("evaluation_metrics", "version") + + # EVALUATION RESULTS ------------------------------------------------------- + + op.drop_column("evaluation_results", "version") + + # EVALUATION SCENARIOS ----------------------------------------------------- + + op.drop_column("evaluation_scenarios", "version") + + # EVALUATION RUNS ---------------------------------------------------------- + + op.drop_column("evaluation_runs", "version") + + # -------------------------------------------------------------------------- diff --git a/api/oss/databases/postgres/migrations/core/versions/d5d4d6bf738f_add_evaluation_queues.py b/api/oss/databases/postgres/migrations/core/versions/d5d4d6bf738f_add_evaluation_queues.py index 05f3b47b90..6d39d973aa 100644 --- a/api/oss/databases/postgres/migrations/core/versions/d5d4d6bf738f_add_evaluation_queues.py +++ b/api/oss/databases/postgres/migrations/core/versions/d5d4d6bf738f_add_evaluation_queues.py @@ -1,4 +1,5 @@ """add evaluation queues + Revision ID: d5d4d6bf738f Revises: fd77265d65dc Create Date: 2025-07-10 17:04:00.000000 diff --git a/api/oss/databases/postgres/migrations/core/versions/fa07e07350bf_add_timestamp_to_metrics.py b/api/oss/databases/postgres/migrations/core/versions/fa07e07350bf_add_timestamp_to_metrics.py new file mode 100644 index 0000000000..c6d85c7467 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core/versions/fa07e07350bf_add_timestamp_to_metrics.py @@ -0,0 +1,34 @@ +"""add timestamp to metrics + +Revision ID: fa07e07350bf +Revises: 30dcf07de96a +Create Date: 2025-07-30 14:55:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "fa07e07350bf" +down_revision: Union[str, None] = "30dcf07de96a" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "evaluation_metrics", + sa.Column("timestamp", sa.TIMESTAMP(timezone=True), nullable=True), + ) + op.add_column( + "evaluation_metrics", + sa.Column("interval", sa.INTEGER(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("evaluation_metrics", "interval") + op.drop_column("evaluation_metrics", "timestamp") diff --git a/api/oss/databases/postgres/migrations/find_head.py b/api/oss/databases/postgres/migrations/find_head.py index 9f024bc62f..c435c485a9 100644 --- a/api/oss/databases/postgres/migrations/find_head.py +++ b/api/oss/databases/postgres/migrations/find_head.py @@ -43,4 +43,6 @@ # head(s) = revisions that are not anyone's down_revision heads = [rev for rev in revisions if rev not in all_down_revisions] +print("---------") +print() print("Heads:", heads) diff --git a/api/oss/databases/postgres/migrations/runner.py b/api/oss/databases/postgres/migrations/runner.py new file mode 100644 index 0000000000..f2b5fbbfb9 --- /dev/null +++ b/api/oss/databases/postgres/migrations/runner.py @@ -0,0 +1,21 @@ +import asyncio + +from oss.databases.postgres.migrations.utils import ( + split_core_and_tracing, + copy_nodes_from_core_to_tracing, +) +from oss.databases.postgres.migrations.core.utils import ( + run_alembic_migration as migrate_core, +) +from oss.databases.postgres.migrations.tracing.utils import ( + run_alembic_migration as migrate_tracing, +) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + + loop.run_until_complete(split_core_and_tracing()) + migrate_core() + migrate_tracing() + loop.run_until_complete(copy_nodes_from_core_to_tracing()) diff --git a/api/oss/databases/postgres/migrations/tracing/versions/847972cfa14a_add_nodes.py b/api/oss/databases/postgres/migrations/tracing/versions/847972cfa14a_add_nodes.py index 30be9c9a30..4b6903973b 100644 --- a/api/oss/databases/postgres/migrations/tracing/versions/847972cfa14a_add_nodes.py +++ b/api/oss/databases/postgres/migrations/tracing/versions/847972cfa14a_add_nodes.py @@ -1,7 +1,7 @@ """add_nodes_dbe Revision ID: 847972cfa14a -Revises: 320a4a7ee0c7 +Revises: 58b1b61e5d6c Create Date: 2024-11-07 12:21:19.080345 """ diff --git a/api/oss/databases/postgres/migrations/tracing/versions/fd77265d65dc_fix_spans.py b/api/oss/databases/postgres/migrations/tracing/versions/fd77265d65dc_fix_spans.py index 9f8fcc28bf..6cb4e3f963 100644 --- a/api/oss/databases/postgres/migrations/tracing/versions/fd77265d65dc_fix_spans.py +++ b/api/oss/databases/postgres/migrations/tracing/versions/fd77265d65dc_fix_spans.py @@ -66,11 +66,25 @@ def upgrade() -> None: "ix_spans_project_id_trace_type", "spans", ["project_id", "trace_type"], + if_not_exists=True, ) op.create_index( "ix_spans_project_id_span_type", "spans", ["project_id", "span_type"], + if_not_exists=True, + ) + op.create_index( + "ix_spans_project_id_trace_id_created_at", + "spans", + ["project_id", "trace_id", sa.text("created_at DESC")], + if_not_exists=True, + ) + op.create_index( + "ix_spans_project_id_trace_id_start_time", + "spans", + ["project_id", "trace_id", sa.text("start_time DESC")], + if_not_exists=True, ) op.create_index( "ix_hashes_gin", @@ -78,35 +92,110 @@ def upgrade() -> None: ["hashes"], postgresql_using="gin", postgresql_ops={"hashes": "jsonb_path_ops"}, + if_not_exists=True, + ) + op.drop_index( + "ix_events_gin", + table_name="spans", + if_exists=True, + ) + op.create_index( + "ix_events_gin", + "spans", # replace with your table name + ["events"], + postgresql_using="gin", + postgresql_ops={"events": "jsonb_path_ops"}, + if_not_exists=True, ) op.create_index( - "ix_spans_fts_gin", + "ix_spans_fts_attributes_gin", "spans", [sa.text("to_tsvector('simple', attributes)")], postgresql_using="gin", + if_not_exists=True, + ) + op.create_index( + "ix_spans_fts_events_gin", + "spans", + [sa.text("to_tsvector('simple', events)")], + postgresql_using="gin", + if_not_exists=True, ) - # op.create_index( - # "ix_spans_fts_events_gin", - # "spans", - # [sa.text("to_tsvector('simple', events)")], - # postgresql_using="gin", - # ) # -------------------------------------------------------------------------- def downgrade() -> None: # - SPANS ------------------------------------------------------------------ - # op.drop_index("ix_spans_fts", table_name="spans") - op.drop_index("ix_hashes_gin", table_name="spans") - op.drop_index("ix_spans_project_id_span_type", table_name="spans") - op.drop_index("ix_spans_project_id_trace_type", table_name="spans") - op.drop_column("spans", "exception") - op.drop_column("spans", "hashes") - op.drop_column("spans", "span_type") - op.drop_column("spans", "trace_type") + op.drop_index( + "ix_spans_fts_events_gin", + table_name="spans", + if_exists=True, + ) + op.drop_index( + "ix_spans_fts_attributes_gin", + table_name="spans", + if_exists=True, + ) + op.drop_index( + "ix_events_gin", + table_name="spans", + if_exists=True, + ) + op.create_index( + "ix_events_gin", + "spans", + ["events"], + postgresql_using="gin", + if_not_exists=True, + ) + op.drop_index( + "ix_hashes_gin", + table_name="spans", + if_exists=True, + ) + op.drop_index( + "ix_spans_project_id_trace_id_start_time", + table_name="spans", + if_exists=True, + ) + op.drop_index( + "ix_spans_project_id_trace_id_created_at", + table_name="spans", + if_exists=True, + ) + op.drop_index( + "ix_spans_project_id_span_type", + table_name="spans", + if_exists=True, + ) + op.drop_index( + "ix_spans_project_id_trace_type", + table_name="spans", + if_exists=True, + ) + op.drop_column( + "spans", + "exception", + if_exists=True, + ) + op.drop_column( + "spans", + "hashes", + if_exists=True, + ) + op.drop_column( + "spans", + "span_type", + if_exists=True, + ) + op.drop_column( + "spans", + "trace_type", + if_exists=True, + ) - span_type_enum = sa.Enum(SpanType, name="tracetype") - trace_type_enum = sa.Enum(TraceType, name="spantype") + span_type_enum = sa.Enum(SpanType, name="spantype") + trace_type_enum = sa.Enum(TraceType, name="tracetype") span_type_enum.drop(op.get_bind(), checkfirst=True) trace_type_enum.drop(op.get_bind(), checkfirst=True) diff --git a/api/oss/databases/postgres/migrations/utils.py b/api/oss/databases/postgres/migrations/utils.py index 1147d9e5c7..12450b767d 100644 --- a/api/oss/databases/postgres/migrations/utils.py +++ b/api/oss/databases/postgres/migrations/utils.py @@ -1,8 +1,10 @@ import os -import tempfile import subprocess +import tempfile from sqlalchemy import create_engine, text +from sqlalchemy.ext.asyncio import create_async_engine + from sqlalchemy.exc import ProgrammingError from oss.src.utils.env import env @@ -13,9 +15,9 @@ os.getenv("POSTGRES_URI") or env.POSTGRES_URI_CORE or env.POSTGRES_URI_TRACING - or "postgresql://username:password@localhost:5432/agenta_oss" + or "postgresql+asyncpg://username:password@localhost:5432/agenta_oss" ) -DB_PROTOCOL = POSTGRES_URI.split("://")[0].replace("+asyncpg", "") +DB_PROTOCOL = POSTGRES_URI.split("://")[0] # .replace("+asyncpg", "") DB_USER = POSTGRES_URI.split("://")[1].split(":")[0] DB_PASS = POSTGRES_URI.split("://")[1].split(":")[1].split("@")[0] DB_HOST = POSTGRES_URI.split("@")[1].split(":")[0] @@ -33,36 +35,43 @@ "agenta_oss_tracing": "agenta_oss_tracing", } + NODES_TF = { "agenta_oss_core": "agenta_oss_tracing", } -def copy_nodes_from_core_to_tracing(): - engine = create_engine( +async def copy_nodes_from_core_to_tracing(): + engine = create_async_engine( POSTGRES_URI_POSTGRES, isolation_level="AUTOCOMMIT", ) - with engine.connect() as conn: + async with engine.begin() as conn: for old_name, new_name in NODES_TF.items(): - old_exists = conn.execute( - text("SELECT 1 FROM pg_database WHERE datname = :name"), - {"name": old_name}, + old_exists = ( + await conn.execute( + text("SELECT 1 FROM pg_database WHERE datname = :name"), + {"name": old_name}, + ) ).scalar() - new_exists = conn.execute( - text("SELECT 1 FROM pg_database WHERE datname = :name"), - {"name": new_name}, + new_exists = ( + await conn.execute( + text("SELECT 1 FROM pg_database WHERE datname = :name"), + {"name": new_name}, + ) ).scalar() if old_exists and new_exists: # Check if the nodes table exists in old_name database check_url = f"{DB_PROTOCOL}://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{old_name}" - check_engine = create_engine(check_url) - with check_engine.connect() as conn: - result = conn.execute( - text("SELECT to_regclass('public.nodes')") + check_engine = create_async_engine(check_url) + async with check_engine.begin() as conn: + result = ( + await conn.execute( + text("SELECT to_regclass('public.nodes')"), + ) ).scalar() if result is None: print( @@ -70,8 +79,10 @@ def copy_nodes_from_core_to_tracing(): ) return - count = conn.execute( - text("SELECT COUNT(*) FROM public.nodes") + count = ( + await conn.execute( + text("SELECT COUNT(*) FROM public.nodes"), + ) ).scalar() if count == 0: @@ -81,14 +92,18 @@ def copy_nodes_from_core_to_tracing(): return check_url = f"{DB_PROTOCOL}://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{new_name}" - check_engine = create_engine(check_url) + check_engine = create_async_engine(check_url) - with check_engine.connect() as conn: - count = conn.execute( - text("SELECT COUNT(*) FROM public.nodes") + async with check_engine.begin() as conn: + count = ( + await conn.execute( + text( + "SELECT COUNT(*) FROM public.nodes", + ) + ) ).scalar() - if count > 0: + if (count or 0) > 0: print( f"⚠️ Table 'nodes' already exists in '{new_name}' with {count} rows. Skipping copy." ) @@ -148,21 +163,26 @@ def copy_nodes_from_core_to_tracing(): print(f"✔ Restored 'nodes' table into '{new_name}'") # Step 3: Verify 'nodes' exists in both DBs, then drop from old - source_engine = create_engine( + source_engine = create_async_engine( f"{DB_PROTOCOL}://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{old_name}" ) - dest_engine = create_engine( + dest_engine = create_async_engine( f"{DB_PROTOCOL}://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{new_name}" ) - with source_engine.connect().execution_options( - autocommit=True - ) as src, dest_engine.connect() as dst: - src_exists = src.execute( - text("SELECT to_regclass('public.nodes')") + async with source_engine.begin() as src, dest_engine.begin() as dst: + src = await src.execution_options(isolation_level="AUTOCOMMIT") + dst = await dst.execution_options(isolation_level="AUTOCOMMIT") + + src_exists = ( + await src.execute( + text("SELECT to_regclass('public.nodes')") + ) ).scalar() - dst_exists = dst.execute( - text("SELECT to_regclass('public.nodes')") + dst_exists = ( + await dst.execute( + text("SELECT to_regclass('public.nodes')"), + ) ).scalar() if src_exists and dst_exists: @@ -184,8 +204,10 @@ def copy_nodes_from_core_to_tracing(): env={**os.environ, "PGPASSWORD": DB_PASS}, ) - count = src.execute( - text("SELECT COUNT(*) FROM public.nodes") + count = ( + await src.execute( + text("SELECT COUNT(*) FROM public.nodes"), + ) ).scalar() print(f"✅ Remaining rows: {count}") @@ -197,28 +219,32 @@ def copy_nodes_from_core_to_tracing(): os.remove(dump_file) -def split_core_and_tracing(): - engine = create_engine( +async def split_core_and_tracing(): + engine = create_async_engine( POSTGRES_URI_POSTGRES, isolation_level="AUTOCOMMIT", ) - with engine.connect() as conn: + async with engine.begin() as conn: for old_name, new_name in RENAME_MAP.items(): - old_exists = conn.execute( - text("SELECT 1 FROM pg_database WHERE datname = :name"), - {"name": old_name}, + old_exists = ( + await conn.execute( + text("SELECT 1 FROM pg_database WHERE datname = :name"), + {"name": old_name}, + ) ).scalar() - new_exists = conn.execute( - text("SELECT 1 FROM pg_database WHERE datname = :name"), - {"name": new_name}, + new_exists = ( + await conn.execute( + text("SELECT 1 FROM pg_database WHERE datname = :name"), + {"name": new_name}, + ) ).scalar() if old_exists and not new_exists: print(f"Renaming database '{old_name}' → '{new_name}'...") try: - conn.execute( + await conn.execute( text(f"ALTER DATABASE {old_name} RENAME TO {new_name}") ) print(f"✔ Renamed '{old_name}' to '{new_name}'") @@ -236,7 +262,7 @@ def split_core_and_tracing(): ) try: # Ensure the role exists - conn.execute( + await conn.execute( text( f""" DO $$ @@ -252,11 +278,11 @@ def split_core_and_tracing(): print(f"✔ Ensured role '{DB_USER}' exists") # Create the new database - conn.execute(text(f"CREATE DATABASE {new_name}")) + await conn.execute(text(f"CREATE DATABASE {new_name}")) print(f"✔ Created database '{new_name}'") # Grant privileges on the database to the role - conn.execute( + await conn.execute( text( f"GRANT ALL PRIVILEGES ON DATABASE {new_name} TO {DB_USER}" ) @@ -266,12 +292,12 @@ def split_core_and_tracing(): ) # Connect to the new database to grant schema permissions - new_db_url = f"postgresql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{new_name}" + new_db_url = f"{DB_PROTOCOL}://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{new_name}" - with create_engine( + async with create_async_engine( new_db_url, isolation_level="AUTOCOMMIT" - ).connect() as new_db_conn: - new_db_conn.execute( + ).begin() as new_db_conn: + await new_db_conn.execute( text(f"GRANT ALL ON SCHEMA public TO {DB_USER}") ) print( diff --git a/api/oss/docker/Dockerfile.dev b/api/oss/docker/Dockerfile.dev index 4b9bf8752b..da2ee0f82d 100644 --- a/api/oss/docker/Dockerfile.dev +++ b/api/oss/docker/Dockerfile.dev @@ -15,10 +15,30 @@ RUN apt-get update && \ RUN pip install --upgrade pip \ && pip install poetry +# COPY ./oss /app/oss/ COPY ./entrypoint.py ./pyproject.toml /app/ RUN poetry config virtualenvs.create false \ && poetry install --no-interaction --no-ansi + # && pip install -e /sdk/ + +# ENV PYTHONPATH=/sdk:$PYTHONPATH + +# +# +# +# + +# +# + +# +# +# +# + +# +# EXPOSE 8000 diff --git a/api/oss/docker/Dockerfile.gh b/api/oss/docker/Dockerfile.gh index e5224f41ae..a245cfcfa6 100644 --- a/api/oss/docker/Dockerfile.gh +++ b/api/oss/docker/Dockerfile.gh @@ -15,10 +15,30 @@ RUN apt-get update && \ RUN pip install --upgrade pip \ && pip install poetry +# COPY ./oss /app/oss/ COPY ./entrypoint.py ./pyproject.toml /app/ RUN poetry config virtualenvs.create false \ && poetry install --no-interaction --no-ansi +# + +# + +# +# +# +# + +# +# + +# +# +# +# + +# +# EXPOSE 8000 diff --git a/api/oss/src/apis/fastapi/annotations/models.py b/api/oss/src/apis/fastapi/annotations/models.py index 627b46a604..0d7444e203 100644 --- a/api/oss/src/apis/fastapi/annotations/models.py +++ b/api/oss/src/apis/fastapi/annotations/models.py @@ -1,92 +1,20 @@ -from typing import Optional, List, Dict -from enum import Enum +from typing import Optional, List from pydantic import BaseModel from oss.src.core.shared.dtos import ( - Lifecycle, - Data, - Tags, - Meta, - Reference, Link, Windowing, ) +from oss.src.core.annotations.types import ( + Annotation, + AnnotationCreate, + AnnotationEdit, + AnnotationQuery, +) -class AnnotationOrigin(str, Enum): - CUSTOM = "custom" - HUMAN = "human" - AUTO = "auto" - - -class AnnotationKind(str, Enum): - ADHOC = "adhoc" # adhoc annotation - EVAL = "eval" # evaluation annotation - - -class AnnotationChannel(str, Enum): - WEB = "web" - SDK = "sdk" # python vs typescript ? - API = "api" # http vs otlp ? - - -class AnnotationReferences(BaseModel): - evaluator: Reference - evaluator_variant: Optional[Reference] = None - evaluator_revision: Optional[Reference] = None - testset: Optional[Reference] = None - testcase: Optional[Reference] = None - - -AnnotationLinks = Dict[str, Link] - - -class Annotation(Link, Lifecycle): - origin: AnnotationOrigin = AnnotationOrigin.CUSTOM - kind: AnnotationKind = AnnotationKind.ADHOC - channel: AnnotationChannel = AnnotationChannel.API - - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - data: Data - - references: AnnotationReferences - links: AnnotationLinks - - -class AnnotationCreate(BaseModel): - origin: AnnotationOrigin = AnnotationOrigin.CUSTOM - kind: AnnotationKind = AnnotationKind.ADHOC - channel: AnnotationChannel = AnnotationChannel.API - - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - data: Data - - references: AnnotationReferences - links: AnnotationLinks - - -class AnnotationEdit(BaseModel): - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - data: Data - - -class AnnotationQuery(BaseModel): - origin: Optional[AnnotationOrigin] = None - kind: Optional[AnnotationKind] = None - channel: Optional[AnnotationChannel] = None - - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - references: Optional[AnnotationReferences] = None - links: Optional[AnnotationLinks | List[Link]] = None +# ANNOTATIONS ------------------------------------------------------------------ class AnnotationCreateRequest(BaseModel): @@ -99,7 +27,9 @@ class AnnotationEditRequest(BaseModel): class AnnotationQueryRequest(BaseModel): annotation: Optional[AnnotationQuery] = None + # annotation_links: Optional[List[Link]] = None + # windowing: Optional[Windowing] = None @@ -116,3 +46,8 @@ class AnnotationsResponse(BaseModel): class AnnotationLinkResponse(BaseModel): count: int = 0 annotation_link: Optional[Link] = None + + +class AnnotationLinksResponse(BaseModel): + count: int = 0 + annotation_links: List[Link] = [] diff --git a/api/oss/src/apis/fastapi/annotations/router.py b/api/oss/src/apis/fastapi/annotations/router.py index 9c541ee800..85e67ec633 100644 --- a/api/oss/src/apis/fastapi/annotations/router.py +++ b/api/oss/src/apis/fastapi/annotations/router.py @@ -1,60 +1,27 @@ -from typing import Optional, List -from uuid import uuid4 +from typing import Optional, Union +from uuid import UUID -from genson import SchemaBuilder - -from fastapi import APIRouter, Request, status, HTTPException, Response +from fastapi import APIRouter, Request, Response, status from oss.src.utils.common import is_ee from oss.src.utils.logging import get_module_logger from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions from oss.src.utils.caching import get_cache, set_cache, invalidate_cache -from oss.src.core.shared.dtos import Flags, Tags, Meta, Data, Reference, Link, Windowing -from oss.src.core.tracing.dtos import OTelLink, OTelReference, TraceType, SpanType -from oss.src.core.tracing.dtos import Focus, Format, Query, Formatting, Filtering -from oss.src.core.workflows.service import WorkflowsService -from oss.src.core.tracing.service import TracingService -from oss.src.apis.fastapi.tracing.router import TracingRouter -from oss.src.apis.fastapi.evaluators.router import SimpleEvaluatorsRouter - -from oss.src.apis.fastapi.tracing.models import ( - OTelFlatSpan, - OTelTracingRequest, - OTelTracingResponse, -) - -from oss.src.apis.fastapi.evaluators.models import ( - SimpleEvaluatorFlags, - SimpleEvaluatorCreate, - SimpleEvaluatorCreateRequest, - SimpleEvaluatorQueryRequest, +from oss.src.core.shared.dtos import ( + Link, ) -from oss.src.apis.fastapi.annotations.models import Annotation - -from oss.src.apis.fastapi.annotations.utils import ( - validate_data_against_schema, - parse_into_attributes, - parse_from_attributes, +from oss.src.core.annotations.service import ( + AnnotationsService, ) from oss.src.apis.fastapi.annotations.models import ( AnnotationCreateRequest, AnnotationEditRequest, + AnnotationQueryRequest, AnnotationResponse, AnnotationsResponse, - AnnotationQueryRequest, AnnotationLinkResponse, - Annotation, - AnnotationOrigin, - AnnotationKind, - AnnotationChannel, - AnnotationReferences, - AnnotationLinks, -) - -from oss.src.apis.fastapi.annotations.utils import ( - AnnotationFlags, ) if is_ee(): @@ -66,29 +33,18 @@ class AnnotationsRouter: - VERSION = "v1" - def __init__( self, *, - tracing_service: TracingService, - workflows_service: WorkflowsService, + annotations_service: AnnotationsService, ): - self.tracing_service = tracing_service - self.workflows_service = workflows_service - - # Needed until we clean up the router/service # FIX ME / REMOVE ME # - self.tracing_router = TracingRouter( - tracing_service=self.tracing_service, - ) - - self.evaluators_router = SimpleEvaluatorsRouter( - workflows_service=self.workflows_service - ) + self.annotations_service = annotations_service self.router = APIRouter() - # POST /api/v1/annotations/ + # ANNOTATIONS ---------------------------------------------------------- + + # POST /api/annotations/ self.router.add_api_route( "/", self.create_annotation, @@ -99,7 +55,18 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/v1/annotations/{trace_id}/{span_id} + # GET /api/annotations/{trace_id} + self.router.add_api_route( + "/{trace_id}", + self.fetch_annotation, + methods=["GET"], + operation_id="fetch_annotation_by_trace_id", + status_code=status.HTTP_200_OK, + response_model=AnnotationResponse, + response_model_exclude_none=True, + ) + + # GET /api/annotations/{trace_id}/{span_id} self.router.add_api_route( "/{trace_id}/{span_id}", self.fetch_annotation, @@ -109,7 +76,19 @@ def __init__( response_model=AnnotationResponse, response_model_exclude_none=True, ) - # PUT /api/v1/annotations/{trace_id}/{span_id} + + # PUT /api/annotations/{trace_id} + self.router.add_api_route( + "/{trace_id}", + self.edit_annotation, + methods=["PATCH"], + operation_id="edit_annotation_by_trace_id", + status_code=status.HTTP_200_OK, + response_model=AnnotationResponse, + response_model_exclude_none=True, + ) + + # PUT /api/annotations/{trace_id}/{span_id} self.router.add_api_route( "/{trace_id}/{span_id}", self.edit_annotation, @@ -120,29 +99,29 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/v1/annotations/{trace_id}/{span_id} + # DELETE /api/annotations/{trace_id} self.router.add_api_route( - "/{trace_id}/{span_id}", + "/{trace_id}", self.delete_annotation, methods=["DELETE"], - operation_id="delete_annotation", + operation_id="delete_annotation_by_trace_id", status_code=status.HTTP_200_OK, response_model=AnnotationLinkResponse, response_model_exclude_none=True, ) - # GET /api/v1/annotations/?... + # DELETE /api/annotations/{trace_id}/{span_id} self.router.add_api_route( - "/", - self.query_annotations, - methods=["GET"], - operation_id="list_annotations", + "/{trace_id}/{span_id}", + self.delete_annotation, + methods=["DELETE"], + operation_id="delete_annotation", status_code=status.HTTP_200_OK, - response_model=AnnotationResponse, + response_model=AnnotationLinkResponse, response_model_exclude_none=True, ) - # POST /api/v1/annotations/query # FIX ME / REMOVE ME # + # POST /api/annotations/query # FIX ME / REMOVE ME # self.router.add_api_route( "/query", self.query_annotations, @@ -153,154 +132,32 @@ def __init__( response_model_exclude_none=True, ) + # ANNOTATIONS -------------------------------------------------------------- + @intercept_exceptions() async def create_annotation( self, - *, request: Request, + *, annotation_create_request: AnnotationCreateRequest, ) -> AnnotationResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_ANNOTATIONS, + permission=Permission.EDIT_ANNOTATIONS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - annotation_origin = annotation_create_request.annotation.origin - - evaluator_flags = SimpleEvaluatorFlags( - is_evaluator=True, - is_custom=annotation_origin == AnnotationOrigin.CUSTOM, - is_human=annotation_origin == AnnotationOrigin.HUMAN, - ) - - evaluator = None - - if annotation_create_request.annotation.references.evaluator.slug: - simple_evaluator_query_request = SimpleEvaluatorQueryRequest( - evaluator_refs=[ - Reference( - slug=annotation_create_request.annotation.references.evaluator.slug - ) - ] - ) - - simple_evaluator_response = ( - await self.evaluators_router.query_simple_evaluators( - request=request, - simple_evaluator_query_request=simple_evaluator_query_request, - ) - ) - - if simple_evaluator_response.count > 0: - evaluator = simple_evaluator_response.evaluators[0] - - if evaluator is None: - builder = SchemaBuilder() - builder.add_object(annotation_create_request.annotation.data) - evaluator_format = builder.to_schema() # pylint: disable=redefined-builtin - - evaluator_slug = ( - annotation_create_request.annotation.references.evaluator.slug - or uuid4().hex - ) - - evaluator_data = dict( - service=dict( - agenta="v0.1.0", - format=evaluator_format, - ) - ) - - simple_evaluator_create_request = SimpleEvaluatorCreateRequest( - evaluator=SimpleEvaluatorCreate( - slug=evaluator_slug, - # - name=evaluator_slug, # yes - # description = - # - flags=evaluator_flags, - tags=annotation_create_request.annotation.tags, - meta=annotation_create_request.annotation.meta, - # - data=evaluator_data, - ) - ) - - simple_evaluator_create_response = ( - await self.evaluators_router.create_simple_evaluator( - request=request, - simple_evaluator_create_request=simple_evaluator_create_request, - ) - ) - - if simple_evaluator_create_response.count > 0: - evaluator = simple_evaluator_create_response.evaluator - - if evaluator is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create evaluator. Please try again or contact support.", - ) - - validate_data_against_schema( - annotation_create_request.annotation.data, - evaluator.data.service.get("format", {}), - ) - - annotation_create_request.annotation.references.evaluator = Reference( - id=evaluator.id, - slug=evaluator.slug, - ) - - kind = annotation_create_request.annotation.kind - channel = annotation_create_request.annotation.channel - - annotation_flags = AnnotationFlags( - is_evaluator=True, - is_custom=evaluator_flags.is_custom, - is_human=evaluator_flags.is_human, - is_sdk=channel == AnnotationChannel.SDK, - is_web=channel == AnnotationChannel.WEB, - is_evaluation=kind == AnnotationKind.EVAL, - ) - - annotation_link: Optional[Link] = await self._create_annotation( - request=request, - # - name=evaluator.name, + annotation = await self.annotations_service.create( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - flags=annotation_flags, - tags=annotation_create_request.annotation.tags, - meta=annotation_create_request.annotation.meta, - # - data=annotation_create_request.annotation.data, - # - references=annotation_create_request.annotation.references, - links=annotation_create_request.annotation.links, + annotation_create=annotation_create_request.annotation, ) - if annotation_link is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create annotation. Please try again or contact support.", - ) - - annotation: Optional[Annotation] = await self._fetch_annotation( - request=request, - annotation_link=annotation_link, - ) - - if annotation is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to fetch annotation. Please try again or contact support.", - ) - annotation_response = AnnotationResponse( - count=1, + count=1 if annotation else 0, annotation=annotation, ) @@ -310,34 +167,28 @@ async def create_annotation( @suppress_exceptions(default=AnnotationResponse()) async def fetch_annotation( self, - *, request: Request, + *, trace_id: str, - span_id: str, - ) -> AnnotationResponse: + span_id: Optional[str] = None, + ) -> Union[Response, AnnotationResponse]: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_ANNOTATIONS, + permission=Permission.VIEW_ANNOTATIONS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - annotation_link = Link( + annotation = await self.annotations_service.fetch( + project_id=UUID(request.state.project_id), + # trace_id=trace_id, span_id=span_id, ) - annotation: Optional[Annotation] = await self._fetch_annotation( - request=request, - annotation_link=annotation_link, - ) - - if annotation is None: - return Response(status_code=status.HTTP_404_NOT_FOUND) - annotation_response = AnnotationResponse( - count=1, + count=1 if annotation else 0, annotation=annotation, ) @@ -346,79 +197,33 @@ async def fetch_annotation( @intercept_exceptions() async def edit_annotation( self, - *, request: Request, + *, trace_id: str, - span_id: str, - annotation_request: AnnotationEditRequest, + span_id: Optional[str] = None, + # + annotation_edit_request: AnnotationEditRequest, ) -> AnnotationResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_ANNOTATIONS, + permission=Permission.EDIT_ANNOTATIONS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - annotation_link = Link( + annotation = await self.annotations_service.edit( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # trace_id=trace_id, span_id=span_id, - ) - - annotation: Optional[Annotation] = await self._fetch_annotation( - request=request, - annotation_link=annotation_link, - ) - - if annotation is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Failed to fetch annotation. Please try again or contact support.", - ) - - annotation_flags = AnnotationFlags( - is_evaluator=True, - is_custom=annotation.origin == AnnotationOrigin.CUSTOM, - is_human=annotation.origin == AnnotationOrigin.HUMAN, - is_sdk=annotation.channel == AnnotationChannel.SDK, - is_web=annotation.channel == AnnotationChannel.WEB, - is_evaluation=annotation.kind == AnnotationKind.EVAL, - ) - - annotation_link: Optional[Link] = await self._edit_annotation( - request=request, # - annotation=annotation, - # - flags=annotation_flags, - tags=annotation_request.annotation.tags, - meta=annotation_request.annotation.meta, - # - data=annotation_request.annotation.data, - # - references=annotation.references, - links=annotation.links, + annotation_edit=annotation_edit_request.annotation, ) - if annotation_link is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create annotation. Please try again or contact support.", - ) - - annotation: Optional[Annotation] = await self._fetch_annotation( - request=request, - annotation_link=annotation_link, - ) - - if annotation is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to fetch annotation. Please try again or contact support.", - ) - annotation_response = AnnotationResponse( - count=1, + count=1 if annotation else 0, annotation=annotation, ) @@ -427,29 +232,27 @@ async def edit_annotation( @intercept_exceptions() async def delete_annotation( self, - *, request: Request, + *, trace_id: str, - span_id: str, + span_id: Optional[str] = None, ) -> AnnotationLinkResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_ANNOTATIONS, + permission=Permission.EDIT_ANNOTATIONS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - annotation_link = Link( + annotation_link: Optional[Link] = await self.annotations_service.delete( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # trace_id=trace_id, span_id=span_id, ) - annotation_link: Optional[Link] = await self._delete_annotation( - request=request, - annotation_link=annotation_link, - ) - annotation_link_response = AnnotationLinkResponse( count=1 if annotation_link else 0, annotation_link=annotation_link, @@ -461,48 +264,26 @@ async def delete_annotation( @suppress_exceptions(default=AnnotationsResponse()) async def query_annotations( self, - *, request: Request, - query_request: Optional[AnnotationQueryRequest] = None, - ) -> AnnotationResponse: + *, + annotation_query_request: AnnotationQueryRequest, + ) -> AnnotationsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_ANNOTATIONS, + permission=Permission.VIEW_ANNOTATIONS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - annotation = query_request.annotation if query_request else None - annotation_flags = AnnotationFlags(is_evaluator=True) - - if annotation: - if annotation.origin: - annotation_flags.is_custom = ( - annotation.origin == AnnotationOrigin.CUSTOM - ) - annotation_flags.is_human = annotation.origin == AnnotationOrigin.HUMAN + raise FORBIDDEN_EXCEPTION # type: ignore - if annotation.channel: - annotation_flags.is_sdk = annotation.channel == AnnotationChannel.SDK - annotation_flags.is_web = annotation.channel == AnnotationChannel.WEB - - if annotation.kind: - annotation_flags.is_evaluation = annotation.kind == AnnotationKind.EVAL - - annotations = await self._query_annotation( - request=request, - # - flags=annotation_flags, - tags=annotation.tags if annotation else None, - meta=annotation.meta if annotation else None, + annotations = await self.annotations_service.query( + project_id=UUID(request.state.project_id), # - references=annotation.references if annotation else None, - links=annotation.links if annotation else None, + annotation_query=annotation_query_request.annotation, # - annotation_links=query_request.annotation_links if query_request else None, + annotation_links=annotation_query_request.annotation_links, # - windowing=query_request.windowing if query_request else None, + windowing=annotation_query_request.windowing, ) annotations_response = AnnotationsResponse( @@ -511,510 +292,3 @@ async def query_annotations( ) return annotations_response - - # - ANNOTATIONS ------------------------------------------------------------ - - @intercept_exceptions() - async def _create_annotation( - self, - *, - request: Request, - # - name: Optional[str] = None, - # - flags: AnnotationFlags, - tags: Optional[Tags] = None, - meta: Optional[Meta] = None, - # - data: Data, - # - references: AnnotationReferences, - links: AnnotationLinks, - ) -> Optional[Link]: - trace_id = uuid4().hex - trace_type = TraceType.ANNOTATION - - span_id = uuid4().hex[16:] - span_type = SpanType.TASK - span_name = name or references.evaluator.slug or "annotation" - - _references = references.model_dump(mode="json", exclude_none=True) - - _links = [ - OTelLink( - trace_id=link.trace_id, - span_id=link.span_id, - attributes={"key": key}, - ).model_dump(mode="json") - for key, link in links.items() - ] - - _flags = flags.model_dump(mode="json", exclude_none=True) - - _type = { - "trace": "annotation", - "span": "task", - } - - _attributes = parse_into_attributes( - type=_type, - flags=_flags, - tags=tags, - meta=meta, - data=data, - references=_references, - ) - - trace_request = OTelTracingRequest( - spans=[ - OTelFlatSpan( - trace_id=trace_id, - trace_type=trace_type, - span_id=span_id, - span_type=span_type, - span_name=span_name, - attributes=_attributes, - links=_links, - ) - ] - ) - - _links_response = await self.tracing_router.create_trace( - request=request, - trace_request=trace_request, - ) - - return _links_response.links[0] if _links_response.links else None - - @intercept_exceptions() - async def _fetch_annotation( - self, - *, - request: Request, - annotation_link: Link, - ) -> Optional[Annotation]: - trace_response: OTelTracingResponse = await self.tracing_router.fetch_trace( - request=request, - trace_id=annotation_link.trace_id, - ) - - if trace_response.count == 0: - return None - - traces = list(trace_response.traces.values()) - trace = traces[0] if traces else None - - spans = list(trace.spans.values()) - root_span = spans[0] if spans else None - - ( - type, - flags, - tags, - meta, - data, - references, - ) = parse_from_attributes(root_span.attributes) - - _references = AnnotationReferences(**references) - - _links = ( - { - link.attributes.get("key"): Link( - trace_id=link.trace_id, - span_id=link.span_id, - ) - for link in root_span.links - if link.attributes.get("key") - } - if root_span.links and isinstance(root_span.links, list) - else None - ) - - _origin = ( - flags.get("is_custom") - and AnnotationOrigin.CUSTOM - or flags.get("is_human") - and AnnotationOrigin.HUMAN - or AnnotationOrigin.AUTO - ) - - _kind = ( - flags.get("is_evaluation") and AnnotationKind.EVAL or AnnotationKind.ADHOC - ) - - _channel = ( - flags.get("is_sdk") - and AnnotationChannel.SDK - or flags.get("is_web") - and AnnotationChannel.WEB - or AnnotationChannel.API - ) - - annotation = Annotation( - trace_id=root_span.trace_id, - span_id=root_span.span_id, - # - created_at=root_span.created_at, - updated_at=root_span.updated_at, - deleted_at=root_span.deleted_at, - created_by_id=root_span.created_by_id, - updated_by_id=root_span.updated_by_id, - deleted_by_id=root_span.deleted_by_id, - # - origin=_origin, - kind=_kind, - channel=_channel, - # - flags=flags, - tags=tags, - meta=meta, - # - data=data, - # - references=_references, - links=_links, - ) - - return annotation - - @intercept_exceptions() - async def _edit_annotation( - self, - *, - request: Request, - # - annotation: Annotation, - # - flags: AnnotationFlags, - tags: Optional[Tags] = None, - meta: Optional[Meta] = None, - # - data: Data, - # - references: AnnotationReferences, - links: AnnotationLinks, - ) -> Optional[Annotation]: - _references = references.model_dump(mode="json", exclude_none=True) - - _links = [ - OTelLink( - trace_id=link.trace_id, - span_id=link.span_id, - attributes={"key": key}, - ).model_dump(mode="json") - for key, link in links.items() - ] - - _flags = flags.model_dump(mode="json", exclude_none=True) - - _type = { - "trace": "annotation", - "span": "task", - } - - _attributes = parse_into_attributes( - type=_type, - flags=_flags, - tags=tags, - meta=meta, - data=data, - references=_references, - ) - - trace_request = OTelTracingRequest( - spans=[ - OTelFlatSpan( - trace_id=annotation.trace_id, - span_id=annotation.span_id, - attributes=_attributes, - links=_links, - ) - ] - ) - - _links_response = await self.tracing_router.edit_trace( - request=request, - trace_id=annotation.trace_id, - trace_request=trace_request, - ) - - return _links_response.links[0] if _links_response.links else None - - @intercept_exceptions() - async def _delete_annotation( - self, - *, - request: Request, - annotation_link: Link, - ) -> Optional[Link]: - link_response = await self.tracing_router.delete_trace( - request=request, - trace_id=annotation_link.trace_id, - ) - - if link_response.count == 0: - return None - - link = link_response.links[0] if link_response.links else None - - annotation_link = Link( - trace_id=link.trace_id, - span_id=link.span_id, - ) - - return annotation_link - - @intercept_exceptions() - async def _query_annotation( - self, - *, - request: Request, - # - flags: Optional[AnnotationFlags] = None, - tags: Optional[Tags] = None, - meta: Optional[Meta] = None, - # - references: Optional[AnnotationReferences] = None, - links: Optional[AnnotationLinks | List[Link]] = None, - # - annotation_links: Optional[List[Link]] = None, - # - windowing: Optional[Windowing] = None, - ) -> List[Annotation]: - formatting = Formatting( - focus=Focus.TRACE, - format=Format.AGENTA, - ) - - filtering = Filtering() - - conditions = [ - { - "field": "attributes", - "key": "ag.type.trace", - "value": "annotation", - "operator": "is", - } - ] - - trace_ids = ( - [annotation_link.trace_id for annotation_link in annotation_links] - if annotation_links - else None - ) - - # span_ids = ( - # [annotation_link.span_id for annotation_link in annotation_links] - # if annotation_links - # else None - # ) - - if trace_ids: - conditions.append( - { - "field": "trace_id", - "value": trace_ids, - "operator": "in", - } - ) - - # if span_ids: - # conditions.append( - # { - # "field": "span_id", - # "value": span_ids, - # "operator": "in", - # } - # ) - - if flags: - for key, value in flags.model_dump(mode="json", exclude_none=True).items(): - conditions.append( - { - "field": "attributes", - "key": f"ag.flags.{key}", - "value": value, - "operator": "is", - } - ) - - if tags: - for key, value in tags.items(): - conditions.append( - { - "field": "attributes", - "key": f"ag.tags.{key}", - "value": value, - "operator": "is", - } - ) - - if meta: - for key, value in meta.items(): - conditions.append( - { - "field": "attributes", - "key": f"ag.meta.{key}", - "value": value, - "operator": "is", - } - ) - - if references: - for _, reference in references.model_dump(mode="json").items(): - if reference: - ref_id = str(reference.get("id")) if reference.get("id") else None - ref_slug = ( - str(reference.get("slug")) if reference.get("slug") else None - ) - conditions.append( - { - "field": "references", - "value": [ - {"id": ref_id, "slug": ref_slug}, - ], - "operator": "in", - } - ) - - if links: - if isinstance(links, dict): - for _, link in links.items(): - if link: - conditions.append( - { - "field": "links", - "value": [ - { - "trace_id": link.trace_id, - "span_id": link.span_id, - }, - ], - "operator": "in", - } - ) - elif isinstance(links, list): - _conditions = [] - for link in links: - link: Link - if link: - _conditions.append( - { - "field": "links", - "value": [ - { - "trace_id": link.trace_id, - "span_id": link.span_id, - }, - ], - "operator": "in", - } - ) - if _conditions: - conditions.append( - { - "operator": "or", - "conditions": _conditions, - } - ) - - if conditions: - filtering = Filtering( - operator="and", - conditions=conditions, - ) - - query = Query( - formatting=formatting, - filtering=filtering, - windowing=windowing, - ) - - spans_response: OTelTracingResponse = await self.tracing_router.query_spans( - request=request, - query=query, - ) - - traces = list(spans_response.traces.values()) - - annotations = [] - - for trace in traces: - spans = list(trace.spans.values()) - - root_span = spans[0] if spans else None - - ( - type, - flags, - tags, - meta, - data, - references, - ) = parse_from_attributes(root_span.attributes) - - _references = AnnotationReferences(**references) - - _links = ( - { - link.attributes.get("key"): Link( - trace_id=link.trace_id, - span_id=link.span_id, - ) - for link in root_span.links - if link.attributes.get("key") - } - if root_span.links and isinstance(root_span.links, list) - else None - ) - - _origin = ( - flags.get("is_custom") - and AnnotationOrigin.CUSTOM - or flags.get("is_human") - and AnnotationOrigin.HUMAN - or AnnotationOrigin.AUTO - ) - - _kind = ( - flags.get("is_evaluation") - and AnnotationKind.EVAL - or AnnotationKind.ADHOC - ) - - _channel = ( - flags.get("is_sdk") - and AnnotationChannel.SDK - or flags.get("is_web") - and AnnotationChannel.WEB - or AnnotationChannel.API - ) - - annotation = Annotation( - trace_id=root_span.trace_id, - span_id=root_span.span_id, - # - created_at=root_span.created_at, - updated_at=root_span.updated_at, - deleted_at=root_span.deleted_at, - created_by_id=root_span.created_by_id, - updated_by_id=root_span.updated_by_id, - deleted_by_id=root_span.deleted_by_id, - # - origin=_origin, - kind=_kind, - channel=_channel, - # - flags=flags, - tags=tags, - meta=meta, - data=data, - # - references=_references, - links=_links, - ) - - annotations.append(annotation) - - return annotations diff --git a/api/oss/src/apis/fastapi/annotations/utils.py b/api/oss/src/apis/fastapi/annotations/utils.py deleted file mode 100644 index 246914275a..0000000000 --- a/api/oss/src/apis/fastapi/annotations/utils.py +++ /dev/null @@ -1,131 +0,0 @@ -from typing import Optional, Tuple, List, Dict - -from jsonschema import ( - Draft202012Validator, - Draft7Validator, - Draft4Validator, - Draft6Validator, - Draft201909Validator, -) - -from fastapi import status, HTTPException - - -from oss.src.utils.logging import get_module_logger - -from oss.src.core.shared.dtos import Flags, Tags, Meta, Data, Reference -from oss.src.core.tracing.dtos import Attributes -from oss.src.core.workflows.dtos import WorkflowFlags - - -log = get_module_logger(__name__) - - -def _get_jsonschema_validator( - format: dict, # pylint: disable=redefined-builtin -): - schema_uri = format.get( - "$schema", - "https://json-schema.org/draft/2020-12/schema", - ) - - if "2020-12" in schema_uri: - return Draft202012Validator - elif "2019-09" in schema_uri: - return Draft201909Validator - elif "draft-07" in schema_uri: - return Draft7Validator - elif "draft-06" in schema_uri: - return Draft6Validator - elif "draft-04" in schema_uri: - return Draft4Validator - return Draft202012Validator # fallback - - -def validate_data_against_schema( - data: dict, - schema: dict, # pylint: disable=redefined-builtin -): - validator_class = _get_jsonschema_validator(schema) - validator = validator_class(schema) - - errors = list(validator.iter_errors(data)) - - if errors: - details = [] - for e in errors: - loc = list(e.absolute_path) - msg = e.message - details.append( - { - "loc": ["body", "annotation", "data"] + loc, - "msg": msg, - "type": "value_error.json_schema", - } - ) - - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=details - ) - - -def parse_into_attributes( - *, - type: Optional[Dict[str, str]] = None, - flags: Optional[Flags] = None, - tags: Optional[Tags] = None, - data: Optional[Data] = None, - meta: Optional[Meta] = None, - references: Optional[List[Reference]] = None, -) -> Attributes: - attributes: Attributes = dict( - ag=( - dict( - type=type, - flags=flags, - tags=tags, - meta=meta, - data=data, - references=references, - ) - if type or flags or tags or meta or data or references - else None - ) - ) - - return attributes - - -def parse_from_attributes( - attributes: Attributes, -) -> Tuple[ - Optional[Dict[str, str]], # type - Optional[Flags], # flags - Optional[Tags], # tags - Optional[Data], # data - Optional[Meta], # meta - Optional[List[Reference]], # references -]: - # TODO - add error handling - ag: dict = attributes.get("ag", {}) - type: dict = ag.get("type", {}) - flags: dict = ag.get("flags") - tags: dict = ag.get("tags") - meta: dict = ag.get("meta") - data: dict = ag.get("data") - references = ag.get("references") - - return ( - type, - flags, - tags, - meta, - data, - references, - ) - - -class AnnotationFlags(WorkflowFlags): - is_sdk: Optional[bool] = None - is_web: Optional[bool] = None - is_evaluation: Optional[bool] = None diff --git a/api/oss/src/apis/fastapi/middleware/__init__.py b/api/oss/src/apis/fastapi/applications/__init__.py similarity index 100% rename from api/oss/src/apis/fastapi/middleware/__init__.py rename to api/oss/src/apis/fastapi/applications/__init__.py diff --git a/api/oss/src/apis/fastapi/applications/models.py b/api/oss/src/apis/fastapi/applications/models.py new file mode 100644 index 0000000000..09e67ffdfc --- /dev/null +++ b/api/oss/src/apis/fastapi/applications/models.py @@ -0,0 +1,155 @@ +from typing import Optional, List + +from pydantic import BaseModel + +from oss.src.core.shared.dtos import ( + Reference, + Windowing, +) +from oss.src.core.applications.dtos import ( + Application, + ApplicationCreate, + ApplicationEdit, + ApplicationQuery, + # + ApplicationVariant, + ApplicationVariantCreate, + ApplicationVariantEdit, + ApplicationVariantQuery, + # + ApplicationRevision, + ApplicationRevisionCreate, + ApplicationRevisionEdit, + ApplicationRevisionQuery, + ApplicationRevisionCommit, + # + LegacyApplication, + LegacyApplicationCreate, + LegacyApplicationEdit, +) + +# APPLICATIONS ----------------------------------------------------------------- + + +class ApplicationCreateRequest(BaseModel): + application: ApplicationCreate + + +class ApplicationEditRequest(BaseModel): + application: ApplicationEdit + + +class ApplicationQueryRequest(BaseModel): + application: Optional[ApplicationQuery] = None + # + application_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class ApplicationResponse(BaseModel): + count: int = 0 + application: Optional[Application] = None + + +class ApplicationsResponse(BaseModel): + count: int = 0 + applications: List[Application] = [] + + +# APPLICATION VARIANTS --------------------------------------------------------- + + +class ApplicationVariantCreateRequest(BaseModel): + application_variant: ApplicationVariantCreate + + +class ApplicationVariantEditRequest(BaseModel): + application_variant: ApplicationVariantEdit + + +class ApplicationVariantQueryRequest(BaseModel): + application_variant: Optional[ApplicationVariantQuery] = None + # + application_refs: Optional[List[Reference]] = None + application_variant_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class ApplicationVariantResponse(BaseModel): + count: int = 0 + application_variant: Optional[ApplicationVariant] = None + + +class ApplicationVariantsResponse(BaseModel): + count: int = 0 + application_variants: List[ApplicationVariant] = [] + + +# APPLICATION REVISIONS -------------------------------------------------------- + + +class ApplicationRevisionCreateRequest(BaseModel): + application_revision: ApplicationRevisionCreate + + +class ApplicationRevisionEditRequest(BaseModel): + application_revision: ApplicationRevisionEdit + + +class ApplicationRevisionQueryRequest(BaseModel): + application_revision: Optional[ApplicationRevisionQuery] = None + # + application_refs: Optional[List[Reference]] = None + application_variant_refs: Optional[List[Reference]] = None + application_revision_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class ApplicationRevisionCommitRequest(BaseModel): + application_revision_commit: ApplicationRevisionCommit + + +class ApplicationRevisionRetrieveRequest(BaseModel): + application_ref: Optional[Reference] = None + application_variant_ref: Optional[Reference] = None + application_revision_ref: Optional[Reference] = None + + +class ApplicationRevisionResponse(BaseModel): + count: int = 0 + application_revision: Optional[ApplicationRevision] = None + + +class ApplicationRevisionsResponse(BaseModel): + count: int = 0 + application_revisions: List[ApplicationRevision] = [] + + +# LEGACY APPLICATIONS ---------------------------------------------------------- + + +class LegacyApplicationCreateRequest(BaseModel): + application: LegacyApplicationCreate + + +class LegacyApplicationEditRequest(BaseModel): + application: LegacyApplicationEdit + + +class LegacyApplicationResponse(BaseModel): + count: int = 0 + application: Optional[LegacyApplication] = None + + +class LegacyApplicationsResponse(BaseModel): + count: int = 0 + applications: List[LegacyApplication] = [] diff --git a/api/oss/src/apis/fastapi/applications/router.py b/api/oss/src/apis/fastapi/applications/router.py new file mode 100644 index 0000000000..059702d0cd --- /dev/null +++ b/api/oss/src/apis/fastapi/applications/router.py @@ -0,0 +1,248 @@ +from uuid import UUID +from typing import Optional + +from fastapi import APIRouter, status, Request, Depends + +from oss.src.utils.common import is_ee +from oss.src.utils.logging import get_module_logger +from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions +from oss.src.utils.caching import get_cache, set_cache, invalidate_cache + + +from oss.src.core.applications.dtos import ( + ApplicationRevision, +) +from oss.src.core.applications.service import ( + LegacyApplicationsService, +) + +from oss.src.apis.fastapi.applications.models import ( + ApplicationRevisionRetrieveRequest, + ApplicationRevisionResponse, + # + LegacyApplicationCreateRequest, + LegacyApplicationEditRequest, + LegacyApplicationResponse, +) +from oss.src.apis.fastapi.applications.utils import ( + parse_application_revision_retrieve_request_from_params, + parse_application_revision_retrieve_request_from_body, +) + +if is_ee(): + from ee.src.models.shared_models import Permission + from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + + +log = get_module_logger(__name__) + + +class LegacyApplicationsRouter: + def __init__( + self, + *, + legacy_applications_service: LegacyApplicationsService, + ): + self.legacy_applications_service = legacy_applications_service + + self.router = APIRouter() + + # APPLICATION REVISIONS ------------------------------------------------ + + self.router.add_api_route( + "/revisions/retrieve", + self.retrieve_application_revision, + methods=["POST"], + operation_id="retrieve_application_revision", + status_code=status.HTTP_200_OK, + response_model=ApplicationRevisionResponse, + response_model_exclude_none=True, + ) + + # LEGACY APPLICATIONS -------------------------------------------------- + + self.router.add_api_route( + "/", + self.create_legacy_application, + methods=["POST"], + response_model=LegacyApplicationResponse, + status_code=status.HTTP_200_OK, + summary="Create Application", + description="Create a new application using workflow data stored in legacy format", + operation_id="create_legacy_application", + ) + + self.router.add_api_route( + "/{application_id}", + self.fetch_legacy_application, + methods=["GET"], + response_model=LegacyApplicationResponse, + status_code=status.HTTP_200_OK, + summary="Fetch Application", + description="Get an application using workflow data stored in legacy format", + operation_id="fetch_legacy_application", + ) + + self.router.add_api_route( + "/{application_id}", + self.edit_legacy_application, + methods=["PUT"], + response_model=LegacyApplicationResponse, + status_code=status.HTTP_200_OK, + summary="Update Application", + description="Update an application using workflow data stored in legacy format", + operation_id="edit_legacy_application", + ) + + # APPLICATION REVISIONS ---------------------------------------------------- + + @intercept_exceptions() + @suppress_exceptions(default=ApplicationRevisionResponse()) + async def retrieve_application_revision( + self, + request: Request, + *, + application_revision_retrieve_request: ApplicationRevisionRetrieveRequest, + ): + if not await check_action_access( # type: ignore + project_id=request.state.project_id, + user_uid=request.state.user_id, + # + permission=Permission.VIEW_APPLICATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + cache_key = { + "artifact_ref": application_revision_retrieve_request.application_ref, # type: ignore + "variant_ref": application_revision_retrieve_request.application_variant_ref, # type: ignore + "revision_ref": application_revision_retrieve_request.application_revision_ref, # type: ignore + } + + application_revision = await get_cache( + namespace="applications:retrieve", + project_id=request.state.project_id, + user_id=request.state.user_id, + key=cache_key, + model=ApplicationRevision, + ) + + if not application_revision: + application_revision = await self.legacy_applications_service.retrieve( + project_id=UUID(request.state.project_id), + # + application_ref=application_revision_retrieve_request.application_ref, # type: ignore + application_variant_ref=application_revision_retrieve_request.application_variant_ref, # type: ignore + application_revision_ref=application_revision_retrieve_request.application_revision_ref, # type: ignore + ) + + await set_cache( + namespace="applications:retrieve", + project_id=request.state.project_id, + user_id=request.state.user_id, + key=cache_key, + value=application_revision, + ) + + application_revision_response = ApplicationRevisionResponse( + count=1 if application_revision else 0, + application_revision=application_revision, + ) + + return application_revision_response + + # LEGACY APPLICATIONS ------------------------------------------------------ + + @intercept_exceptions() + async def create_legacy_application( + self, + request: Request, + *, + legacy_application_create_request: LegacyApplicationCreateRequest, + ): + if is_ee(): + if not await check_action_access( # type: ignore + project_id=request.state.project_id, + user_uid=request.state.user_id, + # + permission=Permission.EDIT_APPLICATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + legacy_application = await self.legacy_applications_service.create( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + legacy_application_create=legacy_application_create_request.application, + ) + + legacy_application_response = LegacyApplicationResponse( + count=1 if legacy_application else 0, + application=legacy_application, + ) + + return legacy_application_response + + @intercept_exceptions() + @suppress_exceptions(default=LegacyApplicationResponse()) + async def fetch_legacy_application( + self, + request: Request, + *, + application_id: UUID, + ): + if is_ee(): + if not await check_action_access( # type: ignore + project_id=request.state.project_id, + user_uid=request.state.user_id, + # + permission=Permission.VIEW_APPLICATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + legacy_application = await self.legacy_applications_service.fetch( + project_id=UUID(request.state.project_id), + # + application_id=application_id, + ) + + legacy_application_response = LegacyApplicationResponse( + count=1 if legacy_application else 0, + application=legacy_application, + ) + + return legacy_application_response + + @intercept_exceptions() + async def edit_legacy_application( + self, + request: Request, + *, + application_id: UUID, + # + legacy_application_edit_request: LegacyApplicationEditRequest, + ): + if is_ee(): + if not await check_action_access( # type: ignore + project_id=request.state.project_id, + user_uid=request.state.user_id, + # + permission=Permission.EDIT_APPLICATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(application_id) != str(legacy_application_edit_request.application.id): + return LegacyApplicationResponse() + + legacy_application = await self.legacy_applications_service.edit( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + legacy_application_edit=legacy_application_edit_request.application, + ) + + legacy_application_response = LegacyApplicationResponse( + count=1 if legacy_application else 0, + application=legacy_application, + ) + + return legacy_application_response diff --git a/api/oss/src/apis/fastapi/applications/utils.py b/api/oss/src/apis/fastapi/applications/utils.py new file mode 100644 index 0000000000..12a613b5bb --- /dev/null +++ b/api/oss/src/apis/fastapi/applications/utils.py @@ -0,0 +1,90 @@ +from uuid import UUID +from typing import Optional + +from oss.src.core.shared.dtos import ( + Reference, +) + +from oss.src.apis.fastapi.applications.models import ( + ApplicationRevisionRetrieveRequest, +) + + +def parse_application_revision_retrieve_request_from_params( + application_id: Optional[UUID] = None, + application_slug: Optional[str] = None, + # + application_variant_id: Optional[UUID] = None, + application_variant_slug: Optional[str] = None, + # + application_revision_id: Optional[UUID] = None, + application_revision_slug: Optional[str] = None, + application_revision_version: Optional[str] = None, +): + return parse_application_revision_retrieve_request_from_body( + application_id=application_id, + application_slug=application_slug, + # + application_variant_id=application_variant_id, + application_variant_slug=application_variant_slug, + # + application_revision_id=application_revision_id, + application_revision_slug=application_revision_slug, + application_revision_version=application_revision_version, + ) + + +def parse_application_revision_retrieve_request_from_body( + application_id: Optional[UUID] = None, + application_slug: Optional[str] = None, + # + application_variant_id: Optional[UUID] = None, + application_variant_slug: Optional[str] = None, + # + application_revision_id: Optional[UUID] = None, + application_revision_slug: Optional[str] = None, + application_revision_version: Optional[str] = None, +) -> Optional[ApplicationRevisionRetrieveRequest]: + return ( + ApplicationRevisionRetrieveRequest( + application_ref=( + Reference( + id=application_id, + slug=application_slug, + ) + if application_id or application_slug + else None + ), + # + application_variant_ref=( + Reference( + id=application_variant_id, + slug=application_variant_slug, + ) + if application_variant_id or application_variant_slug + else None + ), + # + application_revision_ref=( + Reference( + id=application_revision_id, + slug=application_revision_slug, + version=application_revision_version, + ) + if application_revision_id + or application_revision_slug + or application_revision_version + else None + ), + ) + if ( + application_id + or application_slug + or application_variant_id + or application_variant_slug + or application_revision_id + or application_revision_slug + or application_revision_version + ) + else None + ) diff --git a/api/oss/src/apis/fastapi/evaluations/models.py b/api/oss/src/apis/fastapi/evaluations/models.py index b202829b39..c4e740740b 100644 --- a/api/oss/src/apis/fastapi/evaluations/models.py +++ b/api/oss/src/apis/fastapi/evaluations/models.py @@ -2,32 +2,41 @@ from uuid import UUID from pydantic import BaseModel - from fastapi import HTTPException -from oss.src.core.shared.dtos import Windowing - +from oss.src.core.shared.dtos import ( + Windowing, +) from oss.src.core.evaluations.types import ( EvaluationRun, EvaluationRunCreate, EvaluationRunEdit, EvaluationRunQuery, + # EvaluationScenario, EvaluationScenarioCreate, EvaluationScenarioEdit, EvaluationScenarioQuery, - EvaluationStep, - EvaluationStepCreate, - EvaluationStepEdit, - EvaluationStepQuery, - EvaluationMetric, - EvaluationMetricCreate, - EvaluationMetricEdit, - EvaluationMetricQuery, + # + EvaluationResult, + EvaluationResultCreate, + EvaluationResultEdit, + EvaluationResultQuery, + # + EvaluationMetrics, + EvaluationMetricsCreate, + EvaluationMetricsEdit, + EvaluationMetricsQuery, + # EvaluationQueue, EvaluationQueueCreate, EvaluationQueueEdit, EvaluationQueueQuery, + # + SimpleEvaluation, + SimpleEvaluationCreate, + SimpleEvaluationEdit, + SimpleEvaluationQuery, ) @@ -39,8 +48,9 @@ def __init__( message: str = "Cannot modify a closed evaluation.", run_id: Optional[UUID] = None, scenario_id: Optional[UUID] = None, - step_id: Optional[UUID] = None, - metric_id: Optional[UUID] = None, + result_id: Optional[UUID] = None, + metrics_id: Optional[UUID] = None, + queue_id: Optional[UUID] = None, ): details = dict(message=message) @@ -48,20 +58,23 @@ def __init__( details["run_id"] = str(run_id) if scenario_id: details["scenario_id"] = str(scenario_id) - if step_id: - details["step_id"] = str(step_id) - if metric_id: - details["metric_id"] = str(metric_id) + if result_id: + details["result_id"] = str(result_id) + if metrics_id: + details["metrics_id"] = str(metrics_id) + if queue_id: + details["queue_id"] = str(queue_id) super().__init__(status_code=409, detail=details) self.run_id = run_id self.scenario_id = scenario_id - self.step_id = step_id - self.metric_id = metric_id + self.result_id = result_id + self.metrics_id = metrics_id + self.queue_id = queue_id -# - EVALUATION RUN ------------------------------------------------------------- +# EVALUATION RUNS -------------------------------------------------------------- class EvaluationRunsCreateRequest(BaseModel): @@ -77,8 +90,8 @@ class EvaluationRunsEditRequest(BaseModel): class EvaluationRunQueryRequest(BaseModel): - run: EvaluationRunQuery - include_archived: Optional[bool] = False + run: Optional[EvaluationRunQuery] = None + # windowing: Optional[Windowing] = None @@ -106,7 +119,7 @@ class EvaluationRunIdsResponse(BaseModel): run_ids: List[UUID] = [] -# - EVALUATION SCENARIO -------------------------------------------------------- +# - EVALUATION SCENARIOS ------------------------------------------------------- class EvaluationScenariosCreateRequest(BaseModel): @@ -122,7 +135,8 @@ class EvaluationScenariosEditRequest(BaseModel): class EvaluationScenarioQueryRequest(BaseModel): - scenario: EvaluationScenarioQuery + scenario: Optional[EvaluationScenarioQuery] = None + # windowing: Optional[Windowing] = None @@ -150,95 +164,83 @@ class EvaluationScenarioIdsResponse(BaseModel): scenario_ids: List[UUID] = [] -# - EVALUATION STEP ------------------------------------------------------------ +# - EVALUATION RESULTS --------------------------------------------------------- -class EvaluationStepsCreateRequest(BaseModel): - steps: List[EvaluationStepCreate] +class EvaluationResultsCreateRequest(BaseModel): + results: List[EvaluationResultCreate] -class EvaluationStepEditRequest(BaseModel): - step: EvaluationStepEdit +class EvaluationResultEditRequest(BaseModel): + result: EvaluationResultEdit -class EvaluationStepsEditRequest(BaseModel): - steps: List[EvaluationStepEdit] +class EvaluationResultsEditRequest(BaseModel): + results: List[EvaluationResultEdit] -class EvaluationStepQueryRequest(BaseModel): - step: EvaluationStepQuery +class EvaluationResultQueryRequest(BaseModel): + result: Optional[EvaluationResultQuery] = None + # windowing: Optional[Windowing] = None -class EvaluationStepIdsRequest(BaseModel): - step_ids: List[UUID] +class EvaluationResultIdsRequest(BaseModel): + result_ids: List[UUID] -class EvaluationStepResponse(BaseModel): +class EvaluationResultResponse(BaseModel): count: int = 0 - step: Optional[EvaluationStep] = None + result: Optional[EvaluationResult] = None -class EvaluationStepsResponse(BaseModel): +class EvaluationResultsResponse(BaseModel): count: int = 0 - steps: List[EvaluationStep] = [] + results: List[EvaluationResult] = [] -class EvaluationStepIdResponse(BaseModel): +class EvaluationResultIdResponse(BaseModel): count: int = 0 - step_id: Optional[UUID] = None + result_id: Optional[UUID] = None -class EvaluationStepIdsResponse(BaseModel): +class EvaluationResultIdsResponse(BaseModel): count: int = 0 - step_ids: List[UUID] = [] + result_ids: List[UUID] = [] -# - EVALUATION METRIC ---------------------------------------------------------- +# - EVALUATION METRICS --------------------------------------------------------- class EvaluationMetricsCreateRequest(BaseModel): - metrics: List[EvaluationMetricCreate] - - -class EvaluationMetricEditRequest(BaseModel): - metric: EvaluationMetricEdit + metrics: List[EvaluationMetricsCreate] class EvaluationMetricsEditRequest(BaseModel): - metrics: List[EvaluationMetricEdit] + metrics: List[EvaluationMetricsEdit] -class EvaluationMetricQueryRequest(BaseModel): - metric: EvaluationMetricQuery +class EvaluationMetricsQueryRequest(BaseModel): + metrics: Optional[EvaluationMetricsQuery] = None + # windowing: Optional[Windowing] = None -class EvaluationMetricIdsRequest(BaseModel): - metric_ids: List[UUID] - - -class EvaluationMetricResponse(BaseModel): - count: int = 0 - metric: Optional[EvaluationMetric] = None +class EvaluationMetricsIdsRequest(BaseModel): + metrics_ids: List[UUID] class EvaluationMetricsResponse(BaseModel): count: int = 0 - metrics: List[EvaluationMetric] = [] - - -class EvaluationMetricIdResponse(BaseModel): - count: int = 0 - metric_id: Optional[UUID] = None + metrics: List[EvaluationMetrics] = [] -class EvaluationMetricIdsResponse(BaseModel): +class EvaluationMetricsIdsResponse(BaseModel): count: int = 0 - metric_ids: List[UUID] = [] + metrics_ids: List[UUID] = [] -# - EVALUATION QUEUE ----------------------------------------------------------- +# - EVALUATION QUEUES ---------------------------------------------------------- class EvaluationQueuesCreateRequest(BaseModel): @@ -254,7 +256,8 @@ class EvaluationQueuesEditRequest(BaseModel): class EvaluationQueueQueryRequest(BaseModel): - queue: EvaluationQueueQuery + queue: Optional[EvaluationQueueQuery] = None + # windowing: Optional[Windowing] = None @@ -287,4 +290,33 @@ class EvaluationQueueScenarioIdsResponse(BaseModel): scenario_ids: List[List[UUID]] = [] -# ------------------------------------------------------------------------------ +# - SIMPLE EVALUATIONS --------------------------------------------------------- + + +class SimpleEvaluationCreateRequest(BaseModel): + evaluation: SimpleEvaluationCreate + + +class SimpleEvaluationEditRequest(BaseModel): + evaluation: SimpleEvaluationEdit + + +class SimpleEvaluationQueryRequest(BaseModel): + evaluation: Optional[SimpleEvaluationQuery] = None + # + windowing: Optional[Windowing] = None + + +class SimpleEvaluationResponse(BaseModel): + count: int = 0 + evaluation: Optional[SimpleEvaluation] = None + + +class SimpleEvaluationsResponse(BaseModel): + count: int = 0 + evaluations: List[SimpleEvaluation] = [] + + +class SimpleEvaluationIdResponse(BaseModel): + count: int = 0 + evaluation_id: Optional[UUID] = None diff --git a/api/oss/src/apis/fastapi/evaluations/router.py b/api/oss/src/apis/fastapi/evaluations/router.py index bd3049faa6..781922da95 100644 --- a/api/oss/src/apis/fastapi/evaluations/router.py +++ b/api/oss/src/apis/fastapi/evaluations/router.py @@ -1,23 +1,24 @@ from typing import Optional from uuid import UUID -from functools import wraps +from datetime import datetime, timedelta -from fastapi import APIRouter, Request, HTTPException, Depends, Query +from fastapi import APIRouter, Request, Query from oss.src.utils.common import is_ee from oss.src.utils.logging import get_module_logger from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions from oss.src.utils.caching import get_cache, set_cache, invalidate_cache -from oss.src.core.evaluations.service import EvaluationsService -from oss.src.core.evaluations.types import ( - EvaluationClosedConflict, - EvaluationRun, +from oss.src.core.queries.service import ( + QueriesService, +) +from oss.src.core.evaluations.service import ( + EvaluationsService, + SimpleEvaluationsService, ) -from oss.src.apis.fastapi.evaluations.models import EvaluationClosedException from oss.src.apis.fastapi.evaluations.models import ( - # EVALUATION RUN + # EVALUATION RUNS EvaluationRunsCreateRequest, EvaluationRunEditRequest, EvaluationRunsEditRequest, @@ -27,7 +28,7 @@ EvaluationRunsResponse, EvaluationRunIdResponse, EvaluationRunIdsResponse, - # EVALUATION SCENARIO + # EVALUATION SCENARIOS EvaluationScenariosCreateRequest, EvaluationScenarioEditRequest, EvaluationScenariosEditRequest, @@ -37,27 +38,24 @@ EvaluationScenariosResponse, EvaluationScenarioIdResponse, EvaluationScenarioIdsResponse, - # EVALUATION STEP - EvaluationStepsCreateRequest, - EvaluationStepEditRequest, - EvaluationStepsEditRequest, - EvaluationStepQueryRequest, - EvaluationStepIdsRequest, - EvaluationStepResponse, - EvaluationStepsResponse, - EvaluationStepIdResponse, - EvaluationStepIdsResponse, - # EVALUATION METRIC + # EVALUATION RESULTS + EvaluationResultsCreateRequest, + EvaluationResultEditRequest, + EvaluationResultsEditRequest, + EvaluationResultQueryRequest, + EvaluationResultIdsRequest, + EvaluationResultResponse, + EvaluationResultsResponse, + EvaluationResultIdResponse, + EvaluationResultIdsResponse, + # EVALUATION METRICS EvaluationMetricsCreateRequest, - EvaluationMetricEditRequest, EvaluationMetricsEditRequest, - EvaluationMetricQueryRequest, - EvaluationMetricIdsRequest, - EvaluationMetricResponse, + EvaluationMetricsQueryRequest, + EvaluationMetricsIdsRequest, EvaluationMetricsResponse, - EvaluationMetricIdResponse, - EvaluationMetricIdsResponse, - # EVALUATION QUEUE + EvaluationMetricsIdsResponse, + # EVALUATION QUEUES EvaluationQueuesCreateRequest, EvaluationQueueEditRequest, EvaluationQueuesEditRequest, @@ -67,17 +65,20 @@ EvaluationQueuesResponse, EvaluationQueueIdResponse, EvaluationQueueIdsResponse, + # EvaluationQueueScenarioIdsResponse, + # + SimpleEvaluationCreateRequest, + SimpleEvaluationEditRequest, + SimpleEvaluationQueryRequest, + SimpleEvaluationResponse, + SimpleEvaluationsResponse, + SimpleEvaluationIdResponse, ) from oss.src.apis.fastapi.evaluations.utils import ( - parse_run_query_request, - parse_scenario_query_request, - parse_step_query_request, - parse_metric_query_request, - parse_queue_query_request, + handle_evaluation_closed_exception, ) - if is_ee(): from ee.src.models.shared_models import Permission from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION @@ -86,61 +87,40 @@ log = get_module_logger(__name__) -def handle_evaluation_closed_exception(): - def decorator(func): - @wraps(func) - async def wrapper(*args, **kwargs): - try: - return await func(*args, **kwargs) - except EvaluationClosedConflict as e: - raise EvaluationClosedException( - message=e.message, - run_id=e.run_id, - scenario_id=e.scenario_id, - step_id=e.step_id, - metric_id=e.metric_id, - ) from e - except Exception as e: - raise e - - return wrapper - - return decorator - - class EvaluationsRouter: - VERSION = "preview" - def __init__( self, *, evaluations_service: EvaluationsService, + queries_service: QueriesService, ): self.evaluations_service = evaluations_service + self.queries_service = queries_service self.router = APIRouter() - # - EVALUATION RUN ----------------------------------------------------- + self.admin_router = APIRouter() - # POST /api/preview/evaluations/runs/ - self.router.add_api_route( - path="/runs/", + # EVALUATION RUNS ------------------------------------------------------ + + # POST /api/evaluations/runs/refresh + self.admin_router.add_api_route( + path="/runs/refresh", methods=["POST"], - endpoint=self.create_runs, - response_model=EvaluationRunsResponse, + endpoint=self.refresh_runs, response_model_exclude_none=True, ) - # GET /api/preview/evaluations/runs/ + # POST /api/evaluations/runs/ self.router.add_api_route( path="/runs/", - methods=["GET"], - endpoint=self.fetch_runs, + methods=["POST"], + endpoint=self.create_runs, response_model=EvaluationRunsResponse, response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/runs/ + # PATCH /api/evaluations/runs/ self.router.add_api_route( path="/runs/", methods=["PATCH"], @@ -149,7 +129,7 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/runs/ + # DELETE /api/evaluations/runs/ self.router.add_api_route( path="/runs/", methods=["DELETE"], @@ -158,7 +138,7 @@ def __init__( response_model_exclude_none=True, ) - # POST /api/preview/evaluations/runs/query + # POST /api/evaluations/runs/query self.router.add_api_route( path="/runs/query", methods=["POST"], @@ -167,34 +147,25 @@ def __init__( response_model_exclude_none=True, ) - # POST /api/preview/evaluations/runs/archive - self.router.add_api_route( - path="/runs/archive", - methods=["POST"], - endpoint=self.archive_runs, - response_model=EvaluationRunsResponse, - response_model_exclude_none=True, - ) - - # POST /api/preview/evaluations/runs/unarchive + # POST /api/evaluations/runs/close self.router.add_api_route( - path="/runs/unarchive", + path="/runs/close", methods=["POST"], - endpoint=self.unarchive_runs, + endpoint=self.close_runs, response_model=EvaluationRunsResponse, response_model_exclude_none=True, ) - # POST /api/preview/evaluations/runs/close + # POST /api/evaluations/runs/open self.router.add_api_route( - path="/runs/close", + path="/runs/open", methods=["POST"], - endpoint=self.close_runs, + endpoint=self.open_runs, response_model=EvaluationRunsResponse, response_model_exclude_none=True, ) - # GET /api/preview/evaluations/runs/{run_id} + # GET /api/evaluations/runs/{run_id} self.router.add_api_route( path="/runs/{run_id}", methods=["GET"], @@ -203,7 +174,7 @@ def __init__( response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/runs/{run_id} + # PATCH /api/evaluations/runs/{run_id} self.router.add_api_route( path="/runs/{run_id}", methods=["PATCH"], @@ -212,7 +183,7 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/runs/{run_id} + # DELETE /api/evaluations/runs/{run_id} self.router.add_api_route( path="/runs/{run_id}", methods=["DELETE"], @@ -221,36 +192,27 @@ def __init__( response_model_exclude_none=True, ) - # POST /api/preview/evaluations/runs/{run_id}/archive - self.router.add_api_route( - path="/runs/{run_id}/archive", - methods=["POST"], - endpoint=self.archive_run, - response_model=EvaluationRunResponse, - response_model_exclude_none=True, - ) - - # POST /api/preview/evaluations/runs/{run_id}/unarchive + # POST /api/evaluations/runs/{run_id}/close self.router.add_api_route( - path="/runs/{run_id}/unarchive", + path="/runs/{run_id}/close", methods=["POST"], - endpoint=self.unarchive_run, + endpoint=self.close_run, response_model=EvaluationRunResponse, response_model_exclude_none=True, ) - # POST /api/preview/evaluations/runs/{run_id}/close + # POST /api/evaluations/runs/{run_id}/open self.router.add_api_route( - path="/runs/{run_id}/close", + path="/runs/{run_id}/open", methods=["POST"], - endpoint=self.close_run, + endpoint=self.open_run, response_model=EvaluationRunResponse, response_model_exclude_none=True, ) - # - EVALUATION SCENARIO ------------------------------------------------ + # EVALUATION SCENARIOS ------------------------------------------------- - # POST /api/preview/evaluations/scenarios/ + # POST /api/evaluations/scenarios/ self.router.add_api_route( path="/scenarios/", methods=["POST"], @@ -259,16 +221,7 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/preview/evaluations/scenarios/ - self.router.add_api_route( - path="/scenarios/", - methods=["GET"], - endpoint=self.fetch_scenarios, - response_model=EvaluationScenariosResponse, - response_model_exclude_none=True, - ) - - # PATCH /api/preview/evaluations/scenarios/ + # PATCH /api/evaluations/scenarios/ self.router.add_api_route( path="/scenarios/", methods=["PATCH"], @@ -277,7 +230,7 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/scenarios/ + # DELETE /api/evaluations/scenarios/ self.router.add_api_route( path="/scenarios/", methods=["DELETE"], @@ -286,7 +239,7 @@ def __init__( response_model_exclude_none=True, ) - # POST /api/preview/evaluations/scenarios/query + # POST /api/evaluations/scenarios/query self.router.add_api_route( path="/scenarios/query", methods=["POST"], @@ -295,7 +248,7 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/preview/evaluations/scenarios/{scenario_id} + # GET /api/evaluations/scenarios/{scenario_id} self.router.add_api_route( path="/scenarios/{scenario_id}", methods=["GET"], @@ -304,7 +257,7 @@ def __init__( response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/scenarios/{scenario_id} + # PATCH /api/evaluations/scenarios/{scenario_id} self.router.add_api_route( path="/scenarios/{scenario_id}", methods=["PATCH"], @@ -313,7 +266,7 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/scenarios/{scenario_id} + # DELETE /api/evaluations/scenarios/{scenario_id} self.router.add_api_route( path="/scenarios/{scenario_id}", methods=["DELETE"], @@ -322,101 +275,92 @@ def __init__( response_model_exclude_none=True, ) - # - EVALUATION STEP ---------------------------------------------------- + # EVALUATION RESULTS --------------------------------------------------- - # POST /api/preview/evaluations/steps/ + # POST /api/evaluations/results/ self.router.add_api_route( - path="/steps/", + path="/results/", methods=["POST"], - endpoint=self.create_steps, - response_model=EvaluationStepsResponse, - response_model_exclude_none=True, - ) - - # GET /api/preview/evaluations/steps/ - self.router.add_api_route( - path="/steps/", - methods=["GET"], - endpoint=self.fetch_steps, - response_model=EvaluationStepsResponse, + endpoint=self.create_results, + response_model=EvaluationResultsResponse, response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/steps/ + # PATCH /api/evaluations/results/ self.router.add_api_route( - path="/steps/", + path="/results/", methods=["PATCH"], - endpoint=self.edit_steps, - response_model=EvaluationStepsResponse, + endpoint=self.edit_results, + response_model=EvaluationResultsResponse, response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/steps/ + # DELETE /api/evaluations/results/ self.router.add_api_route( - path="/steps/", + path="/results/", methods=["DELETE"], - endpoint=self.delete_steps, - response_model=EvaluationStepIdsResponse, + endpoint=self.delete_results, + response_model=EvaluationResultIdsResponse, response_model_exclude_none=True, ) - # POST /api/preview/evaluations/steps/query + # POST /api/evaluations/results/query self.router.add_api_route( - path="/steps/query", + path="/results/query", methods=["POST"], - endpoint=self.query_steps, - response_model=EvaluationStepsResponse, + endpoint=self.query_results, + response_model=EvaluationResultsResponse, response_model_exclude_none=True, ) - # GET /api/preview/evaluations/steps/{step_id} + # GET /api/evaluations/results/{result_id} self.router.add_api_route( - path="/steps/{step_id}", + path="/results/{result_id}", methods=["GET"], - endpoint=self.fetch_step, - response_model=EvaluationStepResponse, + endpoint=self.fetch_result, + response_model=EvaluationResultResponse, response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/steps/{step_id} + # PATCH /api/evaluations/results/{result_id} self.router.add_api_route( - path="/steps/{step_id}", + path="/results/{result_id}", methods=["PATCH"], - endpoint=self.edit_step, - response_model=EvaluationStepResponse, + endpoint=self.edit_result, + response_model=EvaluationResultResponse, response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/steps/{step_id} + # DELETE /api/evaluations/results/{result_id} self.router.add_api_route( - path="/steps/{step_id}", + path="/results/{result_id}", methods=["DELETE"], - endpoint=self.delete_step, - response_model=EvaluationStepIdResponse, + endpoint=self.delete_result, + response_model=EvaluationResultIdResponse, response_model_exclude_none=True, ) - # - EVALUATION METRIC -------------------------------------------------- + # EVALUATION METRICS --------------------------------------------------- - # POST /api/preview/evaluations/metrics/ + # POST /api/evaluations/metrics/refresh self.router.add_api_route( - path="/metrics/", + path="/metrics/refresh", methods=["POST"], - endpoint=self.create_metrics, + endpoint=self.refresh_metrics, response_model=EvaluationMetricsResponse, response_model_exclude_none=True, ) - # GET /api/preview/evaluations/metrics/ + # POST /api/evaluations/metrics/ self.router.add_api_route( path="/metrics/", - methods=["GET"], - endpoint=self.fetch_metrics, + methods=["POST"], + endpoint=self.create_metrics, response_model=EvaluationMetricsResponse, response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/metrics/ + # PATCH /api/evaluations/metrics/ self.router.add_api_route( path="/metrics/", methods=["PATCH"], @@ -425,16 +369,16 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/metrics/ + # DELETE /api/evaluations/metrics/ self.router.add_api_route( path="/metrics/", methods=["DELETE"], endpoint=self.delete_metrics, - response_model=EvaluationMetricIdsResponse, + response_model=EvaluationMetricsIdsResponse, response_model_exclude_none=True, ) - # POST /api/preview/evaluations/metrics/query + # POST /api/evaluations/metrics/query self.router.add_api_route( path="/metrics/query", methods=["POST"], @@ -443,36 +387,9 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/preview/evaluations/metrics/{metric_id} - self.router.add_api_route( - path="/metrics/{metric_id}", - methods=["GET"], - endpoint=self.fetch_metric, - response_model=EvaluationMetricResponse, - response_model_exclude_none=True, - ) - - # PATCH /api/preview/evaluations/metrics/{metric_id} - self.router.add_api_route( - path="/metrics/{metric_id}", - methods=["PATCH"], - endpoint=self.edit_metric, - response_model=EvaluationMetricResponse, - response_model_exclude_none=True, - ) - - # DELETE /api/preview/evaluations/metrics/{metric_id} - self.router.add_api_route( - path="/metrics/{metric_id}", - methods=["DELETE"], - endpoint=self.delete_metric, - response_model=EvaluationMetricIdResponse, - response_model_exclude_none=True, - ) - - # - EVALUATION QUEUE --------------------------------------------------- + # EVALUATION QUEUES ---------------------------------------------------- - # POST /api/preview/evaluations/queues/ + # POST /api/evaluations/queues/ self.router.add_api_route( path="/queues/", methods=["POST"], @@ -481,16 +398,7 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/preview/evaluations/queues/ - self.router.add_api_route( - path="/queues/", - methods=["GET"], - endpoint=self.fetch_queues, - response_model=EvaluationQueuesResponse, - response_model_exclude_none=True, - ) - - # PATCH /api/preview/evaluations/queues/ + # PATCH /api/evaluations/queues/ self.router.add_api_route( path="/queues/", methods=["PATCH"], @@ -499,7 +407,7 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/queues/ + # DELETE /api/evaluations/queues/ self.router.add_api_route( path="/queues/", methods=["DELETE"], @@ -508,7 +416,7 @@ def __init__( response_model_exclude_none=True, ) - # POST /api/preview/evaluations/queues/query + # POST /api/evaluations/queues/query self.router.add_api_route( path="/queues/query", methods=["POST"], @@ -517,7 +425,7 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/preview/evaluations/queues/{queue_id} + # GET /api/evaluations/queues/{queue_id} self.router.add_api_route( path="/queues/{queue_id}", methods=["GET"], @@ -526,7 +434,7 @@ def __init__( response_model_exclude_none=True, ) - # PATCH /api/preview/evaluations/queues/{queue_id} + # PATCH /api/evaluations/queues/{queue_id} self.router.add_api_route( path="/queues/{queue_id}", methods=["PATCH"], @@ -535,7 +443,7 @@ def __init__( response_model_exclude_none=True, ) - # DELETE /api/preview/evaluations/queues/{queue_id} + # DELETE /api/evaluations/queues/{queue_id} self.router.add_api_route( path="/queues/{queue_id}", methods=["DELETE"], @@ -544,7 +452,7 @@ def __init__( response_model_exclude_none=True, ) - # GET /api/preview/evaluations/queues/{queue_id}/scenarios + # GET /api/evaluations/queues/{queue_id}/scenarios/ self.router.add_api_route( path="/queues/{queue_id}/scenarios", methods=["GET"], @@ -553,29 +461,57 @@ def __init__( response_model_exclude_none=True, ) + # EVALUATION RUNS ---------------------------------------------------------- + + # POST /evaluations/runs/refresh + @intercept_exceptions() + async def refresh_runs( + self, + *, + trigger_interval: Optional[int] = Query(1, ge=1, le=60), + trigger_datetime: Optional[datetime] = Query(None), + ): + # ---------------------------------------------------------------------- + # THIS IS AN ADMIN ENDPOINT + # NO CHECK FOR PERMISSIONS / ENTITLEMENTS # ---------------------------------------------------------------------- - # - EVALUATION RUN --------------------------------------------------------- + if not trigger_datetime or not trigger_interval: + return {"status": "error"} + + timestamp = trigger_datetime - timedelta(minutes=trigger_interval) + interval = trigger_interval + + check = await self.evaluations_service.refresh_runs( + timestamp=timestamp, + interval=interval, + ) + + if not check: + return {"status": "failure"} + + return {"status": "success"} # POST /evaluations/runs/ @intercept_exceptions() async def create_runs( self, - *, request: Request, + *, runs_create_request: EvaluationRunsCreateRequest, ) -> EvaluationRunsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore runs = await self.evaluations_service.create_runs( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # runs=runs_create_request.runs, ) @@ -586,48 +522,32 @@ async def create_runs( return runs_response - # GET /evaluations/runs/ - - @intercept_exceptions() - @suppress_exceptions(default=EvaluationRunsResponse()) - async def fetch_runs( - self, - *, - request: Request, - run_query_request: Optional[EvaluationRunQueryRequest] = Depends( - parse_run_query_request - ), - ) -> EvaluationRunsResponse: - return await self.query_runs( - request=request, - run_query_request=run_query_request, - ) - # PATCH /evaluations/runs/ @intercept_exceptions() async def edit_runs( self, - *, request: Request, + *, runs_edit_request: EvaluationRunsEditRequest, ) -> EvaluationRunsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore runs = await self.evaluations_service.edit_runs( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # runs=runs_edit_request.runs, ) runs_response = EvaluationRunsResponse( count=len(runs), - runs=[EvaluationRun(**r.model_dump()) for r in runs], + runs=runs, ) return runs_response @@ -636,20 +556,21 @@ async def edit_runs( @intercept_exceptions() async def delete_runs( self, - *, request: Request, + *, run_ids_request: EvaluationRunIdsRequest, ) -> EvaluationRunIdsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore run_ids = await self.evaluations_service.delete_runs( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # run_ids=run_ids_request.run_ids, ) @@ -665,54 +586,26 @@ async def delete_runs( @suppress_exceptions(default=EvaluationRunsResponse()) async def query_runs( self, - *, request: Request, + *, run_query_request: EvaluationRunQueryRequest, ) -> EvaluationRunsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore runs = await self.evaluations_service.query_runs( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # run=run_query_request.run, - include_archived=run_query_request.include_archived, + # windowing=run_query_request.windowing, ) - runs_response = EvaluationRunsResponse( - count=len(runs), - runs=[EvaluationRun(**r.model_dump()) for r in runs], - ) - - return runs_response - - # POST /evaluations/runs/archive - @intercept_exceptions() - async def archive_runs( - self, - *, - request: Request, - run_ids_request: EvaluationRunIdsRequest, - ) -> EvaluationRunsResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, - ): - raise FORBIDDEN_EXCEPTION - - runs = await self.evaluations_service.archive_runs( - project_id=request.state.project_id, - user_id=request.state.user_id, - run_ids=run_ids_request.run_ids, - ) - runs_response = EvaluationRunsResponse( count=len(runs), runs=runs, @@ -720,25 +613,26 @@ async def archive_runs( return runs_response - # POST /evaluations/runs/unarchive + # POST /evaluations/runs/close @intercept_exceptions() - async def unarchive_runs( + async def close_runs( self, - *, request: Request, + *, run_ids_request: EvaluationRunIdsRequest, ) -> EvaluationRunsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - runs = await self.evaluations_service.unarchive_runs( - project_id=request.state.project_id, - user_id=request.state.user_id, + runs = await self.evaluations_service.close_runs( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # run_ids=run_ids_request.run_ids, ) @@ -749,25 +643,26 @@ async def unarchive_runs( return runs_response - # POST /evaluations/runs/close + # POST /evaluations/runs/open @intercept_exceptions() - async def close_runs( + async def open_runs( self, - *, request: Request, + *, run_ids_request: EvaluationRunIdsRequest, ) -> EvaluationRunsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - runs = await self.evaluations_service.close_runs( - project_id=request.state.project_id, - user_id=request.state.user_id, + runs = await self.evaluations_service.open_runs( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # run_ids=run_ids_request.run_ids, ) @@ -783,20 +678,21 @@ async def close_runs( @suppress_exceptions(default=EvaluationRunResponse()) async def fetch_run( self, - *, request: Request, + *, run_id: UUID, ) -> EvaluationRunResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore run = await self.evaluations_service.fetch_run( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # run_id=run_id, ) @@ -811,25 +707,27 @@ async def fetch_run( @intercept_exceptions() async def edit_run( self, - *, request: Request, + *, run_id: UUID, + # run_edit_request: EvaluationRunEditRequest, ) -> EvaluationRunResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - if run_id != run_edit_request.run.id: - raise HTTPException(status_code=400, detail="Run ID mismatch") + if str(run_id) != str(run_edit_request.run.id): + return EvaluationRunResponse() run = await self.evaluations_service.edit_run( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # run=run_edit_request.run, ) @@ -844,78 +742,51 @@ async def edit_run( @intercept_exceptions() async def delete_run( self, - *, request: Request, + *, run_id: UUID, ) -> EvaluationRunIdResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - run_id = await self.evaluations_service.delete_run( - project_id=request.state.project_id, + _run_id = await self.evaluations_service.delete_run( + project_id=UUID(request.state.project_id), + # run_id=run_id, ) run_id_response = EvaluationRunIdResponse( - count=1 if run_id else 0, - run_id=run_id, + count=1 if _run_id else 0, + run_id=_run_id, ) return run_id_response - # POST /evaluations/runs/{run_id}/archive + # POST /evaluations/runs/{run_id}/close @intercept_exceptions() - async def archive_run( + async def close_run( self, - *, request: Request, - run_id: UUID, - ) -> EvaluationRunResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, - ): - raise FORBIDDEN_EXCEPTION - - run = await self.evaluations_service.archive_run( - project_id=request.state.project_id, - user_id=request.state.user_id, - run_id=run_id, - ) - - run_response = EvaluationRunResponse( - count=1 if run else 0, - run=run, - ) - - return run_response - - # POST /evaluations/runs/{run_id}/unarchive - @intercept_exceptions() - async def unarchive_run( - self, *, - request: Request, run_id: UUID, ) -> EvaluationRunResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - run = await self.evaluations_service.unarchive_run( - project_id=request.state.project_id, - user_id=request.state.user_id, + run = await self.evaluations_service.close_run( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # run_id=run_id, ) @@ -926,25 +797,26 @@ async def unarchive_run( return run_response - # POST /evaluations/runs/{run_id}/close + # POST /evaluations/runs/{run_id}/open @intercept_exceptions() - async def close_run( + async def open_run( self, - *, request: Request, + *, run_id: UUID, ) -> EvaluationRunResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - run = await self.evaluations_service.close_run( - project_id=request.state.project_id, - user_id=request.state.user_id, + run = await self.evaluations_service.open_run( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # run_id=run_id, ) @@ -955,28 +827,29 @@ async def close_run( return run_response - # - EVALUATION SCENARIO ---------------------------------------------------- + # EVALUATION SCENARIOS ----------------------------------------------------- # POST /evaluations/scenarios/ @intercept_exceptions() @handle_evaluation_closed_exception() async def create_scenarios( self, - *, request: Request, + *, scenarios_create_request: EvaluationScenariosCreateRequest, ) -> EvaluationScenariosResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore scenarios = await self.evaluations_service.create_scenarios( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # scenarios=scenarios_create_request.scenarios, ) @@ -987,42 +860,27 @@ async def create_scenarios( return scenarios_response - # GET /evaluations/scenarios/ - @intercept_exceptions() - @suppress_exceptions(default=EvaluationScenariosResponse()) - async def fetch_scenarios( - self, - *, - request: Request, - scenario_query_request: Optional[EvaluationScenarioQueryRequest] = Depends( - parse_scenario_query_request - ), - ) -> EvaluationScenariosResponse: - return await self.query_scenarios( - request=request, - scenario_query_request=scenario_query_request, - ) - # PATCH /evaluations/scenarios/ @intercept_exceptions() @handle_evaluation_closed_exception() async def edit_scenarios( self, - *, request: Request, + *, scenarios_edit_request: EvaluationScenariosEditRequest, ) -> EvaluationScenariosResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore scenarios = await self.evaluations_service.edit_scenarios( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # scenarios=scenarios_edit_request.scenarios, ) @@ -1038,20 +896,21 @@ async def edit_scenarios( @handle_evaluation_closed_exception() async def delete_scenarios( self, - *, request: Request, + *, scenario_ids_request: EvaluationScenarioIdsRequest, ) -> EvaluationScenarioIdsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore scenario_ids = await self.evaluations_service.delete_scenarios( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # scenario_ids=scenario_ids_request.scenario_ids, ) @@ -1067,21 +926,23 @@ async def delete_scenarios( @suppress_exceptions(default=EvaluationScenariosResponse()) async def query_scenarios( self, - *, request: Request, + *, scenario_query_request: EvaluationScenarioQueryRequest, ) -> EvaluationScenariosResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore scenarios = await self.evaluations_service.query_scenarios( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # scenario=scenario_query_request.scenario, + # windowing=scenario_query_request.windowing, ) @@ -1097,20 +958,21 @@ async def query_scenarios( @suppress_exceptions(default=EvaluationScenarioResponse()) async def fetch_scenario( self, - *, request: Request, + *, scenario_id: UUID, ) -> EvaluationScenarioResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore scenario = await self.evaluations_service.fetch_scenario( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # scenario_id=scenario_id, ) @@ -1126,25 +988,26 @@ async def fetch_scenario( @handle_evaluation_closed_exception() async def edit_scenario( self, - *, request: Request, + *, scenario_id: UUID, + # scenario_edit_request: EvaluationScenarioEditRequest, ) -> EvaluationScenarioResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - if scenario_id != scenario_edit_request.scenario.id: - raise HTTPException(status_code=400, detail="Scenario ID mismatch") + if str(scenario_id) != scenario_edit_request.scenario.id: + return EvaluationScenarioResponse() scenario = await self.evaluations_service.edit_scenario( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), scenario=scenario_edit_request.scenario, ) @@ -1160,282 +1023,283 @@ async def edit_scenario( @handle_evaluation_closed_exception() async def delete_scenario( self, - *, request: Request, + *, scenario_id: UUID, - ) -> EvaluationScenarioIdsResponse: + ) -> EvaluationScenarioIdResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_SCENARIOS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - scenario_id = await self.evaluations_service.delete_scenario( - project_id=request.state.project_id, + _scenario_id = await self.evaluations_service.delete_scenario( + project_id=UUID(request.state.project_id), + # scenario_id=scenario_id, ) scenario_id_response = EvaluationScenarioIdResponse( - count=1 if scenario_id else 0, - scenario_id=scenario_id, + count=1 if _scenario_id else 0, + scenario_id=_scenario_id, ) return scenario_id_response - # - EVALAUTION STEP -------------------------------------------------------- + # EVALAUTION RESULTS ------------------------------------------------------- - # POST /evaluations/steps/ + # POST /evaluations/results/ @intercept_exceptions() @handle_evaluation_closed_exception() - async def create_steps( + async def create_results( self, - *, request: Request, - steps_create_request: EvaluationStepsCreateRequest, - ) -> EvaluationStepsResponse: + *, + results_create_request: EvaluationResultsCreateRequest, + ) -> EvaluationResultsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - steps = await self.evaluations_service.create_steps( - project_id=request.state.project_id, - user_id=request.state.user_id, - steps=steps_create_request.steps, + results = await self.evaluations_service.create_results( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + results=results_create_request.results, ) - steps_response = EvaluationStepsResponse( - count=len(steps), - steps=steps, + results_response = EvaluationResultsResponse( + count=len(results), + results=results, ) - return steps_response - - # GET /evaluations/steps/ - @intercept_exceptions() - @suppress_exceptions(default=EvaluationStepsResponse()) - async def fetch_steps( - self, - *, - request: Request, - step_query_request: Optional[EvaluationStepQueryRequest] = Depends( - parse_step_query_request - ), - ) -> EvaluationStepsResponse: - return await self.query_steps( - request=request, - step_query_request=step_query_request, - ) + return results_response - # PATCH /evaluations/steps/ + # PATCH /evaluations/results/ @intercept_exceptions() @handle_evaluation_closed_exception() - async def edit_steps( + async def edit_results( self, - *, request: Request, - steps_edit_request: EvaluationStepsEditRequest, - ) -> EvaluationStepsResponse: + *, + results_edit_request: EvaluationResultsEditRequest, + ) -> EvaluationResultsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - steps = await self.evaluations_service.edit_steps( - project_id=request.state.project_id, - user_id=request.state.user_id, - steps=steps_edit_request.steps, + results = await self.evaluations_service.edit_results( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + results=results_edit_request.results, ) - steps_response = EvaluationStepsResponse( - count=len(steps), - steps=steps, + results_response = EvaluationResultsResponse( + count=len(results), + results=results, ) - return steps_response + return results_response - # DELETE /evaluations/steps/ + # DELETE /evaluations/results/ @intercept_exceptions() @handle_evaluation_closed_exception() - async def delete_steps( + async def delete_results( self, - *, request: Request, - step_ids_request: EvaluationStepIdsRequest, - ) -> EvaluationStepIdsResponse: + *, + result_ids_request: EvaluationResultIdsRequest, + ) -> EvaluationResultIdsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - step_ids = await self.evaluations_service.delete_steps( - project_id=request.state.project_id, - step_ids=step_ids_request.step_ids, + result_ids = await self.evaluations_service.delete_results( + project_id=UUID(request.state.project_id), + # + result_ids=result_ids_request.result_ids, ) - step_ids_response = EvaluationStepIdsResponse( - count=len(step_ids), - step_ids=step_ids, + result_ids_response = EvaluationResultIdsResponse( + count=len(result_ids), + result_ids=result_ids, ) - return step_ids_response + return result_ids_response - # POST /evaluations/steps/query + # POST /evaluations/results/query @intercept_exceptions() - @suppress_exceptions(default=EvaluationStepsResponse()) - async def query_steps( + @suppress_exceptions(default=EvaluationResultsResponse()) + async def query_results( self, - *, request: Request, - step_query_request: EvaluationStepQueryRequest, - ) -> EvaluationStepsResponse: + *, + result_query_request: EvaluationResultQueryRequest, + ) -> EvaluationResultsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - steps = await self.evaluations_service.query_steps( - project_id=request.state.project_id, - step=step_query_request.step, - windowing=step_query_request.windowing, + results = await self.evaluations_service.query_results( + project_id=UUID(request.state.project_id), + # + result=result_query_request.result, + # + windowing=result_query_request.windowing, ) - steps_response = EvaluationStepsResponse( - count=len(steps), - steps=steps, + results_response = EvaluationResultsResponse( + count=len(results), + results=results, ) - return steps_response + return results_response - # GET /evaluations/steps/{step_id} + # GET /evaluations/results/{result_id} @intercept_exceptions() - @suppress_exceptions(default=EvaluationStepResponse()) - async def fetch_step( + @suppress_exceptions(default=EvaluationResultResponse()) + async def fetch_result( self, - *, request: Request, - step_id: UUID, - ) -> EvaluationStepResponse: + *, + result_id: UUID, + ) -> EvaluationResultResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - step = await self.evaluations_service.fetch_step( - project_id=request.state.project_id, - step_id=step_id, + result = await self.evaluations_service.fetch_result( + project_id=UUID(request.state.project_id), + # + result_id=result_id, ) - step_response = EvaluationStepResponse( - count=1 if step else 0, - step=step, + result_response = EvaluationResultResponse( + count=1 if result else 0, + result=result, ) - return step_response + return result_response - # PATCH /evaluations/steps/{step_id} + # PATCH /evaluations/results/{result_id} @intercept_exceptions() @handle_evaluation_closed_exception() - async def edit_step( + async def edit_result( self, - *, request: Request, - step_id: UUID, - step_edit_request: EvaluationStepEditRequest, - ) -> EvaluationStepResponse: + *, + result_id: UUID, + # + result_edit_request: EvaluationResultEditRequest, + ) -> EvaluationResultResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - if step_id != step_edit_request.step.id: - raise HTTPException(status_code=400, detail="Step ID mismatch") + if str(result_id) != result_edit_request.result.id: + return EvaluationResultResponse() - step = await self.evaluations_service.edit_step( - project_id=request.state.project_id, - user_id=request.state.user_id, - step=step_edit_request.step, + result = await self.evaluations_service.edit_result( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + result=result_edit_request.result, ) - step_response = EvaluationStepResponse( - count=1 if step else 0, - step=step, + result_response = EvaluationResultResponse( + count=1 if result else 0, + result=result, ) - return step_response + return result_response - # DELETE /evaluations/steps/{step_id} + # DELETE /evaluations/results/{result_id} @intercept_exceptions() @handle_evaluation_closed_exception() - async def delete_step( + async def delete_result( self, - *, request: Request, - step_id: UUID, - ) -> EvaluationStepIdsResponse: + *, + result_id: UUID, + ) -> EvaluationResultIdResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_RESULTS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - step_id_val = await self.evaluations_service.delete_step( - project_id=request.state.project_id, - step_id=step_id, + _result_id = await self.evaluations_service.delete_result( + project_id=UUID(request.state.project_id), + # + result_id=result_id, ) - step_id_response = EvaluationStepIdResponse( - count=1 if step_id_val else 0, - step_id=step_id_val, + result_id_response = EvaluationResultIdResponse( + count=1 if _result_id else 0, + result_id=_result_id, ) - return step_id_response + return result_id_response - # - EVALUATION METRIC ------------------------------------------------------ + # EVALUATION METRICS ------------------------------------------------------- - # POST /evaluations/metrics/ + # POST /evaluations/metrics/refresh @intercept_exceptions() - @handle_evaluation_closed_exception() - async def create_metrics( + @suppress_exceptions(default=EvaluationMetricsResponse()) + async def refresh_metrics( self, - *, request: Request, - metrics_create_request: EvaluationMetricsCreateRequest, + *, + run_id: UUID, + scenario_id: Optional[UUID] = None, + timestamp: Optional[datetime] = None, + interval: Optional[int] = None, ) -> EvaluationMetricsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_METRICS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - metrics = await self.evaluations_service.create_metrics( - project_id=request.state.project_id, - user_id=request.state.user_id, - metrics=metrics_create_request.metrics, + metrics = await self.evaluations_service.refresh_metrics( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + run_id=run_id, + scenario_id=scenario_id, + timestamp=timestamp, + interval=interval, ) metrics_response = EvaluationMetricsResponse( @@ -1445,42 +1309,58 @@ async def create_metrics( return metrics_response - # GET /evaluations/metrics/ + # POST /evaluations/metrics/ @intercept_exceptions() - @suppress_exceptions(default=EvaluationMetricsResponse()) - async def fetch_metrics( + @handle_evaluation_closed_exception() + async def create_metrics( self, - *, request: Request, - metric_query_request: Optional[EvaluationMetricQueryRequest] = Depends( - parse_metric_query_request - ), + *, + metrics_create_request: EvaluationMetricsCreateRequest, ) -> EvaluationMetricsResponse: - return await self.query_metrics( - request=request, - metric_query_request=metric_query_request, + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_METRICS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + metrics = await self.evaluations_service.create_metrics( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + metrics=metrics_create_request.metrics, + ) + + metrics_response = EvaluationMetricsResponse( + count=len(metrics), + metrics=metrics, ) + return metrics_response + # PATCH /evaluations/metrics/ @intercept_exceptions() @handle_evaluation_closed_exception() async def edit_metrics( self, - *, request: Request, + *, metrics_edit_request: EvaluationMetricsEditRequest, ) -> EvaluationMetricsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_METRICS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore metrics = await self.evaluations_service.edit_metrics( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # metrics=metrics_edit_request.metrics, ) @@ -1496,50 +1376,53 @@ async def edit_metrics( @handle_evaluation_closed_exception() async def delete_metrics( self, - *, request: Request, - metric_ids_request: EvaluationMetricIdsRequest, - ) -> EvaluationMetricIdsResponse: + *, + metrics_ids_request: EvaluationMetricsIdsRequest, + ) -> EvaluationMetricsIdsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_METRICS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - metric_ids = await self.evaluations_service.delete_metrics( - project_id=request.state.project_id, - metric_ids=metric_ids_request.metric_ids, + metrics_ids = await self.evaluations_service.delete_metrics( + project_id=UUID(request.state.project_id), + # + metrics_ids=metrics_ids_request.metrics_ids, ) - metric_ids_response = EvaluationMetricIdsResponse( - count=len(metric_ids), - metric_ids=metric_ids, + metrics_ids_response = EvaluationMetricsIdsResponse( + count=len(metrics_ids), + metrics_ids=metrics_ids, ) - return metric_ids_response + return metrics_ids_response # POST /evaluations/metrics/query @intercept_exceptions() @suppress_exceptions(default=EvaluationMetricsResponse()) async def query_metrics( self, - *, request: Request, - metric_query_request: EvaluationMetricQueryRequest, + *, + metric_query_request: EvaluationMetricsQueryRequest, ) -> EvaluationMetricsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_METRICS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore metrics = await self.evaluations_service.query_metrics( - project_id=request.state.project_id, - metric=metric_query_request.metric, + project_id=UUID(request.state.project_id), + # + metric=metric_query_request.metrics, + # windowing=metric_query_request.windowing, ) @@ -1550,120 +1433,29 @@ async def query_metrics( return metrics_response - # GET /evaluations/metrics/{metric_id} - @intercept_exceptions() - @suppress_exceptions(default=EvaluationMetricResponse()) - async def fetch_metric( - self, - *, - request: Request, - metric_id: UUID, - ) -> EvaluationMetricResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, - ): - raise FORBIDDEN_EXCEPTION - - metric = await self.evaluations_service.fetch_metric( - project_id=request.state.project_id, - metric_id=metric_id, - ) - - metric_response = EvaluationMetricResponse( - count=1 if metric else 0, - metric=metric, - ) - - return metric_response - - # PATCH /evaluations/metrics/{metric_id} - @intercept_exceptions() - @handle_evaluation_closed_exception() - async def edit_metric( - self, - *, - request: Request, - metric_id: UUID, - metric_edit_request: EvaluationMetricEditRequest, - ) -> EvaluationMetricResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, - ): - raise FORBIDDEN_EXCEPTION - - if metric_id != metric_edit_request.metric.id: - raise HTTPException(status_code=400, detail="Metric ID mismatch") - - metric = await self.evaluations_service.edit_metric( - project_id=request.state.project_id, - user_id=request.state.user_id, - metric=metric_edit_request.metric, - ) - - metric_response = EvaluationMetricResponse( - count=1 if metric else 0, - metric=metric, - ) - - return metric_response - - # DELETE /evaluations/metrics/{metric_id} - @intercept_exceptions() - @handle_evaluation_closed_exception() - async def delete_metric( - self, - *, - request: Request, - metric_id: UUID, - ) -> EvaluationMetricIdsResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, - ): - raise FORBIDDEN_EXCEPTION - - metric_id_val = await self.evaluations_service.delete_metric( - project_id=request.state.project_id, - metric_id=metric_id, - ) - - metric_id_response = EvaluationMetricIdResponse( - count=1 if metric_id_val else 0, - metric_id=metric_id_val, - ) - - return metric_id_response - - # - EVALUATION QUEUE ------------------------------------------------------- + # EVALUATION QUEUES -------------------------------------------------------- # POST /evaluations/queues/ @intercept_exceptions() @handle_evaluation_closed_exception() async def create_queues( self, - *, request: Request, + *, queues_create_request: EvaluationQueuesCreateRequest, ) -> EvaluationQueuesResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore queues = await self.evaluations_service.create_queues( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # queues=queues_create_request.queues, ) @@ -1674,42 +1466,27 @@ async def create_queues( return queues_response - # GET /evaluations/queues/ - @intercept_exceptions() - @suppress_exceptions(default=EvaluationQueuesResponse()) - async def fetch_queues( - self, - *, - request: Request, - queue_query_request: Optional[EvaluationQueueQueryRequest] = Depends( - parse_queue_query_request - ), - ) -> EvaluationQueuesResponse: - return await self.query_queues( - request=request, - queue_query_request=queue_query_request, - ) - # PATCH /evaluations/queues/ @intercept_exceptions() @handle_evaluation_closed_exception() async def edit_queues( self, - *, request: Request, + *, queues_edit_request: EvaluationQueuesEditRequest, ) -> EvaluationQueuesResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore queues = await self.evaluations_service.edit_queues( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # s queues=queues_edit_request.queues, ) @@ -1725,21 +1502,21 @@ async def edit_queues( @handle_evaluation_closed_exception() async def delete_queues( self, - *, request: Request, + *, queue_ids_request: EvaluationQueueIdsRequest, ) -> EvaluationQueueIdsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore queue_ids = await self.evaluations_service.delete_queues( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + # queue_ids=queue_ids_request.queue_ids, ) @@ -1755,23 +1532,23 @@ async def delete_queues( @suppress_exceptions(default=EvaluationQueuesResponse()) async def query_queues( self, - *, request: Request, - queue_query_request: Optional[EvaluationQueueQueryRequest] = Depends( - parse_queue_query_request - ), + *, + queue_query_request: EvaluationQueueQueryRequest, ) -> EvaluationQueuesResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore queues = await self.evaluations_service.query_queues( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # queue=queue_query_request.queue, + # windowing=queue_query_request.windowing, ) @@ -1787,20 +1564,21 @@ async def query_queues( @suppress_exceptions(default=EvaluationQueueResponse()) async def fetch_queue( self, - *, request: Request, + *, queue_id: UUID, ) -> EvaluationQueueResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore queue = await self.evaluations_service.fetch_queue( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), + # queue_id=queue_id, ) @@ -1816,25 +1594,27 @@ async def fetch_queue( @handle_evaluation_closed_exception() async def edit_queue( self, - *, request: Request, + *, queue_id: UUID, + # queue_edit_request: EvaluationQueueEditRequest, ) -> EvaluationQueueResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - if queue_id != queue_edit_request.queue.id: - raise HTTPException(status_code=400, detail="Queue ID mismatch") + if str(queue_id) != queue_edit_request.queue.id: + return EvaluationQueueResponse() queue = await self.evaluations_service.edit_queue( - project_id=request.state.project_id, - user_id=request.state.user_id, + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # queue=queue_edit_request.queue, ) @@ -1850,27 +1630,27 @@ async def edit_queue( @handle_evaluation_closed_exception() async def delete_queue( self, - *, request: Request, + *, queue_id: UUID, ) -> EvaluationQueueIdResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.DELETE_EVALUATIONS, + permission=Permission.EDIT_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - queue_id = await self.evaluations_service.delete_queue( - project_id=request.state.project_id, - user_id=request.state.user_id, + _queue_id = await self.evaluations_service.delete_queue( + project_id=UUID(request.state.project_id), + # queue_id=queue_id, ) - queue_id_response = EvaluationQueueResponse( - count=1 if queue_id else 0, - queue_id=queue_id, + queue_id_response = EvaluationQueueIdResponse( + count=1 if _queue_id else 0, + queue_id=_queue_id, ) return queue_id_response @@ -1880,22 +1660,22 @@ async def delete_queue( @suppress_exceptions(default=EvaluationQueueScenarioIdsResponse()) async def fetch_queue_scenarios( self, - *, request: Request, + *, queue_id: UUID, # user_id: Optional[UUID] = Query(None), ) -> EvaluationQueueScenarioIdsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATIONS, + permission=Permission.VIEW_EVALUATION_QUEUES, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore scenario_ids = await self.evaluations_service.fetch_queue_scenarios( - project_id=request.state.project_id, + project_id=UUID(request.state.project_id), user_id=user_id, # queue_id=queue_id, @@ -1908,4 +1688,372 @@ async def fetch_queue_scenarios( return scenario_ids_response - # -------------------------------------------------------------------------- + +class SimpleEvaluationsRouter: + def __init__( + self, + *, + simple_evaluations_service: SimpleEvaluationsService, + ): + self.simple_evaluations_service = simple_evaluations_service + + self.router = APIRouter() + + # SIMPLE EVALUATIONS --------------------------------------------------- + + # POST /api/simple/evaluations/ + self.router.add_api_route( + path="/", + methods=["POST"], + endpoint=self.create_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # GET /api/simple/evaluations/{evaluation_id} + self.router.add_api_route( + path="/{evaluation_id}", + methods=["GET"], + endpoint=self.fetch_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # PATCH /api/simple/evaluations/{evaluation_id} + self.router.add_api_route( + path="/{evaluation_id}", + methods=["PATCH"], + endpoint=self.edit_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # DELETE /api/simple/evaluations/{evaluation_id} + self.router.add_api_route( + path="/{evaluation_id}", + methods=["DELETE"], + endpoint=self.delete_evaluation, + response_model=SimpleEvaluationIdResponse, + response_model_exclude_none=True, + ) + + # POST /api/simple/evaluations/query + self.router.add_api_route( + path="/query", + methods=["POST"], + endpoint=self.query_evaluations, + response_model=SimpleEvaluationsResponse, + response_model_exclude_none=True, + ) + + # POST /api/simple/evaluations/{evaluation_id}/start + self.router.add_api_route( + path="/{evaluation_id}/start", + methods=["POST"], + endpoint=self.start_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # POST /api/simple/evaluations/{evaluation_id}/stop + self.router.add_api_route( + path="/{evaluation_id}/stop", + methods=["POST"], + endpoint=self.stop_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # POST /api/simpleEvaluations/{evaluation_id}/close + self.router.add_api_route( + path="/{evaluation_id}/close", + methods=["POST"], + endpoint=self.close_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # POST /api/simple/evaluations/{evaluation_id}/open + self.router.add_api_route( + path="/{evaluation_id}/open", + methods=["POST"], + endpoint=self.open_evaluation, + response_model=SimpleEvaluationResponse, + response_model_exclude_none=True, + ) + + # SIMPLE EVALUATIONS ------------------------------------------------------- + + # POST /api/simple/evaluations/ + @intercept_exceptions() + async def create_evaluation( + self, + request: Request, + *, + evaluation_create_request: SimpleEvaluationCreateRequest, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluation = await self.simple_evaluations_service.create( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluation=evaluation_create_request.evaluation, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response + + # GET /api/simple/evaluations/{evaluation_id} + @intercept_exceptions() + @suppress_exceptions(default=SimpleEvaluationResponse()) + async def fetch_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluation = await self.simple_evaluations_service.fetch( + project_id=UUID(request.state.project_id), + # + evaluation_id=evaluation_id, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response + + # PATCH /api/simple/evaluations/{evaluation_id} + @intercept_exceptions() + async def edit_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + # + evaluation_edit_request: SimpleEvaluationEditRequest, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(evaluation_id) != evaluation_edit_request.evaluation.id: + return SimpleEvaluationResponse() + + evaluation = await self.simple_evaluations_service.edit( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluation=evaluation_edit_request.evaluation, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response + + # DELETE /api/simple/evaluations/{evaluation_id} + @intercept_exceptions() + async def delete_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + ) -> SimpleEvaluationIdResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + deleted_evaluation_id = await self.simple_evaluations_service.delete( + project_id=UUID(request.state.project_id), + # + evaluation_id=evaluation_id, + ) + + response = SimpleEvaluationIdResponse( + count=1 if deleted_evaluation_id else 0, + evaluation_id=deleted_evaluation_id, + ) + + return response + + # POST /api/simple/evaluations/query + @intercept_exceptions() + @suppress_exceptions(default=SimpleEvaluationsResponse()) + async def query_evaluations( + self, + request: Request, + *, + evaluation_query_request: SimpleEvaluationQueryRequest, + ) -> SimpleEvaluationsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluations = await self.simple_evaluations_service.query( + project_id=UUID(request.state.project_id), + # + query=evaluation_query_request.evaluation, + ) + + response = SimpleEvaluationsResponse( + count=len(evaluations), + evaluations=evaluations, + ) + + return response + + # POST /api/simple/evaluations/{evaluation_id}/start + @intercept_exceptions() + async def start_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluation = await self.simple_evaluations_service.start( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluation_id=evaluation_id, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response + + # POST /api/simple/evaluations/{evaluation_id}/stop + @intercept_exceptions() + async def stop_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluation = await self.simple_evaluations_service.stop( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluation_id=evaluation_id, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response + + # POST /api/simple/evaluations/{evaluation_id}/close + @intercept_exceptions() + async def close_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluation = await self.simple_evaluations_service.close( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluation_id=evaluation_id, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response + + # POST /api/simple/evaluations/{evaluation_id}/open + @intercept_exceptions() + async def open_evaluation( + self, + request: Request, + *, + evaluation_id: UUID, + ) -> SimpleEvaluationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATION_RUNS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluation = await self.simple_evaluations_service.open( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluation_id=evaluation_id, + ) + + response = SimpleEvaluationResponse( + count=1 if evaluation else 0, + evaluation=evaluation, + ) + + return response diff --git a/api/oss/src/apis/fastapi/evaluations/utils.py b/api/oss/src/apis/fastapi/evaluations/utils.py index 4f0ece1fc5..c625678a8f 100644 --- a/api/oss/src/apis/fastapi/evaluations/utils.py +++ b/api/oss/src/apis/fastapi/evaluations/utils.py @@ -1,79 +1,110 @@ -from typing import Optional, List +from typing import Optional, List, Literal from uuid import UUID from datetime import datetime -from json import loads +from functools import wraps from fastapi import Query from oss.src.utils.logging import get_module_logger -from oss.src.core.shared.dtos import Windowing +from oss.src.core.shared.dtos import ( + Windowing, +) +from oss.src.core.evaluations.types import ( + EvaluationStatus, + EvaluationRunFlags, + EvaluationQueueFlags, + # + EvaluationClosedConflict, +) +from oss.src.apis.fastapi.shared.utils import ( + parse_metadata, +) from oss.src.apis.fastapi.evaluations.models import ( EvaluationRunQuery, EvaluationRunQueryRequest, + # EvaluationScenarioQuery, EvaluationScenarioQueryRequest, - EvaluationStepQuery, - EvaluationStepQueryRequest, - EvaluationMetricQuery, - EvaluationMetricQueryRequest, + # + EvaluationResultQuery, + EvaluationResultQueryRequest, + # + EvaluationMetricsQuery, + EvaluationMetricsQueryRequest, + # EvaluationQueueQuery, EvaluationQueueQueryRequest, + # + EvaluationClosedException, ) log = get_module_logger(__name__) +def handle_evaluation_closed_exception(): + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + try: + return await func(*args, **kwargs) + except EvaluationClosedConflict as e: + raise EvaluationClosedException( + message=e.message, + run_id=e.run_id, + scenario_id=e.scenario_id, + result_id=e.result_id, + metrics_id=e.metrics_id, + ) from e + except Exception as e: + raise e + + return wrapper + + return decorator + + async def parse_run_query_request( - # SCOPING ids: Optional[List[UUID]] = Query(None), - # FILTERING + # flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), - status: Optional[str] = Query(None), - statuses: Optional[List[str]] = Query(None), - # ARCHIVING - include_archived: bool = Query(False), + # + status: Optional[EvaluationStatus] = Query(None), + statuses: Optional[List[EvaluationStatus]] = Query(None), # WINDOWING + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), next: Optional[UUID] = Query(None), # pylint: disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), - order: Optional[str] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), ) -> EvaluationRunQueryRequest: - try: - flags = loads(flags) if flags else None - except Exception: # pylint: disable=broad-exception-caught - pass - - try: - tags = loads(tags) if tags else None - except Exception: # pylint: disable=broad-exception-caught - pass + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + __flags = None try: - meta = loads(meta) if meta else None - except Exception: # pylint: disable=broad-exception-caught + __flags = EvaluationRunFlags(**_flags) if _flags else None # type: ignore + except: pass run_query_request = EvaluationRunQueryRequest( run=EvaluationRunQuery( - flags=flags, - tags=tags, - meta=meta, + flags=__flags, + tags=_tags, + meta=_meta, # status=status, statuses=statuses, # ids=ids, ), - include_archived=include_archived, + # windowing=Windowing( + newest=newest, + oldest=oldest, next=next, - start=start, - stop=stop, limit=limit, order=order, ), @@ -83,48 +114,52 @@ async def parse_run_query_request( async def parse_scenario_query_request( - # SCOPING ids: Optional[List[UUID]] = Query(None), + # run_id: Optional[UUID] = Query(None), run_ids: Optional[List[UUID]] = Query(None), - # FILTERING + timestamp: Optional[datetime] = Query(None), + timestamps: Optional[List[datetime]] = Query(None), + interval: Optional[int] = Query(None), + intervals: Optional[List[int]] = Query(None), + # + flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), - status: Optional[str] = Query(None), - statuses: Optional[List[str]] = Query(None), + # + status: Optional[EvaluationStatus] = Query(None), + statuses: Optional[List[EvaluationStatus]] = Query(None), # WINDOWING + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), next: Optional[UUID] = Query(None), # pylint: disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), - order: Optional[str] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), ) -> EvaluationScenarioQueryRequest: - try: - tags = loads(tags) if tags else None - except Exception: # pylint: disable=broad-exception-caught - pass - - try: - meta = loads(meta) if meta else None - except Exception: # pylint: disable=broad-exception-caught - pass + _flags, _tags, _meta = parse_metadata(flags, tags, meta) scenario_query_request = EvaluationScenarioQueryRequest( scenario=EvaluationScenarioQuery( - tags=tags, - meta=meta, + flags=_flags, + tags=_tags, + meta=_meta, # status=status, statuses=statuses, # + interval=interval, + intervals=intervals, + timestamp=timestamp, + timestamps=timestamps, run_id=run_id, run_ids=run_ids, + # ids=ids, ), windowing=Windowing( + newest=newest, + oldest=oldest, next=next, - start=start, - stop=stop, limit=limit, order=order, ), @@ -133,163 +168,163 @@ async def parse_scenario_query_request( return scenario_query_request -async def parse_step_query_request( - # SCOPING +async def parse_result_query_request( ids: Optional[List[UUID]] = Query(None), + # run_id: Optional[UUID] = Query(None), run_ids: Optional[List[UUID]] = Query(None), scenario_id: Optional[UUID] = Query(None), scenario_ids: Optional[List[UUID]] = Query(None), - # FILTERING + step_key: Optional[str] = Query(None), + step_keys: Optional[List[str]] = Query(None), + repeat_idx: Optional[int] = Query(None), + repeat_idxs: Optional[List[int]] = Query(None), + timestamp: Optional[datetime] = Query(None), + timestamps: Optional[List[datetime]] = Query(None), + interval: Optional[int] = Query(None), + intervals: Optional[List[int]] = Query(None), + # + status: Optional[EvaluationStatus] = Query(None), + statuses: Optional[List[EvaluationStatus]] = Query(None), + # + flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), - timestamp: Optional[datetime] = Query(None), - status: Optional[str] = Query(None), - statuses: Optional[List[str]] = Query(None), - key: Optional[str] = Query(None), - keys: Optional[List[str]] = Query(None), - repeat_id: Optional[UUID] = Query(None), - repeat_ids: Optional[List[UUID]] = Query(None), - retry_id: Optional[UUID] = Query(None), - retry_ids: Optional[List[UUID]] = Query(None), # WINDOWING + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), next: Optional[UUID] = Query(None), # pylint: disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), - order: Optional[str] = Query(None), -) -> EvaluationStepQueryRequest: - try: - tags = loads(tags) if tags else None - except Exception: # pylint: disable=broad-exception-caught - pass - - try: - meta = loads(meta) if meta else None - except Exception: # pylint: disable=broad-exception-caught - pass + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> EvaluationResultQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) - step_query_request = EvaluationStepQueryRequest( - step=EvaluationStepQuery( - tags=tags, - meta=meta, + result_query_request = EvaluationResultQueryRequest( + result=EvaluationResultQuery( + flags=_flags, + tags=_tags, + meta=_meta, # - timestamp=timestamp, status=status, statuses=statuses, # - key=key, - keys=keys, - repeat_id=repeat_id, - repeat_ids=repeat_ids, - retry_id=retry_id, - retry_ids=retry_ids, - # + interval=interval, + intervals=intervals, + timestamp=timestamp, + timestamps=timestamps, + repeat_idx=repeat_idx, + repeat_idxs=repeat_idxs, + step_key=step_key, + step_keys=step_keys, scenario_id=scenario_id, scenario_ids=scenario_ids, run_id=run_id, run_ids=run_ids, + # ids=ids, ), windowing=Windowing( + newest=newest, + oldest=oldest, next=next, - start=start, - stop=stop, limit=limit, order=order, ), ) - return step_query_request + return result_query_request -async def parse_metric_query_request( - # SCOPING +async def parse_metrics_query_request( ids: Optional[List[UUID]] = Query(None), + # run_id: Optional[UUID] = Query(None), run_ids: Optional[List[UUID]] = Query(None), scenario_id: Optional[UUID] = Query(None), scenario_ids: Optional[List[UUID]] = Query(None), - # FILTERING + timestamp: Optional[datetime] = Query(None), + timestamps: Optional[List[datetime]] = Query(None), + interval: Optional[int] = Query(None), + intervals: Optional[List[int]] = Query(None), + # + flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), - status: Optional[str] = Query(None), - statuses: Optional[List[str]] = Query(None), + # + status: Optional[EvaluationStatus] = Query(None), + statuses: Optional[List[EvaluationStatus]] = Query(None), # WINDOWING + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), next: Optional[UUID] = Query(None), # pylint: disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), - order: Optional[str] = Query(None), -) -> EvaluationMetricQueryRequest: - try: - meta = loads(meta) if meta else None - except Exception: # pylint: disable=broad-exception-caught - pass + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> EvaluationMetricsQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) - metric_query_request = EvaluationMetricQueryRequest( - metric=EvaluationMetricQuery( - tags=tags, - meta=meta, + metrics_query_request = EvaluationMetricsQueryRequest( + metrics=EvaluationMetricsQuery( + flags=_flags, + tags=_tags, + meta=_meta, # status=status, statuses=statuses, # + interval=interval, + intervals=intervals, + timestamp=timestamp, + timestamps=timestamps, scenario_id=scenario_id, scenario_ids=scenario_ids, run_id=run_id, run_ids=run_ids, + # ids=ids, ), windowing=Windowing( + newest=newest, + oldest=oldest, next=next, - start=start, - stop=stop, limit=limit, order=order, ), ) - return metric_query_request + return metrics_query_request async def parse_queue_query_request( - # SCOPING ids: Optional[List[UUID]] = Query(None), + # run_id: Optional[UUID] = Query(None), run_ids: Optional[List[UUID]] = Query(None), user_id: Optional[UUID] = Query(None), user_ids: Optional[List[UUID]] = Query(None), - # FILTERING + # flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), # WINDOWING + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), next: Optional[UUID] = Query(None), # pylint: disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), ) -> EvaluationQueueQueryRequest: - try: - flags = loads(flags) if flags else None - except Exception: # pylint: disable=broad-exception-caught - pass + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + __flags = None try: - tags = loads(tags) if tags else None - except Exception: # pylint: disable=broad-exception-caught - pass - - try: - meta = loads(meta) if meta else None - except Exception: # pylint: disable=broad-exception-caught + __flags = EvaluationQueueFlags(**_flags) if _flags else None # type: ignore + except: pass queue_query_request = EvaluationQueueQueryRequest( queue=EvaluationQueueQuery( - flags=flags, - tags=tags, - meta=meta, + flags=__flags, + tags=_tags, + meta=_meta, # run_id=run_id, run_ids=run_ids, @@ -298,10 +333,11 @@ async def parse_queue_query_request( ids=ids, ), windowing=Windowing( + newest=newest, + oldest=oldest, next=next, - start=start, - stop=stop, limit=limit, + order=order, ), ) diff --git a/api/oss/src/apis/fastapi/evaluators/models.py b/api/oss/src/apis/fastapi/evaluators/models.py index 21e2f42bbe..dd31edf1f3 100644 --- a/api/oss/src/apis/fastapi/evaluators/models.py +++ b/api/oss/src/apis/fastapi/evaluators/models.py @@ -1,71 +1,160 @@ -from itertools import chain -from typing import Optional, List, Any, Dict +from typing import Optional, List from pydantic import BaseModel from oss.src.core.shared.dtos import ( - Identifier, - Slug, - Lifecycle, - Header, - Tags, - Meta, Windowing, Reference, ) - -from oss.src.core.workflows.dtos import ( - WorkflowRevisionData, - WorkflowFlags, +from oss.src.core.evaluators.dtos import ( + Evaluator, + EvaluatorCreate, + EvaluatorEdit, + EvaluatorQuery, + EvaluatorFork, + EvaluatorRevisionsLog, + # + EvaluatorVariant, + EvaluatorVariantCreate, + EvaluatorVariantEdit, + EvaluatorVariantQuery, + # + EvaluatorRevision, + EvaluatorRevisionCreate, + EvaluatorRevisionEdit, + EvaluatorRevisionQuery, + EvaluatorRevisionCommit, + # + SimpleEvaluator, + SimpleEvaluatorCreate, + SimpleEvaluatorEdit, + SimpleEvaluatorQuery, ) -class SimpleEvaluatorFlags(WorkflowFlags): - def __init__(self, **data): - data["is_evaluator"] = True +# EVALUATORS ------------------------------------------------------------------- + + +class EvaluatorCreateRequest(BaseModel): + evaluator: EvaluatorCreate + + +class EvaluatorEditRequest(BaseModel): + evaluator: EvaluatorEdit + + +class EvaluatorQueryRequest(BaseModel): + evaluator: Optional[EvaluatorQuery] = None + # + evaluator_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class EvaluatorForkRequest(BaseModel): + evaluator: EvaluatorFork + + +class EvaluatorResponse(BaseModel): + count: int = 0 + evaluator: Optional[Evaluator] = None + + +class EvaluatorsResponse(BaseModel): + count: int = 0 + evaluators: List[Evaluator] = [] - super().__init__(**data) +# EVALUATOR VARIANTS ----------------------------------------------------------- -class SimpleEvaluator( - Identifier, - Slug, - Lifecycle, - Header, -): - flags: Optional[SimpleEvaluatorFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - data: Optional[WorkflowRevisionData] = None +class EvaluatorVariantCreateRequest(BaseModel): + evaluator_variant: EvaluatorVariantCreate -class SimpleEvaluatorCreate( - Slug, - Header, -): - flags: Optional[SimpleEvaluatorFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluatorVariantEditRequest(BaseModel): + evaluator_variant: EvaluatorVariantEdit + + +class EvaluatorVariantQueryRequest(BaseModel): + evaluator_variant: Optional[EvaluatorVariantQuery] = None + # + evaluator_refs: Optional[List[Reference]] = None + evaluator_variant_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None - data: Optional[WorkflowRevisionData] = None +class EvaluatorVariantForkRequest(BaseModel): # TODO: FIX ME + source_evaluator_variant_ref: Reference + target_evaluator_ref: Reference + slug: Optional[str] = None + name: Optional[str] = None + description: Optional[str] = None -class SimpleEvaluatorEdit( - Identifier, - Header, -): - flags: Optional[SimpleEvaluatorFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - data: Optional[WorkflowRevisionData] = None +class EvaluatorRevisionsLogRequest(BaseModel): + evaluator: EvaluatorRevisionsLog + + +class EvaluatorVariantResponse(BaseModel): + count: int = 0 + evaluator_variant: Optional[EvaluatorVariant] = None + + +class EvaluatorVariantsResponse(BaseModel): + count: int = 0 + evaluator_variants: List[EvaluatorVariant] = [] + + +# EVALUATOR REVISIONS ---------------------------------------------------------- + + +class EvaluatorRevisionCreateRequest(BaseModel): + evaluator_revision: EvaluatorRevisionCreate + + +class EvaluatorRevisionEditRequest(BaseModel): + evaluator_revision: EvaluatorRevisionEdit + + +class EvaluatorRevisionQueryRequest(BaseModel): + evaluator_revision: Optional[EvaluatorRevisionQuery] = None + # + evaluator_refs: Optional[List[Reference]] = None + evaluator_variant_refs: Optional[List[Reference]] = None + evaluator_revision_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class EvaluatorRevisionCommitRequest(BaseModel): + evaluator_revision_commit: EvaluatorRevisionCommit + + +class EvaluatorRevisionRetrieveRequest(BaseModel): + evaluator_ref: Optional[Reference] = None + evaluator_variant_ref: Optional[Reference] = None + evaluator_revision_ref: Optional[Reference] = None + + +class EvaluatorRevisionResponse(BaseModel): + count: int = 0 + evaluator_revision: Optional[EvaluatorRevision] = None + + +class EvaluatorRevisionsResponse(BaseModel): + count: int = 0 + evaluator_revisions: List[EvaluatorRevision] = [] -class SimpleEvaluatorQuery(BaseModel): - flags: Optional[SimpleEvaluatorFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +# SIMPLE EVALUATORS ------------------------------------------------------------ class SimpleEvaluatorCreateRequest(BaseModel): diff --git a/api/oss/src/apis/fastapi/evaluators/router.py b/api/oss/src/apis/fastapi/evaluators/router.py index 98bf914dd3..f4d1764454 100644 --- a/api/oss/src/apis/fastapi/evaluators/router.py +++ b/api/oss/src/apis/fastapi/evaluators/router.py @@ -1,78 +1,1042 @@ from typing import Optional, List -from uuid import uuid4, UUID +from uuid import UUID -from fastapi import APIRouter, Request, status, HTTPException - -from oss.src.utils.helpers import get_slug_from_name_and_id - -from oss.src.services.db_manager import fetch_evaluator_config -from oss.src.models.db_models import EvaluatorConfigDB +from fastapi import APIRouter, Request, status, Depends from oss.src.utils.common import is_ee from oss.src.utils.logging import get_module_logger from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions from oss.src.utils.caching import get_cache, set_cache, invalidate_cache -from oss.src.core.shared.dtos import Reference -from oss.src.core.workflows.dtos import WorkflowFlags -from oss.src.core.workflows.service import WorkflowsService - -from oss.src.core.workflows.dtos import ( - Workflow, - WorkflowCreate, - WorkflowEdit, - WorkflowQuery, +from oss.src.core.shared.dtos import ( + Reference, +) +from oss.src.core.evaluators.dtos import ( + EvaluatorFlags, # - WorkflowVariant, - WorkflowVariantCreate, - WorkflowVariantEdit, - WorkflowVariantQuery, + EvaluatorQuery, # - WorkflowRevision, - WorkflowRevisionCreate, - WorkflowRevisionQuery, - WorkflowRevisionCommit, + EvaluatorRevision, # - WorkflowFlags, - WorkflowRevisionData, + SimpleEvaluatorData, + SimpleEvaluatorQuery, + SimpleEvaluator, + SimpleEvaluatorFlags, +) +from oss.src.core.evaluators.service import ( + SimpleEvaluatorsService, + EvaluatorsService, ) from oss.src.apis.fastapi.evaluators.models import ( - SimpleEvaluator, - SimpleEvaluatorCreate, - SimpleEvaluatorEdit, - SimpleEvaluatorQuery, + EvaluatorCreateRequest, + EvaluatorEditRequest, + EvaluatorQueryRequest, + EvaluatorForkRequest, + EvaluatorRevisionsLogRequest, + EvaluatorResponse, + EvaluatorsResponse, + # + EvaluatorVariantCreateRequest, + EvaluatorVariantEditRequest, + EvaluatorVariantQueryRequest, + EvaluatorVariantResponse, + EvaluatorVariantsResponse, + # + EvaluatorRevisionCreateRequest, + EvaluatorRevisionEditRequest, + EvaluatorRevisionQueryRequest, + EvaluatorRevisionCommitRequest, + EvaluatorRevisionRetrieveRequest, + EvaluatorRevisionResponse, + EvaluatorRevisionsResponse, # SimpleEvaluatorCreateRequest, SimpleEvaluatorEditRequest, SimpleEvaluatorQueryRequest, - # SimpleEvaluatorResponse, SimpleEvaluatorsResponse, - # - SimpleEvaluatorFlags, ) +from oss.src.apis.fastapi.evaluators.utils import ( + parse_evaluator_query_request_from_params, + parse_evaluator_query_request_from_body, + merge_evaluator_query_requests, + parse_evaluator_variant_query_request_from_params, + parse_evaluator_variant_query_request_from_body, + merge_evaluator_variant_query_requests, + parse_evaluator_revision_query_request_from_params, + parse_evaluator_revision_query_request_from_body, + merge_evaluator_revision_query_requests, + parse_evaluator_revision_retrieve_request_from_params, + parse_evaluator_revision_retrieve_request_from_body, +) + +if is_ee(): + from ee.src.models.shared_models import Permission + from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + + +log = get_module_logger(__name__) + + +class EvaluatorsRouter: + def __init__( + self, + *, + evaluators_service: EvaluatorsService, + ): + self.evaluators_service = evaluators_service + + self.router = APIRouter() + + # EVALUATORS ----------------------------------------------------------- + + self.router.add_api_route( + "/", + self.create_evaluator, + methods=["POST"], + operation_id="create_evaluator", + status_code=status.HTTP_200_OK, + response_model=EvaluatorResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{evaluator_id}", + self.fetch_evaluator, + methods=["GET"], + operation_id="fetch_evaluator", + status_code=status.HTTP_200_OK, + response_model=EvaluatorResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{evaluator_id}", + self.edit_evaluator, + methods=["PUT"], + operation_id="edit_evaluator", + status_code=status.HTTP_200_OK, + response_model=EvaluatorResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{evaluator_id}/archive", + self.archive_evaluator, + methods=["POST"], + operation_id="archive_evaluator", + status_code=status.HTTP_200_OK, + response_model=EvaluatorResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{evaluator_id}/unarchive", + self.unarchive_evaluator, + methods=["POST"], + operation_id="unarchive_evaluator", + status_code=status.HTTP_200_OK, + response_model=EvaluatorResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/query", + self.query_evaluators, + methods=["POST"], + operation_id="query_evaluators", + status_code=status.HTTP_200_OK, + response_model=EvaluatorsResponse, + response_model_exclude_none=True, + ) + + # EVALUATOR VARIANTS --------------------------------------------------- + + self.router.add_api_route( + "/variants/", + self.create_evaluator_variant, + methods=["POST"], + operation_id="create_evaluator_variant", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{evaluator_variant_id}", + self.fetch_evaluator_variant, + methods=["GET"], + operation_id="fetch_evaluator_variant", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{evaluator_variant_id}", + self.edit_evaluator_variant, + methods=["PUT"], + operation_id="edit_evaluator_variant", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{evaluator_variant_id}/archive", + self.archive_evaluator_variant, + methods=["POST"], + operation_id="archive_evaluator_variant", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{evaluator_variant_id}/unarchive", + self.unarchive_evaluator_variant, + methods=["POST"], + operation_id="unarchive_evaluator_variant", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/query", + self.query_evaluator_variants, + methods=["POST"], + operation_id="query_evaluator_variants", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantsResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/fork", + self.fork_evaluator_variant, + methods=["POST"], + operation_id="fork_evaluator_variant", + status_code=status.HTTP_200_OK, + response_model=EvaluatorVariantResponse, + response_model_exclude_none=True, + ) + + # EVALUATOR REVISIONS -------------------------------------------------- + + self.router.add_api_route( + "/revisions/retrieve", + self.retrieve_evaluator_revision, + methods=["POST"], + operation_id="retrieve_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/", + self.create_evaluator_revision, + methods=["POST"], + operation_id="create_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{evaluator_revision_id}", + self.fetch_evaluator_revision, + methods=["GET"], + operation_id="fetch_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{evaluator_revision_id}", + self.edit_evaluator_revision, + methods=["PUT"], + operation_id="edit_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{evaluator_revision_id}/archive", + self.archive_evaluator_revision, + methods=["POST"], + operation_id="archive_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{evaluator_revision_id}/unarchive", + self.unarchive_evaluator_revision, + methods=["POST"], + operation_id="unarchive_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/query", + self.query_evaluator_revisions, + methods=["POST"], + operation_id="query_evaluator_revisions", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionsResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/commit", + self.commit_evaluator_revision, + methods=["POST"], + operation_id="commit_evaluator_revision", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/log", + self.log_evaluator_revisions, + methods=["POST"], + operation_id="log_evaluator_revisions", + status_code=status.HTTP_200_OK, + response_model=EvaluatorRevisionsResponse, + response_model_exclude_none=True, + ) + + # EVALUATORS --------------------------------------------------------------- + + @intercept_exceptions() + async def create_evaluator( + self, + request: Request, + *, + evaluator_id: Optional[UUID] = None, + # + evaluator_create_request: EvaluatorCreateRequest, + ) -> EvaluatorResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator = await self.evaluators_service.create_evaluator( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_id=evaluator_id, + # + evaluator_create=evaluator_create_request.evaluator, + ) + + return EvaluatorResponse( + count=1 if evaluator else 0, + evaluator=evaluator, + ) + + @intercept_exceptions() + @suppress_exceptions(default=EvaluatorResponse()) + async def fetch_evaluator( + self, + request: Request, + *, + evaluator_id: UUID, + ) -> EvaluatorResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator = await self.evaluators_service.fetch_evaluator( + project_id=UUID(request.state.project_id), + # + evaluator_ref=Reference(id=evaluator_id), + ) + + return EvaluatorResponse( + count=1 if evaluator else 0, + evaluator=evaluator, + ) + + @intercept_exceptions() + async def edit_evaluator( + self, + request: Request, + *, + evaluator_id: UUID, + # + evaluator_edit_request: EvaluatorEditRequest, + ) -> EvaluatorResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(evaluator_id) != str(evaluator_edit_request.evaluator.id): + return EvaluatorResponse() + + evaluator = await self.evaluators_service.edit_evaluator( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_edit=evaluator_edit_request.evaluator, + ) + + return EvaluatorResponse( + count=1 if evaluator else 0, + evaluator=evaluator, + ) + + @intercept_exceptions() + async def archive_evaluator( + self, + request: Request, + *, + evaluator_id: UUID, + ) -> EvaluatorResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator = await self.evaluators_service.archive_evaluator( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_id=evaluator_id, + ) + + return EvaluatorResponse( + count=1 if evaluator else 0, + evaluator=evaluator, + ) + + @intercept_exceptions() + async def unarchive_evaluator( + self, + request: Request, + *, + evaluator_id: UUID, + ) -> EvaluatorResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator = await self.evaluators_service.unarchive_evaluator( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_id=evaluator_id, + ) + + return EvaluatorResponse( + count=1 if evaluator else 0, + evaluator=evaluator, + ) + + @intercept_exceptions() + @suppress_exceptions(default=EvaluatorsResponse()) + async def query_evaluators( + self, + request: Request, + *, + evaluator_query_request: EvaluatorQueryRequest, + ) -> EvaluatorsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluators = await self.evaluators_service.query_evaluators( + project_id=UUID(request.state.project_id), + # + evaluator_query=evaluator_query_request.evaluator, + # + evaluator_refs=evaluator_query_request.evaluator_refs, + # + include_archived=evaluator_query_request.include_archived, + # + windowing=evaluator_query_request.windowing, + ) + + evaluators_response = EvaluatorsResponse( + count=len(evaluators), + evaluators=evaluators, + ) + + return evaluators_response + + # EVALUATOR VARIANTS ------------------------------------------------------- + + @intercept_exceptions() + async def create_evaluator_variant( + self, + request: Request, + *, + evaluator_variant_create_request: EvaluatorVariantCreateRequest, + ) -> EvaluatorVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_variant = await self.evaluators_service.create_evaluator_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_variant_create=evaluator_variant_create_request.evaluator_variant, + ) + + evaluator_variant_response = EvaluatorVariantResponse( + count=1 if evaluator_variant else 0, + evaluator_variant=evaluator_variant, + ) + + return evaluator_variant_response + + @intercept_exceptions() + async def fetch_evaluator_variant( + self, + request: Request, + *, + evaluator_variant_id: UUID, + ) -> EvaluatorVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_variant = await self.evaluators_service.fetch_evaluator_variant( + project_id=UUID(request.state.project_id), + # + evaluator_variant_ref=Reference(id=evaluator_variant_id), + ) + + evaluator_variant_response = EvaluatorVariantResponse( + count=1 if evaluator_variant else 0, + evaluator_variant=evaluator_variant, + ) + + return evaluator_variant_response + + @intercept_exceptions() + async def edit_evaluator_variant( + self, + request: Request, + *, + evaluator_variant_id: UUID, + # + evaluator_variant_edit_request: EvaluatorVariantEditRequest, + ) -> EvaluatorVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(evaluator_variant_id) != str( + evaluator_variant_edit_request.evaluator_variant.id + ): + return EvaluatorVariantResponse() + + evaluator_variant = await self.evaluators_service.edit_evaluator_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_variant_edit=evaluator_variant_edit_request.evaluator_variant, + ) + + evaluator_variant_response = EvaluatorVariantResponse( + count=1 if evaluator_variant else 0, + evaluator_variant=evaluator_variant, + ) + + return evaluator_variant_response + + @intercept_exceptions() + async def archive_evaluator_variant( + self, + request: Request, + *, + evaluator_variant_id: UUID, + ) -> EvaluatorVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_variant = await self.evaluators_service.archive_evaluator_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_variant_id=evaluator_variant_id, + ) + + evaluator_variant_response = EvaluatorVariantResponse( + count=1 if evaluator_variant else 0, + evaluator_variant=evaluator_variant, + ) + + return evaluator_variant_response + + @intercept_exceptions() + async def unarchive_evaluator_variant( + self, + request: Request, + *, + evaluator_variant_id: UUID, + ) -> EvaluatorVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_variant = await self.evaluators_service.unarchive_evaluator_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_variant_id=evaluator_variant_id, + ) + + evaluator_variant_response = EvaluatorVariantResponse( + count=1 if evaluator_variant else 0, + evaluator_variant=evaluator_variant, + ) + + return evaluator_variant_response + + @intercept_exceptions() + async def query_evaluator_variants( + self, + request: Request, + *, + query_request_params: Optional[EvaluatorVariantQueryRequest] = Depends( + parse_evaluator_variant_query_request_from_params + ), + ) -> EvaluatorVariantsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + body_json = None + query_request_body = None + + try: + body_json = await request.json() + + if body_json: + query_request_body = parse_evaluator_variant_query_request_from_body( + **body_json + ) + + except: + pass + + workflow_variant_query_request = merge_evaluator_variant_query_requests( + query_request_params, + query_request_body, + ) + + evaluator_variants = await self.evaluators_service.query_evaluator_variants( + project_id=UUID(request.state.project_id), + # + evaluator_variant_query=workflow_variant_query_request.evaluator_variant, + # + evaluator_refs=workflow_variant_query_request.evaluator_refs, + evaluator_variant_refs=workflow_variant_query_request.evaluator_variant_refs, + # + include_archived=workflow_variant_query_request.include_archived, + # + windowing=workflow_variant_query_request.windowing, + ) + + evaluator_variants_response = EvaluatorVariantsResponse( + count=len(evaluator_variants), + evaluator_variants=evaluator_variants, + ) + + return evaluator_variants_response + + @intercept_exceptions() # TODO: FIX ME + async def fork_evaluator_variant( + self, + request: Request, + *, + evaluator_variant_id: UUID, + # + evaluator_variant_fork_request: EvaluatorForkRequest, + ): + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_variant = await self.evaluators_service.fork_evaluator_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_fork=evaluator_variant_fork_request.evaluator, + ) + + evaluator_variant_response = EvaluatorVariantResponse( + count=1 if evaluator_variant else 0, + evaluator_variant=evaluator_variant, + ) + + return evaluator_variant_response + + # EVALUATOR REVISIONS ------------------------------------------------------ + + @intercept_exceptions() + async def retrieve_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_retrieve_request: EvaluatorRevisionRetrieveRequest, + ) -> EvaluatorRevisionResponse: + if not await check_action_access( # type: ignore + project_id=request.state.project_id, + user_uid=request.state.user_id, + # + permission=Permission.VIEW_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + cache_key = { + "artifact_ref": evaluator_revision_retrieve_request.evaluator_ref, # type: ignore + "variant_ref": evaluator_revision_retrieve_request.evaluator_variant_ref, # type: ignore + "revision_ref": evaluator_revision_retrieve_request.evaluator_revision_ref, # type: ignore + } + + evaluator_revision = await get_cache( + namespace="evaluators:retrieve", + project_id=request.state.project_id, + user_id=request.state.user_id, + key=cache_key, + model=EvaluatorRevision, + ) + + if not evaluator_revision: + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=UUID(request.state.project_id), + # + evaluator_ref=evaluator_revision_retrieve_request.evaluator_ref, # type: ignore + evaluator_variant_ref=evaluator_revision_retrieve_request.evaluator_variant_ref, # type: ignore + evaluator_revision_ref=evaluator_revision_retrieve_request.evaluator_revision_ref, # type: ignore + ) + + await set_cache( + namespace="evaluators:retrieve", + project_id=request.state.project_id, + user_id=request.state.user_id, + key=cache_key, + value=evaluator_revision, + ) + + evaluator_revision_response = EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + return evaluator_revision_response + + @intercept_exceptions() + async def create_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_create_request: EvaluatorRevisionCreateRequest, + ) -> EvaluatorRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_revision = await self.evaluators_service.create_evaluator_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_revision_create=evaluator_revision_create_request.evaluator_revision, + ) + + return EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + @intercept_exceptions() + @suppress_exceptions(default=EvaluatorRevisionResponse()) + async def fetch_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_id: UUID, + ) -> EvaluatorRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=UUID(request.state.project_id), + # + evaluator_revision_ref=Reference(id=evaluator_revision_id), + ) + + return EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + @intercept_exceptions() + async def edit_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_id: UUID, + # + evaluator_revision_edit_request: EvaluatorRevisionEditRequest, + ) -> EvaluatorRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(evaluator_revision_id) != str( + evaluator_revision_edit_request.evaluator_revision.id + ): + return EvaluatorRevisionResponse() + + evaluator_revision = await self.evaluators_service.edit_evaluator_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_revision_edit=evaluator_revision_edit_request.evaluator_revision, + ) + + return EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + @intercept_exceptions() + async def archive_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_id: UUID, + ) -> EvaluatorRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_revision = await self.evaluators_service.archive_evaluator_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_revision_id=evaluator_revision_id, + ) + + return EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + @intercept_exceptions() + async def unarchive_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_id: UUID, + ) -> EvaluatorRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_revision = await self.evaluators_service.unarchive_evaluator_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_revision_id=evaluator_revision_id, + ) + + return EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + @intercept_exceptions() + @suppress_exceptions(default=EvaluatorRevisionsResponse()) + async def query_evaluator_revisions( + self, + request: Request, + *, + evaluator_revision_query_request: EvaluatorRevisionQueryRequest, + ) -> EvaluatorRevisionsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_revisions = await self.evaluators_service.query_evaluator_revisions( + project_id=UUID(request.state.project_id), + # + evaluator_revision_query=evaluator_revision_query_request.evaluator_revision, + # + evaluator_refs=evaluator_revision_query_request.evaluator_refs, + evaluator_variant_refs=evaluator_revision_query_request.evaluator_variant_refs, + evaluator_revision_refs=evaluator_revision_query_request.evaluator_revision_refs, + # + include_archived=evaluator_revision_query_request.include_archived, + # + windowing=evaluator_revision_query_request.windowing, + ) + + return EvaluatorRevisionsResponse( + count=len(evaluator_revisions), + evaluator_revisions=evaluator_revisions, + ) + + @intercept_exceptions() + async def commit_evaluator_revision( + self, + request: Request, + *, + evaluator_revision_commit_request: EvaluatorRevisionCommitRequest, + ) -> EvaluatorRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + evaluator_revision = await self.evaluators_service.commit_evaluator_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + evaluator_revision_commit=evaluator_revision_commit_request.evaluator_revision_commit, + ) + + return EvaluatorRevisionResponse( + count=1 if evaluator_revision else 0, + evaluator_revision=evaluator_revision, + ) + + @intercept_exceptions() + async def log_evaluator_revisions( + self, + request: Request, + *, + evaluator_revisions_log_request: EvaluatorRevisionsLogRequest, + ): + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore -if is_ee(): - from ee.src.models.shared_models import Permission - from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + evaluator_revisions = await self.evaluators_service.log_evaluator_revisions( + project_id=UUID(request.state.project_id), + # + evaluator_revisions_log=evaluator_revisions_log_request.evaluator, + ) + revisions_response = EvaluatorRevisionsResponse( + count=len(evaluator_revisions), + evaluator_revisions=evaluator_revisions, + ) -log = get_module_logger(__name__) + return revisions_response class SimpleEvaluatorsRouter: - VERSION = "1.0.0" - def __init__( self, *, - workflows_service: WorkflowsService, + simple_evaluators_service: SimpleEvaluatorsService, ): - self.workflows_service = workflows_service + self.simple_evaluators_service = simple_evaluators_service + self.evaluators_service = self.simple_evaluators_service.evaluators_service self.router = APIRouter() + # SIMPLE EVALUATORS ---------------------------------------------------- + self.router.add_api_route( "/", self.create_simple_evaluator, @@ -123,16 +1087,6 @@ def __init__( response_model_exclude_none=True, ) - self.router.add_api_route( - "/", - self.list_simple_evaluators, - methods=["GET"], - operation_id="list_simple_evaluators", - status_code=status.HTTP_200_OK, - response_model=SimpleEvaluatorsResponse, - response_model_exclude_none=True, - ) - self.router.add_api_route( "/query", self.query_simple_evaluators, @@ -153,188 +1107,36 @@ def __init__( response_model_exclude_none=True, ) + # SIMPLE EVALUATORS -------------------------------------------------------- + @intercept_exceptions() async def create_simple_evaluator( self, - *, request: Request, - simple_evaluator_create_request: SimpleEvaluatorCreateRequest, + *, evaluator_id: Optional[UUID] = None, + # + simple_evaluator_create_request: SimpleEvaluatorCreateRequest, ) -> SimpleEvaluatorResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - simple_evaluator_flags = ( - SimpleEvaluatorFlags( - **( - simple_evaluator_create_request.evaluator.flags.model_dump( - mode="json" - ) - ) - ) - if simple_evaluator_create_request.evaluator.flags - else SimpleEvaluatorFlags( - is_custom=False, - is_human=False, - is_evaluator=True, - ) - ) - - workflow_flags = WorkflowFlags( - **simple_evaluator_flags.model_dump(mode="json"), - ) - - _workflow_create = WorkflowCreate( - slug=simple_evaluator_create_request.evaluator.slug, - # - name=simple_evaluator_create_request.evaluator.name, - description=simple_evaluator_create_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_create_request.evaluator.tags, - meta=simple_evaluator_create_request.evaluator.meta, - ) - - workflow: Optional[Workflow] = await self.workflows_service.create_workflow( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - workflow_create=_workflow_create, - # - workflow_id=evaluator_id, - ) - - if workflow is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple evaluator. Please try again or contact support.", - ) - - workflow_variant_slug = uuid4().hex - - _workflow_variant_create = WorkflowVariantCreate( - slug=workflow_variant_slug, - # - name=simple_evaluator_create_request.evaluator.name, - description=simple_evaluator_create_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_create_request.evaluator.tags, - meta=simple_evaluator_create_request.evaluator.meta, - # - workflow_id=workflow.id, # type: ignore - ) - - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.create_workflow_variant( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - workflow_variant_create=_workflow_variant_create, - ) - - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple evaluator. Please try again or contact support.", - ) - - workflow_revision_slug = uuid4().hex - - _workflow_revision_create = WorkflowRevisionCreate( - slug=workflow_revision_slug, - # - name=simple_evaluator_create_request.evaluator.name, - description=simple_evaluator_create_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_create_request.evaluator.tags, - meta=simple_evaluator_create_request.evaluator.meta, - # - workflow_id=workflow.id, - workflow_variant_id=workflow_variant.id, - ) - - workflow_revision: Optional[ - WorkflowRevision - ] = await self.workflows_service.create_workflow_revision( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - workflow_revision_create=_workflow_revision_create, - ) - - if workflow_revision is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple evaluator. Please try again or contact support.", - ) - - workflow_revision_slug = uuid4().hex - - _workflow_revision_commit = WorkflowRevisionCommit( - slug=workflow_revision_slug, - # - name=simple_evaluator_create_request.evaluator.name, - description=simple_evaluator_create_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_create_request.evaluator.tags, - meta=simple_evaluator_create_request.evaluator.meta, - # - # message= - # - data=simple_evaluator_create_request.evaluator.data, - # - workflow_id=workflow.id, - workflow_variant_id=workflow_variant.id, - ) + raise FORBIDDEN_EXCEPTION # type: ignore - workflow_revision: Optional[ - WorkflowRevision - ] = await self.workflows_service.commit_workflow_revision( + simple_evaluator = await self.simple_evaluators_service.create( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - workflow_revision_commit=_workflow_revision_commit, - ) - - if workflow_revision is None: - # do something - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple evaluator. Please try again or contact support.", - ) - - simple_evaluator = SimpleEvaluator( - id=workflow.id, - slug=workflow.slug, - # - created_at=workflow.created_at, - updated_at=workflow.updated_at, - deleted_at=workflow.deleted_at, - created_by_id=workflow.created_by_id, - updated_by_id=workflow.updated_by_id, - deleted_by_id=workflow.deleted_by_id, - # - name=workflow.name, - description=workflow.description, - # - flags=simple_evaluator_flags, - tags=workflow.tags, - meta=workflow.meta, + evaluator_id=evaluator_id, # - data=workflow_revision.data, + simple_evaluator_create=simple_evaluator_create_request.evaluator, ) simple_evaluator_response = SimpleEvaluatorResponse( - count=1, + count=1 if simple_evaluator else 0, evaluator=simple_evaluator, ) @@ -344,97 +1146,26 @@ async def create_simple_evaluator( @suppress_exceptions(default=SimpleEvaluatorResponse()) async def fetch_simple_evaluator( self, - *, request: Request, + *, evaluator_id: UUID, ) -> SimpleEvaluatorResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATORS, + permission=Permission.VIEW_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - workflow_ref = Reference( - id=evaluator_id, - ) - - workflow: Optional[Workflow] = await self.workflows_service.fetch_workflow( - project_id=UUID(request.state.project_id), - # - workflow_ref=workflow_ref, - ) - - if workflow is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator not found. Please check the ID and try again.", - ) - - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.fetch_workflow_variant( - project_id=UUID(request.state.project_id), - # - workflow_ref=workflow_ref, - ) - - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator variant not found. Please check the ID and try again.", - ) - - workflow_variant_ref = Reference( - id=workflow_variant.id, - ) + raise FORBIDDEN_EXCEPTION # type: ignore - workflow_revision: Optional[ - WorkflowRevision - ] = await self.workflows_service.fetch_workflow_revision( + simple_evaluator = await self.simple_evaluators_service.fetch( project_id=UUID(request.state.project_id), # - workflow_variant_ref=workflow_variant_ref, - ) - - if workflow_revision is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator revision not found. Please check the ID and try again.", - ) - - simple_evaluator_flags = SimpleEvaluatorFlags( - **( - workflow.flags.model_dump(mode="json") - if workflow.flags - else SimpleEvaluatorFlags() - ) - ) - - simple_evaluator = SimpleEvaluator( - id=workflow.id, - slug=workflow.slug, - # - created_at=workflow.created_at, - updated_at=workflow.updated_at, - deleted_at=workflow.deleted_at, - created_by_id=workflow.created_by_id, - updated_by_id=workflow.updated_by_id, - deleted_by_id=workflow.deleted_by_id, - # - name=workflow.name, - description=workflow.description, - # - flags=simple_evaluator_flags, - tags=workflow.tags, - meta=workflow.meta, - # - data=workflow_revision.data, + evaluator_id=evaluator_id, ) simple_evaluator_response = SimpleEvaluatorResponse( - count=1, + count=1 if simple_evaluator else 0, evaluator=simple_evaluator, ) @@ -443,296 +1174,115 @@ async def fetch_simple_evaluator( @intercept_exceptions() async def edit_simple_evaluator( self, - *, request: Request, + *, evaluator_id: UUID, + # simple_evaluator_edit_request: SimpleEvaluatorEditRequest, ) -> SimpleEvaluatorResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - simple_evaluator_flags = ( - SimpleEvaluatorFlags( - **( - simple_evaluator_edit_request.evaluator.flags.model_dump( - mode="json" - ) - ) - ) - if simple_evaluator_edit_request.evaluator.flags - else SimpleEvaluatorFlags() - ) - - workflow_flags = WorkflowFlags(**simple_evaluator_flags.model_dump()) + raise FORBIDDEN_EXCEPTION # type: ignore if str(evaluator_id) != str(simple_evaluator_edit_request.evaluator.id): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"ID mismatch between path params and body params: {evaluator_id} != {simple_evaluator_edit_request.evaluator.id}", - ) - - workflow_ref = Reference( - id=simple_evaluator_edit_request.evaluator.id, - ) - - workflow: Optional[Workflow] = await self.workflows_service.fetch_workflow( - project_id=UUID(request.state.project_id), - # - workflow_ref=workflow_ref, - ) - - if workflow is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator not found. Please check the ID and try again.", - ) - - _workflow_edit = WorkflowEdit( - id=workflow.id, - # - name=simple_evaluator_edit_request.evaluator.name, - description=simple_evaluator_edit_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_edit_request.evaluator.tags, - meta=simple_evaluator_edit_request.evaluator.meta, - ) - - workflow: Optional[Workflow] = await self.workflows_service.edit_workflow( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - workflow_edit=_workflow_edit, - ) - - if workflow is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to edit simple evaluator. Please try again or contact support.", - ) - - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.fetch_workflow_variant( - project_id=UUID(request.state.project_id), - # - workflow_ref=workflow_ref, - ) - - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator variant not found. Please check the ID and try again.", - ) - - _workflow_variant_edit = WorkflowVariantEdit( - id=workflow_variant.id, - # - name=simple_evaluator_edit_request.evaluator.name, - description=simple_evaluator_edit_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_edit_request.evaluator.tags, - meta=simple_evaluator_edit_request.evaluator.meta, - ) - - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.edit_workflow_variant( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - workflow_variant_edit=_workflow_variant_edit, - ) - - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to edit simple evaluator variant. Please try again or contact support.", - ) - - workflow_variant_ref = Reference( - id=workflow_variant.id, - ) + return SimpleEvaluatorResponse() - workflow_revision: Optional[ - WorkflowRevision - ] = await self.workflows_service.fetch_workflow_revision( - project_id=UUID(request.state.project_id), - # - workflow_variant_ref=workflow_variant_ref, - ) - - if workflow_revision is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator revision not found. Please check the ID and try again.", - ) - - workflow_revision_slug = uuid4().hex - - _workflow_revision_commit = WorkflowRevisionCommit( - slug=workflow_revision_slug, - # - name=simple_evaluator_edit_request.evaluator.name, - description=simple_evaluator_edit_request.evaluator.description, - # - flags=workflow_flags, - tags=simple_evaluator_edit_request.evaluator.tags, - meta=simple_evaluator_edit_request.evaluator.meta, - # - data=simple_evaluator_edit_request.evaluator.data, - # - workflow_id=workflow.id, - workflow_variant_id=workflow_variant.id, - ) - - workflow_revision: Optional[ - WorkflowRevision - ] = await self.workflows_service.commit_workflow_revision( + simple_evaluator = await self.simple_evaluators_service.edit( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - workflow_revision_commit=_workflow_revision_commit, - ) - - if workflow_revision is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to edit simple evaluator revision. Please try again or contact support.", - ) - - simple_evaluator = SimpleEvaluator( - id=workflow.id, - slug=workflow.slug, - # - created_at=workflow.created_at, - updated_at=workflow.updated_at, - deleted_at=workflow.deleted_at, - created_by_id=workflow.created_by_id, - updated_by_id=workflow.updated_by_id, - deleted_by_id=workflow.deleted_by_id, - # - name=workflow.name, - description=workflow.description, - # - flags=simple_evaluator_flags, - tags=workflow.tags, - meta=workflow.meta, - # - data=workflow_revision.data, + simple_evaluator_edit=simple_evaluator_edit_request.evaluator, ) simple_evaluator_response = SimpleEvaluatorResponse( - count=1, + count=1 if simple_evaluator else 0, evaluator=simple_evaluator, ) return simple_evaluator_response @intercept_exceptions() - async def archive_simple_evaluator( + async def archive_simple_evaluator( # TODO: FIX ME self, - *, request: Request, + *, evaluator_id: UUID, ) -> SimpleEvaluatorResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - workflow_ref = Reference( - id=evaluator_id, - ) + raise FORBIDDEN_EXCEPTION # type: ignore - workflow: Optional[Workflow] = await self.workflows_service.fetch_workflow( + evaluator = await self.evaluators_service.fetch_evaluator( project_id=UUID(request.state.project_id), # - workflow_ref=workflow_ref, + evaluator_ref=Reference(id=evaluator_id), ) - if workflow is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator not found. Please check the ID and try again.", - ) + if not evaluator or not evaluator.id: + return SimpleEvaluatorResponse() - workflow: Optional[Workflow] = await self.workflows_service.archive_workflow( + evaluator = await self.evaluators_service.archive_evaluator( # type: ignore project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - workflow_id=evaluator_id, + evaluator_id=evaluator.id, ) - if workflow is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to archive simple evaluator. Please try again or contact support.", - ) + if not evaluator or not evaluator.id: + return SimpleEvaluatorResponse() - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.fetch_workflow_variant( + evaluator_variant = await self.evaluators_service.fetch_evaluator_variant( project_id=UUID(request.state.project_id), # - workflow_ref=workflow_ref, + evaluator_ref=Reference(id=evaluator.id), ) - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to fetch simple evaluator variant. Please try again or contact support.", - ) + if evaluator_variant is None: + return SimpleEvaluatorResponse() - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.archive_workflow_variant( + evaluator_variant = await self.evaluators_service.archive_evaluator_variant( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - workflow_variant_id=workflow_variant.id, + evaluator_variant_id=evaluator_variant.id, # type: ignore ) - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to archive simple evaluator. Please try again or contact support.", - ) + if evaluator_variant is None: + return SimpleEvaluatorResponse() simple_evaluator_flags = ( SimpleEvaluatorFlags( - **workflow.flags.model_dump(mode="json"), + **evaluator.flags.model_dump(mode="json"), ) - if workflow.flags + if evaluator.flags else SimpleEvaluatorFlags() ) simple_evaluator = SimpleEvaluator( - id=workflow.id, - slug=workflow.slug, + id=evaluator.id, + slug=evaluator.slug, # - created_at=workflow.created_at, - updated_at=workflow.updated_at, - deleted_at=workflow.deleted_at, - created_by_id=workflow.created_by_id, - updated_by_id=workflow.updated_by_id, - deleted_by_id=workflow.deleted_by_id, + created_at=evaluator.created_at, + updated_at=evaluator.updated_at, + deleted_at=evaluator.deleted_at, + created_by_id=evaluator.created_by_id, + updated_by_id=evaluator.updated_by_id, + deleted_by_id=evaluator.deleted_by_id, # - name=workflow.name, - description=workflow.description, + name=evaluator.name, + description=evaluator.description, # flags=simple_evaluator_flags, - tags=workflow.tags, - meta=workflow.meta, + tags=evaluator.tags, + meta=evaluator.meta, ) simple_evaluator_response = SimpleEvaluatorResponse( @@ -743,107 +1293,91 @@ async def archive_simple_evaluator( return simple_evaluator_response @intercept_exceptions() - async def unarchive_simple_evaluator( + async def unarchive_simple_evaluator( # TODO: FIX ME self, *, request: Request, evaluator_id: UUID, ) -> SimpleEvaluatorResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - workflow_ref = Reference( + evaluator_ref = Reference( id=evaluator_id, ) - workflow: Optional[Workflow] = await self.workflows_service.fetch_workflow( + evaluator = await self.evaluators_service.fetch_evaluator( project_id=UUID(request.state.project_id), # - workflow_ref=workflow_ref, + evaluator_ref=evaluator_ref, ) - if workflow is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple evaluator not found. Please check the ID and try again.", - ) + if evaluator is None or not evaluator.id: + return SimpleEvaluatorResponse() - workflow: Optional[Workflow] = await self.workflows_service.unarchive_workflow( + evaluator = await self.evaluators_service.unarchive_evaluator( # type: ignore project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - workflow_id=evaluator_id, + evaluator_id=evaluator.id, ) - if workflow is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to unarchive simple evaluator. Please try again or contact support.", - ) + if evaluator is None: + return SimpleEvaluatorResponse() - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.fetch_workflow_variant( + evaluator_variant = await self.evaluators_service.fetch_evaluator_variant( project_id=UUID(request.state.project_id), # - workflow_ref=workflow_ref, + evaluator_ref=evaluator_ref, ) - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to fetch simple evaluator. Please try again or contact support.", - ) + if evaluator_variant is None: + return SimpleEvaluatorResponse() - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.unarchive_workflow_variant( + evaluator_variant = await self.evaluators_service.unarchive_evaluator_variant( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - workflow_variant_id=workflow_variant.id, + evaluator_variant_id=evaluator_variant.id, # type: ignore ) - if workflow_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to unarchive simple evaluator. Please try again or contact support.", - ) + if evaluator_variant is None: + return SimpleEvaluatorResponse() simple_evaluator_flags = ( SimpleEvaluatorFlags( - **workflow.flags.model_dump(mode="json"), + **evaluator.flags.model_dump(mode="json"), ) - if workflow.flags + if evaluator.flags else SimpleEvaluatorFlags() ) simple_evaluator = SimpleEvaluator( - id=workflow.id, - slug=workflow.slug, + id=evaluator.id, + slug=evaluator.slug, # - created_at=workflow.created_at, - updated_at=workflow.updated_at, - deleted_at=workflow.deleted_at, - created_by_id=workflow.created_by_id, - updated_by_id=workflow.updated_by_id, - deleted_by_id=workflow.deleted_by_id, + created_at=evaluator.created_at, + updated_at=evaluator.updated_at, + deleted_at=evaluator.deleted_at, + created_by_id=evaluator.created_by_id, + updated_by_id=evaluator.updated_by_id, + deleted_by_id=evaluator.deleted_by_id, # - name=workflow.name, - description=workflow.description, + name=evaluator.name, + description=evaluator.description, # flags=simple_evaluator_flags, - tags=workflow.tags, - meta=workflow.meta, + tags=evaluator.tags, + meta=evaluator.meta, ) simple_evaluator_response = SimpleEvaluatorResponse( - count=1, + count=1 if simple_evaluator else 0, evaluator=simple_evaluator, ) @@ -853,7 +1387,6 @@ async def unarchive_simple_evaluator( @suppress_exceptions(default=SimpleEvaluatorsResponse()) async def list_simple_evaluators( self, - *, request: Request, ) -> SimpleEvaluatorsResponse: simple_evaluator_query_request = SimpleEvaluatorQueryRequest( @@ -866,24 +1399,25 @@ async def list_simple_evaluators( return await self.query_simple_evaluators( request=request, + # simple_evaluator_query_request=simple_evaluator_query_request, ) @intercept_exceptions() @suppress_exceptions(default=SimpleEvaluatorsResponse()) - async def query_simple_evaluators( + async def query_simple_evaluators( # TODO: FIX ME self, - *, request: Request, + *, simple_evaluator_query_request: SimpleEvaluatorQueryRequest, ) -> SimpleEvaluatorsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_EVALUATORS, + permission=Permission.VIEW_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore simple_evaluator_flags = ( simple_evaluator_query_request.evaluator.flags @@ -891,7 +1425,7 @@ async def query_simple_evaluators( else None ) - flags = WorkflowFlags( + flags = EvaluatorFlags( is_evaluator=True, is_custom=( simple_evaluator_flags.is_custom if simple_evaluator_flags else None @@ -901,7 +1435,7 @@ async def query_simple_evaluators( ), ) - _workflow_query = WorkflowQuery( + evaluator_query = EvaluatorQuery( flags=flags, tags=( simple_evaluator_query_request.evaluator.tags @@ -916,84 +1450,81 @@ async def query_simple_evaluators( # ) - workflows: List[Workflow] = await self.workflows_service.query_workflows( + evaluators = await self.evaluators_service.query_evaluators( project_id=UUID(request.state.project_id), # - workflow_query=_workflow_query, + evaluator_query=evaluator_query, # - workflow_refs=simple_evaluator_query_request.evaluator_refs, + evaluator_refs=simple_evaluator_query_request.evaluator_refs, # include_archived=simple_evaluator_query_request.include_archived, # windowing=simple_evaluator_query_request.windowing, ) - if workflows is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to query simple evaluators. Please try again or contact support.", - ) - simple_evaluators: List[SimpleEvaluator] = [] - for workflow in workflows: - workflow_ref = Reference( - id=workflow.id, + for evaluator in evaluators: + evaluator_ref = Reference( + id=evaluator.id, ) - workflow_variant: Optional[ - WorkflowVariant - ] = await self.workflows_service.fetch_workflow_variant( + evaluator_variant = await self.evaluators_service.fetch_evaluator_variant( project_id=UUID(request.state.project_id), # - workflow_ref=workflow_ref, + evaluator_ref=evaluator_ref, ) - if workflow_variant is None: + if evaluator_variant is None: continue - workflow_variant_ref = Reference( - id=workflow_variant.id, + evaluator_variant_ref = Reference( + id=evaluator_variant.id, ) - workflow_revision: Optional[ - WorkflowRevision - ] = await self.workflows_service.fetch_workflow_revision( + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( project_id=UUID(request.state.project_id), # - workflow_variant_ref=workflow_variant_ref, + evaluator_ref=evaluator_ref, + evaluator_variant_ref=evaluator_variant_ref, ) - if workflow_revision is None: + if evaluator_revision is None: continue simple_evaluator_flags = ( SimpleEvaluatorFlags( - **workflow.flags.model_dump(mode="json"), + **evaluator.flags.model_dump(mode="json"), ) - if workflow.flags + if evaluator.flags else SimpleEvaluatorFlags() ) simple_evaluator = SimpleEvaluator( - id=workflow.id, - slug=workflow.slug, + id=evaluator.id, + slug=evaluator.slug, # - created_at=workflow.created_at, - updated_at=workflow.updated_at, - deleted_at=workflow.deleted_at, - created_by_id=workflow.created_by_id, - updated_by_id=workflow.updated_by_id, - deleted_by_id=workflow.deleted_by_id, + created_at=evaluator.created_at, + updated_at=evaluator.updated_at, + deleted_at=evaluator.deleted_at, + created_by_id=evaluator.created_by_id, + updated_by_id=evaluator.updated_by_id, + deleted_by_id=evaluator.deleted_by_id, # - name=workflow.name, - description=workflow.description, + name=evaluator.name, + description=evaluator.description, # flags=simple_evaluator_flags, - tags=workflow.tags, - meta=workflow.meta, + tags=evaluator.tags, + meta=evaluator.meta, # - data=workflow_revision.data, + data=SimpleEvaluatorData( + **( + evaluator_revision.data.model_dump(mode="json") + if evaluator_revision.data + else {} + ), + ), ) simple_evaluators.append(simple_evaluator) @@ -1013,160 +1544,22 @@ async def transfer_simple_evaluator( evaluator_id: UUID, ) -> SimpleEvaluatorResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - old_evaluator = await fetch_evaluator_config( - evaluator_config_id=str(evaluator_id), - ) + raise FORBIDDEN_EXCEPTION # type: ignore - if old_evaluator is None: - return SimpleEvaluatorsResponse() - - workflow_revision_data = self._transfer_workflow_revision_data( - old_evaluator=old_evaluator, - ) - - new_evaluator = await self.workflows_service.fetch_workflow( + simple_evaluator = await self.simple_evaluators_service.transfer( project_id=UUID(request.state.project_id), - # - workflow_ref=Reference(id=evaluator_id), + user_id=UUID(request.state.user_id), + evaluator_id=evaluator_id, ) - if not new_evaluator: - slug = get_slug_from_name_and_id( - name=old_evaluator.name, - id=evaluator_id, - ) - - simple_evaluator_create_request = SimpleEvaluatorCreateRequest( - evaluator=SimpleEvaluatorCreate( - slug=slug, - name=old_evaluator.name, - description=None, - flags=SimpleEvaluatorFlags( - is_custom=False, - is_human=False, - is_evaluator=True, - ), - tags=None, - meta=None, - data=workflow_revision_data, - ) - ) - - return await self.create_simple_evaluator( - request=request, - evaluator_id=evaluator_id, - simple_evaluator_create_request=simple_evaluator_create_request, - ) - - else: - simple_evaluator_edit_request = SimpleEvaluatorEditRequest( - evaluator=SimpleEvaluatorEdit( - id=evaluator_id, - name=new_evaluator.name, - description=new_evaluator.description, - flags=SimpleEvaluatorFlags(**new_evaluator.flags.model_dump()), - tags=new_evaluator.tags, - meta=new_evaluator.meta, - data=workflow_revision_data, - ) - ) - - return await self.edit_simple_evaluator( - request=request, - evaluator_id=evaluator_id, - simple_evaluator_edit_request=simple_evaluator_edit_request, - ) - - def _transfer_workflow_revision_data( - self, - *, - old_evaluator: EvaluatorConfigDB, - ) -> WorkflowRevisionData: - version = "2025.07.14" - uri = f"agenta:built-in:{old_evaluator.evaluator_key}:v0" - url = ( - old_evaluator.settings_values.get("webhook_url", None) - if old_evaluator.evaluator_key == "auto_webhook_test" - else None - ) - headers = None - mappings = None - properties = ( - {"score": {"type": "number"}, "success": {"type": "boolean"}} - if old_evaluator.evaluator_key - in ( - "auto_levenshtein_distance", - "auto_semantic_similarity", - "auto_similarity_match", - "auto_json_diff", - "auto_webhook_test", - "auto_custom_code_run", - "auto_ai_critique", - "rag_faithfulness", - "rag_context_relevancy", - ) - else {"success": {"type": "boolean"}} - ) - schemas = { - "outputs": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": properties, - "required": ( - list(properties.keys()) - if old_evaluator.evaluator_key - not in ( - "auto_levenshtein_distance", - "auto_semantic_similarity", - "auto_similarity_match", - "auto_json_diff", - "auto_webhook_test", - "auto_custom_code_run", - "auto_ai_critique", - "rag_faithfulness", - "rag_context_relevancy", - ) - else [] - ), - "additionalProperties": False, - } - } - script = ( - old_evaluator.settings_values.get("code", None) - if old_evaluator.evaluator_key == "auto_custom_code_run" - else None - ) - parameters = old_evaluator.settings_values - service = { - "agenta": "0.1.0", - "format": { - "type": "object", - "$schema": "http://json-schema.org/schema#", - "required": ["outputs"], - "properties": { - "outputs": schemas["outputs"], - }, - }, - } - configuration = parameters - - return WorkflowRevisionData( - version=version, - uri=uri, - url=url, - headers=headers, - mappings=mappings, - schemas=schemas, - script=script, - parameters=parameters, - # LEGACY - service=service, - configuration=configuration, + simple_evaluator_response = SimpleEvaluatorResponse( + count=1 if simple_evaluator else 0, + evaluator=simple_evaluator, ) + + return simple_evaluator_response diff --git a/api/oss/src/apis/fastapi/evaluators/utils.py b/api/oss/src/apis/fastapi/evaluators/utils.py index e69de29bb2..c277db32f4 100644 --- a/api/oss/src/apis/fastapi/evaluators/utils.py +++ b/api/oss/src/apis/fastapi/evaluators/utils.py @@ -0,0 +1,631 @@ +from typing import Optional, Literal, List +from uuid import UUID +from datetime import datetime + +from fastapi import Query + +from oss.src.utils.logging import get_module_logger + +from oss.src.core.shared.dtos import ( + Windowing, + Reference, +) +from oss.src.core.evaluators.dtos import ( + EvaluatorFlags, + # + EvaluatorQuery, + EvaluatorVariantQuery, + EvaluatorRevisionQuery, +) + +from oss.src.apis.fastapi.shared.utils import ( + parse_metadata, +) +from oss.src.apis.fastapi.evaluators.models import ( + EvaluatorQueryRequest, + EvaluatorVariantQueryRequest, + EvaluatorRevisionQueryRequest, + EvaluatorRevisionRetrieveRequest, +) + + +log = get_module_logger(__name__) + + +def parse_evaluator_query_request_from_params( + evaluator_id: Optional[UUID] = Query(None), + evaluator_ids: Optional[List[UUID]] = Query(None), + evaluator_slug: Optional[str] = Query(None), + evaluator_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> EvaluatorQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = EvaluatorFlags(**_flags) if _flags else None + + evaluator = ( + EvaluatorQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + evaluator_refs = ( + ( + [ + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + ] + if evaluator_id or evaluator_slug + else [] + ) + + ( + [ + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + for evaluator_id, evaluator_slug in zip( + evaluator_ids, + evaluator_slugs, + ) + ] + if evaluator_ids and evaluator_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_evaluator_query_request_from_body( + evaluator=evaluator, + # + evaluator_refs=evaluator_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + +def parse_evaluator_query_request_from_body( + evaluator: Optional[EvaluatorQuery] = None, + # + evaluator_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, +) -> EvaluatorQueryRequest: + evaluator_query_request = None + + try: + evaluator_query_request = EvaluatorQueryRequest( + evaluator=evaluator, + # + evaluator_refs=evaluator_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + except Exception as e: # pylint: disable=broad-except + evaluator_query_request = EvaluatorQueryRequest() + + return evaluator_query_request + + +def merge_evaluator_query_requests( + query_request_params: Optional[EvaluatorQueryRequest] = None, + query_request_body: Optional[EvaluatorQueryRequest] = None, +) -> EvaluatorQueryRequest: + if query_request_params and not query_request_body: + return query_request_params + + if not query_request_params and query_request_body: + return query_request_body + + if query_request_params and query_request_body: + return EvaluatorQueryRequest( + evaluator=query_request_body.evaluator or query_request_params.evaluator, + # + evaluator_refs=query_request_body.evaluator_refs + or query_request_params.evaluator_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return EvaluatorQueryRequest() + + +def parse_evaluator_variant_query_request_from_params( + evaluator_id: Optional[UUID] = Query(None), + evaluator_ids: Optional[List[UUID]] = Query(None), + evaluator_slug: Optional[str] = Query(None), + evaluator_slugs: Optional[List[str]] = Query(None), + # + evaluator_variant_id: Optional[UUID] = Query(None), + evaluator_variant_ids: Optional[List[UUID]] = Query(None), + evaluator_variant_slug: Optional[str] = Query(None), + evaluator_variant_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> EvaluatorVariantQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = EvaluatorFlags(**_flags) if _flags else None + + evaluator_variant = ( + EvaluatorVariantQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + evaluator_refs = ( + ( + [ + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + ] + if evaluator_id or evaluator_slug + else [] + ) + + ( + [ + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + for evaluator_id, evaluator_slug in zip( + evaluator_ids, + evaluator_slugs, + ) + ] + if evaluator_ids and evaluator_slugs + else [] + ) + ) or None + + evaluator_variant_refs = ( + ( + [ + Reference( + id=evaluator_variant_id, + slug=evaluator_variant_slug, + ) + ] + if evaluator_variant_id or evaluator_variant_slug + else [] + ) + + ( + [ + Reference( + id=evaluator_variant_id, + slug=evaluator_variant_slug, + ) + for evaluator_variant_id, evaluator_variant_slug in zip( + evaluator_variant_ids, + evaluator_variant_slugs, + ) + ] + if evaluator_variant_ids and evaluator_variant_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_evaluator_variant_query_request_from_body( + evaluator_variant=evaluator_variant, + # + evaluator_refs=evaluator_refs or None, + evaluator_variant_refs=evaluator_variant_refs or None, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + +def parse_evaluator_variant_query_request_from_body( + evaluator_variant: Optional[EvaluatorVariantQuery] = None, + # + evaluator_refs: Optional[List[Reference]] = None, + evaluator_variant_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, +) -> EvaluatorVariantQueryRequest: + evaluator_variant_query_request = None + + try: + evaluator_variant_query_request = EvaluatorVariantQueryRequest( + evaluator_variant=evaluator_variant, + # + evaluator_refs=evaluator_refs, + evaluator_variant_refs=evaluator_variant_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + except Exception as e: # pylint: disable=broad-except + evaluator_variant_query_request = EvaluatorVariantQueryRequest() + + return evaluator_variant_query_request + + +def merge_evaluator_variant_query_requests( + query_request_params: Optional[EvaluatorVariantQueryRequest] = None, + query_request_body: Optional[EvaluatorVariantQueryRequest] = None, +) -> EvaluatorVariantQueryRequest: + if query_request_params and not query_request_body: + return query_request_params + + if not query_request_params and query_request_body: + return query_request_body + + if query_request_params and query_request_body: + return EvaluatorVariantQueryRequest( + evaluator_variant=query_request_body.evaluator_variant + or query_request_params.evaluator_variant, + # + evaluator_refs=query_request_body.evaluator_refs + or query_request_params.evaluator_refs, + evaluator_variant_refs=query_request_body.evaluator_variant_refs + or query_request_params.evaluator_variant_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return EvaluatorVariantQueryRequest() + + +def parse_evaluator_revision_query_request_from_params( + evaluator_id: Optional[UUID] = Query(None), + evaluator_ids: Optional[List[UUID]] = Query(None), + evaluator_slug: Optional[str] = Query(None), + evaluator_slugs: Optional[List[str]] = Query(None), + # + evaluator_variant_id: Optional[UUID] = Query(None), + evaluator_variant_ids: Optional[List[UUID]] = Query(None), + evaluator_variant_slug: Optional[str] = Query(None), + evaluator_variant_slugs: Optional[List[str]] = Query(None), + # + evaluator_revision_id: Optional[UUID] = Query(None), + evaluator_revision_ids: Optional[List[UUID]] = Query(None), + evaluator_revision_slug: Optional[str] = Query(None), + evaluator_revision_slugs: Optional[List[str]] = Query(None), + evaluator_revision_version: Optional[str] = Query(None), + evaluator_revision_versions: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> EvaluatorRevisionQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = EvaluatorFlags(**_flags) if _flags else None + + evaluator_revision = ( + EvaluatorRevisionQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + evaluator_refs = ( + [ + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + ] + if evaluator_id or evaluator_slug + else [] + ) + ( + [ + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + for evaluator_id, evaluator_slug in zip( + evaluator_ids, + evaluator_slugs, + ) + ] + if evaluator_ids and evaluator_slugs + else [] + ) + + evaluator_variant_refs = ( + [ + Reference( + id=evaluator_variant_id, + slug=evaluator_variant_slug, + ) + ] + if evaluator_variant_id or evaluator_variant_slug + else [] + ) + ( + [ + Reference( + id=evaluator_variant_id, + slug=evaluator_variant_slug, + ) + for evaluator_variant_id, evaluator_variant_slug in zip( + evaluator_variant_ids, + evaluator_variant_slugs, + ) + ] + if evaluator_variant_ids and evaluator_variant_slugs + else [] + ) + + evaluator_revision_refs = ( + [ + Reference( + id=evaluator_revision_id, + slug=evaluator_revision_slug, + version=evaluator_revision_version, + ) + ] + if evaluator_revision_id + or evaluator_revision_slug + or evaluator_revision_version + else [] + ) + ( + [ + Reference( + id=evaluator_revision_id, + slug=evaluator_revision_slug, + version=evaluator_revision_version, + ) + for evaluator_revision_id, evaluator_revision_slug, evaluator_revision_version in zip( + evaluator_revision_ids, + evaluator_revision_slugs, + evaluator_revision_versions, + ) + ] + if evaluator_revision_ids + and evaluator_revision_slugs + and evaluator_revision_versions + else [] + ) + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_evaluator_revision_query_request_from_body( + evaluator_revision=evaluator_revision, + # + evaluator_refs=evaluator_refs, + evaluator_variant_refs=evaluator_variant_refs, + evaluator_revision_refs=evaluator_revision_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + +def parse_evaluator_revision_query_request_from_body( + evaluator_revision: Optional[EvaluatorRevisionQuery] = None, + # + evaluator_refs: Optional[List[Reference]] = None, + evaluator_variant_refs: Optional[List[Reference]] = None, + evaluator_revision_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, +) -> EvaluatorRevisionQueryRequest: + evaluator_revision_query_request = None + + try: + evaluator_revision_query_request = EvaluatorRevisionQueryRequest( + evaluator_revision=evaluator_revision, + # + evaluator_refs=evaluator_refs, + evaluator_variant_refs=evaluator_variant_refs, + evaluator_revision_refs=evaluator_revision_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + except Exception as e: # pylint: disable=broad-except + log.warn(e) + + evaluator_revision_query_request = EvaluatorRevisionQueryRequest() + + return evaluator_revision_query_request + + +def merge_evaluator_revision_query_requests( + query_request_params: Optional[EvaluatorRevisionQueryRequest] = None, + query_request_body: Optional[EvaluatorRevisionQueryRequest] = None, +) -> EvaluatorRevisionQueryRequest: + if query_request_params and not query_request_body: + return query_request_params + + if not query_request_params and query_request_body: + return query_request_body + + if query_request_params and query_request_body: + return EvaluatorRevisionQueryRequest( + evaluator_revision=query_request_body.evaluator_revision + or query_request_params.evaluator_revision, + # + evaluator_refs=query_request_body.evaluator_refs + or query_request_params.evaluator_refs, + evaluator_variant_refs=query_request_body.evaluator_variant_refs + or query_request_params.evaluator_variant_refs, + evaluator_revision_refs=query_request_body.evaluator_revision_refs + or query_request_params.evaluator_revision_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return EvaluatorRevisionQueryRequest() + + +def parse_evaluator_revision_retrieve_request_from_params( + evaluator_id: Optional[UUID] = Query(None), + evaluator_slug: Optional[str] = Query(None), + # + evaluator_variant_id: Optional[UUID] = Query(None), + evaluator_variant_slug: Optional[str] = Query(None), + # + evaluator_revision_id: Optional[UUID] = Query(None), + evaluator_revision_slug: Optional[str] = Query(None), + evaluator_revision_version: Optional[str] = Query(None), +): + evaluator_ref = ( + Reference( + id=evaluator_id, + slug=evaluator_slug, + ) + if evaluator_id or evaluator_slug + else None + ) + + evaluator_variant_ref = ( + Reference( + id=evaluator_variant_id, + slug=evaluator_variant_slug, + ) + if evaluator_variant_id or evaluator_variant_slug + else None + ) + + evaluator_revision_ref = ( + Reference( + id=evaluator_revision_id, + slug=evaluator_revision_slug, + version=evaluator_revision_version, + ) + if evaluator_revision_id + or evaluator_revision_slug + or evaluator_revision_version + else None + ) + + return parse_evaluator_revision_retrieve_request_from_body( + evaluator_ref=evaluator_ref, + evaluator_variant_ref=evaluator_variant_ref, + evaluator_revision_ref=evaluator_revision_ref, + ) + + +def parse_evaluator_revision_retrieve_request_from_body( + evaluator_ref: Optional[Reference] = None, + evaluator_variant_ref: Optional[Reference] = None, + evaluator_revision_ref: Optional[Reference] = None, +) -> EvaluatorRevisionRetrieveRequest: + return EvaluatorRevisionRetrieveRequest( + evaluator_ref=evaluator_ref, + evaluator_variant_ref=evaluator_variant_ref, + evaluator_revision_ref=evaluator_revision_ref, + ) diff --git a/api/oss/src/apis/fastapi/invocations/__init__.py b/api/oss/src/apis/fastapi/invocations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/apis/fastapi/invocations/models.py b/api/oss/src/apis/fastapi/invocations/models.py new file mode 100644 index 0000000000..8554ba88ee --- /dev/null +++ b/api/oss/src/apis/fastapi/invocations/models.py @@ -0,0 +1,52 @@ +from typing import Optional, List + +from pydantic import BaseModel + +from oss.src.core.shared.dtos import ( + Link, + Windowing, +) +from oss.src.core.invocations.types import ( + Invocation, + InvocationCreate, + InvocationEdit, + InvocationQuery, +) + +# INVOCATIONS ------------------------------------------------------------------ + + +class InvocationCreateRequest(BaseModel): + invocation: InvocationCreate + + +class InvocationEditRequest(BaseModel): + invocation: InvocationEdit + + +class InvocationQueryRequest(BaseModel): + invocation: Optional[InvocationQuery] = None + # + invocation_links: Optional[List[Link]] = None + # + windowing: Optional[Windowing] = None + + +class InvocationResponse(BaseModel): + count: int = 0 + invocation: Optional[Invocation] = None + + +class InvocationsResponse(BaseModel): + count: int = 0 + invocations: List[Invocation] = [] + + +class InvocationLinkResponse(BaseModel): + count: int = 0 + invocation_link: Optional[Link] = None + + +class InvocationLinksResponse(BaseModel): + count: int = 0 + invocation_links: List[Link] = [] diff --git a/api/oss/src/apis/fastapi/invocations/router.py b/api/oss/src/apis/fastapi/invocations/router.py new file mode 100644 index 0000000000..b4ff285f40 --- /dev/null +++ b/api/oss/src/apis/fastapi/invocations/router.py @@ -0,0 +1,294 @@ +from typing import Optional, Union +from uuid import UUID + +from fastapi import APIRouter, Request, status, Response + +from oss.src.utils.common import is_ee +from oss.src.utils.logging import get_module_logger +from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions +from oss.src.utils.caching import get_cache, set_cache, invalidate_cache + +from oss.src.core.invocations.service import ( + InvocationsService, +) +from oss.src.core.shared.dtos import ( + Link, +) + +from oss.src.apis.fastapi.invocations.models import ( + InvocationCreateRequest, + InvocationEditRequest, + InvocationResponse, + InvocationsResponse, + InvocationQueryRequest, + InvocationLinkResponse, +) + +if is_ee(): + from ee.src.models.shared_models import Permission + from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + + +log = get_module_logger(__name__) + + +class InvocationsRouter: + def __init__( + self, + *, + invocations_service: InvocationsService, + ): + self.invocations_service = invocations_service + + self.router = APIRouter() + + # INVOCATIONS ---------------------------------------------------------- + + # POST /api/invocations/ + self.router.add_api_route( + "/", + self.create_invocation, + methods=["POST"], + operation_id="create_invocation", + status_code=status.HTTP_200_OK, + response_model=InvocationResponse, + response_model_exclude_none=True, + ) + + # GET /api/invocations/{trace_id} + self.router.add_api_route( + "/{trace_id}", + self.fetch_invocation, + methods=["GET"], + operation_id="fetch_invocation_by_trace_id", + status_code=status.HTTP_200_OK, + response_model=InvocationResponse, + response_model_exclude_none=True, + ) + + # GET /api/invocations/{trace_id}/{span_id} + self.router.add_api_route( + "/{trace_id}/{span_id}", + self.fetch_invocation, + methods=["GET"], + operation_id="fetch_invocation", + status_code=status.HTTP_200_OK, + response_model=InvocationResponse, + response_model_exclude_none=True, + ) + + # PUT /api/invocations/{trace_id} + self.router.add_api_route( + "/{trace_id}", + self.edit_invocation, + methods=["PATCH"], + operation_id="edit_invocation_by_trace_id", + status_code=status.HTTP_200_OK, + response_model=InvocationResponse, + response_model_exclude_none=True, + ) + + # PUT /api/invocations/{trace_id}/{span_id} + self.router.add_api_route( + "/{trace_id}/{span_id}", + self.edit_invocation, + methods=["PATCH"], + operation_id="edit_invocation", + status_code=status.HTTP_200_OK, + response_model=InvocationResponse, + response_model_exclude_none=True, + ) + + # DELETE /api/invocations/{trace_id} + self.router.add_api_route( + "/{trace_id}", + self.delete_invocation, + methods=["DELETE"], + operation_id="delete_invocation_by_trace_id", + status_code=status.HTTP_200_OK, + response_model=InvocationLinkResponse, + response_model_exclude_none=True, + ) + + # DELETE /api/invocations/{trace_id}/{span_id} + self.router.add_api_route( + "/{trace_id}/{span_id}", + self.delete_invocation, + methods=["DELETE"], + operation_id="delete_invocation", + status_code=status.HTTP_200_OK, + response_model=InvocationLinkResponse, + response_model_exclude_none=True, + ) + + # POST /api/invocations/query + self.router.add_api_route( + "/query", + self.query_invocations, + methods=["POST"], + operation_id="query_invocations", + status_code=status.HTTP_200_OK, + response_model=InvocationsResponse, + response_model_exclude_none=True, + ) + + # INVOCATIONS -------------------------------------------------------------- + + @intercept_exceptions() + async def create_invocation( + self, + request: Request, + *, + invocation_create_request: InvocationCreateRequest, + ) -> InvocationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_INVOCATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + invocation = await self.invocations_service.create( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + invocation_create=invocation_create_request.invocation, + ) + + invocation_response = InvocationResponse( + count=1 if invocation else 0, + invocation=invocation, + ) + + return invocation_response + + @intercept_exceptions() + @suppress_exceptions(default=InvocationResponse()) + async def fetch_invocation( + self, + request: Request, + *, + trace_id: str, + span_id: Optional[str] = None, + ) -> Union[Response, InvocationResponse]: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_INVOCATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + invocation = await self.invocations_service.fetch( + project_id=UUID(request.state.project_id), + # + trace_id=trace_id, + span_id=span_id, + ) + + invocation_response = InvocationResponse( + count=1 if invocation else 0, + invocation=invocation, + ) + + return invocation_response + + @intercept_exceptions() + async def edit_invocation( + self, + request: Request, + *, + trace_id: str, + span_id: Optional[str] = None, + # + invocation_edit_request: InvocationEditRequest, + ) -> InvocationResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_INVOCATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + invocation = await self.invocations_service.edit( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + trace_id=trace_id, + span_id=span_id, + # + invocation_edit=invocation_edit_request.invocation, + ) + + invocation_response = InvocationResponse( + count=1 if invocation else 0, + invocation=invocation, + ) + + return invocation_response + + @intercept_exceptions() + async def delete_invocation( + self, + request: Request, + *, + trace_id: str, + span_id: Optional[str] = None, + ) -> InvocationLinkResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_INVOCATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + invocation_link: Optional[Link] = await self.invocations_service.delete( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + trace_id=trace_id, + span_id=span_id, + ) + + invocation_link_response = InvocationLinkResponse( + count=1 if invocation_link else 0, + invocation_link=invocation_link, + ) + + return invocation_link_response + + @intercept_exceptions() + @suppress_exceptions(default=InvocationsResponse()) + async def query_invocations( + self, + request: Request, + *, + invocation_query_request: InvocationQueryRequest, + ) -> InvocationsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_INVOCATIONS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + invocations = await self.invocations_service.query( + project_id=UUID(request.state.project_id), + # + invocation_query=invocation_query_request.invocation, + # + invocation_links=invocation_query_request.invocation_links, + # + windowing=invocation_query_request.windowing, + ) + + invocations_response = InvocationsResponse( + count=len(invocations), + invocations=invocations, + ) + + return invocations_response diff --git a/api/oss/src/apis/fastapi/middleware/request.py b/api/oss/src/apis/fastapi/middleware/request.py deleted file mode 100644 index ace4ac3030..0000000000 --- a/api/oss/src/apis/fastapi/middleware/request.py +++ /dev/null @@ -1,17 +0,0 @@ -import uuid - -from fastapi import Request - -from oss.src.utils.context import request_id_ctx - - -async def request_id_middleware(request: Request, call_next): - request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4()) - - request_id_ctx.set(request_id) - - response = await call_next(request) - - response.headers["X-Request-ID"] = request_id - - return response diff --git a/api/oss/src/apis/fastapi/observability/extractors/adapters/openinference_adapter.py b/api/oss/src/apis/fastapi/observability/extractors/adapters/openinference_adapter.py index 5afdcd61f2..71fc43f644 100644 --- a/api/oss/src/apis/fastapi/observability/extractors/adapters/openinference_adapter.py +++ b/api/oss/src/apis/fastapi/observability/extractors/adapters/openinference_adapter.py @@ -136,11 +136,11 @@ def process(self, bag: CanonicalAttributes, features: SpanFeatures) -> None: transformed_attributes["ag.type.node"] = mapped_kind has_data = True else: - log.warning( + log.warn( f"OpenInferenceAdapter: Unknown or unmapped openinference.span.kind '{value}'" ) else: - log.warning( + log.warn( f"OpenInferenceAdapter: Expected string for openinference.span.kind, got {type(value)}" ) continue # Move to next attribute diff --git a/api/oss/src/apis/fastapi/observability/extractors/normalizer.py b/api/oss/src/apis/fastapi/observability/extractors/normalizer.py index 5201592c3d..f8e940c15d 100644 --- a/api/oss/src/apis/fastapi/observability/extractors/normalizer.py +++ b/api/oss/src/apis/fastapi/observability/extractors/normalizer.py @@ -48,7 +48,7 @@ def normalize(self, otel_span_dto: OTelSpanDTO) -> CanonicalAttributes: except ValueError: # Fallback or error handling for non-ISO timestamps if necessary # For now, let's assume UTC now as a fallback, or log an error - # log.warning(f"Could not parse event timestamp: {event_dto.timestamp}") + # log.warn(f"Could not parse event timestamp: {event_dto.timestamp}") dt_timestamp = datetime.now(timezone.utc) events_data.append( diff --git a/api/oss/src/apis/fastapi/observability/extractors/span_processor.py b/api/oss/src/apis/fastapi/observability/extractors/span_processor.py index 7d1e578c21..a17fd7baa6 100644 --- a/api/oss/src/apis/fastapi/observability/extractors/span_processor.py +++ b/api/oss/src/apis/fastapi/observability/extractors/span_processor.py @@ -33,7 +33,7 @@ def __init__(self, builders: List[SpanDataBuilder]): self.normalizer = Normalizer() self.adapter_registry = AdapterRegistry() if not builders: - log.warning( + log.warn( "SpanProcessor initialized with no builders. Process method will return an empty dict." ) self.builders = builders @@ -86,7 +86,7 @@ def process( ) if not results and self.builders: - log.warning( + log.warn( "All builders failed or returned no data for span_id %s (trace_id %s). OTelSpan: %s", otel_span_dto.context.span_id[2:] if otel_span_dto.context else "N/A", otel_span_dto.context.trace_id[2:] if otel_span_dto.context else "N/A", diff --git a/api/oss/src/apis/fastapi/observability/models.py b/api/oss/src/apis/fastapi/observability/models.py index 0cff80ed04..1620585aed 100644 --- a/api/oss/src/apis/fastapi/observability/models.py +++ b/api/oss/src/apis/fastapi/observability/models.py @@ -3,8 +3,6 @@ from pydantic import BaseModel, ConfigDict -from oss.src.apis.fastapi.shared.models import VersionedModel - from oss.src.core.observability.dtos import ( OTelSpanDTO, SpanDTO, @@ -14,11 +12,11 @@ ) -class CollectStatusResponse(VersionedModel): +class CollectStatusResponse(BaseModel): status: str -class OTelTracingResponse(VersionedModel): +class OTelTracingResponse(BaseModel): count: Optional[int] = None spans: List[OTelSpanDTO] @@ -58,15 +56,15 @@ class AgentaRootsDTO(BaseModel): roots: Optional[List[AgentaRootDTO]] = [] -class AgentaNodesResponse(VersionedModel, AgentaNodesDTO): +class AgentaNodesResponse(AgentaNodesDTO): count: Optional[int] = None -class AgentaTreesResponse(VersionedModel, AgentaTreesDTO): +class AgentaTreesResponse(AgentaTreesDTO): count: Optional[int] = None -class AgentaRootsResponse(VersionedModel, AgentaRootsDTO): +class AgentaRootsResponse(AgentaRootsDTO): count: Optional[int] = None @@ -93,6 +91,6 @@ class LegacyAnalyticsResponse(LegacySummary): data: List[LegacyDataPoint] -class AnalyticsResponse(VersionedModel): +class OldAnalyticsResponse(BaseModel): count: Optional[int] = None buckets: List[BucketDTO] diff --git a/api/oss/src/apis/fastapi/observability/opentelemetry/otlp.py b/api/oss/src/apis/fastapi/observability/opentelemetry/otlp.py index 80fa5a4343..20b3098739 100644 --- a/api/oss/src/apis/fastapi/observability/opentelemetry/otlp.py +++ b/api/oss/src/apis/fastapi/observability/opentelemetry/otlp.py @@ -85,7 +85,7 @@ def _decode_value(any_value): elif which is None: return None else: - log.warning(f"Unknown value type at _decode_value: {which}") + log.warn(f"Unknown value type at _decode_value: {which}") return str(any_value) diff --git a/api/oss/src/apis/fastapi/observability/router.py b/api/oss/src/apis/fastapi/observability/router.py index 673aecef51..b95b115997 100644 --- a/api/oss/src/apis/fastapi/observability/router.py +++ b/api/oss/src/apis/fastapi/observability/router.py @@ -32,7 +32,7 @@ parse_otlp_stream, ) from oss.src.apis.fastapi.observability.utils.processing import ( - parse_query_request, + parse_query_from_params_request, parse_analytics_dto, parse_from_otel_span_dto, parse_to_otel_span_dto, @@ -50,7 +50,7 @@ AgentaTreeDTO, AgentaRootDTO, LegacyAnalyticsResponse, - AnalyticsResponse, + OldAnalyticsResponse, ) if is_ee(): @@ -86,8 +86,6 @@ class ObservabilityRouter: - VERSION = "1.0.0" - def __init__( self, observability_service: ObservabilityService, @@ -161,7 +159,7 @@ def __init__( status_code=status.HTTP_200_OK, response_model=Union[ LegacyAnalyticsResponse, - AnalyticsResponse, + OldAnalyticsResponse, ], response_model_exclude_none=True, ) @@ -202,7 +200,7 @@ async def otlp_status(self): Status of OTLP endpoint. """ - return CollectStatusResponse(version=self.VERSION, status="ready") + return CollectStatusResponse(status="ready") @intercept_exceptions() async def otlp_receiver( @@ -220,9 +218,9 @@ async def otlp_receiver( # ---------------------------------------------------------------- # except Exception as e: log.error( - "Failed to process OTLP stream from project %s with error %s", + "Failed to process OTLP stream from project %s with error:", request.state.project_id, - str(e), + exc_info=True, ) err_status = ProtoStatus( message="Invalid request body: not a valid OTLP stream." @@ -256,9 +254,9 @@ async def otlp_receiver( # ---------------------------------------------------------------- # except Exception as e: log.error( - "Failed to parse OTLP stream from project %s with error %s", + "Failed to parse OTLP stream from project %s with error:", request.state.project_id, - str(e), + exc_info=True, ) log.error( "OTLP stream: %s", @@ -323,9 +321,9 @@ async def otlp_receiver( # ---------------------------------------------------------------- # except Exception as e: log.error( - "Failed to parse spans from project %s with error %s", + "Failed to parse spans from project %s with error:", request.state.project_id, - str(e), + exc_info=True, ) for otel_span in otel_spans: log.error( @@ -370,9 +368,9 @@ async def otlp_receiver( # ---------------------------------------------------------------- # except Exception as e: log.error( - "Failed to ingest spans from project %s with error %s", + "Failed to ingest spans from project %s with error:", request.state.project_id, - str(e), + exc_info=True, ) for span_dto in span_dtos: log.error( @@ -398,9 +396,9 @@ async def otlp_receiver( # ---------------------------------------------------------------- # except Exception as e: log.warn( - "Failed to create spans from project %s with error %s", + "Failed to create spans from project %s with error:", request.state.project_id, - str(e), + exc_info=True, ) for span in tracing_spans: span: OTelFlatSpan @@ -432,7 +430,7 @@ async def otlp_receiver( async def query_traces( self, request: Request, - query_dto: QueryDTO = Depends(parse_query_request), + query_dto: QueryDTO = Depends(parse_query_from_params_request), format: Literal[ # pylint: disable=W0622 "opentelemetry", "agenta", @@ -463,7 +461,6 @@ async def query_traces( spans = [parse_to_otel_span_dto(span_dto) for span_dto in span_dtos] return OTelTracingResponse( - version=self.VERSION, count=count, spans=spans, ) @@ -490,7 +487,6 @@ async def query_traces( # focus = tree if query_dto.grouping.focus.value == "tree": return AgentaTreesResponse( - version=self.VERSION, count=count, trees=[ AgentaTreeDTO( @@ -518,7 +514,6 @@ async def query_traces( _nodes_by_root[nodes[0].root.id].append( AgentaTreeDTO( - version=self.VERSION, tree=TreeDTO( id=tree_id, type=_types_by_tree[tree_id], @@ -530,7 +525,6 @@ async def query_traces( ) return AgentaRootsResponse( - version=self.VERSION, count=count, roots=[ AgentaRootDTO( @@ -543,7 +537,6 @@ async def query_traces( # focus = node return AgentaNodesResponse( - version=self.VERSION, count=count, nodes=[AgentaNodeDTO(**span.model_dump()) for span in spans], ) @@ -576,8 +569,7 @@ async def query_analytics( **summary.model_dump(), ) - return AnalyticsResponse( - version=self.VERSION, + return OldAnalyticsResponse( count=count, buckets=bucket_dtos, ) @@ -664,4 +656,4 @@ async def delete_traces( node_ids=node_ids, ) - return CollectStatusResponse(version=self.VERSION, status="deleted") + return CollectStatusResponse(status="deleted") diff --git a/api/oss/src/apis/fastapi/observability/utils/processing.py b/api/oss/src/apis/fastapi/observability/utils/processing.py index 75afc0d53c..54c019702e 100644 --- a/api/oss/src/apis/fastapi/observability/utils/processing.py +++ b/api/oss/src/apis/fastapi/observability/utils/processing.py @@ -77,12 +77,12 @@ def _parse_windowing( oldest: Optional[str] = None, newest: Optional[str] = None, - window: Optional[int] = None, + interval: Optional[int] = None, ) -> Optional[WindowingDTO]: _windowing = None if oldest or newest: - _windowing = WindowingDTO(oldest=oldest, newest=newest, window=window) + _windowing = WindowingDTO(oldest=oldest, newest=newest, interval=interval) return _windowing @@ -153,7 +153,7 @@ def _parse_pagination( return _pagination -def parse_query_request( +def parse_query_from_params_request( # GROUPING # - Option 2: Flat query parameters focus: Optional[str] = Query(None), @@ -187,14 +187,14 @@ def parse_analytics_dto( # - Option 2: Flat query parameters oldest: Optional[str] = Query(None), newest: Optional[str] = Query(None), - window: Optional[int] = Query(None), + interval: Optional[int] = Query(None), # FILTERING # - Option 1: Single query parameter as JSON filtering: Optional[str] = Query(None), ) -> AnalyticsDTO: return AnalyticsDTO( grouping=_parse_grouping(focus=focus), - windowing=_parse_windowing(oldest=oldest, newest=newest, window=window), + windowing=_parse_windowing(oldest=oldest, newest=newest, interval=interval), filtering=_parse_filtering(filtering=filtering), ) @@ -466,9 +466,9 @@ def parse_to_agenta_span_dto( def _parse_time_range( - window_text: str, + interval_text: str, ) -> Tuple[datetime, datetime, int]: - quantity, unit = window_text.split("_") + quantity, unit = interval_text.split("_") quantity = int(quantity) today = datetime.now() @@ -476,13 +476,13 @@ def _parse_time_range( if unit == "hours": oldest = newest - timedelta(hours=quantity) - window = 60 # 1 hour - return newest, oldest, window + interval = 60 # 1 hour + return newest, oldest, interval elif unit == "days": oldest = newest - timedelta(days=quantity) - window = 1440 # 1 day - return newest, oldest, window + interval = 1440 # 1 day + return newest, oldest, interval else: raise ValueError(f"Unknown time unit: {unit}") @@ -539,8 +539,8 @@ def parse_legacy_analytics_dto( windowing = None if timeRange: - newest, oldest, window = _parse_time_range(timeRange) - windowing = WindowingDTO(newest=newest, oldest=oldest, window=window) + newest, oldest, interval = _parse_time_range(timeRange) + windowing = WindowingDTO(newest=newest, oldest=oldest, interval=interval) grouping = GroupingDTO(focus="tree") diff --git a/api/oss/src/apis/fastapi/queries/__init__.py b/api/oss/src/apis/fastapi/queries/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/apis/fastapi/queries/models.py b/api/oss/src/apis/fastapi/queries/models.py new file mode 100644 index 0000000000..710b3282d4 --- /dev/null +++ b/api/oss/src/apis/fastapi/queries/models.py @@ -0,0 +1,182 @@ +from typing import Optional, List + +from pydantic import BaseModel + +from oss.src.core.shared.dtos import ( + Reference, + Windowing, +) +from oss.src.core.queries.dtos import ( + Query, + QueryCreate, + QueryEdit, + QueryQuery, + # + QueryVariant, + QueryVariantCreate, + QueryVariantEdit, + QueryVariantQuery, + # + QueryRevision, + QueryRevisionCreate, + QueryRevisionEdit, + QueryRevisionQuery, + QueryRevisionCommit, + QueryRevisionsLog, + # + SimpleQuery, + SimpleQueryCreate, + SimpleQueryEdit, + SimpleQueryQuery, +) + + +# QUERIES ---------------------------------------------------------------------- + + +class QueryCreateRequest(BaseModel): + query: QueryCreate + + +class QueryEditRequest(BaseModel): + query: QueryEdit + + +class QueryQueryRequest(BaseModel): + query: Optional[QueryQuery] = None + # + query_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class QueryResponse(BaseModel): + count: int = 0 + query: Optional[Query] = None + + +class QueriesResponse(BaseModel): + count: int = 0 + queries: List[Query] = [] + + +# QUERY VARIANTS --------------------------------------------------------------- + + +class QueryVariantCreateRequest(BaseModel): + query_variant: QueryVariantCreate + + +class QueryVariantEditRequest(BaseModel): + query_variant: QueryVariantEdit + + +class QueryVariantQueryRequest(BaseModel): + query_variant: Optional[QueryVariantQuery] = None + # + query_refs: Optional[List[Reference]] = None + query_variant_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class QueryVariantForkRequest(BaseModel): + source_query_variant_ref: Reference + target_query_ref: Reference + # + slug: Optional[str] = None + # + name: Optional[str] = None + description: Optional[str] = None + + +class QueryVariantResponse(BaseModel): + count: int = 0 + query_variant: Optional[QueryVariant] = None + + +class QueryVariantsResponse(BaseModel): + count: int = 0 + query_variants: List[QueryVariant] = [] + + +# QUERY REVISIONS -------------------------------------------------------------- + + +class QueryRevisionCreateRequest(BaseModel): + query_revision: QueryRevisionCreate + + +class QueryRevisionEditRequest(BaseModel): + query_revision: QueryRevisionEdit + + +class QueryRevisionQueryRequest(BaseModel): + query_revision: Optional[QueryRevisionQuery] = None + # + query_refs: Optional[List[Reference]] = None + query_variant_refs: Optional[List[Reference]] = None + query_revision_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class QueryRevisionCommitRequest(BaseModel): + query_revision_commit: QueryRevisionCommit + + +class QueryRevisionsLogRequest(BaseModel): + query_revisions: QueryRevisionsLog + + +class QueryRevisionRetrieveRequest(BaseModel): + query_ref: Optional[Reference] = None + query_variant_ref: Optional[Reference] = None + query_revision_ref: Optional[Reference] = None + + +class QueryRevisionResponse(BaseModel): + count: int = 0 + query_revision: Optional[QueryRevision] = None + + +class QueryRevisionsResponse(BaseModel): + count: int = 0 + query_revisions: List[QueryRevision] = [] + + +# SIMPLE QUERIES --------------------------------------------------------------- + + +class SimpleQueryCreateRequest(BaseModel): + query: SimpleQueryCreate + + +class SimpleQueryEditRequest(BaseModel): + query: SimpleQueryEdit + + +class SimpleQueryQueryRequest(BaseModel): + query: Optional[SimpleQueryQuery] = None + # + query_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = False + # + windowing: Optional[Windowing] = None + + +class SimpleQueryResponse(BaseModel): + count: int = 0 + query: Optional[SimpleQuery] = None + + +class SimpleQueriesResponse(BaseModel): + count: int = 0 + queries: List[SimpleQuery] = [] diff --git a/api/oss/src/apis/fastapi/queries/router.py b/api/oss/src/apis/fastapi/queries/router.py new file mode 100644 index 0000000000..3d684f5081 --- /dev/null +++ b/api/oss/src/apis/fastapi/queries/router.py @@ -0,0 +1,989 @@ +from typing import Optional +from uuid import UUID + +from fastapi import APIRouter, Request, status, Depends + +from oss.src.utils.common import is_ee +from oss.src.utils.logging import get_module_logger +from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions +from oss.src.utils.caching import get_cache, set_cache, invalidate_cache + +from oss.src.core.shared.dtos import ( + Reference, +) +from oss.src.core.queries.service import ( + QueriesService, + SimpleQueriesService, +) + +from oss.src.apis.fastapi.queries.models import ( + QueryCreateRequest, + QueryEditRequest, + QueryQueryRequest, + QueryResponse, + QueriesResponse, + # + QueryRevisionCreateRequest, + QueryRevisionEditRequest, + QueryRevisionQueryRequest, + QueryRevisionCommitRequest, + QueryRevisionRetrieveRequest, + QueryRevisionsLogRequest, + QueryRevisionResponse, + QueryRevisionsResponse, + # + SimpleQueryCreateRequest, + SimpleQueryEditRequest, + SimpleQueryQueryRequest, + SimpleQueryResponse, + SimpleQueriesResponse, +) + +from oss.src.apis.fastapi.queries.utils import ( + parse_query_query_request_from_params, + parse_query_query_request_from_body, + merge_query_query_requests, + parse_query_variant_query_request_from_params, + parse_query_variant_query_request_from_body, + merge_query_variant_query_requests, + parse_query_revision_query_request_from_params, + parse_query_revision_query_request_from_body, + merge_query_revision_query_requests, + parse_query_revision_retrieve_request_from_params, + parse_query_revision_retrieve_request_from_body, +) + +if is_ee(): + from ee.src.models.shared_models import Permission + from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + + +log = get_module_logger(__name__) + + +class QueriesRouter: + def __init__(self, *, queries_service: QueriesService): + self.queries_service = queries_service + self.router = APIRouter() + + # QUERIES -------------------------------------------------------------- + + self.router.add_api_route( + "/", + self.create_query, + methods=["POST"], + operation_id="create_query", + status_code=status.HTTP_200_OK, + response_model=QueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}", + self.fetch_query, + methods=["GET"], + operation_id="fetch_query", + status_code=status.HTTP_200_OK, + response_model=QueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}", + self.edit_query, + methods=["PUT"], + operation_id="edit_query", + status_code=status.HTTP_200_OK, + response_model=QueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}/archive", + self.archive_query, + methods=["POST"], + operation_id="archive_query", + status_code=status.HTTP_200_OK, + response_model=QueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}/unarchive", + self.unarchive_query, + methods=["POST"], + operation_id="unarchive_query", + status_code=status.HTTP_200_OK, + response_model=QueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/query", + self.query_queries, + methods=["POST"], + operation_id="query_queries", + status_code=status.HTTP_200_OK, + response_model=QueriesResponse, + response_model_exclude_none=True, + ) + + # QUERY VARIANTS ------------------------------------------------------- + + # TODO: IMPLEMENT ME + + # QUERY REVISIONS ------------------------------------------------------ + + self.router.add_api_route( + "/revisions/retrieve", + self.retrieve_query_revision, + methods=["POST"], + operation_id="retrieve_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/", + self.create_query_revision, + methods=["POST"], + operation_id="create_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{query_revision_id}", + self.fetch_query_revision, + methods=["GET"], + operation_id="fetch_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{query_revision_id}", + self.edit_query_revision, + methods=["PUT"], + operation_id="edit_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{query_revision_id}/archive", + self.archive_query_revision, + methods=["POST"], + operation_id="archive_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/{query_revision_id}/unarchive", + self.unarchive_query_revision, + methods=["POST"], + operation_id="unarchive_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/query", + self.query_query_revisions, + methods=["POST"], + operation_id="query_query_revisions", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionsResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/commit", + self.commit_query_revision, + methods=["POST"], + operation_id="commit_query_revision", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/log", + self.log_query_revisions, + methods=["POST"], + operation_id="log_query_revisions", + status_code=status.HTTP_200_OK, + response_model=QueryRevisionsResponse, + response_model_exclude_none=True, + ) + + # QUERIES ------------------------------------------------------------------ + + @intercept_exceptions() + async def create_query( + self, + request: Request, + *, + query_id: Optional[UUID] = None, + # + query_create_request: QueryCreateRequest, + ) -> QueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query = await self.queries_service.create_query( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + # + query_create=query_create_request.query, + ) + + query_response = QueryResponse( + count=1 if query else 0, + query=query, + ) + + return query_response + + @intercept_exceptions() + @suppress_exceptions(default=QueryResponse()) + async def fetch_query( + self, + request: Request, + *, + query_id: UUID, + ) -> QueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query = await self.queries_service.fetch_query( + project_id=UUID(request.state.project_id), + # + query_ref=Reference(id=query_id), + ) + + query_response = QueryResponse( + count=1 if query else 0, + query=query, + ) + + return query_response + + @intercept_exceptions() + async def edit_query( + self, + request: Request, + *, + query_edit_request: QueryEditRequest, + # + query_id: UUID, + ) -> QueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(query_id) != str(query_edit_request.query.id): + return QueryResponse() + + query = await self.queries_service.edit_query( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_edit=query_edit_request.query, + ) + + query_response = QueryResponse( + count=1 if query else 0, + query=query, + ) + + return query_response + + @intercept_exceptions() + async def archive_query( + self, + request: Request, + *, + query_id: UUID, + ) -> QueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query = await self.queries_service.archive_query( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + ) + + return QueryResponse( + count=1 if query else 0, + query=query, + ) + + @intercept_exceptions() + async def unarchive_query( + self, + request: Request, + *, + query_id: UUID, + ) -> QueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query = await self.queries_service.unarchive_query( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + ) + + query_response = QueryResponse( + count=1 if query else 0, + query=query, + ) + + return query_response + + @intercept_exceptions() + @suppress_exceptions(default=QueriesResponse()) + async def query_queries( + self, + request: Request, + *, + query_request_params: QueryQueryRequest = Depends( + parse_query_query_request_from_params + ), + ) -> QueriesResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + body_json = None + query_request_body = None + + try: + body_json = await request.json() + + if body_json: + query_request_body = parse_query_query_request_from_body(**body_json) + + except: # pylint: disable=bare-except + pass + + query_query_request = query_request_params or query_request_body + + queries = await self.queries_service.query_queries( + project_id=UUID(request.state.project_id), + # + query=query_query_request.query if query_query_request else None, + # + query_refs=query_query_request.query_refs if query_query_request else None, + # + include_archived=( + query_query_request.include_archived if query_query_request else None + ), + # + windowing=query_query_request.windowing if query_query_request else None, + ) + + queries_response = QueriesResponse( + count=len(queries), + queries=queries, + ) + + return queries_response + + # QUERY REVISIONS ---------------------------------------------------------- + + @intercept_exceptions() + async def create_query_revision( + self, + request: Request, + *, + query_revision_create_request: QueryRevisionCreateRequest, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revision = await self.queries_service.create_query_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_revision_create=query_revision_create_request.query_revision, + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + @intercept_exceptions() + @suppress_exceptions(default=QueryRevisionResponse()) + async def fetch_query_revision( + self, + request: Request, + *, + query_revision_id: UUID, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revision = await self.queries_service.fetch_query_revision( + project_id=UUID(request.state.project_id), + # + query_revision_ref=Reference(id=query_revision_id), + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + @intercept_exceptions() + async def edit_query_revision( + self, + request: Request, + *, + query_revision_edit_request: QueryRevisionEditRequest, + # + query_revision_id: UUID, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(query_revision_id) != str(query_revision_edit_request.query_revision.id): + return QueryRevisionResponse() + + query_revision = await self.queries_service.edit_query_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_revision_edit=query_revision_edit_request.query_revision, + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + @intercept_exceptions() + async def archive_query_revision( + self, + request: Request, + *, + query_revision_id: UUID, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revision = await self.queries_service.archive_query_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_revision_id=query_revision_id, + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + @intercept_exceptions() + async def unarchive_query_revision( + self, + request: Request, + *, + query_revision_id: UUID, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revision = await self.queries_service.unarchive_query_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_revision_id=query_revision_id, + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + @intercept_exceptions() + @suppress_exceptions(default=QueryRevisionsResponse()) + async def query_query_revisions( + self, + request: Request, + *, + query_revision_query_request: QueryRevisionQueryRequest, + ) -> QueryRevisionsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revisions = await self.queries_service.query_query_revisions( + project_id=UUID(request.state.project_id), + # + query_revision=query_revision_query_request.query_revision, + # + query_refs=query_revision_query_request.query_refs, + query_variant_refs=query_revision_query_request.query_variant_refs, + query_revision_refs=query_revision_query_request.query_revision_refs, + # + include_archived=query_revision_query_request.include_archived, + # + windowing=query_revision_query_request.windowing, + ) + + query_revisions_response = QueryRevisionsResponse( + count=len(query_revisions), + query_revisions=query_revisions, + ) + + return query_revisions_response + + @intercept_exceptions() + async def commit_query_revision( + self, + request: Request, + *, + query_revision_commit_request: QueryRevisionCommitRequest, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revision = await self.queries_service.commit_query_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_revision_commit=query_revision_commit_request.query_revision_commit, + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + @intercept_exceptions() + @suppress_exceptions(default=QueryRevisionsResponse()) + async def log_query_revisions( + self, + request: Request, + *, + query_revisions_log_request: QueryRevisionsLogRequest, + ) -> QueryRevisionsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revisions = await self.queries_service.log_query_revisions( + project_id=UUID(request.state.project_id), + # + query_revisions_log=query_revisions_log_request.query_revisions, + ) + + revisions_response = QueryRevisionsResponse( + count=len(query_revisions), + query_revisions=query_revisions, + ) + + return revisions_response + + @intercept_exceptions() + @suppress_exceptions(default=QueryRevisionResponse()) + async def retrieve_query_revision( + self, + request: Request, + *, + query_revision_retrieve_request: QueryRevisionRetrieveRequest, + ) -> QueryRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + query_revision = await self.queries_service.fetch_query_revision( + project_id=UUID(request.state.project_id), + # + query_ref=query_revision_retrieve_request.query_ref, + query_variant_ref=query_revision_retrieve_request.query_variant_ref, + query_revision_ref=query_revision_retrieve_request.query_revision_ref, + ) + + query_revision_response = QueryRevisionResponse( + count=1 if query_revision else 0, + query_revision=query_revision, + ) + + return query_revision_response + + +class SimpleQueriesRouter: + def __init__( + self, + *, + simple_queries_service: SimpleQueriesService, + ): + self.simple_queries_service = simple_queries_service + + self.router = APIRouter() + + # SIMPLE QUERIES ------------------------------------------------------- + + self.router.add_api_route( + "/", + self.create_simple_query, + methods=["POST"], + operation_id="create_simple_query", + status_code=status.HTTP_200_OK, + response_model=SimpleQueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}", + self.fetch_simple_query, + methods=["GET"], + operation_id="fetch_simple_query", + status_code=status.HTTP_200_OK, + response_model=SimpleQueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}", + self.edit_simple_query, + methods=["PUT"], + operation_id="edit_simple_query", + status_code=status.HTTP_200_OK, + response_model=SimpleQueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}/archive", + self.archive_simple_query, + methods=["POST"], + operation_id="archive_simple_query", + status_code=status.HTTP_200_OK, + response_model=SimpleQueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/{query_id}/unarchive", + self.unarchive_simple_query, + methods=["POST"], + operation_id="unarchive_simple_query", + status_code=status.HTTP_200_OK, + response_model=SimpleQueryResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/query", + self.query_simple_queries, + methods=["POST"], + operation_id="query_simple_queries", + status_code=status.HTTP_200_OK, + response_model=SimpleQueriesResponse, + response_model_exclude_none=True, + ) + + # SIMPLE QUERIES ----------------------------------------------------------- + + @intercept_exceptions() + async def create_simple_query( + self, + request: Request, + *, + query_id: Optional[UUID] = None, + # + simple_query_create_request: SimpleQueryCreateRequest, + ) -> SimpleQueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_query = await self.simple_queries_service.create( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + # + simple_query_create=simple_query_create_request.query, + ) + + simple_query_response = SimpleQueryResponse( + count=1 if simple_query else 0, + query=simple_query, + ) + + return simple_query_response + + @intercept_exceptions() + @suppress_exceptions(default=SimpleQueryResponse()) + async def fetch_simple_query( + self, + request: Request, + *, + query_id: UUID, + ) -> SimpleQueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_query = await self.simple_queries_service.fetch( + project_id=UUID(request.state.project_id), + # + query_id=query_id, + ) + + simple_query_response = SimpleQueryResponse( + count=1 if simple_query else 0, + query=simple_query, + ) + + return simple_query_response + + @intercept_exceptions() + async def edit_simple_query( + self, + request: Request, + *, + query_id: UUID, + # + simple_query_edit_request: SimpleQueryEditRequest, + ) -> SimpleQueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_query = await self.simple_queries_service.edit( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + # + simple_query_edit=simple_query_edit_request.query, + ) + + simple_query_response = SimpleQueryResponse( + count=1 if simple_query else 0, + query=simple_query, + ) + + return simple_query_response + + @intercept_exceptions() + async def archive_simple_query( + self, + request: Request, + *, + query_id: UUID, + ) -> SimpleQueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_query = await self.simple_queries_service.archive( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + ) + + simple_query_response = SimpleQueryResponse( + count=1 if simple_query else 0, + query=simple_query, + ) + + return simple_query_response + + @intercept_exceptions() + async def unarchive_simple_query( + self, + request: Request, + *, + query_id: UUID, + ) -> SimpleQueryResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_query = await self.simple_queries_service.unarchive( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + query_id=query_id, + ) + + simple_query_response = SimpleQueryResponse( + count=1 if simple_query else 0, + query=simple_query, + ) + + return simple_query_response + + @intercept_exceptions() + @suppress_exceptions(default=SimpleQueriesResponse()) + async def query_simple_queries( + self, + *, + request: Request, + # + simple_query_query_request: SimpleQueryQueryRequest, + ) -> SimpleQueriesResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + # + permission=Permission.VIEW_QUERIES, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_queries = await self.simple_queries_service.query( + project_id=UUID(request.state.project_id), + # + query=simple_query_query_request.query, + # + query_refs=simple_query_query_request.query_refs, + # + include_archived=simple_query_query_request.include_archived, + # + windowing=simple_query_query_request.windowing, + ) + + simple_queries_response = SimpleQueriesResponse( + count=len(simple_queries), + queries=simple_queries, + ) + + return simple_queries_response diff --git a/api/oss/src/apis/fastapi/queries/utils.py b/api/oss/src/apis/fastapi/queries/utils.py new file mode 100644 index 0000000000..99ecc5a0bb --- /dev/null +++ b/api/oss/src/apis/fastapi/queries/utils.py @@ -0,0 +1,622 @@ +from typing import Optional, Literal, List +from uuid import UUID +from datetime import datetime + +from fastapi import Query + +from oss.src.utils.logging import get_module_logger + +from oss.src.core.shared.dtos import ( + Windowing, + Reference, +) +from oss.src.core.queries.dtos import ( + # QueryFlags, + # + QueryQuery, + QueryVariantQuery, + QueryRevisionQuery, +) + +from oss.src.apis.fastapi.shared.utils import ( + parse_metadata, +) +from oss.src.apis.fastapi.queries.models import ( + QueryQueryRequest, + QueryVariantQueryRequest, + QueryRevisionQueryRequest, + QueryRevisionRetrieveRequest, +) + + +log = get_module_logger(__name__) + + +def parse_query_query_request_from_params( + query_id: Optional[UUID] = Query(None), + query_ids: Optional[List[UUID]] = Query(None), + query_slug: Optional[str] = Query(None), + query_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> QueryQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = _flags # QueryFlags(**_flags) if _flags else None + + query = ( + QueryQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + query_refs = ( + ( + [ + Reference( + id=query_id, + slug=query_slug, + ) + ] + if query_id or query_slug + else [] + ) + + ( + [ + Reference( + id=query_id, + slug=query_slug, + ) + for query_id, query_slug in zip( + query_ids, + query_slugs, + ) + ] + if query_ids and query_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_query_query_request_from_body( + query=query, + # + query_refs=query_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + +def parse_query_query_request_from_body( + query: Optional[QueryQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, +) -> QueryQueryRequest: + query_query_request = None + + try: + query_query_request = QueryQueryRequest( + query=query, + # + query_refs=query_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + except Exception as e: # pylint: disable=broad-except + query_query_request = QueryQueryRequest() + + return query_query_request + + +def merge_query_query_requests( + query_request_params: Optional[QueryQueryRequest] = None, + query_request_body: Optional[QueryQueryRequest] = None, +) -> QueryQueryRequest: + if query_request_params and not query_request_body: + return query_request_params + + if not query_request_params and query_request_body: + return query_request_body + + if query_request_params and query_request_body: + return QueryQueryRequest( + query=query_request_body.query or query_request_params.query, + # + query_refs=query_request_body.query_refs or query_request_params.query_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return QueryQueryRequest() + + +def parse_query_variant_query_request_from_params( + query_id: Optional[UUID] = Query(None), + query_ids: Optional[List[UUID]] = Query(None), + query_slug: Optional[str] = Query(None), + query_slugs: Optional[List[str]] = Query(None), + # + query_variant_id: Optional[UUID] = Query(None), + query_variant_ids: Optional[List[UUID]] = Query(None), + query_variant_slug: Optional[str] = Query(None), + query_variant_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> QueryVariantQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = _flags # QueryFlags(**_flags) if _flags else None + + query_variant = ( + QueryVariantQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + query_refs = ( + ( + [ + Reference( + id=query_id, + slug=query_slug, + ) + ] + if query_id or query_slug + else [] + ) + + ( + [ + Reference( + id=query_id, + slug=query_slug, + ) + for query_id, query_slug in zip( + query_ids, + query_slugs, + ) + ] + if query_ids and query_slugs + else [] + ) + ) or None + + query_variant_refs = ( + ( + [ + Reference( + id=query_variant_id, + slug=query_variant_slug, + ) + ] + if query_variant_id or query_variant_slug + else [] + ) + + ( + [ + Reference( + id=query_variant_id, + slug=query_variant_slug, + ) + for query_variant_id, query_variant_slug in zip( + query_variant_ids, + query_variant_slugs, + ) + ] + if query_variant_ids and query_variant_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_query_variant_query_request_from_body( + query_variant=query_variant, + # + query_refs=query_refs or None, + query_variant_refs=query_variant_refs or None, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + +def parse_query_variant_query_request_from_body( + query_variant: Optional[QueryVariantQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + query_variant_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, +) -> QueryVariantQueryRequest: + query_variant_query_request = None + + try: + query_variant_query_request = QueryVariantQueryRequest( + query_variant=query_variant, + # + query_refs=query_refs, + query_variant_refs=query_variant_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + except Exception as e: # pylint: disable=broad-except + query_variant_query_request = QueryVariantQueryRequest() + + return query_variant_query_request + + +def merge_query_variant_query_requests( + query_request_params: Optional[QueryVariantQueryRequest] = None, + query_request_body: Optional[QueryVariantQueryRequest] = None, +) -> QueryVariantQueryRequest: + if query_request_params and not query_request_body: + return query_request_params + + if not query_request_params and query_request_body: + return query_request_body + + if query_request_params and query_request_body: + return QueryVariantQueryRequest( + query_variant=query_request_body.query_variant + or query_request_params.query_variant, + # + query_refs=query_request_body.query_refs or query_request_params.query_refs, + query_variant_refs=query_request_body.query_variant_refs + or query_request_params.query_variant_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return QueryVariantQueryRequest() + + +def parse_query_revision_query_request_from_params( + query_id: Optional[UUID] = Query(None), + query_ids: Optional[List[UUID]] = Query(None), + query_slug: Optional[str] = Query(None), + query_slugs: Optional[List[str]] = Query(None), + # + query_variant_id: Optional[UUID] = Query(None), + query_variant_ids: Optional[List[UUID]] = Query(None), + query_variant_slug: Optional[str] = Query(None), + query_variant_slugs: Optional[List[str]] = Query(None), + # + query_revision_id: Optional[UUID] = Query(None), + query_revision_ids: Optional[List[UUID]] = Query(None), + query_revision_slug: Optional[str] = Query(None), + query_revision_slugs: Optional[List[str]] = Query(None), + query_revision_version: Optional[str] = Query(None), + query_revision_versions: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> QueryRevisionQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = _flags # QueryFlags(**_flags) if _flags else None + + query_revision = ( + QueryRevisionQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + query_refs = ( + [ + Reference( + id=query_id, + slug=query_slug, + ) + ] + if query_id or query_slug + else [] + ) + ( + [ + Reference( + id=query_id, + slug=query_slug, + ) + for query_id, query_slug in zip( + query_ids, + query_slugs, + ) + ] + if query_ids and query_slugs + else [] + ) + + query_variant_refs = ( + [ + Reference( + id=query_variant_id, + slug=query_variant_slug, + ) + ] + if query_variant_id or query_variant_slug + else [] + ) + ( + [ + Reference( + id=query_variant_id, + slug=query_variant_slug, + ) + for query_variant_id, query_variant_slug in zip( + query_variant_ids, + query_variant_slugs, + ) + ] + if query_variant_ids and query_variant_slugs + else [] + ) + + query_revision_refs = ( + [ + Reference( + id=query_revision_id, + slug=query_revision_slug, + version=query_revision_version, + ) + ] + if query_revision_id or query_revision_slug or query_revision_version + else [] + ) + ( + [ + Reference( + id=query_revision_id, + slug=query_revision_slug, + version=query_revision_version, + ) + for query_revision_id, query_revision_slug, query_revision_version in zip( + query_revision_ids, + query_revision_slugs, + query_revision_versions, + ) + ] + if query_revision_ids and query_revision_slugs and query_revision_versions + else [] + ) + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_query_revision_query_request_from_body( + query_revision=query_revision, + # + query_refs=query_refs, + query_variant_refs=query_variant_refs, + query_revision_refs=query_revision_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + +def parse_query_revision_query_request_from_body( + query_revision: Optional[QueryRevisionQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + query_variant_refs: Optional[List[Reference]] = None, + query_revision_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, +) -> QueryRevisionQueryRequest: + query_revision_query_request = None + + try: + query_revision_query_request = QueryRevisionQueryRequest( + query_revision=query_revision, + # + query_refs=query_refs, + query_variant_refs=query_variant_refs, + query_revision_refs=query_revision_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + except Exception as e: # pylint: disable=broad-except + log.warn(e) + + query_revision_query_request = QueryRevisionQueryRequest() + + return query_revision_query_request + + +def merge_query_revision_query_requests( + query_request_params: Optional[QueryRevisionQueryRequest] = None, + query_request_body: Optional[QueryRevisionQueryRequest] = None, +) -> QueryRevisionQueryRequest: + if query_request_params and not query_request_body: + return query_request_params + + if not query_request_params and query_request_body: + return query_request_body + + if query_request_params and query_request_body: + return QueryRevisionQueryRequest( + query_revision=query_request_body.query_revision + or query_request_params.query_revision, + # + query_refs=query_request_body.query_refs or query_request_params.query_refs, + query_variant_refs=query_request_body.query_variant_refs + or query_request_params.query_variant_refs, + query_revision_refs=query_request_body.query_revision_refs + or query_request_params.query_revision_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return QueryRevisionQueryRequest() + + +def parse_query_revision_retrieve_request_from_params( + query_id: Optional[UUID] = Query(None), + query_slug: Optional[str] = Query(None), + # + query_variant_id: Optional[UUID] = Query(None), + query_variant_slug: Optional[str] = Query(None), + # + query_revision_id: Optional[UUID] = Query(None), + query_revision_slug: Optional[str] = Query(None), + query_revision_version: Optional[str] = Query(None), +): + query_ref = ( + Reference( + id=query_id, + slug=query_slug, + ) + if query_id or query_slug + else None + ) + + query_variant_ref = ( + Reference( + id=query_variant_id, + slug=query_variant_slug, + ) + if query_variant_id or query_variant_slug + else None + ) + + query_revision_ref = ( + Reference( + id=query_revision_id, + slug=query_revision_slug, + version=query_revision_version, + ) + if query_revision_id or query_revision_slug or query_revision_version + else None + ) + + return parse_query_revision_retrieve_request_from_body( + query_ref=query_ref, + query_variant_ref=query_variant_ref, + query_revision_ref=query_revision_ref, + ) + + +def parse_query_revision_retrieve_request_from_body( + query_ref: Optional[Reference] = None, + query_variant_ref: Optional[Reference] = None, + query_revision_ref: Optional[Reference] = None, +) -> QueryRevisionRetrieveRequest: + return QueryRevisionRetrieveRequest( + query_ref=query_ref, + query_variant_ref=query_variant_ref, + query_revision_ref=query_revision_ref, + ) diff --git a/api/oss/src/apis/fastapi/shared/models.py b/api/oss/src/apis/fastapi/shared/models.py deleted file mode 100644 index eee02072de..0000000000 --- a/api/oss/src/apis/fastapi/shared/models.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class VersionedModel(BaseModel): - version: Optional[str] = None diff --git a/api/oss/src/apis/fastapi/shared/utils.py b/api/oss/src/apis/fastapi/shared/utils.py new file mode 100644 index 0000000000..b9accb73b9 --- /dev/null +++ b/api/oss/src/apis/fastapi/shared/utils.py @@ -0,0 +1,34 @@ +from typing import Optional, Tuple +from json import loads + +from oss.src.core.shared.dtos import ( + Flags, + Tags, + Meta, +) + + +def parse_metadata( + flags: Optional[str] = None, + tags: Optional[str] = None, + meta: Optional[str] = None, +) -> Tuple[Optional[Flags], Optional[Tags], Optional[Meta],]: + _flags = None + try: + _flags = loads(flags) if flags else None + except Exception: # pylint: disable=broad-exception-caught + pass + + _tags = None + try: + _tags = loads(tags) if tags else None + except Exception: # pylint: disable=broad-exception-caught + pass + + _meta = None + try: + _meta = loads(meta) if meta else None + except Exception: # pylint: disable=broad-exception-caught + pass + + return _flags, _tags, _meta diff --git a/api/oss/src/apis/fastapi/testcases/__init__.py b/api/oss/src/apis/fastapi/testcases/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/apis/fastapi/testcases/models.py b/api/oss/src/apis/fastapi/testcases/models.py new file mode 100644 index 0000000000..a6a1c76e27 --- /dev/null +++ b/api/oss/src/apis/fastapi/testcases/models.py @@ -0,0 +1,32 @@ +from typing import Optional, List +from uuid import UUID + +from pydantic import BaseModel + +from oss.src.core.shared.dtos import ( + Windowing, +) +from oss.src.core.testcases.dtos import ( + Testcase, +) + + +# TESTCASES -------------------------------------------------------------------- + + +class TestcasesQueryRequest(BaseModel): + testcase_ids: Optional[List[UUID]] = None + # + testset_id: Optional[UUID] = None + # + windowing: Optional[Windowing] = None + + +class TestcaseResponse(BaseModel): + count: int = 0 + testcase: Optional[Testcase] = None + + +class TestcasesResponse(BaseModel): + count: int = 0 + testcases: List[Testcase] = [] diff --git a/api/oss/src/apis/fastapi/testcases/router.py b/api/oss/src/apis/fastapi/testcases/router.py new file mode 100644 index 0000000000..e7fc999aa5 --- /dev/null +++ b/api/oss/src/apis/fastapi/testcases/router.py @@ -0,0 +1,137 @@ +from uuid import UUID + +from fastapi import APIRouter, Request, status + +from oss.src.utils.common import is_ee +from oss.src.utils.logging import get_module_logger +from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions +from oss.src.utils.caching import get_cache, set_cache, invalidate_cache + +from oss.src.core.testcases.service import ( + TestcasesService, +) + +from oss.src.apis.fastapi.testcases.models import ( + TestcasesQueryRequest, + TestcaseResponse, + TestcasesResponse, +) + +if is_ee(): + from ee.src.models.shared_models import Permission + from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + + +log = get_module_logger(__name__) + + +class TestcasesRouter: + """ + FastAPI router for testcase endpoints. + """ + + def __init__( + self, + *, + testcases_service: TestcasesService, + ): + self.testcases_service = testcases_service + self.router = APIRouter() + + # TESTCASES ------------------------------------------------------------ + + self.router.add_api_route( + "/{testcase_id}", + self.fetch_testcase, + methods=["GET"], + status_code=status.HTTP_200_OK, + response_model=TestcaseResponse, + ) + + self.router.add_api_route( + "/query", + self.query_testcases, + methods=["POST"], + status_code=status.HTTP_200_OK, + response_model=TestcasesResponse, + ) + + # TESTCASES ---------------------------------------------------------------- + + @intercept_exceptions() + @suppress_exceptions(default=TestcaseResponse()) + async def fetch_testcase( + self, + request: Request, + *, + testcase_id: UUID, + ) -> TestcaseResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_TESTSETS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testcases = await self.testcases_service.fetch_testcases( + project_id=UUID(request.state.project_id), + # + testcase_ids=[testcase_id], + ) + + testcase = testcases[0] if len(testcases) > 0 else None + + testcase_response = TestcaseResponse( + count=1 if testcase else 0, + testcase=testcase, + ) + + return testcase_response + + @intercept_exceptions() + @suppress_exceptions(default=TestcasesResponse()) + async def list_testcases( + self, + request: Request, + ) -> TestcasesResponse: + testcase_query_request = TestcasesQueryRequest() + + return await self.query_testcases( + request=request, + # + testcases_query_request=testcase_query_request, + ) + + @intercept_exceptions() + @suppress_exceptions(default=TestcasesResponse()) + async def query_testcases( + self, + request: Request, + *, + testcases_query_request: TestcasesQueryRequest, + ) -> TestcasesResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_TESTSETS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testcases = await self.testcases_service.fetch_testcases( + project_id=UUID(request.state.project_id), + # + testcase_ids=testcases_query_request.testcase_ids, + # + testset_id=testcases_query_request.testset_id, + # + windowing=testcases_query_request.windowing, + ) + + testcase_response = TestcasesResponse( + count=len(testcases), + testcases=testcases if testcases else [], + ) + + return testcase_response diff --git a/api/oss/src/apis/fastapi/testsets/models.py b/api/oss/src/apis/fastapi/testsets/models.py index 8afeb4df7d..df293019b4 100644 --- a/api/oss/src/apis/fastapi/testsets/models.py +++ b/api/oss/src/apis/fastapi/testsets/models.py @@ -1,109 +1,172 @@ from typing import Optional, List -from uuid import UUID from pydantic import BaseModel from oss.src.core.shared.dtos import ( - Identifier, - Slug, - Lifecycle, - Header, - Tags, - Meta, Windowing, Reference, ) - from oss.src.core.testsets.dtos import ( - TestsetRevisionData, - TestsetFlags, + Testset, + TestsetCreate, + TestsetEdit, + TestsetQuery, + TestsetLog, + # + TestsetVariant, + TestsetVariantCreate, + TestsetVariantEdit, + TestsetVariantQuery, + # + TestsetRevision, + TestsetRevisionQuery, + TestsetRevisionCreate, + TestsetRevisionEdit, + TestsetRevisionCommit, + # + SimpleTestset, + SimpleTestsetCreate, + SimpleTestsetEdit, + SimpleTestsetQuery, ) -from oss.src.core.testcases.dtos import Testcase +# TESTSETS --------------------------------------------------------------------- -class SimpleTestset( - Identifier, - Slug, - Lifecycle, - Header, -): - flags: Optional[TestsetFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - data: Optional[TestsetRevisionData] = None +class TestsetCreateRequest(BaseModel): + testset: TestsetCreate -class SimpleTestsetCreate( - Slug, - Header, -): - # flags: Optional[TestsetFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class TestsetEditRequest(BaseModel): + testset: TestsetEdit - data: Optional[TestsetRevisionData] = None +class TestsetQueryRequest(BaseModel): + testset: Optional[TestsetQuery] = None + # + testset_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None -class SimpleTestsetEdit( - Identifier, - Header, -): - # flags: Optional[TestsetFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - data: Optional[TestsetRevisionData] = None +class TestsetLogRequest(BaseModel): + testset: TestsetLog -class SimpleTestsetQuery(BaseModel): - # flags: Optional[TestsetFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class TestsetResponse(BaseModel): + count: int = 0 + testset: Optional[Testset] = None -class SimpleTestsetCreateRequest(BaseModel): - testset: SimpleTestsetCreate +class TestsetsResponse(BaseModel): + count: int = 0 + testsets: List[Testset] = [] -class SimpleTestsetEditRequest(BaseModel): - testset: SimpleTestsetEdit +# TESTSET VARIANTS ------------------------------------------------------------- -class SimpleTestsetQueryRequest(BaseModel): - testset: Optional[SimpleTestsetQuery] = None +class TestsetVariantCreateRequest(BaseModel): + testset_variant: TestsetVariantCreate + + +class TestsetVariantEditRequest(BaseModel): + testset_variant: TestsetVariantEdit + + +class TestsetVariantQueryRequest(BaseModel): + testset_variant: Optional[TestsetVariantQuery] = None # testset_refs: Optional[List[Reference]] = None + testset_variant_refs: Optional[List[Reference]] = None # include_archived: Optional[bool] = None # windowing: Optional[Windowing] = None -class SimpleTestsetResponse(BaseModel): +class TestsetVariantResponse(BaseModel): count: int = 0 - testset: Optional[SimpleTestset] = None + testset_variant: Optional[TestsetVariant] = None -class SimpleTestsetsResponse(BaseModel): +class TestsetVariantsResponse(BaseModel): count: int = 0 - testsets: List[SimpleTestset] = [] + testset_variants: List[TestsetVariant] = [] + +# TESTSET REVISIONS ------------------------------------------------------------ -class TestcasesQueryRequest(BaseModel): - testcase_ids: Optional[List[UUID]] = None + +class TestsetRevisionCreateRequest(BaseModel): + testset_revision: TestsetRevisionCreate + + +class TestsetRevisionEditRequest(BaseModel): + testset_revision: TestsetRevisionEdit + + +class TestsetRevisionQueryRequest(BaseModel): + testset_revision: Optional[TestsetRevisionQuery] = None + # + testset_refs: Optional[List[Reference]] = None + testset_variant_refs: Optional[List[Reference]] = None + testset_revision_refs: Optional[List[Reference]] = None # - testset_id: Optional[UUID] = None + include_archived: Optional[bool] = None # windowing: Optional[Windowing] = None -class TestcaseResponse(BaseModel): +class TestsetRevisionCommitRequest(BaseModel): + testset_revision_commit: TestsetRevisionCommit + + +class TestsetRevisionRetrieveRequest(BaseModel): + testset_ref: Optional[Reference] = None + testset_variant_ref: Optional[Reference] = None + testset_revision_ref: Optional[Reference] = None + + +class TestsetRevisionResponse(BaseModel): + count: int = 0 + testset_revision: Optional[TestsetRevision] = None + + +class TestsetRevisionsResponse(BaseModel): + count: int = 0 + testset_revisions: List[TestsetRevision] = [] + + +# SIMPLE TESTSETS -------------------------------------------------------------- + + +class SimpleTestsetCreateRequest(BaseModel): + testset: SimpleTestsetCreate + + +class SimpleTestsetEditRequest(BaseModel): + testset: SimpleTestsetEdit + + +class SimpleTestsetQueryRequest(BaseModel): + testset: Optional[SimpleTestsetQuery] = None + # + testset_refs: Optional[List[Reference]] = None + # + include_archived: Optional[bool] = None + # + windowing: Optional[Windowing] = None + + +class SimpleTestsetResponse(BaseModel): count: int = 0 - testcase: Optional[Testcase] = None + testset: Optional[SimpleTestset] = None -class TestcasesResponse(BaseModel): +class SimpleTestsetsResponse(BaseModel): count: int = 0 - testcases: List[Testcase] = [] + testsets: List[SimpleTestset] = [] diff --git a/api/oss/src/apis/fastapi/testsets/router.py b/api/oss/src/apis/fastapi/testsets/router.py index f8da6424ef..1e5727d212 100644 --- a/api/oss/src/apis/fastapi/testsets/router.py +++ b/api/oss/src/apis/fastapi/testsets/router.py @@ -1,36 +1,37 @@ -from typing import Optional, List, Literal, Dict +from typing import Optional, List, Literal, Dict, Any from uuid import uuid4, UUID from json import loads, JSONDecodeError from io import BytesIO -### import orjson import pandas as pd - -### - from pydantic import ValidationError -from fastapi.responses import StreamingResponse -from fastapi import APIRouter, Request, status, HTTPException, UploadFile, File, Form - -from oss.src.utils.helpers import get_slug_from_name_and_id -from oss.src.services.db_manager import fetch_testset_by_id -from oss.src.models.db_models import TestSetDB - -from fastapi import APIRouter, Request, status, HTTPException, UploadFile, File, Form +from fastapi.responses import StreamingResponse +from fastapi import ( + APIRouter, + Request, + status, + UploadFile, + File, + Form, + Depends, + HTTPException, +) from oss.src.utils.common import is_ee from oss.src.utils.logging import get_module_logger from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions from oss.src.utils.caching import get_cache, set_cache, invalidate_cache -from oss.src.core.shared.dtos import Reference -from oss.src.core.testsets.dtos import TestsetFlags, TestsetRevisionData -from oss.src.core.testsets.service import TestsetsService -from oss.src.core.testcases.dtos import Testcase - +from oss.src.core.shared.dtos import ( + Reference, +) +from oss.src.core.testcases.dtos import ( + Testcase, +) from oss.src.core.testsets.dtos import ( + TestsetFlags, Testset, TestsetCreate, TestsetEdit, @@ -41,38 +42,58 @@ TestsetVariantEdit, TestsetVariantQuery, # + TestsetRevisionData, TestsetRevision, TestsetRevisionCreate, TestsetRevisionEdit, TestsetRevisionQuery, TestsetRevisionCommit, -) -from oss.src.apis.fastapi.testsets.models import ( + # SimpleTestset, SimpleTestsetCreate, SimpleTestsetEdit, SimpleTestsetQuery, +) +from oss.src.core.testsets.service import ( + TestsetsService, + SimpleTestsetsService, +) + +from oss.src.apis.fastapi.testsets.models import ( + TestsetCreateRequest, + TestsetEditRequest, + TestsetQueryRequest, + TestsetLogRequest, + TestsetResponse, + TestsetsResponse, + # + TestsetVariantCreateRequest, + TestsetVariantEditRequest, + TestsetVariantQueryRequest, + TestsetVariantResponse, + TestsetVariantsResponse, + # + TestsetRevisionCreateRequest, + TestsetRevisionEditRequest, + TestsetRevisionQueryRequest, + TestsetRevisionRetrieveRequest, + TestsetRevisionCommitRequest, + TestsetRevisionResponse, + TestsetRevisionsResponse, # SimpleTestsetCreateRequest, SimpleTestsetEditRequest, SimpleTestsetQueryRequest, - # SimpleTestsetResponse, SimpleTestsetsResponse, - # - TestcasesQueryRequest, - # - TestcaseResponse, - TestcasesResponse, ) - from oss.src.apis.fastapi.testsets.utils import ( + parse_testset_revision_retrieve_request_from_params, + parse_testset_revision_retrieve_request_from_body, + # csv_file_to_json_array, json_file_to_json_array, json_array_to_json_object, - json_array_to_csv_file, - json_array_to_json_file, - format_validation_error, validate_testset_limits, TESTSETS_SIZE_EXCEPTION, TESTSETS_SIZE_LIMIT, @@ -85,698 +106,1078 @@ log = get_module_logger(__name__) -# --- LATER -# TODO: ADD DEDUPLICATION AS OPTIONAL -# TODO: CLEAN UP ! - - -class SimpleTestsetsRouter: - VERSION = "1.0.0" +class TestsetsRouter: TESTCASES_FLAGS = TestsetFlags( has_testcases=True, - has_links=False, + has_traces=False, ) - def __init__( - self, - *, - testsets_service: TestsetsService, - ): + def __init__(self, *, testsets_service: TestsetsService): self.testsets_service = testsets_service self.router = APIRouter() - # POST /api/preview/simple/testsets/ + # TESTSETS ------------------------------------------------------------- + self.router.add_api_route( "/", - self.create_simple_testset, + self.create_testset, methods=["POST"], - operation_id="create_simple_testset", + operation_id="create_testset", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetResponse, response_model_exclude_none=True, ) - # GET /api/preview/simple/testsets/{testset_id} self.router.add_api_route( "/{testset_id}", - self.fetch_simple_testset, + self.fetch_testset, methods=["GET"], - operation_id="fetch_simple_testset", + operation_id="fetch_testset", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetResponse, response_model_exclude_none=True, ) - # PUT /api/preview/simple/testsets/{testset_id} self.router.add_api_route( "/{testset_id}", - self.edit_simple_testset, + self.edit_testset, methods=["PUT"], - operation_id="edit_simple_testset", + operation_id="edit_testset", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/{testset_id}/archive self.router.add_api_route( "/{testset_id}/archive", - self.archive_simple_testset, + self.archive_testset, methods=["POST"], - operation_id="archive_simple_testset", + operation_id="archive_testset", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/{testset_id}/unarchive self.router.add_api_route( "/{testset_id}/unarchive", - self.unarchive_simple_testset, + self.unarchive_testset, methods=["POST"], - operation_id="unarchive_simple_testset", + operation_id="unarchive_testset", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetResponse, response_model_exclude_none=True, ) - # GET /api/preview/simple/testsets/ self.router.add_api_route( - "/", - self.list_simple_testsets, - methods=["GET"], - operation_id="list_simple_testsets", + "/query", + self.query_testsets, + methods=["POST"], + operation_id="query_testsets", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetsResponse, + response_model=TestsetsResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/query + # TESTSET VARIANTS ----------------------------------------------------- + self.router.add_api_route( - "/query", - self.query_simple_testsets, + "/variants/", + self.create_testset_variant, methods=["POST"], - operation_id="query_simple_testsets", + operation_id="create_testset_variant", + status_code=status.HTTP_201_CREATED, + response_model=TestsetVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{testset_variant_id}", + self.fetch_testset_variant, + methods=["GET"], + operation_id="fetch_testset_variant", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetsResponse, + response_model=TestsetVariantResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/upload self.router.add_api_route( - "/upload", - self.create_simple_testset_from_file, + "/variants/{testset_variant_id}", + self.edit_testset_variant, + methods=["PUT"], + operation_id="edit_testset_variant", + status_code=status.HTTP_200_OK, + response_model=TestsetVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{testset_variant_id}/archive", + self.archive_testset_variant, + methods=["PUT"], + operation_id="archive_testset_variant", + status_code=status.HTTP_200_OK, + response_model=TestsetVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/{testset_variant_id}/unarchive", + self.unarchive_testset_variant, + methods=["PUT"], + operation_id="unarchive_testset_variant", + status_code=status.HTTP_200_OK, + response_model=TestsetVariantResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/variants/query", + self.query_testset_variants, methods=["POST"], - operation_id="create_simple_testset_from_file", + operation_id="query_testset_variants", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetVariantsResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/{testset_id}/upload + # TESTSET REVISIONS ---------------------------------------------------- + self.router.add_api_route( - "/{testset_id}/upload", - self.edit_simple_testset_from_file, + "/revisions/retrieve", + self.retrieve_testset_revision, methods=["POST"], - operation_id="edit_simple_testset_from_file", + operation_id="retrieve_testset_revision", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetRevisionResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/{testset_id}/download self.router.add_api_route( - "/{testset_id}/download", - self.fetch_simple_testset_to_file, + "/revisions/", + self.create_testset_revision, methods=["POST"], - operation_id="fetch_simple_testset_to_file", + operation_id="create_testset_revision", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetRevisionResponse, response_model_exclude_none=True, ) - # GET /api/preview/simple/testcases/{testcase_id} self.router.add_api_route( - "/testcases/{testcase_id}", - self.fetch_testcase, + "/revisions/{testset_revision_id}", + self.fetch_testset_revision, methods=["GET"], - operation_id="fetch_testcase", + operation_id="fetch_testset_revision", status_code=status.HTTP_200_OK, - response_model=TestcaseResponse, + response_model=TestsetRevisionResponse, response_model_exclude_none=True, ) - # GET /api/preview/simple/testcases/ self.router.add_api_route( - "/testcases/", - self.list_testcases, - methods=["GET"], - operation_id="list_testcases", + "/revisions/{testset_revision_id}", + self.edit_testset_revision, + methods=["PUT"], + operation_id="edit_testset_revision", status_code=status.HTTP_200_OK, - response_model=TestcasesResponse, + response_model=TestsetRevisionResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testcases/query self.router.add_api_route( - "/testcases/query", - self.query_testcases, + "/revisions/{testset_revision_id}/archive", + self.archive_testset_revision, methods=["POST"], - operation_id="query_testcases", + operation_id="archive_testset_revision", status_code=status.HTTP_200_OK, - response_model=TestcasesResponse, + response_model=TestsetRevisionResponse, response_model_exclude_none=True, ) - # POST /api/preview/simple/testsets/{testset_id}/transfer self.router.add_api_route( - "/{testset_id}/transfer", - self.transfer_simple_testset, + "/revisions/{testset_revision_id}/unarchive", + self.unarchive_testset_revision, methods=["POST"], - operation_id="transfer_simple_testset", + operation_id="unarchive_testset_revision", status_code=status.HTTP_200_OK, - response_model=SimpleTestsetResponse, + response_model=TestsetRevisionResponse, response_model_exclude_none=True, ) - @intercept_exceptions() - async def create_simple_testset( + self.router.add_api_route( + "/revisions/query", + self.query_testset_revisions, + methods=["POST"], + operation_id="query_testset_revisions", + status_code=status.HTTP_200_OK, + response_model=TestsetRevisionsResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/commit", + self.commit_testset_revision, + methods=["POST"], + operation_id="commit_testset_revision", + status_code=status.HTTP_200_OK, + response_model=TestsetRevisionResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/revisions/log", + self.log_testset_revisions, + methods=["POST"], + operation_id="log_testset_revisions", + status_code=status.HTTP_200_OK, + response_model=TestsetRevisionsResponse, + response_model_exclude_none=True, + ) + + # TESTSETS ----------------------------------------------------------------- + + async def create_testset( self, - *, request: Request, - simple_testset_create_request: SimpleTestsetCreateRequest, + *, testset_id: Optional[UUID] = None, - ) -> SimpleTestsetResponse: + # + testset_create_request: TestsetCreateRequest, + ) -> TestsetResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - try: - testcases = simple_testset_create_request.testset.data.testcases + testset = await self.testsets_service.create_testset( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + testset_id=testset_id, + # + testset_create=testset_create_request.testset, + ) - testcases_data = [testcase.data for testcase in testcases] + testset_response = TestsetResponse( + count=1 if testset else 0, + testset=testset, + ) - testcases_data = json_array_to_json_object( - data=testcases_data, - ) + return testset_response - validate_testset_limits(testcases_data) + async def fetch_testset( + self, + request: Request, + *, + testset_id: UUID, + ) -> TestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore - for i, testcase_data in enumerate(testcases_data.values()): - simple_testset_create_request.testset.data.testcases[ - i - ].data = testcase_data + testset = await self.testsets_service.fetch_testset( + project_id=UUID(request.state.project_id), + # + testset_ref=Reference(id=testset_id), + ) - except Exception as e: - raise HTTPException( - status_code=400, - detail=f"Failed to parse testcases as JSON array: {e}", - ) from e + testset_response = TestsetResponse( + count=1 if testset else 0, + testset=testset, + ) - try: - testset_revision_data = TestsetRevisionData( - testcases=simple_testset_create_request.testset.data.testcases, - ) + return testset_response - except ValidationError as e: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=format_validation_error( - e, simple_testset_create_request.model_dump() - ), - ) from e + async def edit_testset( + self, + request: Request, + *, + testset_id: UUID, + # + testset_edit_request: TestsetEditRequest, + ) -> TestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore - _testset_create = TestsetCreate( - slug=simple_testset_create_request.testset.slug, - # - name=simple_testset_create_request.testset.name, - description=simple_testset_create_request.testset.description, - # - # flags = - tags=simple_testset_create_request.testset.tags, - meta=simple_testset_create_request.testset.meta, - ) + if str(testset_id) != str(testset_edit_request.testset.id): + return TestsetResponse() - testset: Optional[Testset] = await self.testsets_service.create_testset( + testset = await self.testsets_service.edit_testset( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - testset_create=_testset_create, - # - testset_id=testset_id, + testset_edit=testset_edit_request.testset, ) - if testset is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple testset. Please try again or contact support.", - ) + testset_response = TestsetResponse( + count=1 if testset else 0, + testset=testset, + ) - testset_variant_slug = uuid4().hex + return testset_response - _testset_variant_create = TestsetVariantCreate( - slug=testset_variant_slug, - # - name=simple_testset_create_request.testset.name, - description=simple_testset_create_request.testset.description, - # - # flags = - tags=simple_testset_create_request.testset.tags, - meta=simple_testset_create_request.testset.meta, - # - testset_id=testset.id, - ) + async def archive_testset( + self, + request: Request, + *, + testset_id: UUID, + ) -> TestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.create_testset_variant( + testset = await self.testsets_service.archive_testset( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - testset_variant_create=_testset_variant_create, + testset_id=testset_id, ) - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple testset. Please try again or contact support.", - ) + testset_response = TestsetResponse( + count=1 if testset else 0, + testset=testset, + ) - testset_revision_slug = uuid4().hex + return testset_response - _testset_revision_create = TestsetRevisionCreate( - slug=testset_revision_slug, - # - name=simple_testset_create_request.testset.name, - description=simple_testset_create_request.testset.description, - # - # flags = - tags=simple_testset_create_request.testset.tags, - meta=simple_testset_create_request.testset.meta, - # - testset_id=testset.id, - testset_variant_id=testset_variant.id, - ) + async def unarchive_testset( + self, + request: Request, + *, + testset_id: UUID, + ) -> TestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore - testset_revision: Optional[ - TestsetRevision - ] = await self.testsets_service.create_testset_revision( + testset = await self.testsets_service.unarchive_testset( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - testset_revision_create=_testset_revision_create, + testset_id=testset_id, ) - if testset_revision is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple testset. Please try again or contact support.", - ) + testset_response = TestsetResponse( + count=1 if testset else 0, + testset=testset, + ) - testset_revision_slug = uuid4().hex + return testset_response - _testset_revision_commit = TestsetRevisionCommit( - slug=testset_revision_slug, - # - name=simple_testset_create_request.testset.name, - description=simple_testset_create_request.testset.description, + async def query_testsets( + self, + request: Request, + *, + testset_query_request: TestsetQueryRequest, + ) -> TestsetsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testsets = await self.testsets_service.query_testsets( + project_id=UUID(request.state.project_id), # - # flags = - tags=simple_testset_create_request.testset.tags, - meta=simple_testset_create_request.testset.meta, + testset_query=testset_query_request.testset, # - # message = + testset_refs=testset_query_request.testset_refs, # - data=testset_revision_data, + include_archived=testset_query_request.include_archived, # - testset_id=testset.id, - testset_variant_id=testset_variant.id, + windowing=testset_query_request.windowing, ) - testset_revision: Optional[ - TestsetRevision - ] = await self.testsets_service.commit_testset_revision( + testsets_response = TestsetsResponse( + count=len(testsets), + testsets=testsets, + ) + + return testsets_response + + # TESTSET VARIANTS --------------------------------------------------------- + + async def create_testset_variant( + self, + request: Request, + *, + testset_variant_create_request: TestsetVariantCreateRequest, + ) -> TestsetVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_variant = await self.testsets_service.create_testset_variant( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - testset_revision_commit=_testset_revision_commit, + testset_variant_create=testset_variant_create_request.testset_variant, ) - if testset_revision is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to create simple testset. Please try again or contact support.", - ) + testset_variant_response = TestsetVariantResponse( + count=1 if testset_variant else 0, + testset_variant=testset_variant, + ) - simple_testset = SimpleTestset( - id=testset.id, - slug=testset.slug, - # - created_at=testset.created_at, - updated_at=testset.updated_at, - deleted_at=testset.deleted_at, - created_by_id=testset.created_by_id, - updated_by_id=testset.updated_by_id, - deleted_by_id=testset.deleted_by_id, - # - name=testset.name, - description=testset.description, - # - # flags = - tags=testset.tags, - meta=testset.meta, + return testset_variant_response + + async def fetch_testset_variant( + self, request: Request, *, testset_variant_id: UUID + ) -> TestsetVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_variant = await self.testsets_service.fetch_testset_variant( + project_id=UUID(request.state.project_id), # - data=testset_revision.data, + testset_variant_ref=Reference(id=testset_variant_id), ) - simple_testset_response = SimpleTestsetResponse( - count=1, - testset=simple_testset, + testset_variant_response = TestsetVariantResponse( + count=1 if testset_variant else 0, + testset_variant=testset_variant, ) - return simple_testset_response + return testset_variant_response - @intercept_exceptions() - @suppress_exceptions(default=SimpleTestsetResponse()) - async def fetch_simple_testset( + async def edit_testset_variant( self, + request: Request, *, + testset_variant_id: UUID, + # + testset_variant_edit_request: TestsetVariantEditRequest, + ) -> TestsetVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(testset_variant_id) not in request.state.user_id: + return TestsetVariantResponse() + + testset_variant = await self.testsets_service.edit_testset_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + testset_variant_edit=testset_variant_edit_request.testset_variant, + ) + + testset_variant_response = TestsetVariantResponse( + count=1 if testset_variant else 0, + testset_variant=testset_variant, + ) + + return testset_variant_response + + async def archive_testset_variant( + self, request: Request, - testset_id: UUID, - ) -> SimpleTestsetResponse: + *, + testset_variant_id: UUID, + ) -> TestsetVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_TESTSETS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_variant = await self.testsets_service.archive_testset_variant( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + testset_variant_id=testset_variant_id, + ) - testset_ref = Reference( - id=testset_id, + testset_variant_response = TestsetVariantResponse( + count=1 if testset_variant else 0, + testset_variant=testset_variant, ) - testset: Optional[Testset] = await self.testsets_service.fetch_testset( + return testset_variant_response + + async def unarchive_testset_variant( + self, + request: Request, + *, + testset_variant_id: UUID, + ) -> TestsetVariantResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_variant = await self.testsets_service.unarchive_testset_variant( project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - testset_ref=testset_ref, + testset_variant_id=testset_variant_id, ) - if testset is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset not found. Please check the ID and try again.", - ) + testset_variant_response = TestsetVariantResponse( + count=1 if testset_variant else 0, + testset_variant=testset_variant, + ) - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.fetch_testset_variant( + return testset_variant_response + + async def query_testset_variants( + self, + request: Request, + *, + testset_variant_query_request: TestsetVariantQueryRequest, + ) -> TestsetVariantsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_variants = await self.testsets_service.query_variants( project_id=UUID(request.state.project_id), # - testset_ref=testset_ref, + testset_variant_query=testset_variant_query_request.testset_variant, + # + include_archived=testset_variant_query_request.include_archived, + # + windowing=testset_variant_query_request.windowing, ) - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset variant not found. Please check the ID and try again.", + testset_variant_response = TestsetVariantsResponse( + count=len(testset_variants), + testset_variants=testset_variants, + ) + + return testset_variant_response + + # TESTSET REVISIONS -------------------------------------------------------- + + async def retrieve_testset_revision( + self, + request: Request, + *, + testset_revision_retrieve_request: TestsetRevisionRetrieveRequest, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + cache_key = { + "artifact_ref": testset_revision_retrieve_request.testset_ref, # type: ignore + "variant_ref": testset_revision_retrieve_request.testset_variant_ref, # type: ignore + "revision_ref": testset_revision_retrieve_request.testset_revision_ref, # type: ignore + } + + testset_revision = await get_cache( + namespace="testsets:retrieve", + project_id=request.state.project_id, + user_id=request.state.user_id, + key=cache_key, + model=TestsetRevision, + ) + + if not testset_revision: + testset_revision = await self.testsets_service.fetch_testset_revision( + project_id=UUID(request.state.project_id), + # + testset_ref=testset_revision_retrieve_request.testset_ref, # type: ignore + testset_variant_ref=testset_revision_retrieve_request.testset_variant_ref, # type: ignore + testset_revision_ref=testset_revision_retrieve_request.testset_revision_ref, # type: ignore + ) + + await set_cache( + namespace="testsets:retrieve", + project_id=request.state.project_id, + user_id=request.state.user_id, + key=cache_key, + value=testset_revision, ) - testset_variant_ref = Reference( - id=testset_variant.id, + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, ) - testset_revision: Optional[ - TestsetRevision - ] = await self.testsets_service.fetch_testset_revision( + return testset_revision_response + + async def create_testset_revision( + self, + request: Request, + *, + testset_revision_create_request: TestsetRevisionCreateRequest, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_revision = await self.testsets_service.create_testset_revision( project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - testset_variant_ref=testset_variant_ref, + testset_revision_create=testset_revision_create_request.testset_revision, ) - if testset_revision is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset revision not found. Please check the ID and try again.", - ) + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, + ) - simple_testset = SimpleTestset( - id=testset.id, - slug=testset.slug, + return testset_revision_response + + async def fetch_testset_revision( + self, + request: Request, + *, + testset_revision_id: UUID, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_revision = await self.testsets_service.fetch_testset_revision( + project_id=UUID(request.state.project_id), # - created_at=testset.created_at, - updated_at=testset.updated_at, - deleted_at=testset.deleted_at, - created_by_id=testset.created_by_id, - updated_by_id=testset.updated_by_id, - deleted_by_id=testset.deleted_by_id, + testset_revision_ref=Reference(id=testset_revision_id), + ) + + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, + ) + + return testset_revision_response + + async def edit_testset_revision( + self, + request: Request, + *, + testset_revision_id: UUID, + # + testset_revision_edit_request: TestsetRevisionEditRequest, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + if str(testset_revision_id) != str( + testset_revision_edit_request.testset_revision.id + ): + return TestsetRevisionResponse() + + testset_revision = await self.testsets_service.edit_testset_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - name=testset.name, - description=testset.description, + testset_revision_edit=testset_revision_edit_request.testset_revision, + ) + + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, + ) + + return testset_revision_response + + async def archive_testset_revision( + self, + request: Request, + *, + testset_revision_id: UUID, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_revision = await self.testsets_service.archive_testset_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - # flags = - tags=testset.tags, - meta=testset.meta, + testset_revision_id=testset_revision_id, + ) + + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, + ) + + return testset_revision_response + + async def unarchive_testset_revision( + self, + request: Request, + *, + testset_revision_id: UUID, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_revision = await self.testsets_service.unarchive_testset_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - data=testset_revision.data, + testset_revision_id=testset_revision_id, ) - simple_testset_response = SimpleTestsetResponse( - count=1, - testset=simple_testset, + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, ) - return simple_testset_response + return testset_revision_response - @intercept_exceptions() - async def edit_simple_testset( + async def query_testset_revisions( + self, + request: Request, + *, + testset_revision_query_request: TestsetRevisionQueryRequest, + ) -> TestsetRevisionsResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_revisions = await self.testsets_service.query_testset_revisions( + project_id=UUID(request.state.project_id), + # + testset_revision_query=testset_revision_query_request.testset_revision, + ) + + testset_revisions_response = TestsetRevisionsResponse( + count=len(testset_revisions), + testset_revisions=testset_revisions, + ) + + return testset_revisions_response + + async def commit_testset_revision( self, + request: Request, *, + testset_revision_commit_request: TestsetRevisionCommitRequest, + ) -> TestsetRevisionResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_EVALUATORS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset_revision = await self.testsets_service.commit_testset_revision( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + testset_revision_commit=testset_revision_commit_request.testset_revision_commit, + ) + + testset_revision_response = TestsetRevisionResponse( + count=1 if testset_revision else 0, + testset_revision=testset_revision, + ) + + return testset_revision_response + + async def log_testset_revisions( + self, request: Request, - testset_id: UUID, - simple_testset_edit_request: SimpleTestsetEditRequest, - ) -> SimpleTestsetResponse: + *, + testset_log_request: TestsetLogRequest, + ) -> TestsetRevisionsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_EVALUATORS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - try: - testcases = simple_testset_edit_request.testset.data.testcases + testset_revisions = await self.testsets_service.log_testset_revisions( + project_id=UUID(request.state.project_id), + # + testset_log=testset_log_request.testset, + ) - testcases_data = [testcase.data for testcase in testcases] + testset_revisions_response = TestsetRevisionsResponse( + count=len(testset_revisions), + testset_revisions=testset_revisions, + ) - testcases_data = json_array_to_json_object( - data=testcases_data, - ) + return testset_revisions_response - validate_testset_limits(testcases_data) - for i, testcase_data in enumerate(testcases_data.values()): - simple_testset_edit_request.testset.data.testcases[ - i - ].data = testcase_data +class SimpleTestsetsRouter: + TESTCASES_FLAGS = TestsetFlags( + has_testcases=True, + has_traces=False, + ) - except Exception as e: - raise HTTPException( - status_code=400, - detail=f"Failed to parse testcases as JSON array: {e}", - ) from e + def __init__( + self, + *, + simple_testsets_service: SimpleTestsetsService, + ): + self.simple_testsets_service = simple_testsets_service - try: - testset_revision_data = TestsetRevisionData( - testcases=testcases, - ) + self.router = APIRouter() - except ValidationError as e: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=format_validation_error( - e, simple_testset_edit_request.model_dump() - ), - ) from e + # SIMPLE TESTSETS ------------------------------------------------------ - if str(testset_id) != str(simple_testset_edit_request.testset.id): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"ID mismatch between path params and body params: {testset_id} != {simple_testset_edit_request.testset.id}", - ) + # POST /api/preview/simple/testsets/ + self.router.add_api_route( + "/", + self.create_simple_testset, + methods=["POST"], + operation_id="create_simple_testset", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # GET /api/preview/simple/testsets/{testset_id} + self.router.add_api_route( + "/{testset_id}", + self.fetch_simple_testset, + methods=["GET"], + operation_id="fetch_simple_testset", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # PUT /api/preview/simple/testsets/{testset_id} + self.router.add_api_route( + "/{testset_id}", + self.edit_simple_testset, + methods=["PUT"], + operation_id="edit_simple_testset", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # POST /api/preview/simple/testsets/{testset_id}/archive + self.router.add_api_route( + "/{testset_id}/archive", + self.archive_simple_testset, + methods=["POST"], + operation_id="archive_simple_testset", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # POST /api/preview/simple/testsets/{testset_id}/unarchive + self.router.add_api_route( + "/{testset_id}/unarchive", + self.unarchive_simple_testset, + methods=["POST"], + operation_id="unarchive_simple_testset", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # POST /api/preview/simple/testsets/query + self.router.add_api_route( + "/query", + self.query_simple_testsets, + methods=["POST"], + operation_id="query_simple_testsets", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetsResponse, + response_model_exclude_none=True, + ) + + # POST /api/preview/simple/testsets/upload + self.router.add_api_route( + "/upload", + self.create_simple_testset_from_file, + methods=["POST"], + operation_id="create_simple_testset_from_file", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # POST /api/preview/simple/testsets/{testset_id}/upload + self.router.add_api_route( + "/{testset_id}/upload", + self.edit_simple_testset_from_file, + methods=["POST"], + operation_id="edit_simple_testset_from_file", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) + + # POST /api/preview/simple/testsets/{testset_id}/download + self.router.add_api_route( + "/{testset_id}/download", + self.fetch_simple_testset_to_file, + methods=["POST"], + operation_id="fetch_simple_testset_to_file", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, + ) - testset_ref = Reference( - id=simple_testset_edit_request.testset.id, + # POST /api/preview/simple/testsets/{testset_id}/transfer + self.router.add_api_route( + "/{testset_id}/transfer", + self.transfer_simple_testset, + methods=["POST"], + operation_id="transfer_simple_testset", + status_code=status.HTTP_200_OK, + response_model=SimpleTestsetResponse, + response_model_exclude_none=True, ) - testset: Optional[Testset] = await self.testsets_service.fetch_testset( + # SIMPLE TESTSETS ---------------------------------------------------------- + + @intercept_exceptions() + async def create_simple_testset( + self, + request: Request, + *, + testset_id: Optional[UUID] = None, + # + simple_testset_create_request: SimpleTestsetCreateRequest, + ) -> SimpleTestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_TESTSETS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + simple_testset = await self.simple_testsets_service.create( + project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # + testset_id=testset_id, + # + simple_testset_create_request=simple_testset_create_request, + ) + + simple_testset_response = SimpleTestsetResponse( + count=1 if simple_testset else 0, + testset=simple_testset, + ) + + return simple_testset_response + + @intercept_exceptions() + @suppress_exceptions(default=SimpleTestsetResponse()) + async def fetch_simple_testset( + self, + request: Request, + *, + testset_id: UUID, + ) -> SimpleTestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_TESTSETS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + testset = await self.simple_testsets_service.testsets_service.fetch_testset( project_id=UUID(request.state.project_id), # - testset_ref=testset_ref, + testset_ref=Reference(id=testset_id), ) if testset is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset not found. Please check the ID and try again.", - ) - - has_changes = ( - testset.name != simple_testset_edit_request.testset.name - or testset.description != simple_testset_edit_request.testset.description - or testset.tags != simple_testset_edit_request.testset.tags - or testset.meta != simple_testset_edit_request.testset.meta - ) - - if has_changes: - _testset_edit = TestsetEdit( - id=testset.id, - # - name=simple_testset_edit_request.testset.name, - description=simple_testset_edit_request.testset.description, - # - # flags = - tags=simple_testset_edit_request.testset.tags, - meta=simple_testset_edit_request.testset.meta, - ) - - testset: Optional[Testset] = await self.testsets_service.edit_testset( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - testset_edit=_testset_edit, - ) - - if testset is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to edit simple testset. Please try again or contact support.", - ) + return SimpleTestsetResponse() - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.fetch_testset_variant( + testset_variant = await self.simple_testsets_service.testsets_service.fetch_testset_variant( project_id=UUID(request.state.project_id), # - testset_ref=testset_ref, + testset_ref=Reference(id=testset.id), ) if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset variant not found. Please check the ID and try again.", - ) - - has_changes = ( - testset_variant.name != simple_testset_edit_request.testset.name - or testset_variant.description - != simple_testset_edit_request.testset.description - or testset_variant.tags != simple_testset_edit_request.testset.tags - or testset_variant.meta != simple_testset_edit_request.testset.meta - ) - - if has_changes: - _testset_variant_edit = TestsetVariant( - id=testset_variant.id, - # - name=simple_testset_edit_request.testset.name, - description=simple_testset_edit_request.testset.description, - # - # flags = - tags=simple_testset_edit_request.testset.tags, - meta=simple_testset_edit_request.testset.meta, - ) - - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.edit_testset_variant( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - testset_variant_edit=_testset_variant_edit, - ) - - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to edit simple testset variant. Please try again or contact support.", - ) - - testset_variant_ref = Reference( - id=testset_variant.id, - ) + return SimpleTestsetResponse() - testset_revision: Optional[ - TestsetRevision - ] = await self.testsets_service.fetch_testset_revision( + testset_revision = await self.simple_testsets_service.testsets_service.fetch_testset_revision( project_id=UUID(request.state.project_id), # - testset_variant_ref=testset_variant_ref, + testset_variant_ref=Reference(id=testset_variant.id), ) if testset_revision is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset revisions not found. Please check the ID and try again.", - ) - - old_testcase_ids = [ - testcase.data for testcase in testset_revision.data.testcases - ] - - new_testcase_ids = [ - testcase.data - for testcase in simple_testset_edit_request.testset.data.testcases - ] - - has_changes = ( - testset_revision.name != simple_testset_edit_request.testset.name - or testset_revision.description - != simple_testset_edit_request.testset.description - or testset_revision.tags != simple_testset_edit_request.testset.tags - or testset_revision.meta != simple_testset_edit_request.testset.meta - or old_testcase_ids != new_testcase_ids - ) - - if has_changes: - testset_revision_slug = uuid4().hex - - _testset_revision_commit = TestsetRevisionCommit( - slug=testset_revision_slug, - # - name=simple_testset_edit_request.testset.name, - description=simple_testset_edit_request.testset.description, - # - # flags = - tags=simple_testset_edit_request.testset.tags, - meta=simple_testset_edit_request.testset.meta, - # - data=testset_revision_data, - # - testset_id=testset.id, - testset_variant_id=testset_variant.id, - ) - - testset_revision: Optional[ - TestsetRevision - ] = await self.testsets_service.commit_testset_revision( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - testset_revision_commit=_testset_revision_commit, - ) - - if testset_revision is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to edit simple testset revision. Please try again or contact support.", - ) + return SimpleTestsetResponse() simple_testset = SimpleTestset( id=testset.id, @@ -800,84 +1201,72 @@ async def edit_simple_testset( ) simple_testset_response = SimpleTestsetResponse( - count=1, + count=1 if simple_testset else 0, testset=simple_testset, ) return simple_testset_response @intercept_exceptions() - async def archive_simple_testset( + async def edit_simple_testset( self, - *, request: Request, + *, testset_id: UUID, + # + simple_testset_edit_request: SimpleTestsetEditRequest, ) -> SimpleTestsetResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - testset_ref = Reference( - id=testset_id, - ) - - testset: Optional[Testset] = await self.testsets_service.fetch_testset( - project_id=UUID(request.state.project_id), - # - testset_ref=testset_ref, - ) - - if testset is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset not found. Please check the ID and try again.", - ) + if str(testset_id) != str(simple_testset_edit_request.testset.id): + return SimpleTestsetResponse() - testset: Optional[Testset] = await self.testsets_service.archive_testset( + simple_testset: Optional[ + SimpleTestset + ] = await self.simple_testsets_service.edit( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - testset_id=testset_id, + simple_testset_edit_request=simple_testset_edit_request, ) - if testset is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to archive simple testset. Please try again or contact support.", - ) - - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.fetch_testset_variant( - project_id=UUID(request.state.project_id), - # - testset_ref=testset_ref, + simple_testset_response = SimpleTestsetResponse( + count=1 if simple_testset else 0, + testset=simple_testset, ) - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Failed to fetch simple testset variant. Please try again or contact support.", - ) + return simple_testset_response + + @intercept_exceptions() + async def archive_simple_testset( + self, + request: Request, + *, + testset_id: UUID, + ) -> SimpleTestsetResponse: + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_TESTSETS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.archive_testset_variant( + testset = await self.simple_testsets_service.testsets_service.archive_testset( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # - testset_variant_id=testset_variant.id, + testset_id=testset_id, ) - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to archive simple testset variant. Please try again or contact support.", - ) + if not testset: + return SimpleTestsetResponse() simple_testset = SimpleTestset( id=testset.id, @@ -899,7 +1288,7 @@ async def archive_simple_testset( ) simple_testset_response = SimpleTestsetResponse( - count=1, + count=1 if simple_testset else 0, testset=simple_testset, ) @@ -908,75 +1297,27 @@ async def archive_simple_testset( @intercept_exceptions() async def unarchive_simple_testset( self, - *, request: Request, + *, testset_id: UUID, ) -> SimpleTestsetResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - testset_ref = Reference( - id=testset_id, - ) - - testset: Optional[Testset] = await self.testsets_service.fetch_testset( - project_id=UUID(request.state.project_id), - # - testset_ref=testset_ref, - ) - - if testset is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Simple testset not found. Please check the ID and try again.", - ) + raise FORBIDDEN_EXCEPTION # type: ignore - testset: Optional[Testset] = await self.testsets_service.unarchive_testset( + testset = await self.simple_testsets_service.testsets_service.unarchive_testset( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # testset_id=testset_id, ) - if testset is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to unarchive simple testset. Please try again or contact support.", - ) - - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.fetch_testset_variant( - project_id=UUID(request.state.project_id), - # - testset_ref=testset_ref, - ) - - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Failed to fetch simple testset variant. Please try again or contact support.", - ) - - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.unarchive_testset_variant( - project_id=UUID(request.state.project_id), - user_id=UUID(request.state.user_id), - # - testset_variant_id=testset_variant.id, - ) - - if testset_variant is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to unarchive simple testset variant. Please try again or contact support.", - ) + if not testset: + return SimpleTestsetResponse() simple_testset = SimpleTestset( id=testset.id, @@ -998,7 +1339,7 @@ async def unarchive_simple_testset( ) simple_testset_response = SimpleTestsetResponse( - count=1, + count=1 if simple_testset else 0, testset=simple_testset, ) @@ -1008,13 +1349,13 @@ async def unarchive_simple_testset( @suppress_exceptions(default=SimpleTestsetsResponse()) async def list_simple_testsets( self, - *, request: Request, ) -> SimpleTestsetsResponse: simple_testset_query_request = SimpleTestsetQueryRequest() return await self.query_simple_testsets( request=request, + # simple_testset_query_request=simple_testset_query_request, ) @@ -1022,19 +1363,19 @@ async def list_simple_testsets( @suppress_exceptions(default=SimpleTestsetsResponse()) async def query_simple_testsets( self, - *, request: Request, + *, simple_testset_query_request: SimpleTestsetQueryRequest, ) -> SimpleTestsetsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_TESTSETS, + permission=Permission.VIEW_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - testsets: List[Testset] = await self.testsets_service.query_testsets( + testsets = await self.simple_testsets_service.testsets_service.query_testsets( project_id=UUID(request.state.project_id), # testset_query=simple_testset_query_request.testset, @@ -1046,40 +1387,22 @@ async def query_simple_testsets( windowing=simple_testset_query_request.windowing, ) - if testsets is None: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to query simple testsets. Please try again or contact support.", - ) - simple_testsets: List[SimpleTestset] = [] for testset in testsets: - testset_ref = Reference( - id=testset.id, - ) - - testset_variant: Optional[ - TestsetVariant - ] = await self.testsets_service.fetch_testset_variant( + testset_variant = await self.simple_testsets_service.testsets_service.fetch_testset_variant( project_id=UUID(request.state.project_id), # - testset_ref=testset_ref, + testset_ref=Reference(id=testset.id), ) - if testset_variant is None: + if not testset_variant: continue - testset_variant_ref = Reference( - id=testset_variant.id, - ) - - testset_revision: Optional[ - TestsetRevision - ] = await self.testsets_service.fetch_testset_revision( + testset_revision = await self.simple_testsets_service.testsets_service.fetch_testset_revision( project_id=UUID(request.state.project_id), # - testset_variant_ref=testset_variant_ref, + testset_variant_ref=Reference(id=testset_variant.id), ) if testset_revision is None: @@ -1118,8 +1441,8 @@ async def query_simple_testsets( @intercept_exceptions() async def create_simple_testset_from_file( self, - *, request: Request, + *, file: UploadFile = File(...), file_type: Literal["csv", "json"] = Form("csv"), testset_slug: Optional[str] = Form(None), @@ -1129,27 +1452,26 @@ async def create_simple_testset_from_file( testset_meta: Optional[str] = Form(None), ) -> SimpleTestsetResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore if file_type is None or file_type not in ["csv", "json"]: - log.error(e) raise HTTPException( status_code=400, detail="Invalid file type. Supported types are 'csv' and 'json'.", ) - if file.size > TESTSETS_SIZE_LIMIT: # Preemptively check file size + if (file.size or 0) > TESTSETS_SIZE_LIMIT: # Preemptively check file size raise TESTSETS_SIZE_EXCEPTION # deserialize tags and meta if provided try: - testset_tags = loads(testset_tags) if testset_tags else None - testset_meta = loads(testset_meta) if testset_meta else None + _testset_tags = loads(testset_tags) if testset_tags else None + _testset_meta = loads(testset_meta) if testset_meta else None except JSONDecodeError as e: log.error(e) raise HTTPException( @@ -1158,7 +1480,7 @@ async def create_simple_testset_from_file( ) from e testcases = [] - testcases_data: Dict[str, list] = {} + testcases_data = {} if file_type.lower() == "json": try: @@ -1193,6 +1515,7 @@ async def create_simple_testset_from_file( testcase_id_key="__id__", testcase_dedup_id_key="__dedup_id__", ) + validate_testset_limits(testcases_data) for testcase_data in testcases_data.values(): @@ -1236,8 +1559,8 @@ async def create_simple_testset_from_file( description=testset_description, # # flags = - tags=testset_tags, - meta=testset_meta, + tags=_testset_tags, + meta=_testset_meta, # data=testset_revision_data, ) @@ -1251,9 +1574,10 @@ async def create_simple_testset_from_file( @intercept_exceptions() async def edit_simple_testset_from_file( self, - *, request: Request, + *, testset_id: UUID, + # file: UploadFile = File(...), file_type: Literal["csv", "json"] = Form("csv"), testset_name: Optional[str] = File(None), @@ -1262,12 +1586,12 @@ async def edit_simple_testset_from_file( testset_meta: Optional[str] = Form(None), ) -> SimpleTestsetResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore if file_type is None or file_type not in ["csv", "json"]: raise HTTPException( @@ -1275,13 +1599,13 @@ async def edit_simple_testset_from_file( detail="Invalid file type. Supported types are 'CSV' and 'JSON'.", ) - if file.size > TESTSETS_SIZE_LIMIT: # Preemptively check file size + if (file.size or 0) > TESTSETS_SIZE_LIMIT: # Preemptively check file size raise TESTSETS_SIZE_EXCEPTION # deserialize tags and meta if provided try: - testset_tags = loads(testset_tags) if testset_tags else None - testset_meta = loads(testset_meta) if testset_meta else None + _testset_tags = loads(testset_tags) if testset_tags else None + _testset_meta = loads(testset_meta) if testset_meta else None except JSONDecodeError as e: raise HTTPException( status_code=400, @@ -1289,11 +1613,11 @@ async def edit_simple_testset_from_file( ) from e testcases = [] - testcases_data: Dict[str, list] = {} + testcases_data = {} if file_type.lower() == "json": try: - testcases_data = await json_file_to_json_array(file) + testcases_data = await json_file_to_json_array(json_file=file) except Exception as e: raise HTTPException( @@ -1303,7 +1627,7 @@ async def edit_simple_testset_from_file( elif file_type.lower() == "csv": try: - testcases_data = await csv_file_to_json_array(file) + testcases_data = await csv_file_to_json_array(csv_file=file) except Exception as e: raise HTTPException( @@ -1377,8 +1701,8 @@ async def edit_simple_testset_from_file( or simple_testset_response.testset.description, # # flags = - tags=testset_tags or simple_testset_response.testset.tags, - meta=testset_meta or simple_testset_response.testset.meta, + tags=_testset_tags or simple_testset_response.testset.tags, + meta=_testset_meta or simple_testset_response.testset.meta, # data=testset_revision_data, ) @@ -1386,26 +1710,29 @@ async def edit_simple_testset_from_file( return await self.edit_simple_testset( request=request, + # testset_id=testset_id, + # simple_testset_edit_request=simple_testset_edit_request, ) @intercept_exceptions() async def fetch_simple_testset_to_file( self, - *, request: Request, + *, testset_id: UUID, + # file_type: Optional[Literal["csv", "json"]] = None, file_name: Optional[str] = None, - ) -> StreamingResponse: + ) -> StreamingResponse: # type: ignore if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_TESTSETS, + permission=Permission.VIEW_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore if file_type is None or file_type not in ["csv", "json"]: raise HTTPException( @@ -1415,10 +1742,11 @@ async def fetch_simple_testset_to_file( simple_testset_response = await self.fetch_simple_testset( request=request, + # testset_id=testset_id, ) - if simple_testset_response.count == 0: + if not simple_testset_response.count and not simple_testset_response.testset: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Simple testset not found. Please check the testset_id and try again.", @@ -1437,7 +1765,7 @@ async def fetch_simple_testset_to_file( "__tags__": testcase.tags, "__meta__": testcase.meta, } - for testcase in testcases + for testcase in testcases or [] ] if file_type.lower() == "json": @@ -1460,168 +1788,38 @@ async def fetch_simple_testset_to_file( headers={"Content-Disposition": f"attachment; filename={filename}"}, ) - @intercept_exceptions() - @suppress_exceptions(default=TestcaseResponse()) - async def fetch_testcase( - self, - *, - request: Request, - testcase_id: UUID, - ) -> TestcaseResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.VIEW_TESTSETS, - ): - raise FORBIDDEN_EXCEPTION - - testcases = await self.testsets_service.testcases_service.fetch_testcases( - project_id=UUID(request.state.project_id), - # - testcase_ids=[testcase_id], - ) - - testcase_response = TestcaseResponse( - count=1 if len(testcases) > 0 else 0, - testcase=testcases[0] if len(testcases) > 0 else None, - ) - - return testcase_response - - @intercept_exceptions() - @suppress_exceptions(default=TestcasesResponse()) - async def list_testcases( - self, - *, - request: Request, - ) -> TestcasesResponse: - testcase_query_request = TestcasesQueryRequest() - - return await self.query_testcases( - request=request, - testcases_query_request=testcase_query_request, - ) - - @intercept_exceptions() - @suppress_exceptions(default=TestcasesResponse()) - async def query_testcases( - self, - *, - request: Request, - testcases_query_request: TestcasesQueryRequest, - ) -> TestcasesResponse: - if is_ee(): - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.VIEW_TESTSETS, - ): - raise FORBIDDEN_EXCEPTION - - testcases = await self.testsets_service.testcases_service.fetch_testcases( - project_id=UUID(request.state.project_id), - # - testcase_ids=testcases_query_request.testcase_ids, - # - testset_id=testcases_query_request.testset_id, - # - windowing=testcases_query_request.windowing, - ) - - testcase_response = TestcasesResponse( - count=len(testcases), - testcases=testcases if testcases else [], - ) - - return testcase_response + else: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid file type. Supported types are 'csv' and 'json'.", + ) @intercept_exceptions() + @suppress_exceptions(default=SimpleTestsetResponse()) async def transfer_simple_testset( self, - *, request: Request, + *, testset_id: UUID, ) -> SimpleTestsetResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_TESTSETS, + permission=Permission.EDIT_TESTSETS, # type: ignore ): - raise FORBIDDEN_EXCEPTION - - old_testset = await fetch_testset_by_id( - testset_id=str(testset_id), - ) - - if old_testset is None: - return SimpleTestsetResponse() - - testset_revision_data = self._transfer_simple_testset_revision_data( - old_testset=old_testset, - ) + raise FORBIDDEN_EXCEPTION # type: ignore - new_testset = await self.testsets_service.fetch_testset( + simple_testset = await self.simple_testsets_service.transfer( project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), # - testset_ref=Reference(id=testset_id), + testset_id=testset_id, ) - if not new_testset: - slug = get_slug_from_name_and_id( - name=old_testset.name, - id=testset_id, - ) - - simple_testset_create_request = SimpleTestsetCreateRequest( - testset=SimpleTestsetCreate( - slug=slug, - name=old_testset.name, - description=None, - flags=None, - tags=None, - meta=None, - data=testset_revision_data, - ) - ) - - simple_testset_response = await self.create_simple_testset( - request=request, - testset_id=testset_id, - simple_testset_create_request=simple_testset_create_request, - ) - - return simple_testset_response - - else: - simple_testset_edit_request = SimpleTestsetEditRequest( - testset=SimpleTestsetEdit( - id=testset_id, - name=new_testset.name, - description=new_testset.description, - flags=new_testset.flags, - tags=new_testset.tags, - meta=new_testset.meta, - data=testset_revision_data, - ) - ) - - simple_testset_response = await self.edit_simple_testset( - request=request, - testset_id=testset_id, - simple_testset_edit_request=simple_testset_edit_request, - ) - - return simple_testset_response - - def _transfer_simple_testset_revision_data( - self, - *, - old_testset: TestSetDB, - ) -> TestsetRevisionData: - return TestsetRevisionData( - testcases=[ - Testcase(data=testcase_data) for testcase_data in old_testset.csvdata - ], + simple_testset_response = SimpleTestsetResponse( + count=1 if simple_testset else 0, + testset=simple_testset, ) + + return simple_testset_response diff --git a/api/oss/src/apis/fastapi/testsets/utils.py b/api/oss/src/apis/fastapi/testsets/utils.py index 8bb4427ba0..c9d30f9823 100644 --- a/api/oss/src/apis/fastapi/testsets/utils.py +++ b/api/oss/src/apis/fastapi/testsets/utils.py @@ -1,358 +1,679 @@ -from typing import Optional, List, Dict, Any -from json import loads, dumps -from uuid import UUID, uuid4 -from io import BytesIO -from hashlib import blake2b as digest - -import orjson -import pandas - -from fastapi import Query, HTTPException +from typing import Optional, Literal, List +from uuid import UUID +from datetime import datetime -from oss.src.core.blobs.utils import compute_blob_id +from fastapi import Query from oss.src.utils.logging import get_module_logger -from oss.src.core.shared.dtos import Reference, Meta -from oss.src.core.testsets.dtos import TestsetFlags - -from oss.src.apis.fastapi.testsets.models import SimpleTestsetQuery as TestsetQuery - - -log = get_module_logger(__name__) - - -TESTSETS_COUNT_LIMIT = 10 * 1_000 # 10,000 testcases per testset -TESTSETS_SIZE_LIMIT = 10 * 1024 * 1024 # 10 MB per testset -TESTSETS_COUNT_WARNING = f"Test set exceeds the maximum count of {TESTSETS_COUNT_LIMIT} test cases per test set." -TESTSETS_SIZE_WARNING = f"Test set exceeds the maximum size of {TESTSETS_SIZE_LIMIT // (1024 * 1024)} MB per test set." - -TESTSETS_SIZE_EXCEPTION = HTTPException( - status_code=400, - detail=TESTSETS_SIZE_WARNING, +from oss.src.core.shared.dtos import ( + Windowing, + Reference, ) - -TESTSETS_COUNT_EXCEPTION = HTTPException( - status_code=400, - detail=TESTSETS_COUNT_WARNING, +from oss.src.core.testsets.dtos import ( + TestsetFlags, + # + TestsetQuery, + TestsetVariantQuery, + TestsetRevisionQuery, ) +from oss.src.apis.fastapi.shared.utils import ( + parse_metadata, +) +from oss.src.apis.fastapi.testsets.models import ( + TestsetQueryRequest, + TestsetVariantQueryRequest, + TestsetRevisionQueryRequest, + TestsetRevisionRetrieveRequest, +) -def validate_testset_limits(rows: List[dict]) -> tuple[int, int]: - i = -1 - total_size = 2 - for i, row in enumerate(rows): - row_str = dumps(row) - total_size += len(row_str.encode("utf-8")) - if i > 0: - total_size += 1 - if i + 1 > TESTSETS_COUNT_LIMIT: - log.error(TESTSETS_COUNT_WARNING) - raise TESTSETS_COUNT_EXCEPTION - if total_size > TESTSETS_SIZE_LIMIT: - log.error(TESTSETS_SIZE_WARNING) - raise TESTSETS_SIZE_EXCEPTION - return i + 1, total_size - - -def format_validation_error(e, request_body=None): - formatted_errors = [] - - for error in e.errors(): - loc = error.get("loc", []) - - if not loc or loc[0] != "body": - loc = ["body"] + list(loc) - - error_detail = { - "type": error.get("type", "value_error"), - "loc": loc, - "msg": error.get("msg", "Validation error"), - } - - if "input" in error: - error_detail["input"] = error.get("input") - elif request_body is not None: - error_detail["input"] = request_body - - formatted_errors.append(error_detail) - return formatted_errors +log = get_module_logger(__name__) -def parse_testset_query_request( - testset_ref: Optional[str] = Query( - None, - description='JSON string of ref, e.g. {"key": value}', - ), - testset_flags: Optional[str] = Query( - None, description='JSON string of flags, e.g. {"key": value}' - ), - testset_meta: Optional[str] = Query( - None, description='JSON string of meta, e.g. {"key": value}' - ), +def parse_testset_query_request_from_params( + testset_id: Optional[UUID] = Query(None), + testset_ids: Optional[List[UUID]] = Query(None), + testset_slug: Optional[str] = Query(None), + testset_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # include_archived: Optional[bool] = Query(None), -) -> TestsetQuery: - if testset_ref: - try: - testset_ref = Reference(**loads(testset_ref)) - except Exception: # pylint: disable=broad-except - testset_ref = None - - log.error("Failed to parse testset_ref (%s)", testset_ref) - - if testset_flags: - try: - testset_flags = TestsetFlags(**loads(testset_flags)) - except Exception: # pylint: disable=broad-except - testset_flags = None - - log.error("Failed to parse testset_flags (%s)", testset_flags) - - if testset_meta: - try: - testset_meta = loads(testset_meta) - except Exception: # pylint: disable=broad-except - testset_meta = None + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> TestsetQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = TestsetFlags(**_flags) if _flags else None # type: ignore + + testset = ( + TestsetQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) - log.error(f"Failed to parse testset_meta ({testset_meta})") + testset_refs = ( + ( + [ + Reference( + id=testset_id, + slug=testset_slug, + ) + ] + if testset_id or testset_slug + else [] + ) + + ( + [ + Reference( + id=testset_id, + slug=testset_slug, + ) + for testset_id, testset_slug in zip( + testset_ids, + testset_slugs, + ) + ] + if testset_ids and testset_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) - return parse_testset_body_request( - testset_ref=testset_ref, + return parse_testset_query_request_from_body( + testset=testset, # - testset_flags=testset_flags, - testset_meta=testset_meta, + testset_refs=testset_refs, # include_archived=include_archived, + # + windowing=windowing, ) -def parse_testset_body_request( - testset_ref: Optional[Reference] = None, +def parse_testset_query_request_from_body( + testset: Optional[TestsetQuery] = None, # - testset_flags: Optional[TestsetFlags] = None, - testset_meta: Optional[Meta] = None, + testset_refs: Optional[List[Reference]] = None, # include_archived: Optional[bool] = None, -) -> TestsetQuery: - _query = None + # + windowing: Optional[Windowing] = None, +) -> TestsetQueryRequest: + testset_query_request = None try: - _query = TestsetQuery( - testset_ref=testset_ref, + testset_query_request = TestsetQueryRequest( + testset=testset, # - flags=testset_flags, - meta=testset_meta, + testset_refs=testset_refs, # include_archived=include_archived, + # + windowing=windowing, ) except Exception as e: # pylint: disable=broad-except - log.warn("Error parsing testset body request: %s", e) - - _query = None - - return _query - - -def parse_variant_query_request( - testset_ref: Optional[str] = Query( - None, - description='JSON string of reference, e.g. {"key": value}', - ), - variant_ref: Optional[str] = Query( - None, - description='JSON string of reference, e.g. {"key": value}', - ), - variant_meta: Optional[str] = Query( - None, description='JSON string of meta, e.g. {"key": value}' - ), - variant_flags: Optional[str] = Query( - None, description='JSON string of flags, e.g. {"key": value}' - ), - include_archived: Optional[bool] = Query(None), -) -> TestsetQuery: - if testset_ref: - try: - testset_ref = Reference(**loads(testset_ref)) - except Exception: # pylint: disable=broad-except - testset_ref = None + testset_query_request = TestsetQueryRequest() - log.error("Failed to parse testset_ref (%s)", testset_ref) + return testset_query_request - if variant_ref: - try: - variant_ref = Reference(**loads(variant_ref)) - except Exception: # pylint: disable=broad-except - variant_ref = None - log.error("Failed to parse variant_ref (%s)", variant_ref) +def merge_testset_query_requests( + query_request_params: Optional[TestsetQueryRequest] = None, + query_request_body: Optional[TestsetQueryRequest] = None, +) -> TestsetQueryRequest: + if query_request_params and not query_request_body: + return query_request_params - if variant_flags: - try: - variant_flags = TestsetFlags(**loads(variant_flags)) - except Exception: # pylint: disable=broad-except - variant_flags = None + if not query_request_params and query_request_body: + return query_request_body - log.error("Failed to parse variant_flags (%s)", variant_flags) + if query_request_params and query_request_body: + return TestsetQueryRequest( + testset=query_request_body.testset or query_request_params.testset, + # + testset_refs=query_request_body.testset_refs + or query_request_params.testset_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) - if variant_meta: - try: - variant_meta = loads(variant_meta) - except Exception: # pylint: disable=broad-except - variant_meta = None + return TestsetQueryRequest() - log.error(f"Failed to parse variant_meta ({variant_meta})") - return parse_variant_body_request( - testset_ref=testset_ref, - variant_ref=variant_ref, +def parse_testset_variant_query_request_from_params( + testset_id: Optional[UUID] = Query(None), + testset_ids: Optional[List[UUID]] = Query(None), + testset_slug: Optional[str] = Query(None), + testset_slugs: Optional[List[str]] = Query(None), + # + testset_variant_id: Optional[UUID] = Query(None), + testset_variant_ids: Optional[List[UUID]] = Query(None), + testset_variant_slug: Optional[str] = Query(None), + testset_variant_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> TestsetVariantQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = TestsetFlags(**_flags) if _flags else None # type: ignore + + testset_variant = ( + TestsetVariantQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + testset_refs = ( + ( + [ + Reference( + id=testset_id, + slug=testset_slug, + ) + ] + if testset_id or testset_slug + else [] + ) + + ( + [ + Reference( + id=testset_id, + slug=testset_slug, + ) + for testset_id, testset_slug in zip( + testset_ids, + testset_slugs, + ) + ] + if testset_ids and testset_slugs + else [] + ) + ) or None + + testset_variant_refs = ( + ( + [ + Reference( + id=testset_variant_id, + slug=testset_variant_slug, + ) + ] + if testset_variant_id or testset_variant_slug + else [] + ) + + ( + [ + Reference( + id=testset_variant_id, + slug=testset_variant_slug, + ) + for testset_variant_id, testset_variant_slug in zip( + testset_variant_ids, + testset_variant_slugs, + ) + ] + if testset_variant_ids and testset_variant_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_testset_variant_query_request_from_body( + testset_variant=testset_variant, # - variant_flags=variant_flags, - variant_meta=variant_meta, + testset_refs=testset_refs or None, + testset_variant_refs=testset_variant_refs or None, # include_archived=include_archived, + # + windowing=windowing, ) -def parse_variant_body_request( - testset_ref: Optional[Reference] = None, - variant_ref: Optional[Reference] = None, +def parse_testset_variant_query_request_from_body( + testset_variant: Optional[TestsetVariantQuery] = None, # - variant_flags: Optional[TestsetFlags] = None, - variant_meta: Optional[Meta] = None, + testset_refs: Optional[List[Reference]] = None, + testset_variant_refs: Optional[List[Reference]] = None, # include_archived: Optional[bool] = None, -) -> TestsetQuery: - _query = None + # + windowing: Optional[Windowing] = None, +) -> TestsetVariantQueryRequest: + testset_variant_query_request = None try: - _query = TestsetQuery( - artifact_ref=testset_ref, - variant_ref=variant_ref, + testset_variant_query_request = TestsetVariantQueryRequest( + testset_variant=testset_variant, # - flags=variant_flags, - meta=variant_meta, + testset_refs=testset_refs, + testset_variant_refs=testset_variant_refs, # include_archived=include_archived, + # + windowing=windowing, ) except Exception as e: # pylint: disable=broad-except - log.warn("Error parsing variant body request: %s", e) - - _query = None - - return _query - - -def parse_revision_query_request( - variant_ref: Optional[str] = Query( - None, - description='JSON string of ref, e.g. {"key": value}', - ), - revision_ref: Optional[str] = Query( - None, - description='JSON string of ref, e.g. {"key": value}', - ), - revision_meta: Optional[str] = Query( - None, description='JSON string of meta, e.g. {"key": value}' - ), - revision_flags: Optional[str] = Query( - None, description='JSON string of flags, e.g. {"key": value}' - ), - include_archived: Optional[bool] = Query(None), -) -> TestsetQuery: - if variant_ref: - try: - variant_ref = Reference(**loads(variant_ref)) - except Exception: # pylint: disable=broad-except - variant_ref = None + testset_variant_query_request = TestsetVariantQueryRequest() - log.error("Failed to parse variant_ref (%s)", variant_ref) + return testset_variant_query_request - if revision_ref: - try: - revision_ref = Reference(**loads(revision_ref)) - except Exception: # pylint: disable=broad-except - revision_ref = None - log.error("Failed to parse revision_ref (%s)", revision_ref) +def merge_testset_variant_query_requests( + query_request_params: Optional[TestsetVariantQueryRequest] = None, + query_request_body: Optional[TestsetVariantQueryRequest] = None, +) -> TestsetVariantQueryRequest: + if query_request_params and not query_request_body: + return query_request_params - if revision_flags: - try: - revision_flags = TestsetFlags(**loads(revision_flags)) - except Exception: # pylint: disable=broad-except - revision_flags = None + if not query_request_params and query_request_body: + return query_request_body - log.error("Failed to parse revision_flags (%s)", revision_flags) + if query_request_params and query_request_body: + return TestsetVariantQueryRequest( + testset_variant=query_request_body.testset_variant + or query_request_params.testset_variant, + # + testset_refs=query_request_body.testset_refs + or query_request_params.testset_refs, + testset_variant_refs=query_request_body.testset_variant_refs + or query_request_params.testset_variant_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) - if revision_meta: - try: - revision_meta = loads(revision_meta) - except Exception: # pylint: disable=broad-except - revision_meta = None + return TestsetVariantQueryRequest() + + +def parse_testset_revision_query_request_from_params( + testset_id: Optional[UUID] = Query(None), + testset_ids: Optional[List[UUID]] = Query(None), + testset_slug: Optional[str] = Query(None), + testset_slugs: Optional[List[str]] = Query(None), + # + testset_variant_id: Optional[UUID] = Query(None), + testset_variant_ids: Optional[List[UUID]] = Query(None), + testset_variant_slug: Optional[str] = Query(None), + testset_variant_slugs: Optional[List[str]] = Query(None), + # + testset_revision_id: Optional[UUID] = Query(None), + testset_revision_ids: Optional[List[UUID]] = Query(None), + testset_revision_slug: Optional[str] = Query(None), + testset_revision_slugs: Optional[List[str]] = Query(None), + testset_revision_version: Optional[str] = Query(None), + testset_revision_versions: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # + flags: Optional[str] = Query(None), + tags: Optional[str] = Query(None), + meta: Optional[str] = Query(None), + # + include_archived: Optional[bool] = Query(None), + # + next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), + limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), +) -> TestsetRevisionQueryRequest: + _flags, _tags, _meta = parse_metadata(flags, tags, meta) + + __flags = TestsetFlags(**_flags) if _flags else None # type: ignore + + testset_revision = ( + TestsetRevisionQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) + + testset_refs = ( + [ + Reference( + id=testset_id, + slug=testset_slug, + ) + ] + if testset_id or testset_slug + else [] + ) + ( + [ + Reference( + id=testset_id, + slug=testset_slug, + ) + for testset_id, testset_slug in zip( + testset_ids, + testset_slugs, + ) + ] + if testset_ids and testset_slugs + else [] + ) + + testset_variant_refs = ( + [ + Reference( + id=testset_variant_id, + slug=testset_variant_slug, + ) + ] + if testset_variant_id or testset_variant_slug + else [] + ) + ( + [ + Reference( + id=testset_variant_id, + slug=testset_variant_slug, + ) + for testset_variant_id, testset_variant_slug in zip( + testset_variant_ids, + testset_variant_slugs, + ) + ] + if testset_variant_ids and testset_variant_slugs + else [] + ) - log.error(f"Failed to parse revision_meta ({revision_meta})") + testset_revision_refs = ( + [ + Reference( + id=testset_revision_id, + slug=testset_revision_slug, + version=testset_revision_version, + ) + ] + if testset_revision_id or testset_revision_slug or testset_revision_version + else [] + ) + ( + [ + Reference( + id=testset_revision_id, + slug=testset_revision_slug, + version=testset_revision_version, + ) + for testset_revision_id, testset_revision_slug, testset_revision_version in zip( + testset_revision_ids, + testset_revision_slugs, + testset_revision_versions, + ) + ] + if testset_revision_ids and testset_revision_slugs and testset_revision_versions + else [] + ) - return parse_revision_body_request( - variant_ref=variant_ref, - revision_ref=revision_ref, + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) + + return parse_testset_revision_query_request_from_body( + testset_revision=testset_revision, # - revision_flags=revision_flags, - revision_meta=revision_meta, + testset_refs=testset_refs, + testset_variant_refs=testset_variant_refs, + testset_revision_refs=testset_revision_refs, # include_archived=include_archived, + # + windowing=windowing, ) -def parse_revision_body_request( - variant_ref: Optional[Reference] = None, - revision_ref: Optional[Reference] = None, +def parse_testset_revision_query_request_from_body( + testset_revision: Optional[TestsetRevisionQuery] = None, # - revision_flags: Optional[TestsetFlags] = None, - revision_meta: Optional[Meta] = None, + testset_refs: Optional[List[Reference]] = None, + testset_variant_refs: Optional[List[Reference]] = None, + testset_revision_refs: Optional[List[Reference]] = None, # include_archived: Optional[bool] = None, -) -> TestsetQuery: - _query = None + # + windowing: Optional[Windowing] = None, +) -> TestsetRevisionQueryRequest: + testset_revision_query_request = None try: - _query = TestsetQuery( - variant_ref=variant_ref, - revision_ref=revision_ref, + testset_revision_query_request = TestsetRevisionQueryRequest( + testset_revision=testset_revision, # - flags=revision_flags, - meta=revision_meta, + testset_refs=testset_refs, + testset_variant_refs=testset_variant_refs, + testset_revision_refs=testset_revision_refs, # include_archived=include_archived, + # + windowing=windowing, ) + except Exception as e: # pylint: disable=broad-except log.warn(e) - _query = None + testset_revision_query_request = TestsetRevisionQueryRequest() - return _query + return testset_revision_query_request -def merge_requests( - query_param: Optional[TestsetQuery] = None, - query_body: Optional[TestsetQuery] = None, -) -> TestsetQuery: - if query_body is None: - return query_param +def merge_testset_revision_query_requests( + query_request_params: Optional[TestsetRevisionQueryRequest] = None, + query_request_body: Optional[TestsetRevisionQueryRequest] = None, +) -> TestsetRevisionQueryRequest: + if query_request_params and not query_request_body: + return query_request_params - if query_param is None: - return query_body + if not query_request_params and query_request_body: + return query_request_body - return TestsetQuery( - artifact_ref=query_body.artifact_ref or query_param.artifact_ref, - variant_ref=query_body.variant_ref or query_param.variant_ref, - revision_ref=query_body.revision_ref or query_param.revision_ref, - # - flags=query_body.flags or query_param.flags, - meta=query_body.meta or query_param.meta, - # - include_archived=query_body.include_archived or query_param.include_archived, + if query_request_params and query_request_body: + return TestsetRevisionQueryRequest( + testset_revision=query_request_body.testset_revision + or query_request_params.testset_revision, + # + testset_refs=query_request_body.testset_refs + or query_request_params.testset_refs, + testset_variant_refs=query_request_body.testset_variant_refs + or query_request_params.testset_variant_refs, + testset_revision_refs=query_request_body.testset_revision_refs + or query_request_params.testset_revision_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return TestsetRevisionQueryRequest() + + +def parse_testset_revision_retrieve_request_from_params( + testset_id: Optional[UUID] = Query(None), + testset_slug: Optional[str] = Query(None), + # + testset_variant_id: Optional[UUID] = Query(None), + testset_variant_slug: Optional[str] = Query(None), + # + testset_revision_id: Optional[UUID] = Query(None), + testset_revision_slug: Optional[str] = Query(None), + testset_revision_version: Optional[str] = Query(None), +): + testset_ref = ( + Reference( + id=testset_id, + slug=testset_slug, + ) + if testset_id or testset_slug + else None ) + testset_variant_ref = ( + Reference( + id=testset_variant_id, + slug=testset_variant_slug, + ) + if testset_variant_id or testset_variant_slug + else None + ) + + testset_revision_ref = ( + Reference( + id=testset_revision_id, + slug=testset_revision_slug, + version=testset_revision_version, + ) + if testset_revision_id or testset_revision_slug or testset_revision_version + else None + ) + + return parse_testset_revision_retrieve_request_from_body( + testset_ref=testset_ref, + testset_variant_ref=testset_variant_ref, + testset_revision_ref=testset_revision_ref, + ) + + +def parse_testset_revision_retrieve_request_from_body( + testset_ref: Optional[Reference] = None, + testset_variant_ref: Optional[Reference] = None, + testset_revision_ref: Optional[Reference] = None, +) -> TestsetRevisionRetrieveRequest: + return TestsetRevisionRetrieveRequest( + testset_ref=testset_ref, + testset_variant_ref=testset_variant_ref, + testset_revision_ref=testset_revision_ref, + ) + + +# ---------------------------------------------------------------------------- # + +from typing import Dict, Any +from uuid import uuid4 +from json import loads, dumps +from io import BytesIO +from hashlib import blake2b as digest + +import orjson +import pandas + +from fastapi import HTTPException + +from oss.src.core.blobs.utils import compute_blob_id + + +TESTSETS_COUNT_LIMIT = 10 * 1_000 # 10,000 testcases per testset +TESTSETS_SIZE_LIMIT = 10 * 1024 * 1024 # 10 MB per testset + +TESTSETS_COUNT_WARNING = f"Test set exceeds the maximum count of {TESTSETS_COUNT_LIMIT} test cases per test set." +TESTSETS_SIZE_WARNING = f"Test set exceeds the maximum size of {TESTSETS_SIZE_LIMIT // (1024 * 1024)} MB per test set." + +TESTSETS_SIZE_EXCEPTION = HTTPException( + status_code=400, + detail=TESTSETS_SIZE_WARNING, +) + +TESTSETS_COUNT_EXCEPTION = HTTPException( + status_code=400, + detail=TESTSETS_COUNT_WARNING, +) + + +def validate_testset_limits(rows: List[dict]) -> tuple[int, int]: + i = -1 + total_size = 2 + for i, row in enumerate(rows): + row_str = dumps(row) + total_size += len(row_str.encode("utf-8")) + if i > 0: + total_size += 1 + if i + 1 > TESTSETS_COUNT_LIMIT: + log.error(TESTSETS_COUNT_WARNING) + raise TESTSETS_COUNT_EXCEPTION + if total_size > TESTSETS_SIZE_LIMIT: + log.error(TESTSETS_SIZE_WARNING) + raise TESTSETS_SIZE_EXCEPTION + return i + 1, total_size + def to_uuid(id, data): """Ensure value is a valid UUID; generate a new one if missing/invalid.""" @@ -539,7 +860,8 @@ def json_array_to_csv_file( def csv_data_to_json_array( - csv_data: List[Dict[str, Any]], column_types: Dict[str, type] = None + csv_data: List[Dict[str, Any]], + column_types: Dict[str, type] = {}, ) -> List[Dict[str, Any]]: """ Converts CSV-like data (list of dictionaries) into a JSON array, preserving JSON types if specified. @@ -555,7 +877,7 @@ def csv_data_to_json_array( isinstance(row, dict) for row in csv_data ): print("Error: Expected a list of dictionaries (CSV-like structure).") - return None + return [] # Convert column types if specified if column_types: diff --git a/api/oss/src/apis/fastapi/tracing/models.py b/api/oss/src/apis/fastapi/tracing/models.py index 2caa8e23f0..a5b4f3f0b5 100644 --- a/api/oss/src/apis/fastapi/tracing/models.py +++ b/api/oss/src/apis/fastapi/tracing/models.py @@ -2,7 +2,6 @@ from pydantic import BaseModel -from oss.src.apis.fastapi.shared.models import VersionedModel from oss.src.core.tracing.dtos import ( OTelLink, # needed for annotations at the moment OTelLinks, @@ -10,6 +9,9 @@ OTelFlatSpans, OTelTraceTree, Bucket, + MetricsBucket, + TracingQuery, + MetricSpec, ) @@ -18,17 +20,25 @@ class OTelTracingRequest(BaseModel): traces: Optional[OTelTraceTree] = None -class OTelLinksResponse(VersionedModel): +class OTelLinksResponse(BaseModel): count: int = 0 links: Optional[OTelLinks] = None -class OTelTracingResponse(VersionedModel): +class OTelTracingResponse(BaseModel): count: int = 0 spans: Optional[OTelFlatSpans] = None traces: Optional[OTelTraceTree] = None -class AnalyticsResponse(VersionedModel): - count: Optional[int] = None +class OldAnalyticsResponse(BaseModel): + count: int = 0 buckets: List[Bucket] = [] + + +class AnalyticsResponse(BaseModel): + count: int = 0 + buckets: List[MetricsBucket] = [] + # + query: TracingQuery = TracingQuery() + specs: List[MetricSpec] = [] diff --git a/api/oss/src/apis/fastapi/tracing/router.py b/api/oss/src/apis/fastapi/tracing/router.py index 4b606513fb..9e6b082286 100644 --- a/api/oss/src/apis/fastapi/tracing/router.py +++ b/api/oss/src/apis/fastapi/tracing/router.py @@ -1,4 +1,4 @@ -from typing import Union, Optional +from typing import Optional, List, Tuple, Dict from uuid import UUID from fastapi import APIRouter, Request, Depends, status, HTTPException @@ -10,16 +10,20 @@ from oss.src.apis.fastapi.tracing.utils import ( merge_queries, - parse_query_request, - parse_body_request, + parse_query_from_params_request, + parse_query_from_body_request, parse_trace_id_to_uuid, parse_spans_from_request, parse_spans_into_response, + parse_analytics_from_params_request, + parse_analytics_from_body_request, + merge_analytics, ) from oss.src.apis.fastapi.tracing.models import ( OTelLinksResponse, OTelTracingRequest, OTelTracingResponse, + OldAnalyticsResponse, AnalyticsResponse, ) from oss.src.core.tracing.service import TracingService @@ -28,19 +32,19 @@ OTelLinks, OTelSpan, OTelFlatSpans, + OTelFlatSpan, OTelTraceTree, - OTelSpansTree, - Query, + TracingQuery, Focus, Format, + MetricType, + MetricSpec, ) log = get_module_logger(__name__) class TracingRouter: - VERSION = "1.0.0" - def __init__( self, tracing_service: TracingService, @@ -114,30 +118,30 @@ def __init__( ) self.router.add_api_route( - "/spans/", + "/spans/query", self.query_spans, - methods=["GET"], - operation_id="query_spans", + methods=["POST"], + operation_id="query_spans_rpc", status_code=status.HTTP_200_OK, response_model=OTelTracingResponse, response_model_exclude_none=True, ) self.router.add_api_route( - "/spans/query", - self.query_spans, + "/spans/analytics", + self.fetch_legacy_analytics, methods=["POST"], - operation_id="query_spans_rpc", + operation_id="fetch_analytics", status_code=status.HTTP_200_OK, - response_model=OTelTracingResponse, + response_model=OldAnalyticsResponse, response_model_exclude_none=True, ) self.router.add_api_route( - "/spans/analytics", + "/analytics/query", self.fetch_analytics, methods=["POST"], - operation_id="fetch_analytics", + operation_id="fetch_new_analytics", status_code=status.HTTP_200_OK, response_model=AnalyticsResponse, response_model_exclude_none=True, @@ -148,22 +152,39 @@ def __init__( async def _upsert( self, project_id: UUID, + user_id: UUID, + # spans: Optional[OTelFlatSpans] = None, traces: Optional[OTelTraceTree] = None, strict: Optional[bool] = False, - user_id: Optional[UUID] = None, ) -> OTelLinks: - _spans = {} + _spans: Dict[str, OTelSpan | OTelFlatSpans] = dict() if spans: - _spans = {"spans": [OTelSpan(**span.model_dump()) for span in spans]} - + _spans = { + "spans": [ + OTelFlatSpan( + **span.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + ) + for span in spans + ] + } elif traces: - for spans in traces.values(): - spans: OTelSpansTree - - for span in spans.spans.values(): - _spans[span.span_id] = OTelSpan(**span.model_dump()) + for spans_tree in traces.values(): + if spans_tree.spans: + for span in spans_tree.spans.values(): + if not isinstance(span, list): + _spans[span.span_id] = OTelSpan( + **span.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + ) span_dtos = parse_spans_from_request(_spans) @@ -171,8 +192,9 @@ async def _upsert( links = ( await self.service.create( project_id=project_id, - span_dtos=span_dtos, user_id=user_id, + # + span_dtos=span_dtos, ) or [] ) @@ -180,8 +202,9 @@ async def _upsert( links = ( await self.service.update( project_id=project_id, - span_dtos=span_dtos, user_id=user_id, + # + span_dtos=span_dtos, ) or [] ) @@ -200,15 +223,15 @@ async def create_trace( # CREATE if trace_request.traces: if len(trace_request.traces) == 0: - return HTTPException( + raise HTTPException( status_code=400, - detail="Missing trace.", + detail="Missing trace", ) if len(trace_request.traces) > 1: - return HTTPException( + raise HTTPException( status_code=400, - detail="Too many traces.", + detail="Too many traces", ) spans = list(trace_request.traces.values())[0].spans @@ -217,47 +240,47 @@ async def create_trace( # CREATE spans = {span.span_id: span for span in trace_request.spans} else: - return HTTPException( + raise HTTPException( status_code=400, - detail="Missing spans.", + detail="Missing spans", ) - if len(spans) == 0: - return HTTPException( + if not spans: + raise HTTPException( status_code=400, - detail="Missing spans.", + detail="Missing spans", ) root_spans = 0 for span in spans.values(): - if span.parent_id is None: + if not isinstance(span, list) and span.parent_id is None: root_spans += 1 if root_spans == 0: - return HTTPException( + raise HTTPException( status_code=400, - detail="Missing root span.", + detail="Missing root span", ) if root_spans > 1: - return HTTPException( + raise HTTPException( status_code=400, - detail="Too many root spans.", + detail="Too many root spans", ) links = await self._upsert( project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # spans=trace_request.spans, traces=trace_request.traces, strict=True, - user_id=UUID(request.state.user_id), ) link_response = OTelLinksResponse( - version=self.VERSION, - links=links, count=len(links), + links=links, ) return link_response @@ -267,7 +290,7 @@ async def create_trace( # CREATE async def fetch_trace( # READ self, request: Request, - trace_id: Union[str, int], + trace_id: str, ) -> OTelTracingResponse: try: trace_id = parse_trace_id_to_uuid(trace_id) @@ -277,13 +300,11 @@ async def fetch_trace( # READ spans = await self.service.read( project_id=UUID(request.state.project_id), - trace_id=trace_id, + # + trace_id=UUID(trace_id), ) - trace_response = OTelTracingResponse( - version=self.VERSION, - count=0, - ) + trace_response = OTelTracingResponse() if spans is not None: traces = parse_spans_into_response( @@ -292,10 +313,12 @@ async def fetch_trace( # READ format=Format.AGENTA, ) + if not traces or isinstance(traces, list): + return OTelTracingResponse() + trace_response = OTelTracingResponse( - version=self.VERSION, + count=len(traces.keys()), traces=traces, - count=len(traces.values()), ) return trace_response @@ -304,22 +327,24 @@ async def fetch_trace( # READ async def edit_trace( # UPDATE self, request: Request, - trace_id: Union[str, int], + # trace_request: OTelTracingRequest, + # + trace_id: str, ) -> OTelLinksResponse: spans = None if trace_request.traces: if len(trace_request.traces) == 0: - return HTTPException( + raise HTTPException( status_code=400, - detail="Missing trace.", + detail="Missing trace", ) if len(trace_request.traces) > 1: - return HTTPException( + raise HTTPException( status_code=400, - detail="Too many traces.", + detail="Too many traces", ) spans = list(trace_request.traces.values())[0].spans @@ -328,47 +353,47 @@ async def edit_trace( # UPDATE spans = {span.span_id: span for span in trace_request.spans} else: - return HTTPException( + raise HTTPException( status_code=400, - detail="Missing spans.", + detail="Missing spans", ) - if len(spans) == 0: - return HTTPException( + if not spans: + raise HTTPException( status_code=400, - detail="Missing spans.", + detail="Missing spans", ) root_spans = 0 for span in spans.values(): - if span.parent_id is None: + if not isinstance(span, list) and span.parent_id is None: root_spans += 1 if root_spans == 0: - return HTTPException( + raise HTTPException( status_code=400, - detail="Missing root span.", + detail="Missing root span", ) if root_spans > 1: - return HTTPException( + raise HTTPException( status_code=400, - detail="Too many root spans.", + detail="Too many root spans", ) links = await self._upsert( project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # spans=trace_request.spans, traces=trace_request.traces, strict=False, - user_id=UUID(request.state.user_id), ) link_response = OTelLinksResponse( - version=self.VERSION, - links=links, count=len(links), + links=links, ) return link_response @@ -377,23 +402,26 @@ async def edit_trace( # UPDATE async def delete_trace( # DELETE self, request: Request, - trace_id: Union[str, int], + trace_id: str, ) -> OTelLinksResponse: try: trace_id = parse_trace_id_to_uuid(trace_id) except Exception as e: - raise HTTPException(status_code=400, detail="Invalid trace_id.") from e + raise HTTPException( + status_code=400, + detail="Invalid trace_id", + ) from e links = await self.service.delete( project_id=UUID(request.state.project_id), - trace_id=trace_id, + # + trace_id=UUID(trace_id), ) link_response = OTelLinksResponse( - version=self.VERSION, + count=len(links), links=links, - count=len(links) if links else 0, ) return link_response @@ -408,16 +436,16 @@ async def ingest_spans( # MUTATION ) -> OTelLinksResponse: links = await self._upsert( project_id=UUID(request.state.project_id), + user_id=UUID(request.state.user_id), + # spans=spans_request.spans, traces=spans_request.traces, strict=True, - user_id=UUID(request.state.user_id), ) link_response = OTelLinksResponse( - version=self.VERSION, - links=links, count=len(links), + links=links, ) return link_response @@ -427,7 +455,7 @@ async def ingest_spans( # MUTATION async def query_spans( # QUERY self, request: Request, - query: Optional[Query] = Depends(parse_query_request), + query: Optional[TracingQuery] = Depends(parse_query_from_params_request), ) -> OTelTracingResponse: body_json = None query_from_body = None @@ -436,9 +464,9 @@ async def query_spans( # QUERY body_json = await request.json() if body_json: - query_from_body = parse_body_request(**body_json) + query_from_body = parse_query_from_body_request(**body_json) - except: # pylint: disable=bare-except + except: pass merged_query = merge_queries(query, query_from_body) @@ -446,6 +474,7 @@ async def query_spans( # QUERY try: span_dtos = await self.service.query( project_id=UUID(request.state.project_id), + # query=merged_query, ) except FilteringException as e: @@ -454,54 +483,45 @@ async def query_spans( # QUERY detail=str(e), ) from e - oldest = None - newest = None - - for span in span_dtos: - if oldest is None or span.start_time < oldest: - oldest = span.start_time - - if newest is None or span.start_time > newest: - newest = span.start_time - - _spans_or_traces = parse_spans_into_response( + spans_or_traces = parse_spans_into_response( span_dtos, - focus=query.formatting.focus, - format=query.formatting.format, + focus=(merged_query.formatting.focus if merged_query.formatting else None) + or Focus.TRACE, + format=(merged_query.formatting.format if merged_query.formatting else None) + or Format.AGENTA, ) - spans: OTelFlatSpans = None - traces: OTelTraceTree = None + spans: Optional[OTelFlatSpans] = None + traces: Optional[OTelTraceTree] = None - if isinstance(_spans_or_traces, list): - spans = _spans_or_traces + if isinstance(spans_or_traces, list): + count = len(spans_or_traces) + spans = spans_or_traces traces = None - count = len(_spans_or_traces) - elif isinstance(_spans_or_traces, dict): + elif isinstance(spans_or_traces, dict): + count = len(spans_or_traces.values()) spans = None - traces = _spans_or_traces - count = len(_spans_or_traces.values()) + traces = spans_or_traces else: + count = 0 spans = None traces = None - count = 0 spans_response = OTelTracingResponse( - version=self.VERSION, + count=count, spans=spans, traces=traces, - count=count, ) return spans_response @intercept_exceptions() - @suppress_exceptions(default=AnalyticsResponse()) - async def fetch_analytics( + @suppress_exceptions(default=OldAnalyticsResponse()) + async def fetch_legacy_analytics( self, request: Request, - query: Optional[Query] = Depends(parse_query_request), - ) -> AnalyticsResponse: + query: Optional[TracingQuery] = Depends(parse_query_from_params_request), + ) -> OldAnalyticsResponse: body_json = None query_from_body = None @@ -509,24 +529,104 @@ async def fetch_analytics( body_json = await request.json() if body_json: - query_from_body = parse_body_request(**body_json) + query_from_body = parse_query_from_body_request( + **body_json, + ) except: # pylint: disable=bare-except pass - merged_query = merge_queries(query, query_from_body) + merged_query = merge_queries( + query, + query_from_body, + ) # DEBUGGING # log.trace(merged_query.model_dump(mode="json", exclude_none=True)) # --------- - buckets = await self.service.analytics( + buckets = await self.service.legacy_analytics( project_id=UUID(request.state.project_id), query=merged_query, ) + # DEBUGGING + # log.trace([b.model_dump(mode="json", exclude_none=True) for b in buckets]) + # --------- + + return OldAnalyticsResponse( + count=len(buckets), + buckets=buckets, + ) + + @intercept_exceptions() + @suppress_exceptions(default=AnalyticsResponse()) + async def fetch_analytics( + self, + request: Request, + analytics: Tuple[Optional[TracingQuery], Optional[List[MetricSpec]]] = Depends( + parse_analytics_from_params_request + ), + ) -> AnalyticsResponse: + body_json = None + analytics_from_body = (None, None) + + try: + body_json = await request.json() + + if body_json: + analytics_from_body = parse_analytics_from_body_request( + **body_json, + ) + + except: # pylint: disable=bare-except + pass + + ( + query, + specs, + ) = merge_analytics( + analytics, + analytics_from_body, + ) + + if not specs: + specs = [ + MetricSpec( + type=MetricType.NUMERIC_CONTINUOUS, + path="attributes.ag.metrics.duration.cumulative", + ), + MetricSpec( + type=MetricType.NUMERIC_CONTINUOUS, + path="attributes.ag.metrics.errors.cumulative", + ), + MetricSpec( + type=MetricType.NUMERIC_CONTINUOUS, + path="attributes.ag.metrics.costs.cumulative.total", + ), + MetricSpec( + type=MetricType.NUMERIC_CONTINUOUS, + path="attributes.ag.metrics.tokens.cumulative.total", + ), + MetricSpec( + type=MetricType.CATEGORICAL_SINGLE, + path="attributes.ag.type.trace", + ), + MetricSpec( + type=MetricType.CATEGORICAL_SINGLE, + path="attributes.ag.type.span", + ), + ] + + buckets = await self.service.analytics( + project_id=UUID(request.state.project_id), + query=query, + specs=specs, + ) + return AnalyticsResponse( - version=self.VERSION, count=len(buckets), buckets=buckets, + query=query, + specs=specs, ) diff --git a/api/oss/src/apis/fastapi/tracing/utils.py b/api/oss/src/apis/fastapi/tracing/utils.py index b7ff8bac03..cb6d30195a 100644 --- a/api/oss/src/apis/fastapi/tracing/utils.py +++ b/api/oss/src/apis/fastapi/tracing/utils.py @@ -1,10 +1,11 @@ -from typing import Optional, Union, Dict, Tuple +from typing import Optional, Union, Dict, Tuple, List from json import loads, dumps from copy import deepcopy from hashlib import blake2b from traceback import format_exc +from datetime import datetime -from fastapi import Query as _Query +from fastapi import Query from oss.src.utils.logging import get_module_logger @@ -30,9 +31,10 @@ Formatting, Windowing, Filtering, - Query, + TracingQuery, Focus, Format, + MetricSpec, ) from oss.src.core.tracing.utils import ( @@ -56,11 +58,23 @@ def _parse_windowing( - oldest: Optional[Union[str, int]] = None, - newest: Optional[Union[str, int]] = None, + oldest: Optional[Union[str, int, datetime]] = None, + newest: Optional[Union[str, int, datetime]] = None, limit: Optional[int] = None, - window: Optional[int] = None, + interval: Optional[int] = None, + rate: Optional[float] = None, ) -> Optional[Windowing]: + if all( + [ + oldest is None, + newest is None, + limit is None, + interval is None, + rate is None, + ] + ): + return None + oldest = parse_timestamp_to_datetime(oldest) newest = parse_timestamp_to_datetime(newest) @@ -68,7 +82,8 @@ def _parse_windowing( oldest=oldest, newest=newest, limit=limit, - window=window, + interval=interval, + rate=rate, ) return _windowing @@ -112,32 +127,34 @@ def _parse_formatting( return _formatting -def parse_query_request( +def parse_query_from_params_request( # GROUPING - focus: Optional[Focus] = _Query(None), - format: Optional[Format] = _Query(None), # pylint: disable=redefined-builtin + focus: Optional[Focus] = Query(None), + format: Optional[Format] = Query(None), # pylint: disable=redefined-builtin # WINDOWING - oldest: Optional[Union[str, int]] = _Query(None), - newest: Optional[Union[str, int]] = _Query(None), - limit: Optional[int] = _Query(None), - window: Optional[int] = _Query(None), + oldest: Optional[Union[str, int]] = Query(None), + newest: Optional[Union[str, int]] = Query(None), + limit: Optional[int] = Query(None), + interval: Optional[int] = Query(None), + rate: Optional[float] = Query(None), # FILTERING - filter=_Query(None), # pylint: disable=redefined-builtin -) -> Query: - return parse_body_request( + filter=Query(None), # pylint: disable=redefined-builtin +) -> TracingQuery: + return parse_query_from_body_request( focus=focus, format=format, # oldest=oldest, newest=newest, limit=limit, - window=window, + interval=interval, + rate=rate, # filter=filter, ) -def parse_body_request( +def parse_query_from_body_request( # GROUPING focus: Optional[Focus] = None, format: Optional[Format] = None, # pylint: disable=redefined-builtin @@ -145,12 +162,13 @@ def parse_body_request( oldest: Optional[Union[str, int]] = None, newest: Optional[Union[str, int]] = None, limit: Optional[int] = None, - window: Optional[int] = None, + interval: Optional[int] = None, + rate: Optional[float] = None, # FILTERING filter: Optional[Union[dict, str]] = None, # pylint: disable=redefined-builtin -) -> Query: +) -> TracingQuery: try: - _query = Query( + _query = TracingQuery( formatting=_parse_formatting( focus=focus, format=format, @@ -159,7 +177,8 @@ def parse_body_request( oldest=oldest, newest=newest, limit=limit, - window=window, + interval=interval, + rate=rate, ), filtering=_parse_filtering( filter=filter, @@ -168,41 +187,43 @@ def parse_body_request( except Exception as e: # pylint: disable=broad-except log.warn(e) - _query = None + _query = TracingQuery() return _query def merge_queries( - query_param: Optional[Query] = None, - query_body: Optional[Query] = None, -) -> Query: - if query_body is None: - if query_param.filtering is None: - query_param.filtering = Filtering() + query_param: Optional[TracingQuery] = None, + query_body: Optional[TracingQuery] = None, +) -> TracingQuery: + if query_param is None and query_body is None: + return TracingQuery( + formatting=_parse_formatting(), + windowing=_parse_windowing(), + filtering=_parse_filtering(), + ) + + if query_body is None and query_param is not None: + query_param.filtering = query_param.filtering or Filtering() + return query_param - if query_param is None: - if query_body.filtering is None: - query_body.filtering = Filtering() + if query_param is None and query_body is not None: + query_body.filtering = query_body.filtering or Filtering() + return query_body - return Query( - formatting=Formatting( - focus=query_param.formatting.focus - or query_body.formatting.focus - or Focus.TRACE, - format=query_param.formatting.format - or query_body.formatting.format - or Format.AGENTA, - ), - windowing=Windowing( - oldest=query_param.windowing.oldest or query_body.windowing.oldest, - newest=query_param.windowing.newest or query_body.windowing.newest, - limit=query_param.windowing.limit or query_body.windowing.limit, - window=query_param.windowing.window or query_body.windowing.window, - ), - filtering=query_param.filtering or query_body.filtering or Filtering(), + if query_param is not None and query_body is not None: + return TracingQuery( + formatting=query_body.formatting or query_param.formatting or Formatting(), + windowing=query_body.windowing or query_param.windowing or Windowing(), + filtering=query_body.filtering or query_param.filtering or Filtering(), + ) + + return TracingQuery( + formatting=_parse_formatting(), + windowing=_parse_windowing(), + filtering=_parse_filtering(), ) @@ -393,7 +414,7 @@ def initialize_ag_attributes(attributes: Optional[dict]) -> dict: for key in ["flags", "tags", "meta", "exception", "hashes"]: cleaned_ag[key] = ag.get(key, None) - # --- move ag.meta.configuration to ag.data.parameters --- + # --- move ag.meta.configuration to ag.data.parameters --- if "meta" in cleaned_ag and cleaned_ag["meta"] is not None: if "configuration" in cleaned_ag["meta"]: if cleaned_ag["data"]["parameters"] is None: @@ -414,20 +435,25 @@ def initialize_ag_attributes(attributes: Optional[dict]) -> dict: REFERENCE_KEYS = [ - "testset", "testcase", + "testset", + "testset_variant", + "testset_revision", + "query", + "query_variant", + "query_revision", "workflow", - "workflow_variants", - "workflow_revisions", + "workflow_variant", + "workflow_revision", "application", - "application_variants", - "application_revisions", + "application_variant", + "application_revision", "evaluator", - "evaluator_variants", - "evaluator_revisions", + "evaluator_variant", + "evaluator_revision", "environment", - "environment_variants", - "environment_revisions", + "environment_variant", + "environment_revision", ] @@ -565,7 +591,6 @@ def _parse_span_from_request(raw_span: OTelSpan) -> Optional[OTelFlatSpans]: if references or links: hash_id = make_hash_id(references=references, links=links) - # log.debug("parsing span with hash_id", hash_id=hash_id) if hash_id: hashes = OTelHash( @@ -578,7 +603,7 @@ def _parse_span_from_request(raw_span: OTelSpan) -> Optional[OTelFlatSpans]: raw_span.hashes = [hashes] # --- Children --- - if raw_span.spans: + if isinstance(raw_span, OTelSpan) and raw_span.spans is not None: raw_span_dtos.extend(parse_spans_from_request(raw_span.spans)) raw_span.spans = None @@ -648,8 +673,8 @@ def _parse_span_into_response( def parse_spans_into_response( span_dtos: OTelFlatSpans, - focus: Focus, - format: Format, + focus: Focus = Focus.TRACE, + format: Format = Format.AGENTA, ) -> Optional[Union[OTelFlatSpans, OTelTraceTree]]: clean_span_dtos: OTelFlatSpans = [] @@ -692,3 +717,112 @@ def parse_spans_into_response( spans: OTelFlatSpans = [] return spans if spans else traces + + +# -- ANALYTICS + + +def parse_specs_from_body_request( + specs: Optional[Union[list, str]] = None, +) -> Optional[List[MetricSpec]]: + if not specs: + return None + + if isinstance(specs, str): + try: + specs = loads(specs) + except Exception as e: # pylint: disable=broad-except + log.warn(f"Error parsing specs string to JSON: {e}") + return None + + if isinstance(specs, list): + return [MetricSpec(**spec) for spec in specs if isinstance(spec, dict)] + + log.warn("Specs should be a list or a JSON string") + + return None + + +def merge_specs( + specs_params: Optional[List[MetricSpec]], + specs_body: Optional[List[MetricSpec]], +) -> List[MetricSpec]: + if not specs_params and not specs_body: + return [] + if not specs_params: + return specs_body or [] + if not specs_body: + return specs_params or [] + + return [] + + +def parse_analytics_from_params_request( + # GROUPING + focus: Optional[Focus] = Query(None), + format: Optional[Format] = Query(None), # pylint: disable=redefined-builtin + # WINDOWING + oldest: Optional[Union[str, int]] = Query(None), + newest: Optional[Union[str, int]] = Query(None), + interval: Optional[int] = Query(None), + rate: Optional[float] = Query(None), + # FILTERING + filter=Query(None), # pylint: disable=redefined-builtin + # METRICS SPECS + specs=Query(None), +) -> Tuple[Optional[TracingQuery], Optional[List[MetricSpec]]]: + return parse_analytics_from_body_request( + focus=focus, + format=format, + # + oldest=oldest, + newest=newest, + interval=interval, + rate=rate, + # + filter=filter, + # + specs=specs, + ) + + +def parse_analytics_from_body_request( + # GROUPING + focus: Optional[Focus] = None, + format: Optional[Format] = None, # pylint: disable=redefined-builtin + # WINDOWING + oldest: Optional[Union[str, int]] = None, + newest: Optional[Union[str, int]] = None, + interval: Optional[int] = None, + rate: Optional[float] = None, + # FILTERING + filter: Optional[Union[dict, str]] = None, # pylint: disable=redefined-builtin + # METRICS SPECS + specs: Optional[Union[list, str]] = None, +) -> Tuple[Optional[TracingQuery], Optional[List[MetricSpec]]]: + return ( + parse_query_from_body_request( + focus=focus, + format=format, + # + oldest=oldest, + newest=newest, + interval=interval, + rate=rate, + # + filter=filter, + ), + parse_specs_from_body_request( + specs=specs, + ), + ) + + +def merge_analytics( + analytics_params: Tuple[Optional[TracingQuery], Optional[List[MetricSpec]]], + analytics_body: Tuple[Optional[TracingQuery], Optional[List[MetricSpec]]], +) -> Tuple[TracingQuery, List[MetricSpec]]: + return ( + merge_queries(analytics_params[0], analytics_body[0]), + merge_specs(analytics_params[1], analytics_body[1]), + ) diff --git a/api/oss/src/apis/fastapi/vault/router.py b/api/oss/src/apis/fastapi/vault/router.py index c7b21f8f06..38ff0d9ec6 100644 --- a/api/oss/src/apis/fastapi/vault/router.py +++ b/api/oss/src/apis/fastapi/vault/router.py @@ -6,7 +6,7 @@ from oss.src.utils.common import is_ee from oss.src.utils.logging import get_module_logger -from oss.src.utils.exceptions import intercept_exceptions +from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions from oss.src.utils.caching import get_cache, set_cache, invalidate_cache from oss.src.core.secrets.services import VaultService @@ -31,7 +31,7 @@ def __init__(self, vault_service: VaultService): self.router = APIRouter() self.router.add_api_route( - "/secrets", + "/secrets/", self.create_secret, methods=["POST"], operation_id="create_secret", @@ -39,7 +39,7 @@ def __init__(self, vault_service: VaultService): response_model=SecretResponseDTO, ) self.router.add_api_route( - "/secrets", + "/secrets/", self.list_secrets, methods=["GET"], operation_id="list_secrets", diff --git a/api/oss/src/apis/fastapi/workflows/models.py b/api/oss/src/apis/fastapi/workflows/models.py index 63da7dc318..6f00e38005 100644 --- a/api/oss/src/apis/fastapi/workflows/models.py +++ b/api/oss/src/apis/fastapi/workflows/models.py @@ -2,7 +2,10 @@ from pydantic import BaseModel -from oss.src.core.shared.dtos import Windowing, Reference +from oss.src.core.shared.dtos import ( + Windowing, + Reference, +) from oss.src.core.workflows.dtos import ( # Workflow, @@ -10,7 +13,7 @@ WorkflowEdit, WorkflowQuery, WorkflowFork, - WorkflowLog, + WorkflowRevisionsLog, # WorkflowVariant, WorkflowVariantCreate, @@ -22,13 +25,12 @@ WorkflowRevisionEdit, WorkflowRevisionQuery, WorkflowRevisionCommit, - # - WorkflowServiceRequest, - WorkflowServiceResponse, - WorkflowServiceInterface, ) +# WORKFLOWS -------------------------------------------------------------------- + + class WorkflowCreateRequest(BaseModel): workflow: WorkflowCreate @@ -39,13 +41,16 @@ class WorkflowEditRequest(BaseModel): class WorkflowQueryRequest(BaseModel): workflow: Optional[WorkflowQuery] = None + # workflow_refs: Optional[List[Reference]] = None + # include_archived: Optional[bool] = None + # windowing: Optional[Windowing] = None -class WorkflowRequest(BaseModel): - workflow: Workflow +class WorkflowForkRequest(BaseModel): + workflow: WorkflowFork class WorkflowResponse(BaseModel): @@ -58,6 +63,9 @@ class WorkflowsResponse(BaseModel): workflows: List[Workflow] = [] +# WORKFLOW VARIANTS ------------------------------------------------------------ + + class WorkflowVariantCreateRequest(BaseModel): workflow_variant: WorkflowVariantCreate @@ -68,9 +76,12 @@ class WorkflowVariantEditRequest(BaseModel): class WorkflowVariantQueryRequest(BaseModel): workflow_variant: Optional[WorkflowVariantQuery] = None + # workflow_refs: Optional[List[Reference]] = None workflow_variant_refs: Optional[List[Reference]] = None + # include_archived: Optional[bool] = None + # windowing: Optional[Windowing] = None @@ -84,6 +95,9 @@ class WorkflowVariantsResponse(BaseModel): workflow_variants: List[WorkflowVariant] = [] +# WORKFLOW REVISIONS ----------------------------------------------------------- + + class WorkflowRevisionCreateRequest(BaseModel): workflow_revision: WorkflowRevisionCreate @@ -94,10 +108,13 @@ class WorkflowRevisionEditRequest(BaseModel): class WorkflowRevisionQueryRequest(BaseModel): workflow_revision: Optional[WorkflowRevisionQuery] = None + # workflow_refs: Optional[List[Reference]] = None workflow_variant_refs: Optional[List[Reference]] = None workflow_revision_refs: Optional[List[Reference]] = None + # include_archived: Optional[bool] = None + # windowing: Optional[Windowing] = None @@ -105,6 +122,16 @@ class WorkflowRevisionCommitRequest(BaseModel): workflow_revision: WorkflowRevisionCommit +class WorkflowRevisionRetrieveRequest(BaseModel): + workflow_ref: Optional[Reference] = None + workflow_variant_ref: Optional[Reference] = None + workflow_revision_ref: Optional[Reference] = None + + +class WorkflowRevisionsLogRequest(BaseModel): + workflow: WorkflowRevisionsLog + + class WorkflowRevisionResponse(BaseModel): count: int = 0 workflow_revision: Optional[WorkflowRevision] = None @@ -113,17 +140,3 @@ class WorkflowRevisionResponse(BaseModel): class WorkflowRevisionsResponse(BaseModel): count: int = 0 workflow_revisions: List[WorkflowRevision] = [] - - -class WorkflowForkRequest(BaseModel): - workflow: WorkflowFork - - -class WorkflowLogRequest(BaseModel): - workflow: WorkflowLog - - -class WorkflowRevisionRetrieveRequest(BaseModel): - workflow_ref: Optional[Reference] = None - workflow_variant_ref: Optional[Reference] = None - workflow_revision_ref: Optional[Reference] = None diff --git a/api/oss/src/apis/fastapi/workflows/router.py b/api/oss/src/apis/fastapi/workflows/router.py index e8191e3338..1d5f8a5c94 100644 --- a/api/oss/src/apis/fastapi/workflows/router.py +++ b/api/oss/src/apis/fastapi/workflows/router.py @@ -8,14 +8,26 @@ from oss.src.utils.exceptions import intercept_exceptions, suppress_exceptions from oss.src.utils.caching import get_cache, set_cache, invalidate_cache -from oss.src.core.shared.dtos import Reference -from oss.src.core.workflows.service import WorkflowsService +from oss.src.core.shared.dtos import ( + Reference, +) +from oss.src.core.workflows.dtos import ( + WorkflowRevisionData, + WorkflowRevision, + # + WorkflowServiceRequest, + WorkflowServiceResponse, + WorkflowServiceInterface, +) +from oss.src.core.workflows.service import ( + WorkflowsService, +) + from oss.src.apis.fastapi.workflows.models import ( WorkflowCreateRequest, WorkflowEditRequest, WorkflowQueryRequest, WorkflowForkRequest, - WorkflowLogRequest, WorkflowResponse, WorkflowsResponse, # @@ -29,21 +41,11 @@ WorkflowRevisionEditRequest, WorkflowRevisionQueryRequest, WorkflowRevisionCommitRequest, + WorkflowRevisionRetrieveRequest, + WorkflowRevisionsLogRequest, WorkflowRevisionResponse, WorkflowRevisionsResponse, - # - WorkflowRevisionRetrieveRequest, - # - WorkflowServiceRequest, - WorkflowServiceResponse, - WorkflowServiceInterface, -) - -from oss.src.core.workflows.dtos import ( - WorkflowRevision, - WorkflowRevisionData, ) - from oss.src.apis.fastapi.workflows.utils import ( parse_workflow_query_request_from_params, parse_workflow_query_request_from_body, @@ -67,8 +69,6 @@ class WorkflowsRouter: - VERSION = "1.0.0" - def __init__( self, workflows_service: WorkflowsService, @@ -77,7 +77,7 @@ def __init__( self.router = APIRouter() - # — workflows —————————————————————————————————————————————————————————— + # WORKFLOWS ------------------------------------------------------------ self.router.add_api_route( "/", @@ -129,16 +129,6 @@ def __init__( response_model_exclude_none=True, ) - self.router.add_api_route( - "/", - self.query_workflows, - methods=["GET"], - operation_id="list_workflows", - status_code=status.HTTP_200_OK, - response_model=WorkflowsResponse, - response_model_exclude_none=True, - ) - self.router.add_api_route( "/query", self.query_workflows, @@ -149,9 +139,7 @@ def __init__( response_model_exclude_none=True, ) - # —————————————————————————————————————————————————————————————————————— - - # — workflow variants —————————————————————————————————————————————————— + # WORKFLOW VARIANTS ---------------------------------------------------- self.router.add_api_route( "/variants/", @@ -203,16 +191,6 @@ def __init__( response_model_exclude_none=True, ) - self.router.add_api_route( - "/variants/", - self.query_workflow_variants, - methods=["GET"], - operation_id="list_workflow_variants", - status_code=status.HTTP_200_OK, - response_model=WorkflowVariantsResponse, - response_model_exclude_none=True, - ) - self.router.add_api_route( "/variants/query", self.query_workflow_variants, @@ -223,8 +201,6 @@ def __init__( response_model_exclude_none=True, ) - # ---------------------------------------------------------------------- - self.router.add_api_route( "/variants/fork", self.fork_workflow_variant, @@ -235,9 +211,7 @@ def __init__( response_model_exclude_none=True, ) - # —————————————————————————————————————————————————————————————————————— - - # — workflow revisions ————————————————————————————————————————————————— + # WORKFLOW REVISIONS --------------------------------------------------- self.router.add_api_route( "/revisions/retrieve", @@ -299,16 +273,6 @@ def __init__( response_model_exclude_none=True, ) - self.router.add_api_route( - "/revisions/", - self.query_workflow_revisions, - methods=["GET"], - operation_id="list_workflow_revisions", - status_code=status.HTTP_200_OK, - response_model=WorkflowRevisionsResponse, - response_model_exclude_none=True, - ) - self.router.add_api_route( "/revisions/query", self.query_workflow_revisions, @@ -319,8 +283,6 @@ def __init__( response_model_exclude_none=True, ) - # ---------------------------------------------------------------------- - self.router.add_api_route( "/revisions/commit", self.commit_workflow_revision, @@ -341,9 +303,7 @@ def __init__( response_model_exclude_none=True, ) - # —————————————————————————————————————————————————————————————————————— - - # — workflow executions ———————————————————————————————————————————————— + # WORKFLOW SERVICES ---------------------------------------------------- self.router.add_api_route( "/invoke", @@ -358,16 +318,14 @@ def __init__( self.router.add_api_route( "/inspect", self.inspect_workflow, - methods=["GET"], + methods=["POST"], operation_id="inspect_workflow", status_code=status.HTTP_200_OK, response_model=WorkflowServiceInterface, response_model_exclude_none=True, ) - # —————————————————————————————————————————————————————————————————————— - - # — artifacts —————————————————————————————————————————————————————————————— + # WORKFLOWS ---------------------------------------------------------------- @intercept_exceptions() async def create_workflow( @@ -377,12 +335,12 @@ async def create_workflow( workflow_create_request: WorkflowCreateRequest, ) -> WorkflowResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow = await self.workflows_service.create_workflow( project_id=UUID(request.state.project_id), @@ -392,7 +350,7 @@ async def create_workflow( ) workflow_response = WorkflowResponse( - count=1 if workflow is not None else 0, + count=1 if workflow else 0, workflow=workflow, ) @@ -407,12 +365,12 @@ async def fetch_workflow( workflow_id: UUID, ) -> WorkflowResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow = await self.workflows_service.fetch_workflow( project_id=UUID(request.state.project_id), @@ -421,7 +379,7 @@ async def fetch_workflow( ) workflow_response = WorkflowResponse( - count=1 if workflow is not None else 0, + count=1 if workflow else 0, workflow=workflow, ) @@ -433,21 +391,19 @@ async def edit_workflow( request: Request, *, workflow_id: UUID, + # workflow_edit_request: WorkflowEditRequest, ) -> WorkflowResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore if str(workflow_id) != str(workflow_edit_request.workflow.id): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"ID mismatch between path params and body params: {workflow_id} != {workflow_edit_request.workflow.id}", - ) + return WorkflowResponse() workflow = await self.workflows_service.edit_workflow( project_id=UUID(request.state.project_id), @@ -457,7 +413,7 @@ async def edit_workflow( ) workflow_response = WorkflowResponse( - count=1 if workflow is not None else 0, + count=1 if workflow else 0, workflow=workflow, ) @@ -471,12 +427,12 @@ async def archive_workflow( workflow_id: UUID, ) -> WorkflowResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow = await self.workflows_service.archive_workflow( project_id=UUID(request.state.project_id), @@ -486,7 +442,7 @@ async def archive_workflow( ) workflow_response = WorkflowResponse( - count=1 if workflow is not None else 0, + count=1 if workflow else 0, workflow=workflow, ) @@ -500,12 +456,12 @@ async def unarchive_workflow( workflow_id: UUID, ) -> WorkflowResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow = await self.workflows_service.unarchive_workflow( project_id=UUID(request.state.project_id), @@ -515,7 +471,7 @@ async def unarchive_workflow( ) workflow_response = WorkflowResponse( - count=1 if workflow is not None else 0, + count=1 if workflow else 0, workflow=workflow, ) @@ -549,12 +505,12 @@ async def query_workflows( ) if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflows = await self.workflows_service.query_workflows( project_id=UUID(request.state.project_id), @@ -575,9 +531,7 @@ async def query_workflows( return workflows_response - # —————————————————————————————————————————————————————————————————————————— - - # — workflow variants —————————————————————————————————————————————————————— + # WORKFLOW VARIANTS -------------------------------------------------------- @intercept_exceptions() async def create_workflow_variant( @@ -587,12 +541,12 @@ async def create_workflow_variant( workflow_variant_create_request: WorkflowVariantCreateRequest, ) -> WorkflowVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_variant = await self.workflows_service.create_workflow_variant( project_id=UUID(request.state.project_id), @@ -602,7 +556,7 @@ async def create_workflow_variant( ) workflow_variant_response = WorkflowVariantResponse( - count=1 if workflow_variant is not None else 0, + count=1 if workflow_variant else 0, workflow_variant=workflow_variant, ) @@ -617,12 +571,12 @@ async def fetch_workflow_variant( workflow_variant_id: UUID, ) -> WorkflowVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_variant = await self.workflows_service.fetch_workflow_variant( project_id=UUID(request.state.project_id), @@ -631,7 +585,7 @@ async def fetch_workflow_variant( ) workflow_variant_response = WorkflowVariantResponse( - count=1 if workflow_variant is not None else 0, + count=1 if workflow_variant else 0, workflow_variant=workflow_variant, ) @@ -643,23 +597,21 @@ async def edit_workflow_variant( request: Request, *, workflow_variant_id: UUID, + # workflow_variant_edit_request: WorkflowVariantEditRequest, ) -> WorkflowVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore if str(workflow_variant_id) != str( workflow_variant_edit_request.workflow_variant.id ): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"ID mismatch between path params and body params: {workflow_variant_id} != {workflow_variant_edit_request.variant.id}", - ) + return WorkflowVariantResponse() workflow_variant = await self.workflows_service.edit_workflow_variant( project_id=UUID(request.state.project_id), @@ -669,8 +621,8 @@ async def edit_workflow_variant( ) workflow_variant_response = WorkflowVariantResponse( - count=1 if workflow_variant is not None else 0, - variant=workflow_variant, + count=1 if workflow_variant else 0, + workflow_variant=workflow_variant, ) return workflow_variant_response @@ -683,12 +635,12 @@ async def archive_workflow_variant( workflow_variant_id: UUID, ) -> WorkflowVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_variant = await self.workflows_service.archive_workflow_variant( project_id=UUID(request.state.project_id), @@ -698,7 +650,7 @@ async def archive_workflow_variant( ) workflow_variant_response = WorkflowVariantResponse( - count=1 if workflow_variant is not None else 0, + count=1 if workflow_variant else 0, workflow_variant=workflow_variant, ) @@ -712,12 +664,12 @@ async def unarchive_workflow_variant( workflow_variant_id: UUID, ) -> WorkflowVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_variant = await self.workflows_service.unarchive_workflow_variant( project_id=UUID(request.state.project_id), @@ -727,7 +679,7 @@ async def unarchive_workflow_variant( ) workflow_variant_response = WorkflowVariantResponse( - count=1 if workflow_variant is not None else 0, + count=1 if workflow_variant else 0, workflow_variant=workflow_variant, ) @@ -763,12 +715,12 @@ async def query_workflow_variants( ) if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_variants = await self.workflows_service.query_workflow_variants( project_id=UUID(request.state.project_id), @@ -790,8 +742,6 @@ async def query_workflow_variants( return workflow_variants_response - # -------------------------------------------------------------------------- - @intercept_exceptions() async def fork_workflow_variant( self, @@ -800,12 +750,12 @@ async def fork_workflow_variant( workflow_fork_request: WorkflowForkRequest, ) -> WorkflowVariantResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_variant = await self.workflows_service.fork_workflow_variant( project_id=UUID(request.state.project_id), @@ -815,15 +765,13 @@ async def fork_workflow_variant( ) workflow_variant_response = WorkflowVariantResponse( - count=1 if workflow_variant is not None else 0, + count=1 if workflow_variant else 0, workflow_variant=workflow_variant, ) return workflow_variant_response - # —————————————————————————————————————————————————————————————————————————— - - # — workflow revisions ————————————————————————————————————————————————————— + # WORKFLOW REVISIONS ------------------------------------------------------- @intercept_exceptions() async def create_workflow_revision( @@ -833,12 +781,12 @@ async def create_workflow_revision( workflow_revision_create_request: WorkflowRevisionCreateRequest, ) -> WorkflowRevisionResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_revision = await self.workflows_service.create_workflow_revision( project_id=UUID(request.state.project_id), @@ -848,7 +796,7 @@ async def create_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) @@ -863,12 +811,12 @@ async def fetch_workflow_revision( workflow_revision_id: UUID, ) -> WorkflowRevisionResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_revision = await self.workflows_service.fetch_workflow_revision( project_id=UUID(request.state.project_id), @@ -877,7 +825,7 @@ async def fetch_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) @@ -889,23 +837,21 @@ async def edit_workflow_revision( request: Request, *, workflow_revision_id: UUID, + # workflow_revision_request: WorkflowRevisionEditRequest, ) -> WorkflowRevisionResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore if str(workflow_revision_id) != str( workflow_revision_request.workflow_revision.id ): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"ID mismatch between path params and body params: {workflow_revision_id} != {workflow_revision_request.workflow_revision.id}", - ) + return WorkflowRevisionResponse() workflow_revision = await self.workflows_service.edit_workflow_revision( project_id=UUID(request.state.project_id), @@ -915,7 +861,7 @@ async def edit_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) @@ -929,12 +875,12 @@ async def archive_workflow_revision( workflow_revision_id: UUID, ) -> WorkflowRevisionResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_revision = await self.workflows_service.archive_workflow_revision( project_id=UUID(request.state.project_id), @@ -944,7 +890,7 @@ async def archive_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) @@ -958,12 +904,12 @@ async def unarchive_workflow_revision( workflow_revision_id: UUID, ) -> WorkflowRevisionResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_revision = await self.workflows_service.unarchive_workflow_revision( project_id=UUID(request.state.project_id), @@ -973,7 +919,7 @@ async def unarchive_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) @@ -1007,12 +953,12 @@ async def query_workflow_revisions( ) if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_revisions = await self.workflows_service.query_workflow_revisions( project_id=UUID(request.state.project_id), @@ -1035,32 +981,27 @@ async def query_workflow_revisions( return workflow_revisions_response - # -------------------------------------------------------------------------- - @intercept_exceptions() async def commit_workflow_revision( self, request: Request, *, - workflow_revision_commit_request: WorkflowRevisionCommitRequest, workflow_variant_id: Optional[UUID] = None, + # + workflow_revision_commit_request: WorkflowRevisionCommitRequest, ) -> WorkflowRevisionResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_WORKFLOWS, + permission=Permission.EDIT_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - if workflow_variant_id: - if str(workflow_variant_id) != str( - workflow_revision_commit_request.workflow_revision.workflow_variant_id - ): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"ID mismatch between path params and body params: {workflow_variant_id} != {workflow_revision_commit_request.workflow_revision.workflow_variant_id}", - ) + if str(workflow_variant_id) != str( + workflow_revision_commit_request.workflow_revision.workflow_variant_id + ): + return WorkflowRevisionResponse() workflow_revision = await self.workflows_service.commit_workflow_revision( project_id=UUID(request.state.project_id), @@ -1070,7 +1011,7 @@ async def commit_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) @@ -1082,28 +1023,28 @@ async def log_workflow_revisions( self, request: Request, *, - workflow_log_request: WorkflowLogRequest, + workflow_revisions_log_request: WorkflowRevisionsLogRequest, ) -> WorkflowRevisionsResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore - revisions = await self.workflows_service.log_workflow_revisions( + workflow_revisions = await self.workflows_service.log_workflow_revisions( project_id=UUID(request.state.project_id), # - workflow_log=workflow_log_request.workflow, + workflow_revisions_log=workflow_revisions_log_request.workflow, ) - revisions_response = WorkflowRevisionsResponse( - count=len(revisions), - revisions=revisions, + workflow_revisions_response = WorkflowRevisionsResponse( + count=len(workflow_revisions), + workflow_revisions=workflow_revisions, ) - return revisions_response + return workflow_revisions_response @intercept_exceptions() @suppress_exceptions(default=WorkflowRevisionResponse()) @@ -1111,33 +1052,15 @@ async def retrieve_workflow_revision( self, request: Request, *, - retrieve_request_params: Optional[WorkflowRevisionRetrieveRequest] = Depends( - parse_workflow_revision_retrieve_request_from_params - ), + workflow_revision_retrieve_request: WorkflowRevisionRetrieveRequest, ) -> WorkflowRevisionResponse: - body_json = None - retrieve_request_body = None - - try: - body_json = await request.json() - - if body_json: - retrieve_request_body = ( - parse_workflow_revision_retrieve_request_from_body(**body_json) - ) - - except: # pylint: disable=bare-except - pass - - workflow_revision_retrieve_request = ( - retrieve_request_params or retrieve_request_body - ) - - if not workflow_revision_retrieve_request: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Could not retrieve workflow revision. Please provide a valid request.", - ) + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.VIEW_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore cache_key = { "artifact_ref": workflow_revision_retrieve_request.workflow_ref, @@ -1145,7 +1068,7 @@ async def retrieve_workflow_revision( "revision_ref": workflow_revision_retrieve_request.workflow_revision_ref, } - workflow_revision = get_cache( + workflow_revision = await get_cache( namespace="workflows:retrieve", project_id=request.state.project_id, user_id=request.state.user_id, @@ -1162,7 +1085,7 @@ async def retrieve_workflow_revision( workflow_revision_ref=workflow_revision_retrieve_request.workflow_revision_ref, ) - set_cache( + await set_cache( namespace="workflows:retrieve", project_id=request.state.project_id, user_id=request.state.user_id, @@ -1171,15 +1094,13 @@ async def retrieve_workflow_revision( ) workflow_revision_response = WorkflowRevisionResponse( - count=1 if workflow_revision is not None else 0, + count=1 if workflow_revision else 0, workflow_revision=workflow_revision, ) return workflow_revision_response - # —————————————————————————————————————————————————————————————————————————— - - # — workflow executions ———————————————————————————————————————————————————— + # WORKFLOW SERVICES -------------------------------------------------------- @intercept_exceptions() @suppress_exceptions(default=WorkflowServiceResponse()) @@ -1188,18 +1109,20 @@ async def invoke_workflow( request: Request, *, workflow_service_request: WorkflowServiceRequest, + # workflow_revision_data: Optional[WorkflowRevisionData] = None, + # workflow_retrieve_request: Optional[WorkflowRevisionRetrieveRequest] = Depends( parse_workflow_revision_retrieve_request_from_params ), ) -> WorkflowServiceResponse: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.RUN_WORKFLOWS, + permission=Permission.RUN_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore workflow_revision = None @@ -1228,13 +1151,13 @@ async def invoke_workflow( detail="Workflow revision could not be found.", ) - if workflow_revision.data.url: + if workflow_revision.data and workflow_revision.data.url: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Workflows with URLs cannot be invoked with this endpoint. Please send a request directly to the URL.", ) - if not workflow_revision.data.uri: + if workflow_revision.data and not workflow_revision.data.uri: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Workflows without URIs cannot be invoked with this endpoint. Please use a workflow revision with URI.", @@ -1259,19 +1182,19 @@ async def inspect_workflow( workflow_service_interface: WorkflowServiceInterface, ) -> WorkflowServiceInterface: if is_ee(): - if not await check_action_access( + if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_WORKFLOWS, + permission=Permission.VIEW_WORKFLOWS, # type: ignore ): - raise FORBIDDEN_EXCEPTION + raise FORBIDDEN_EXCEPTION # type: ignore cache_key = { "uri": workflow_service_interface.uri, "url": workflow_service_interface.url, } - workflow_service_interface = get_cache( + _workflow_service_interface = await get_cache( namespace="workflows:inspect", project_id=request.state.project_id, user_id=request.state.user_id, @@ -1279,22 +1202,20 @@ async def inspect_workflow( model=WorkflowServiceInterface, ) - if not workflow_service_interface: - workflow_service_interface = await self.workflows_service.inspect_workflow( + if not _workflow_service_interface: + _workflow_service_interface = await self.workflows_service.inspect_workflow( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), # interface=workflow_service_interface, ) - set_cache( + await set_cache( namespace="workflows:inspect", project_id=request.state.project_id, user_id=request.state.user_id, key=cache_key, - value=workflow_service_interface, + value=_workflow_service_interface, ) - return workflow_service_interface - - # —————————————————————————————————————————————————————————————————————————— + return _workflow_service_interface diff --git a/api/oss/src/apis/fastapi/workflows/utils.py b/api/oss/src/apis/fastapi/workflows/utils.py index bcd69dde4e..e3fde5860c 100644 --- a/api/oss/src/apis/fastapi/workflows/utils.py +++ b/api/oss/src/apis/fastapi/workflows/utils.py @@ -1,12 +1,15 @@ -from typing import Optional -from json import loads +from typing import Optional, Literal, List from uuid import UUID from datetime import datetime from fastapi import Query from oss.src.utils.logging import get_module_logger -from oss.src.core.shared.dtos import Reference, Meta, Flags, Windowing + +from oss.src.core.shared.dtos import ( + Windowing, + Reference, +) from oss.src.core.workflows.dtos import ( WorkflowFlags, # @@ -15,6 +18,9 @@ WorkflowRevisionQuery, ) +from oss.src.apis.fastapi.shared.utils import ( + parse_metadata, +) from oss.src.apis.fastapi.workflows.models import ( WorkflowQueryRequest, WorkflowVariantQueryRequest, @@ -28,6 +34,13 @@ def parse_workflow_query_request_from_params( workflow_id: Optional[UUID] = Query(None), + workflow_ids: Optional[List[UUID]] = Query(None), + workflow_slug: Optional[str] = Query(None), + workflow_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), @@ -35,98 +48,101 @@ def parse_workflow_query_request_from_params( include_archived: Optional[bool] = Query(None), # next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), ) -> WorkflowQueryRequest: - if flags: - try: - flags = WorkflowFlags(**loads(flags)) if flags else WorkflowFlags() + _flags, _tags, _meta = parse_metadata(flags, tags, meta) - except Exception: # pylint: disable=broad-except - flags = None + __flags = WorkflowFlags(**_flags) if _flags else None # type: ignore - log.warn("Failed to parse flags (%s)", flags) - - if tags: - try: - tags = loads(tags) - except Exception: # pylint: disable=broad-except - tags = None - - log.warn("Failed to parse tags (%s)", tags) - - if meta: - try: - meta = loads(meta) - except Exception: # pylint: disable=broad-except - meta = None + workflow = ( + WorkflowQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) - log.warn(f"Failed to parse meta ({meta})") + workflow_refs = ( + ( + [ + Reference( + id=workflow_id, + slug=workflow_slug, + ) + ] + if workflow_id or workflow_slug + else [] + ) + + ( + [ + Reference( + id=workflow_id, + slug=workflow_slug, + ) + for workflow_id, workflow_slug in zip( + workflow_ids, + workflow_slugs, + ) + ] + if workflow_ids and workflow_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) return parse_workflow_query_request_from_body( - workflow_id=workflow_id, - flags=flags, - tags=tags, - meta=meta, + workflow=workflow, + # + workflow_refs=workflow_refs, # include_archived=include_archived, # - next=next, - start=start, - stop=stop, - limit=limit, + windowing=windowing, ) def parse_workflow_query_request_from_body( - workflow_id: Optional[UUID] = None, - flags: Optional[WorkflowFlags] = None, - tags: Optional[dict] = None, - meta: Optional[Meta] = None, + workflow: Optional[WorkflowQuery] = None, + # + workflow_refs: Optional[List[Reference]] = None, # include_archived: Optional[bool] = None, # - next: Optional[UUID] = None, # pylint disable=redefined-builtin - start: Optional[datetime] = None, - stop: Optional[datetime] = None, - limit: Optional[int] = None, - order: Optional[str] = None, + windowing: Optional[Windowing] = None, ) -> WorkflowQueryRequest: workflow_query_request = None try: workflow_query_request = WorkflowQueryRequest( - workflow=( - WorkflowQuery( - workflow_id=workflow_id, - # - flags=flags, - meta=meta, - tags=tags, - ) - if workflow_id or flags or meta or tags - else None - ), + workflow=workflow, + # + workflow_refs=workflow_refs, # include_archived=include_archived, # - windowing=( - Windowing( - next=next, - start=start, - stop=stop, - limit=limit, - order=order, - ) - if next or start or stop or limit - else None - ), + windowing=windowing, ) except Exception as e: # pylint: disable=broad-except - log.warn("Error parsing workflow query request: %s", e) - - workflow_query_request = None + workflow_query_request = WorkflowQueryRequest() return workflow_query_request @@ -135,25 +151,42 @@ def merge_workflow_query_requests( query_request_params: Optional[WorkflowQueryRequest] = None, query_request_body: Optional[WorkflowQueryRequest] = None, ) -> WorkflowQueryRequest: - if query_request_body is None: + if query_request_params and not query_request_body: return query_request_params - if query_request_params is None: + if not query_request_params and query_request_body: return query_request_body - return WorkflowQueryRequest( - workflow=query_request_body.workflow or query_request_params.workflow, - # - include_archived=query_request_body.include_archived - or query_request_params.include_archived, - # - windowing=query_request_body.windowing or query_request_params.windowing, - ) + if query_request_params and query_request_body: + return WorkflowQueryRequest( + workflow=query_request_body.workflow or query_request_params.workflow, + # + workflow_refs=query_request_body.workflow_refs + or query_request_params.workflow_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return WorkflowQueryRequest() def parse_workflow_variant_query_request_from_params( workflow_id: Optional[UUID] = Query(None), + workflow_ids: Optional[List[UUID]] = Query(None), + workflow_slug: Optional[str] = Query(None), + workflow_slugs: Optional[List[str]] = Query(None), + # workflow_variant_id: Optional[UUID] = Query(None), + workflow_variant_ids: Optional[List[UUID]] = Query(None), + workflow_variant_slug: Optional[str] = Query(None), + workflow_variant_slugs: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), @@ -161,100 +194,131 @@ def parse_workflow_variant_query_request_from_params( include_archived: Optional[bool] = Query(None), # next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), ) -> WorkflowVariantQueryRequest: - if flags: - try: - flags = WorkflowFlags(**loads(flags)) if flags else WorkflowFlags() - - except Exception: # pylint: disable=broad-except - flags = None - - log.warn("Failed to parse flags (%s)", flags) + _flags, _tags, _meta = parse_metadata(flags, tags, meta) - if tags: - try: - tags = loads(tags) - except Exception: # pylint: disable=broad-except - tags = None + __flags = WorkflowFlags(**_flags) if _flags else None # type: ignore - log.warn("Failed to parse tags (%s)", tags) + workflow_variant = ( + WorkflowVariantQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) - if meta: - try: - meta = loads(meta) - except Exception: # pylint: disable=broad-except - meta = None + workflow_refs = ( + ( + [ + Reference( + id=workflow_id, + slug=workflow_slug, + ) + ] + if workflow_id or workflow_slug + else [] + ) + + ( + [ + Reference( + id=workflow_id, + slug=workflow_slug, + ) + for workflow_id, workflow_slug in zip( + workflow_ids, + workflow_slugs, + ) + ] + if workflow_ids and workflow_slugs + else [] + ) + ) or None - log.warn(f"Failed to parse meta ({meta})") + workflow_variant_refs = ( + ( + [ + Reference( + id=workflow_variant_id, + slug=workflow_variant_slug, + ) + ] + if workflow_variant_id or workflow_variant_slug + else [] + ) + + ( + [ + Reference( + id=workflow_variant_id, + slug=workflow_variant_slug, + ) + for workflow_variant_id, workflow_variant_slug in zip( + workflow_variant_ids, + workflow_variant_slugs, + ) + ] + if workflow_variant_ids and workflow_variant_slugs + else [] + ) + ) or None + + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) return parse_workflow_variant_query_request_from_body( - workflow_id=workflow_id, - workflow_variant_id=workflow_variant_id, - flags=flags, - tags=tags, - meta=meta, + workflow_variant=workflow_variant, + # + workflow_refs=workflow_refs or None, + workflow_variant_refs=workflow_variant_refs or None, # include_archived=include_archived, # - next=next, - start=start, - stop=stop, - limit=limit, + windowing=windowing, ) def parse_workflow_variant_query_request_from_body( - workflow_id: Optional[UUID] = None, - workflow_variant_id: Optional[UUID] = None, - flags: Optional[WorkflowFlags] = None, - tags: Optional[dict] = None, - meta: Optional[Meta] = None, + workflow_variant: Optional[WorkflowVariantQuery] = None, + # + workflow_refs: Optional[List[Reference]] = None, + workflow_variant_refs: Optional[List[Reference]] = None, # include_archived: Optional[bool] = None, # - next: Optional[UUID] = None, # pylint disable=redefined-builtin - start: Optional[datetime] = None, - stop: Optional[datetime] = None, - limit: Optional[int] = None, - order: Optional[str] = None, + windowing: Optional[Windowing] = None, ) -> WorkflowVariantQueryRequest: workflow_variant_query_request = None try: workflow_variant_query_request = WorkflowVariantQueryRequest( - workflow_variant=( - WorkflowVariantQuery( - workflow_id=workflow_id, - workflow_variant_id=workflow_variant_id, - flags=flags, - meta=meta, - tags=tags, - ) - if workflow_id or workflow_variant_id or flags or meta or tags - else None - ), + workflow_variant=workflow_variant, + # + workflow_refs=workflow_refs, + workflow_variant_refs=workflow_variant_refs, # include_archived=include_archived, # - windowing=( - Windowing( - next=next, - start=start, - stop=stop, - limit=limit, - order=order, - ) - if next or start or stop or limit - else None - ), + windowing=windowing, ) except Exception as e: # pylint: disable=broad-except - log.warn("Error parsing workflow variant body request: %s", e) - - workflow_variant_query_request = None + workflow_variant_query_request = WorkflowVariantQueryRequest() return workflow_variant_query_request @@ -263,27 +327,52 @@ def merge_workflow_variant_query_requests( query_request_params: Optional[WorkflowVariantQueryRequest] = None, query_request_body: Optional[WorkflowVariantQueryRequest] = None, ) -> WorkflowVariantQueryRequest: - if query_request_body is None: + if query_request_params and not query_request_body: return query_request_params - if query_request_params is None: + if not query_request_params and query_request_body: return query_request_body - return WorkflowVariantQueryRequest( - workflow_variant=query_request_body.workflow_variant - or query_request_params.workflow_variant, - # - include_archived=query_request_body.include_archived - or query_request_params.include_archived, - # - windowing=query_request_body.windowing or query_request_params.windowing, - ) + if query_request_params and query_request_body: + return WorkflowVariantQueryRequest( + workflow_variant=query_request_body.workflow_variant + or query_request_params.workflow_variant, + # + workflow_refs=query_request_body.workflow_refs + or query_request_params.workflow_refs, + workflow_variant_refs=query_request_body.workflow_variant_refs + or query_request_params.workflow_variant_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return WorkflowVariantQueryRequest() def parse_workflow_revision_query_request_from_params( workflow_id: Optional[UUID] = Query(None), + workflow_ids: Optional[List[UUID]] = Query(None), + workflow_slug: Optional[str] = Query(None), + workflow_slugs: Optional[List[str]] = Query(None), + # workflow_variant_id: Optional[UUID] = Query(None), + workflow_variant_ids: Optional[List[UUID]] = Query(None), + workflow_variant_slug: Optional[str] = Query(None), + workflow_variant_slugs: Optional[List[str]] = Query(None), + # workflow_revision_id: Optional[UUID] = Query(None), + workflow_revision_ids: Optional[List[UUID]] = Query(None), + workflow_revision_slug: Optional[str] = Query(None), + workflow_revision_slugs: Optional[List[str]] = Query(None), + workflow_revision_version: Optional[str] = Query(None), + workflow_revision_versions: Optional[List[str]] = Query(None), + # + name: Optional[str] = Query(None), + description: Optional[str] = Query(None), + # flags: Optional[str] = Query(None), tags: Optional[str] = Query(None), meta: Optional[str] = Query(None), @@ -291,137 +380,198 @@ def parse_workflow_revision_query_request_from_params( include_archived: Optional[bool] = Query(None), # next: Optional[UUID] = Query(None), # pylint disable=redefined-builtin - start: Optional[datetime] = Query(None), - stop: Optional[datetime] = Query(None), + newest: Optional[datetime] = Query(None), + oldest: Optional[datetime] = Query(None), limit: Optional[int] = Query(None), + order: Optional[Literal["ascending", "descending"]] = Query(None), ) -> WorkflowRevisionQueryRequest: - if flags: - try: - flags = WorkflowFlags(**loads(flags)) if flags else WorkflowFlags() + _flags, _tags, _meta = parse_metadata(flags, tags, meta) - except Exception: # pylint: disable=broad-except - flags = None + __flags = WorkflowFlags(**_flags) if _flags else None # type: ignore - log.warn("Failed to parse flags (%s)", flags) + workflow_revision = ( + WorkflowRevisionQuery( + name=name, + description=description, + # + flags=__flags, + meta=_meta, + tags=_tags, + ) + if __flags or _meta or _tags + else None + ) - if tags: - try: - tags = loads(tags) - except Exception: - tags = None + workflow_refs = ( + [ + Reference( + id=workflow_id, + slug=workflow_slug, + ) + ] + if workflow_id or workflow_slug + else [] + ) + ( + [ + Reference( + id=workflow_id, + slug=workflow_slug, + ) + for workflow_id, workflow_slug in zip( + workflow_ids, + workflow_slugs, + ) + ] + if workflow_ids and workflow_slugs + else [] + ) - log.warn("Failed to parse tags (%s)", tags) + workflow_variant_refs = ( + [ + Reference( + id=workflow_variant_id, + slug=workflow_variant_slug, + ) + ] + if workflow_variant_id or workflow_variant_slug + else [] + ) + ( + [ + Reference( + id=workflow_variant_id, + slug=workflow_variant_slug, + ) + for workflow_variant_id, workflow_variant_slug in zip( + workflow_variant_ids, + workflow_variant_slugs, + ) + ] + if workflow_variant_ids and workflow_variant_slugs + else [] + ) - if meta: - try: - meta = loads(meta) - except Exception: - meta = None + workflow_revision_refs = ( + [ + Reference( + id=workflow_revision_id, + slug=workflow_revision_slug, + version=workflow_revision_version, + ) + ] + if workflow_revision_id or workflow_revision_slug or workflow_revision_version + else [] + ) + ( + [ + Reference( + id=workflow_revision_id, + slug=workflow_revision_slug, + version=workflow_revision_version, + ) + for workflow_revision_id, workflow_revision_slug, workflow_revision_version in zip( + workflow_revision_ids, + workflow_revision_slugs, + workflow_revision_versions, + ) + ] + if workflow_revision_ids + and workflow_revision_slugs + and workflow_revision_versions + else [] + ) - log.warn(f"Failed to parse meta ({meta})") + windowing = ( + Windowing( + next=next, + newest=newest, + oldest=oldest, + limit=limit, + order=order, + ) + if next or newest or oldest or limit or order + else None + ) return parse_workflow_revision_query_request_from_body( - workflow_id=workflow_id, - workflow_variant_id=workflow_variant_id, - workflow_revision_id=workflow_revision_id, - flags=flags, - tags=tags, - meta=meta, + workflow_revision=workflow_revision, + # + workflow_refs=workflow_refs, + workflow_variant_refs=workflow_variant_refs, + workflow_revision_refs=workflow_revision_refs, # include_archived=include_archived, # - next=next, - start=start, - stop=stop, - limit=limit, + windowing=windowing, ) def parse_workflow_revision_query_request_from_body( - workflow_id: Optional[UUID] = None, - workflow_variant_id: Optional[UUID] = None, - workflow_revision_id: Optional[UUID] = None, - flags: Optional[WorkflowFlags] = None, - tags: Optional[dict] = None, - meta: Optional[Meta] = None, + workflow_revision: Optional[WorkflowRevisionQuery] = None, + # + workflow_refs: Optional[List[Reference]] = None, + workflow_variant_refs: Optional[List[Reference]] = None, + workflow_revision_refs: Optional[List[Reference]] = None, # include_archived: Optional[bool] = None, # - next: Optional[UUID] = None, # pylint disable=redefined-builtin - start: Optional[datetime] = None, - stop: Optional[datetime] = None, - limit: Optional[int] = None, - order: Optional[str] = None, + windowing: Optional[Windowing] = None, ) -> WorkflowRevisionQueryRequest: workflow_revision_query_request = None try: workflow_revision_query_request = WorkflowRevisionQueryRequest( - workflow_revision=( - WorkflowRevisionQuery( - workflow_id=workflow_id, - workflow_variant_id=workflow_variant_id, - workflow_revision_id=workflow_revision_id, - flags=flags, - meta=meta, - tags=tags, - ) - if workflow_id - or workflow_variant_id - or workflow_revision_id - or flags - or meta - or tags - else None - ), + workflow_revision=workflow_revision, + # + workflow_refs=workflow_refs, + workflow_variant_refs=workflow_variant_refs, + workflow_revision_refs=workflow_revision_refs, # include_archived=include_archived, # - windowing=( - Windowing( - next=next, - start=start, - stop=stop, - limit=limit, - order=order, - ) - if next or start or stop or limit - else None - ), + windowing=windowing, ) except Exception as e: # pylint: disable=broad-except log.warn(e) - workflow_revision_query_request = None + workflow_revision_query_request = WorkflowRevisionQueryRequest() return workflow_revision_query_request def merge_workflow_revision_query_requests( - query_request_param: Optional[WorkflowRevisionQueryRequest] = None, + query_request_params: Optional[WorkflowRevisionQueryRequest] = None, query_request_body: Optional[WorkflowRevisionQueryRequest] = None, ) -> WorkflowRevisionQueryRequest: - if query_request_body is None: - return query_request_param + if query_request_params and not query_request_body: + return query_request_params - if query_request_param is None: + if not query_request_params and query_request_body: return query_request_body - return WorkflowRevisionQueryRequest( - workflow_revision=query_request_body.workflow_revision - or query_request_param.workflow_revision, - # - include_archived=query_request_body.include_archived - or query_request_param.include_archived, - # - windowing=query_request_body.windowing or query_request_param.windowing, - ) + if query_request_params and query_request_body: + return WorkflowRevisionQueryRequest( + workflow_revision=query_request_body.workflow_revision + or query_request_params.workflow_revision, + # + workflow_refs=query_request_body.workflow_refs + or query_request_params.workflow_refs, + workflow_variant_refs=query_request_body.workflow_variant_refs + or query_request_params.workflow_variant_refs, + workflow_revision_refs=query_request_body.workflow_revision_refs + or query_request_params.workflow_revision_refs, + # + include_archived=query_request_body.include_archived + or query_request_params.include_archived, + # + windowing=query_request_body.windowing or query_request_params.windowing, + ) + + return WorkflowRevisionQueryRequest() def parse_workflow_revision_retrieve_request_from_params( workflow_id: Optional[UUID] = Query(None), - workflow_slug: Optional[UUID] = Query(None), + workflow_slug: Optional[str] = Query(None), # workflow_variant_id: Optional[UUID] = Query(None), workflow_variant_slug: Optional[str] = Query(None), @@ -430,68 +580,48 @@ def parse_workflow_revision_retrieve_request_from_params( workflow_revision_slug: Optional[str] = Query(None), workflow_revision_version: Optional[str] = Query(None), ): - return parse_workflow_revision_retrieve_request_from_body( - workflow_id=workflow_id, - workflow_slug=workflow_slug, - # - workflow_variant_id=workflow_variant_id, - workflow_variant_slug=workflow_variant_slug, - # - workflow_revision_id=workflow_revision_id, - workflow_revision_slug=workflow_revision_slug, - workflow_revision_version=workflow_revision_version, + workflow_ref = ( + Reference( + id=workflow_id, + slug=workflow_slug, + ) + if workflow_id or workflow_slug + else None ) - -def parse_workflow_revision_retrieve_request_from_body( - workflow_id: Optional[UUID] = None, - workflow_slug: Optional[UUID] = None, - # - workflow_variant_id: Optional[UUID] = None, - workflow_variant_slug: Optional[str] = None, - # - workflow_revision_id: Optional[UUID] = None, - workflow_revision_slug: Optional[str] = None, - workflow_revision_version: Optional[str] = None, -) -> Optional[WorkflowRevisionRetrieveRequest]: - return ( - WorkflowRevisionRetrieveRequest( - workflow_ref=( - Reference( - id=workflow_id, - slug=workflow_slug, - ) - if workflow_id or workflow_slug - else None - ), - # - workflow_variant_ref=( - Reference( - id=workflow_variant_id, - slug=workflow_variant_slug, - ) - if workflow_variant_id or workflow_variant_slug - else None - ), - # - workflow_revision_ref=( - Reference( - id=workflow_revision_id, - slug=workflow_revision_slug, - version=workflow_revision_version, - ) - if workflow_revision_id - or workflow_revision_slug - or workflow_revision_version - else None - ), + workflow_variant_ref = ( + Reference( + id=workflow_variant_id, + slug=workflow_variant_slug, ) - if ( - workflow_variant_id - or workflow_variant_slug - or workflow_revision_id - or workflow_revision_slug - or workflow_revision_version + if workflow_variant_id or workflow_variant_slug + else None + ) + + workflow_revision_ref = ( + Reference( + id=workflow_revision_id, + slug=workflow_revision_slug, + version=workflow_revision_version, ) + if workflow_revision_id or workflow_revision_slug or workflow_revision_version else None ) + + return parse_workflow_revision_retrieve_request_from_body( + workflow_ref=workflow_ref, + workflow_variant_ref=workflow_variant_ref, + workflow_revision_ref=workflow_revision_ref, + ) + + +def parse_workflow_revision_retrieve_request_from_body( + workflow_ref: Optional[Reference] = None, + workflow_variant_ref: Optional[Reference] = None, + workflow_revision_ref: Optional[Reference] = None, +) -> WorkflowRevisionRetrieveRequest: + return WorkflowRevisionRetrieveRequest( + workflow_ref=workflow_ref, + workflow_variant_ref=workflow_variant_ref, + workflow_revision_ref=workflow_revision_ref, + ) diff --git a/api/oss/src/celery_config.py b/api/oss/src/celery_config.py index 2ddbbb5457..5e60096278 100644 --- a/api/oss/src/celery_config.py +++ b/api/oss/src/celery_config.py @@ -12,15 +12,13 @@ worker_hijack_root_logger = False # CELERY_TASK_TRACK_STARTED = True -task_queues = ( - Queue( - "src.tasks.evaluations.evaluate", - Exchange("src.tasks.evaluations.evaluate"), - routing_key="src.tasks.evaluations.evaluate", - ), - Queue( - "src.tasks.evaluations.annotate", - Exchange("src.tasks.evaluations.annotate"), - routing_key="src.tasks.evaluations.annotate", - ), -) +TASK_NAMES = [ + "src.tasks.evaluations.legacy.annotate", + "src.tasks.evaluations.live.evaluate", + "src.tasks.evaluations.batch.evaluate_queries", + "src.tasks.evaluations.batch.evaluate_testsets", +] + +task_routes = {name: {"queue": name, "routing_key": name} for name in TASK_NAMES} + +task_queues = [Queue(name, Exchange(name), routing_key=name) for name in TASK_NAMES] diff --git a/api/oss/src/core/annotations/__init__.py b/api/oss/src/core/annotations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/core/annotations/service.py b/api/oss/src/core/annotations/service.py new file mode 100644 index 0000000000..71afe821a8 --- /dev/null +++ b/api/oss/src/core/annotations/service.py @@ -0,0 +1,1162 @@ +from typing import Optional, List, Dict, Any +from uuid import UUID, uuid4 + +from fastapi import Request +from genson import SchemaBuilder + +from oss.src.utils.logging import get_module_logger + +from oss.src.core.evaluators.service import EvaluatorsService +from oss.src.core.evaluators.service import SimpleEvaluatorsService + +from oss.src.core.shared.dtos import ( + Tags, + Meta, + Data, + Reference, + Link, + Windowing, +) +from oss.src.core.tracing.dtos import ( + Focus, + Format, + TracingQuery, + Formatting, + Condition, + Filtering, + OTelReference, + OTelLink, + LogicalOperator, + ComparisonOperator, + ListOperator, + TraceType, + SpanType, +) +from oss.src.core.evaluators.dtos import ( + SimpleEvaluatorFlags, + SimpleEvaluatorData, +) + + +from oss.src.core.tracing.utils import ( + parse_into_attributes, + parse_from_attributes, +) +from oss.src.core.annotations.types import ( + AnnotationOrigin, + AnnotationKind, + AnnotationChannel, + AnnotationReferences, + AnnotationLinks, + AnnotationFlags, + # + Annotation, + AnnotationCreate, + AnnotationEdit, + AnnotationQuery, +) + +from oss.src.apis.fastapi.tracing.router import TracingRouter +from oss.src.apis.fastapi.tracing.models import ( + OTelFlatSpan, + OTelTracingRequest, + OTelTracingResponse, +) +from oss.src.apis.fastapi.evaluators.models import SimpleEvaluatorCreate + +from oss.src.core.annotations.utils import validate_data_against_schema + + +log = get_module_logger(__name__) + + +class AnnotationsService: + def __init__( + self, + *, + evaluators_service: EvaluatorsService, + simple_evaluators_service: SimpleEvaluatorsService, + tracing_router: TracingRouter, + ): + self.evaluators_service = evaluators_service + self.simple_evaluators_service = simple_evaluators_service + self.tracing_router = tracing_router + + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + annotation_create: AnnotationCreate, + ) -> Optional[Annotation]: + simple_evaluator_slug = ( + annotation_create.references.evaluator.slug + if annotation_create.references.evaluator + else None + ) or uuid4().hex + + simple_evaluator_flags = SimpleEvaluatorFlags( + is_evaluator=True, + is_custom=annotation_create.origin == AnnotationOrigin.CUSTOM, + is_human=annotation_create.origin == AnnotationOrigin.HUMAN, + ) + + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_ref=annotation_create.references.evaluator, + evaluator_variant_ref=annotation_create.references.evaluator_variant, + evaluator_revision_ref=annotation_create.references.evaluator_revision, + ) + + if evaluator_revision is None: + builder = SchemaBuilder() + builder.add_object(annotation_create.data) + evaluator_outputs_schema: Dict[str, Any] = builder.to_schema() + + simple_evaluator_data = SimpleEvaluatorData( + schemas=dict( + outputs=evaluator_outputs_schema, + ), + service=dict( + agenta="v0.1.0", + format=evaluator_outputs_schema, + ), + ) + + simple_evaluator_create = SimpleEvaluatorCreate( + slug=simple_evaluator_slug, + # + name=simple_evaluator_slug, + # + flags=simple_evaluator_flags, + # + data=simple_evaluator_data, + ) + + simple_evaluator = await self.simple_evaluators_service.create( + project_id=project_id, + user_id=user_id, + # + simple_evaluator_create=simple_evaluator_create, + ) + + if simple_evaluator: + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_ref=Reference(id=simple_evaluator.id), + ) + elif evaluator_revision.evaluator_id: + simple_evaluator = await self.simple_evaluators_service.fetch( + project_id=project_id, + evaluator_id=evaluator_revision.evaluator_id, + ) + else: + simple_evaluator = None + + if not evaluator_revision or not evaluator_revision.data: + return None + + validate_data_against_schema( + annotation_create.data, + ( + evaluator_revision.data.service.get("format", {}) + if evaluator_revision.data.service + else {} + ), + ) + + annotation_create.references.evaluator = Reference( + id=evaluator_revision.evaluator_id, + slug=( + annotation_create.references.evaluator.slug + if annotation_create.references.evaluator + else None + ), + ) + + annotation_create.references.evaluator_variant = Reference( + id=evaluator_revision.evaluator_variant_id, + slug=( + annotation_create.references.evaluator_variant.slug + if annotation_create.references.evaluator_variant + else None + ), + ) + + annotation_create.references.evaluator_revision = Reference( + id=evaluator_revision.id, + slug=evaluator_revision.slug, + version=evaluator_revision.version, + ) + + annotation_flags = AnnotationFlags( + is_evaluator=True, + is_custom=annotation_create.origin == AnnotationOrigin.CUSTOM, + is_human=annotation_create.origin == AnnotationOrigin.HUMAN, + is_sdk=annotation_create.channel == AnnotationChannel.SDK, + is_web=annotation_create.channel == AnnotationChannel.WEB, + is_evaluation=annotation_create.kind == AnnotationKind.EVAL, + ) + + annotation_references = AnnotationReferences( + **annotation_create.references.model_dump(), + ) + + annotation_link = await self._create_annotation( + project_id=project_id, + user_id=user_id, + # + name=simple_evaluator.name if simple_evaluator else None, + # + flags=annotation_flags, + tags=annotation_create.tags, + meta=annotation_create.meta, + # + data=annotation_create.data, + # + references=annotation_references, + links=annotation_create.links, + ) + + if annotation_link is None: + return None + + annotation = await self._fetch_annotation( + project_id=project_id, + # + annotation_link=annotation_link, + ) + + return annotation + + async def fetch( + self, + *, + project_id: UUID, + # + trace_id: str, + span_id: Optional[str] = None, + ): + annotation_link = Link( + trace_id=trace_id, + span_id=span_id, + ) + + annotation: Optional[Annotation] = await self._fetch_annotation( + project_id=project_id, + # + annotation_link=annotation_link, + ) + return annotation + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + trace_id: str, + span_id: Optional[str] = None, + # + annotation_edit: AnnotationEdit, + ): + annotation_link = Link( + trace_id=trace_id, + span_id=span_id, + ) + + annotation: Optional[Annotation] = await self._fetch_annotation( + project_id=project_id, + # + annotation_link=annotation_link, + ) + + if annotation is None: + return None + + simple_evaluator_slug = ( + annotation.references.evaluator.slug + if annotation.references.evaluator + else None + ) or uuid4().hex + + simple_evaluator_flags = SimpleEvaluatorFlags( + is_evaluator=True, + is_custom=annotation.origin == AnnotationOrigin.CUSTOM, + is_human=annotation.origin == AnnotationOrigin.HUMAN, + ) + + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_ref=annotation.references.evaluator, + evaluator_variant_ref=annotation.references.evaluator_variant, + evaluator_revision_ref=annotation.references.evaluator_revision, + ) + + if not evaluator_revision: + builder = SchemaBuilder() + builder.add_object(annotation_edit.data) + evaluator_outputs_schema: Dict[str, Any] = builder.to_schema() + + simple_evaluator_data = SimpleEvaluatorData( + schemas=dict( + outputs=evaluator_outputs_schema, + ), + service=dict( + agenta="v0.1.0", + format=evaluator_outputs_schema, + ), + ) + + simple_evaluator_create = SimpleEvaluatorCreate( + slug=simple_evaluator_slug, + # + name=simple_evaluator_slug, + # + flags=simple_evaluator_flags, + # + data=simple_evaluator_data, + ) + + simple_evaluator = await self.simple_evaluators_service.create( + project_id=project_id, + user_id=user_id, + # + simple_evaluator_create=simple_evaluator_create, + ) + + if simple_evaluator: + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_ref=Reference(id=simple_evaluator.id), + ) + + if not evaluator_revision or not evaluator_revision.data: + return None + + validate_data_against_schema( + annotation_edit.data, + ( + evaluator_revision.data.service.get("format", {}) + if evaluator_revision.data.service + else {} + ), + ) + + if evaluator_revision: + annotation.references.evaluator = Reference( + id=evaluator_revision.evaluator_id, + slug=( + annotation.references.evaluator.slug + if annotation.references.evaluator + else None + ), + ) + + annotation.references.evaluator_variant = Reference( + id=evaluator_revision.evaluator_variant_id, + slug=( + annotation.references.evaluator_variant.slug + if annotation.references.evaluator_variant + else None + ), + ) + + annotation.references.evaluator_revision = Reference( + id=evaluator_revision.id, + slug=evaluator_revision.slug, + version=evaluator_revision.version, + ) + + annotation_flags = AnnotationFlags( + is_evaluator=True, + is_custom=annotation.origin == AnnotationOrigin.CUSTOM, + is_human=annotation.origin == AnnotationOrigin.HUMAN, + is_sdk=annotation.channel == AnnotationChannel.SDK, + is_web=annotation.channel == AnnotationChannel.WEB, + is_evaluation=annotation.kind == AnnotationKind.EVAL, + ) + + annotation_references = AnnotationReferences( + **annotation.references.model_dump(), + ) + + annotation_link = await self._edit_annotation( + project_id=project_id, + user_id=user_id, + # + annotation=annotation, + # + flags=annotation_flags, + tags=annotation_edit.tags, + meta=annotation_edit.meta, + # + data=annotation_edit.data, + # + references=annotation_references, + links=annotation.links, + ) + + if annotation_link is None: + return None + + annotation = await self._fetch_annotation( + project_id=project_id, + # + annotation_link=annotation_link, + ) + + return annotation + + async def delete( + self, + *, + project_id: UUID, + user_id: UUID, + # + trace_id: str, + span_id: Optional[str] = None, + ): + annotation_link = Link( + trace_id=trace_id, + span_id=span_id, + ) + + annotation_link = await self._delete_annotation( + project_id=project_id, + user_id=user_id, + # + annotation_link=annotation_link, + ) + + return annotation_link + + async def query( + self, + *, + project_id: UUID, + # + annotation_query: Optional[AnnotationQuery] = None, + # + annotation_links: Optional[AnnotationLinks] = None, + # + windowing: Optional[Windowing] = None, + ): + annotation = annotation_query if annotation_query else None + annotation_flags = AnnotationFlags(is_evaluator=True) + + if annotation: + if annotation.origin: + annotation_flags.is_custom = ( + annotation.origin == AnnotationOrigin.CUSTOM + ) + annotation_flags.is_human = annotation.origin == AnnotationOrigin.HUMAN + + if annotation.channel: + annotation_flags.is_sdk = annotation.channel == AnnotationChannel.SDK + annotation_flags.is_web = annotation.channel == AnnotationChannel.WEB + + if annotation.kind: + annotation_flags.is_evaluation = annotation.kind == AnnotationKind.EVAL + + annotation_tags = annotation.tags if annotation else None + annotation_meta = annotation.meta if annotation else None + + annotation_references = ( + AnnotationReferences( + **annotation.references.model_dump(), + ) + if annotation and annotation.references + else None + ) + + _annotation_links = annotation.links if annotation else None + + annotations = await self._query_annotation( + project_id=project_id, + # + flags=annotation_flags, + tags=annotation_tags, + meta=annotation_meta, + # + references=annotation_references, + links=_annotation_links, + # + annotation_links=annotation_links, + # + windowing=windowing, + ) + return annotations + + # -------- Internal Functions ------------------------------------------------------------------- + + async def _create_annotation( + self, + *, + project_id: UUID, + user_id: UUID, + # + name: Optional[str], + # + flags: AnnotationFlags, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + # + data: Data, + # + references: AnnotationReferences, + links: AnnotationLinks, + ) -> Optional[Link]: + trace_id = uuid4().hex + trace_type = TraceType.ANNOTATION + + span_id = uuid4().hex[16:] + span_type = SpanType.TASK + span_name = name or references.evaluator.slug or "annotation" + + _references = references.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + + if isinstance(links, dict): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": key}, # type: ignore + ) + for key, link in links.items() + if link.trace_id and link.span_id + ] + elif isinstance(links, list): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": "key"}, # type: ignore + ) + for link in links + if link.trace_id and link.span_id + ] + + else: + _links = None + + _flags = flags.model_dump(mode="json", exclude_none=True) + + _type = { + "trace": "annotation", + "span": "task", + } + + _attributes = parse_into_attributes( + type=_type, + flags=_flags, + tags=tags, + meta=meta, + data=data, + references=_references, + ) + + trace_request = OTelTracingRequest( + spans=[ + OTelFlatSpan( + trace_id=trace_id, + trace_type=trace_type, + span_id=span_id, + span_type=span_type, + span_name=span_name, + attributes=_attributes, + links=_links, + ) + ] + ) + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + + _links_response = await self.tracing_router.create_trace( + request=request, + # + trace_request=trace_request, + ) + + _link = ( + Link( + trace_id=_links_response.links[0].trace_id, + span_id=_links_response.links[0].span_id, + ) + if _links_response.links and len(_links_response.links) > 0 + else None + ) + + return _link + + async def _fetch_annotation( + self, + *, + project_id: UUID, + user_id: Optional[UUID] = None, + # + annotation_link: Link, + ) -> Optional[Annotation]: + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) if user_id else None + + if not annotation_link.trace_id: + return None + + trace_response: OTelTracingResponse = await self.tracing_router.fetch_trace( + request=request, + # + trace_id=annotation_link.trace_id, + ) + + if not trace_response or not trace_response.traces: + return None + + traces = list(trace_response.traces.values()) + trace = traces[0] if traces else None + + if not trace or not trace.spans: + return None + + spans = list(trace.spans.values()) + root_span = spans[0] if spans else None + + if not root_span or isinstance(root_span, list): + return None + + ( + type, + flags, + tags, + meta, + data, + references, + ) = parse_from_attributes(root_span.attributes or {}) + + if not data: + return None + + _references = AnnotationReferences( + **{ + key: Reference( + id=ref.get("id"), + slug=ref.get("slug"), + version=ref.get("version"), + ) + for key, ref in (references or {}).items() + } + ) + + _links = dict( + **{ + str(link.attributes["key"]): Link( + trace_id=link.trace_id, + span_id=link.span_id, + ) + for link in root_span.links or [] + if link.attributes and "key" in link.attributes + } + ) + + _origin = ( + ( + flags.get("is_custom") + and AnnotationOrigin.CUSTOM + or flags.get("is_human") + and AnnotationOrigin.HUMAN + or AnnotationOrigin.AUTO + ) + if flags + else AnnotationOrigin.CUSTOM + ) + + _kind = ( + (flags.get("is_evaluation") and AnnotationKind.EVAL or AnnotationKind.ADHOC) + if flags + else AnnotationKind.ADHOC + ) + + _channel = ( + ( + flags.get("is_sdk") + and AnnotationChannel.SDK + or flags.get("is_web") + and AnnotationChannel.WEB + or AnnotationChannel.API + ) + if flags + else AnnotationChannel.API + ) + + annotation = Annotation( + trace_id=root_span.trace_id, + span_id=root_span.span_id, + # + created_at=root_span.created_at, + updated_at=root_span.updated_at, + deleted_at=root_span.deleted_at, + created_by_id=root_span.created_by_id, + updated_by_id=root_span.updated_by_id, + deleted_by_id=root_span.deleted_by_id, + # + origin=_origin, + kind=_kind, + channel=_channel, + # + tags=tags, + meta=meta, + # + data=data, + # + references=_references, + links=_links, + ) + + return annotation + + async def _edit_annotation( + self, + *, + project_id: UUID, + user_id: UUID, + # + annotation: Annotation, + # + flags: AnnotationFlags, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + # + data: Data, + # + references: AnnotationReferences, + links: AnnotationLinks, + ) -> Optional[Link]: + if not annotation.trace_id or not annotation.span_id: + return None + + _references = references.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + + if isinstance(links, dict): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": key}, # type: ignore + ) + for key, link in links.items() + if link.trace_id and link.span_id + ] + elif isinstance(links, list): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": "key"}, # type: ignore + ) + for link in links + if link.trace_id and link.span_id + ] + else: + _links = None + + _flags = flags.model_dump(mode="json", exclude_none=True) + + _type = { + "trace": "annotation", + "span": "task", + } + + _attributes = parse_into_attributes( + type=_type, + flags=_flags, + tags=tags, + meta=meta, + data=data, + references=_references, + ) + + trace_request = OTelTracingRequest( + spans=[ + OTelFlatSpan( + trace_id=annotation.trace_id, + span_id=annotation.span_id, + attributes=_attributes, + links=_links, + ) + ] + ) + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + + _links_response = await self.tracing_router.edit_trace( + request=request, + # + trace_id=annotation.trace_id, + # + trace_request=trace_request, + ) + + _link = ( + Link( + trace_id=_links_response.links[0].trace_id, + span_id=_links_response.links[0].span_id, + ) + if _links_response.links and len(_links_response.links) > 0 + else None + ) + + return _link + + async def _delete_annotation( + self, + *, + project_id: UUID, + user_id: UUID, + # + annotation_link: Link, + ) -> Optional[Link]: + if not annotation_link.trace_id: + return None + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + + link_response = await self.tracing_router.delete_trace( + request=request, + # + trace_id=annotation_link.trace_id, + ) + + link = link_response.links[0] if link_response.links else None + + if not link or not link.trace_id or not link.span_id: + return None + + annotation_link = Link( + trace_id=link.trace_id, + span_id=link.span_id, + ) + + return annotation_link + + async def _query_annotation( + self, + *, + project_id: UUID, + user_id: Optional[UUID] = None, + # + flags: Optional[AnnotationFlags] = None, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + # + references: Optional[AnnotationReferences] = None, + links: Optional[AnnotationLinks] = None, + # + annotation_links: Optional[List[Link]] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[Annotation]: + formatting = Formatting( + focus=Focus.TRACE, + format=Format.AGENTA, + ) + + filtering = Filtering() + + conditions: List[Condition | Filtering] = [ + Condition( + field="attributes", + key="ag.type.trace", + value="annotation", + operator=ComparisonOperator.IS, + ) + ] + + trace_ids = ( + [annotation_link.trace_id for annotation_link in annotation_links] + if annotation_links + else None + ) + + # span_ids = ( + # [annotation_link.span_id for annotation_link in annotation_links] + # if annotation_links + # else None + # ) + + if trace_ids: + conditions.append( + Condition( + field="trace_id", + value=trace_ids, + operator=ListOperator.IN, + ) + ) + + # if span_ids: + # conditions.append( + # Condition( + # field="span_id", + # value=span_ids, + # operator=ListOperator.IN, + # ) + # ) + + if flags: + for key, value in flags.model_dump(mode="json", exclude_none=True).items(): + conditions.append( + Condition( + field="attributes", + key=f"ag.flags.{key}", + value=value, + operator=ComparisonOperator.IS, + ) + ) + + if tags: + for key, value in tags.items(): + conditions.append( + Condition( + field="attributes", + key=f"ag.tags.{key}", + value=value, # type:ignore + operator=ComparisonOperator.IS, + ) + ) + + if meta: + for key, value in meta.items(): + conditions.append( + Condition( + field="attributes", + key=f"ag.meta.{key}", + value=value, # type:ignore + operator=ComparisonOperator.IS, + ) + ) + + if references: + for _, reference in references.model_dump(mode="json").items(): + if reference: + ref_id = str(reference.get("id")) if reference.get("id") else None + ref_slug = ( + str(reference.get("slug")) if reference.get("slug") else None + ) + conditions.append( + Condition( + field="references", + value=[ + {"id": ref_id, "slug": ref_slug}, + ], + operator=ListOperator.IN, + ) + ) + + if links: + if isinstance(links, dict): + for _, link in links.items(): + if link: + conditions.append( + Condition( + field="links", + value=[ + { + "trace_id": link.trace_id, + "span_id": link.span_id, + }, + ], + operator=ListOperator.IN, + ) + ) + elif isinstance(links, list): + _conditions = [] + for link in links: + if link: + _conditions.append( + Condition( + field="links", + value=[ + { + "trace_id": link.trace_id, + "span_id": link.span_id, + }, + ], + operator=ListOperator.IN, + ) + ) + if _conditions: + conditions.append( + Filtering( + operator=LogicalOperator.OR, + conditions=_conditions, + ) + ) + + if conditions: + filtering = Filtering( + operator=LogicalOperator.AND, + conditions=conditions, + ) + + query = TracingQuery( + formatting=formatting, + filtering=filtering, + windowing=windowing, + ) + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) if user_id else None + + spans_response: OTelTracingResponse = await self.tracing_router.query_spans( + request=request, + # + query=query, + ) + + if not spans_response or not spans_response.traces: + return [] + + traces = list(spans_response.traces.values()) + + annotations = [] + + for trace in traces: + if not trace or not trace.spans: + continue + + spans = list(trace.spans.values()) + root_span = spans[0] if spans else None + + if not root_span or isinstance(root_span, list): + continue + + ( + __type, + __flags, + __tags, + __meta, + __data, + __references, + ) = parse_from_attributes(root_span.attributes or {}) + + if not __data: + continue + + _references = AnnotationReferences( + **{ + key: Reference( + id=ref.get("id"), + slug=ref.get("slug"), + version=ref.get("version"), + ) + for key, ref in (__references or {}).items() + } + ) + + _links = dict( + **{ + str(link.attributes["key"]): Link( + trace_id=link.trace_id, + span_id=link.span_id, + ) + for link in root_span.links or [] + if link.attributes and "key" in link.attributes + } + ) + + _origin = ( + ( + __flags.get("is_custom") + and AnnotationOrigin.CUSTOM + or __flags.get("is_human") + and AnnotationOrigin.HUMAN + or AnnotationOrigin.AUTO + ) + if __flags + else AnnotationOrigin.CUSTOM + ) + + _kind = ( + ( + __flags.get("is_evaluation") + and AnnotationKind.EVAL + or AnnotationKind.ADHOC + ) + if __flags + else AnnotationKind.ADHOC + ) + + _channel = ( + ( + __flags.get("is_sdk") + and AnnotationChannel.SDK + or __flags.get("is_web") + and AnnotationChannel.WEB + or AnnotationChannel.API + ) + if __flags + else AnnotationChannel.API + ) + + annotation = Annotation( + trace_id=root_span.trace_id, + span_id=root_span.span_id, + # + created_at=root_span.created_at, + updated_at=root_span.updated_at, + deleted_at=root_span.deleted_at, + created_by_id=root_span.created_by_id, + updated_by_id=root_span.updated_by_id, + deleted_by_id=root_span.deleted_by_id, + # + origin=_origin, + kind=_kind, + channel=_channel, + # + tags=__tags, + meta=__meta, + # + data=__data, + # + references=_references, + links=_links, + ) + + annotations.append(annotation) + + return annotations diff --git a/api/oss/src/core/annotations/types.py b/api/oss/src/core/annotations/types.py new file mode 100644 index 0000000000..9d88e525d6 --- /dev/null +++ b/api/oss/src/core/annotations/types.py @@ -0,0 +1,48 @@ +from typing import Optional + +from oss.src.core.shared.dtos import Reference +from oss.src.core.workflows.dtos import WorkflowFlags +from oss.src.core.tracing.dtos import ( + SimpleTraceOrigin, + SimpleTraceKind, + SimpleTraceChannel, + SimpleTraceReferences, + SimpleTraceLinks, + # + SimpleTrace, + SimpleTraceCreate, + SimpleTraceEdit, + SimpleTraceQuery, +) + + +AnnotationOrigin = SimpleTraceOrigin +AnnotationKind = SimpleTraceKind +AnnotationChannel = SimpleTraceChannel +AnnotationLinks = SimpleTraceLinks + + +class AnnotationFlags(WorkflowFlags): + is_sdk: Optional[bool] = None + is_web: Optional[bool] = None + is_evaluation: Optional[bool] = None + + +class AnnotationReferences(SimpleTraceReferences): + evaluator: Reference # type: ignore + + +class Annotation(SimpleTrace): + pass + + +class AnnotationCreate(SimpleTraceCreate): + pass + + +class AnnotationEdit(SimpleTraceEdit): + pass + + +class AnnotationQuery(SimpleTraceQuery): + pass diff --git a/api/oss/src/core/annotations/utils.py b/api/oss/src/core/annotations/utils.py new file mode 100644 index 0000000000..ba9f178897 --- /dev/null +++ b/api/oss/src/core/annotations/utils.py @@ -0,0 +1,57 @@ +from jsonschema import ( + Draft202012Validator, + Draft7Validator, + Draft4Validator, + Draft6Validator, + Draft201909Validator, +) + +from fastapi import status, HTTPException + + +def _get_jsonschema_validator( + format: dict, # pylint: disable=redefined-builtin +): + schema_uri = format.get( + "$schema", + "https://json-schema.org/draft/2020-12/schema", + ) + + if "2020-12" in schema_uri: + return Draft202012Validator + elif "2019-09" in schema_uri: + return Draft201909Validator + elif "draft-07" in schema_uri: + return Draft7Validator + elif "draft-06" in schema_uri: + return Draft6Validator + elif "draft-04" in schema_uri: + return Draft4Validator + return Draft202012Validator # fallback + + +def validate_data_against_schema( + data: dict, + schema: dict, # pylint: disable=redefined-builtin +): + validator_class = _get_jsonschema_validator(schema) + validator = validator_class(schema) + + errors = list(validator.iter_errors(data)) + + if errors: + details = [] + for e in errors: + loc = list(e.absolute_path) + msg = e.message + details.append( + { + "loc": ["body", "annotation", "data"] + loc, + "msg": msg, + "type": "value_error.json_schema", + } + ) + + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=details + ) diff --git a/api/oss/src/core/applications/__init__.py b/api/oss/src/core/applications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/core/applications/dtos.py b/api/oss/src/core/applications/dtos.py new file mode 100644 index 0000000000..86a7e1fe7e --- /dev/null +++ b/api/oss/src/core/applications/dtos.py @@ -0,0 +1,232 @@ +from typing import Optional +from uuid import UUID + +from pydantic import Field + +from oss.src.core.shared.dtos import sync_alias, AliasConfig +from oss.src.core.shared.dtos import ( + Identifier, + Slug, + Lifecycle, + Header, + Metadata, +) +from oss.src.core.workflows.dtos import ( + Workflow, + WorkflowCreate, + WorkflowEdit, + WorkflowQuery, + WorkflowFlags, + # + WorkflowVariant, + WorkflowVariantCreate, + WorkflowVariantEdit, + WorkflowVariantQuery, + # + WorkflowRevisionData, + # + WorkflowRevision, + WorkflowRevisionCreate, + WorkflowRevisionEdit, + WorkflowRevisionQuery, + WorkflowRevisionCommit, + WorkflowRevisionsLog, +) + + +# aliases ---------------------------------------------------------------------- + + +class ApplicationIdAlias(AliasConfig): + application_id: Optional[UUID] = None + workflow_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="application_id", + ) + + +class ApplicationVariantIdAlias(AliasConfig): + application_variant_id: Optional[UUID] = None + workflow_variant_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="application_variant_id", + ) + + +class ApplicationRevisionIdAlias(AliasConfig): + application_revision_id: Optional[UUID] = None + workflow_revision_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="application_revision_id", + ) + + +# globals ---------------------------------------------------------------------- + + +class ApplicationFlags(WorkflowFlags): + def __init__(self, **data): + data["is_evaluator"] = True + + super().__init__(**data) + + +# applications ------------------------------------------------------------------- + + +class Application(Workflow): + flags: Optional[ApplicationFlags] = None + + +class ApplicationCreate(WorkflowCreate): + flags: Optional[ApplicationFlags] = None + + +class ApplicationEdit(WorkflowEdit): + flags: Optional[ApplicationFlags] = None + + +class ApplicationQuery(WorkflowQuery): + flags: Optional[ApplicationFlags] = None + + +# application variants ----------------------------------------------------------- + + +class ApplicationVariant( + WorkflowVariant, + ApplicationIdAlias, +): + flags: Optional[ApplicationFlags] = None + + def model_post_init(self, __context) -> None: + sync_alias("application_id", "workflow_id", self) + + +class ApplicationVariantCreate( + WorkflowVariantCreate, + ApplicationIdAlias, +): + flags: Optional[ApplicationFlags] = None + + def model_post_init(self, __context) -> None: + sync_alias("application_id", "workflow_id", self) + + +class ApplicationVariantEdit(WorkflowVariantEdit): + flags: Optional[ApplicationFlags] = None + + +class ApplicationVariantQuery(WorkflowVariantQuery): + flags: Optional[ApplicationFlags] = None + + +# application revisions ----------------------------------------------------- + + +class ApplicationRevisionData(WorkflowRevisionData): + pass + + +class ApplicationRevision( + WorkflowRevision, + ApplicationIdAlias, + ApplicationVariantIdAlias, +): + flags: Optional[ApplicationFlags] = None + + data: Optional[ApplicationRevisionData] = None + + def model_post_init(self, __context) -> None: + sync_alias("application_id", "workflow_id", self) + sync_alias("application_variant_id", "workflow_variant_id", self) + + +class ApplicationRevisionCreate( + WorkflowRevisionCreate, + ApplicationIdAlias, + ApplicationVariantIdAlias, +): + flags: Optional[ApplicationFlags] = None + + def model_post_init(self, __context) -> None: + sync_alias("application_id", "workflow_id", self) + sync_alias("application_variant_id", "workflow_variant_id", self) + + +class ApplicationRevisionEdit(WorkflowRevisionEdit): + flags: Optional[ApplicationFlags] = None + + +class ApplicationRevisionQuery(WorkflowRevisionQuery): + flags: Optional[ApplicationFlags] = None + + +class ApplicationRevisionCommit( + WorkflowRevisionCommit, + ApplicationIdAlias, + ApplicationVariantIdAlias, +): + flags: Optional[ApplicationFlags] = None + + data: Optional[ApplicationRevisionData] = None + + def model_post_init(self, __context) -> None: + sync_alias("application_id", "workflow_id", self) + sync_alias("application_variant_id", "workflow_variant_id", self) + + +class ApplicationRevisionsLog( + WorkflowRevisionsLog, + ApplicationIdAlias, + ApplicationVariantIdAlias, + ApplicationRevisionIdAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("application_id", "workflow_id", self) + sync_alias("application_variant_id", "workflow_variant_id", self) + sync_alias("application_revision_id", "workflow_revision_id", self) + + +# simple applications ------------------------------------------------------------ + + +class LegacyApplicationFlags(WorkflowFlags): + pass + + +class LegacyApplicationData(WorkflowRevisionData): + pass + + +class LegacyApplication(Identifier, Slug, Lifecycle, Header, Metadata): + flags: Optional[LegacyApplicationFlags] = None + + data: Optional[LegacyApplicationData] = None + + +class LegacyApplicationCreate(Slug, Header, Metadata): + flags: Optional[LegacyApplicationFlags] = None + + data: Optional[LegacyApplicationData] = None + + +class LegacyApplicationEdit(Identifier, Header, Metadata): + flags: Optional[LegacyApplicationFlags] = None + + data: Optional[LegacyApplicationData] = None + + +class LegacyApplicationQuery( + Identifier, + Slug, + Lifecycle, + ApplicationQuery, +): + data: Optional[LegacyApplicationData] = None + + +# ------------------------------------------------------------------------------ diff --git a/api/oss/src/core/applications/service.py b/api/oss/src/core/applications/service.py new file mode 100644 index 0000000000..dc23e8ca19 --- /dev/null +++ b/api/oss/src/core/applications/service.py @@ -0,0 +1,705 @@ +from uuid import UUID, uuid4 +from typing import Optional, Dict, Any + +from pydantic import ValidationError + +from oss.src.services import db_manager +from oss.src.utils.logging import get_module_logger +from oss.src.core.shared.dtos import Reference +from oss.src.core.workflows.dtos import WorkflowRevisionData +from oss.src.core.applications.dtos import ( + LegacyApplication, + LegacyApplicationCreate, + LegacyApplicationEdit, + LegacyApplicationData, + # + Application, + ApplicationCreate, + ApplicationEdit, + # + ApplicationVariant, + ApplicationVariantCreate, + # + ApplicationRevision, + ApplicationRevisionData, + ApplicationRevisionCommit, +) +from oss.src.services import db_manager + +from oss.src.models.shared_models import AppType +from oss.src.utils.helpers import get_slug_from_name_and_id + + +log = get_module_logger(__name__) + +# Constants +WORKFLOW_MARKER_KEY = "__workflow__" + + +class LegacyApplicationsService: + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + legacy_application_create: LegacyApplicationCreate, + ) -> Optional[LegacyApplication]: + # ------------------------------------------------------------------ + # Application + # ------------------------------------------------------------------ + application_create = ApplicationCreate( + slug=legacy_application_create.slug, + # + name=legacy_application_create.name, + # + tags=legacy_application_create.tags, + ) + + user = await db_manager.get_user_with_id( + user_id=str(user_id), + ) + + if not user: + return None + + # Create app and initialize environments + app_db = await db_manager.create_app_and_envs( + project_id=str(project_id), + # + app_name=application_create.name or uuid4().hex, + # + template_key=AppType.CUSTOM, + # + user_id=str(user_id), + ) + + # Create variant config + config_db = await db_manager.create_new_config( + config_name="default", + # + parameters={}, + ) + + # Create variant base + db_base = await db_manager.create_new_variant_base( + project_id=str(project_id), + # + base_name="app", + # + app=app_db, + ) + + # ------------------------------------------------------------------ + # Application variant + # ------------------------------------------------------------------ + application_variant_slug = uuid4().hex + + application_variant_create = ApplicationVariantCreate( + slug=application_variant_slug, + # + name=application_create.name or uuid4().hex, + # + tags=application_create.tags, + # + application_id=app_db.id, # type: ignore[arg-type] + ) + + # Create default app variant + app_variant_db = await db_manager.create_new_app_variant( + project_id=str(project_id), + # + variant_name=application_variant_create.name, # type: ignore + # + base_name=db_base.base_name, # type: ignore + commit_message="initial commit", + # + user=user, + base=db_base, + config=config_db, + app=app_db, + ) + + # ----------------------------------------------------------------- + # Second revision commit + # ------------------------------------------------------------------ + application_revision_slug = uuid4().hex + + application_revision_commit = ApplicationRevisionCommit( + slug=application_revision_slug, + # + name=application_create.name or uuid4().hex, + # + tags=application_create.tags, + # + data=ApplicationRevisionData( + **( + legacy_application_create.data.model_dump(mode="json") + if legacy_application_create.data + else {} + ), + ), + # + application_id=app_db.id, # type: ignore + application_variant_id=app_variant_db.id, # type: ignore + ) + + # Serialize application revision data with marker + serialized_data = {} + + if application_revision_commit.data: + serialized_data = self._serialize_workflow_data( + workflow_data=application_revision_commit.data, + ) + + # Create deployment + url = application_revision_commit.data.url + + deployment = await db_manager.create_deployment( + project_id=str(project_id), + # + app_id=str(app_variant_db.app.id), + uri="" if app_db.app_type == AppType.CUSTOM else url, # type: ignore + ) + + # Update variant base + await db_manager.update_base( + str(app_variant_db.base_id), + # + deployment_id=deployment.id, # type: ignore + ) + + # Update variant parameters (creates a new revision) + app_variant_db = await db_manager.update_variant_parameters( + project_id=str(project_id), + user_uid=str(user.id), + # + app_variant_id=str(app_variant_db.id), + # + parameters=serialized_data, + # commit_message="...", + ) + + # Deserialize the data back to return application revision + application_revision_data = None + + if serialized_data and WORKFLOW_MARKER_KEY in serialized_data: + data_copy = serialized_data.copy() + del data_copy[WORKFLOW_MARKER_KEY] + + try: + application_revision_data = LegacyApplicationData(**data_copy) + application_revision_data.version = str(app_variant_db.revision) # type: ignore + + except ValidationError as e: + log.warning(f"Failed to deserialize application data: {e}") + + legacy_application = LegacyApplication( + id=app_db.id, # type: ignore + slug=app_db.app_name, # type: ignore + # + name=app_db.app_name, # type: ignore + # + created_at=app_db.created_at, # type: ignore + updated_at=app_db.updated_at, # type: ignore + created_by_id=app_db.modified_by_id, # type: ignore + # + tags={"type": app_db.app_type}, # type: ignore + # + data=application_revision_data, + ) + + return legacy_application + + async def fetch( + self, + *, + project_id: UUID, + # + application_id: UUID, + ) -> Optional[LegacyApplication]: + # Fetch application details ---------------------------------------------------------- + app_db = await db_manager.fetch_app_by_id( + app_id=str(application_id), + ) + + application = Application( + id=app_db.id, # type: ignore + slug=app_db.app_name, # type: ignore + # + name=app_db.app_name, # type: ignore + # + created_at=app_db.created_at, # type: ignore + updated_at=app_db.updated_at, # type: ignore + created_by_id=app_db.modified_by_id, # type: ignore + # + tags={"type": app_db.app_type}, # type: ignore + ) + + # Fetch application variant details -------------------------------------------------- + app_variant_db = await db_manager.fetch_latest_app_variant( + app_id=str(app_db.id) + ) + if not app_variant_db: + return None + + application_variant_slug = get_slug_from_name_and_id( + str(app_variant_db.variant_name), + UUID(str(app_variant_db.id)), + ) + + application_variant = ApplicationVariant( + id=app_variant_db.id, # type: ignore + slug=application_variant_slug, # type: ignore + # + name=app_variant_db.variant_name, # type:ignore + # + created_at=app_variant_db.created_at, # type: ignore + updated_at=app_variant_db.updated_at, # type: ignore + deleted_at=app_variant_db.updated_at if app_variant_db.hidden else None, # type: ignore + created_by_id=app_variant_db.modified_by_id, # type: ignore + updated_by_id=( + app_variant_db.modified_by_id # type: ignore + if app_variant_db.updated_at # type: ignore + else None + ), + deleted_by_id=( + app_variant_db.modified_by_id # type: ignore + if app_variant_db.hidden # type: ignore + else None + ), + # + tags=application.tags, + # + application_id=application.id, + ) + + # Fetch application variant revision details ------------------------------------------ + variant_revision_db = await db_manager.fetch_app_variant_revision( + app_variant=str(app_variant_db.id), + revision_number=app_variant_db.revision, # type: ignore + ) + + if not variant_revision_db: + return None + + # Deserialize data if marked as workflow ---------------------------------------------- + application_revision_data: Optional[LegacyApplicationData] = None + + if isinstance(variant_revision_db.config_parameters, dict): + wf_data = self._deserialize_workflow_data( + variant_revision_db.config_parameters + ) + if wf_data is not None: + try: + application_revision_data = LegacyApplicationData( + **wf_data.model_dump(mode="json") + ) + application_revision_data.version = str(app_variant_db.revision) # type: ignore + except ValidationError as e: + log.warning( + f"Failed to cast workflow data to LegacyApplicationData: {e}" + ) + + application_revision_slug = get_slug_from_name_and_id( + str(variant_revision_db.config_name), + UUID(str(variant_revision_db.id)), + ) + + application_revision = ApplicationRevision( + id=variant_revision_db.id, # type: ignore + slug=application_revision_slug, # type: ignore + # + name=variant_revision_db.config_name, # type: ignore + # + created_at=variant_revision_db.created_at, # type: ignore + updated_at=variant_revision_db.updated_at, # type: ignore + deleted_at=( + variant_revision_db.updated_at # type: ignore + if variant_revision_db.hidden # type: ignore + else None + ), + created_by_id=variant_revision_db.modified_by_id, # type: ignore + updated_by_id=( + variant_revision_db.modified_by_id # type: ignore + if variant_revision_db.updated_at # type: ignore + else None + ), + deleted_by_id=( + variant_revision_db.modified_by_id # type: ignore + if variant_revision_db.hidden # type: ignore + else None + ), + # + tags=application_variant.tags, + # + data=ApplicationRevisionData( + **( + application_revision_data.model_dump(mode="json") + if application_revision_data + else {} + ), + ), + # + application_id=application.id, + application_variant_id=application_variant.id, + ) + + legacy_application = LegacyApplication( + id=application.id, + slug=application.slug, + # + name=application.name, + # + created_at=application.created_at, + updated_at=application.updated_at, + created_by_id=application.created_by_id, + # + tags=application_variant.tags, + # + data=LegacyApplicationData( + **( + application_revision_data.model_dump(mode="json") + if application_revision_data + else {} + ), + ), + ) + + return legacy_application + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + legacy_application_edit: LegacyApplicationEdit, + ) -> Optional[LegacyApplication]: + # Ensure user (for commit) -------------------------------------------- + user = await db_manager.get_user_with_id(user_id=str(user_id)) + + if not user: + return None + + # Edit application (name, etc.) --------------------------------------- + application_edit = ApplicationEdit( + id=legacy_application_edit.id, + # + name=legacy_application_edit.name, + ) + + app_db = await db_manager.update_app( + app_id=str(application_edit.id), + values_to_update=application_edit.model_dump( + mode="json", + exclude_none=True, + exclude={ + "flags", + "meta", + "tags", + "id", + }, + ), + ) + if app_db is None: + return None + + app_variant_db = await db_manager.fetch_latest_app_variant( + app_id=str(app_db.id) + ) + if not app_variant_db: + return None + + # ----------------------------------------------------------------- + # Second revision commit + # ------------------------------------------------------------------ + application_revision_slug = uuid4().hex + + application_revision_commit = ApplicationRevisionCommit( + slug=application_revision_slug, + # + name=application_edit.name, + # + data=ApplicationRevisionData( + **( + legacy_application_edit.data.model_dump() + if legacy_application_edit.data + else {} + ) + ), + # + application_id=app_db.id, # type: ignore + application_variant_id=app_variant_db.id, # type: ignore + ) + + # Serialize application revision data with marker + serialized_data = {} + + if application_revision_commit.data: + serialized_data = application_revision_commit.data.model_dump(mode="json") + serialized_data[WORKFLOW_MARKER_KEY] = True + + # Update variant parameters (creates a new revision) + app_variant_db = await db_manager.update_variant_parameters( + project_id=str(project_id), + user_uid=str(user.id), + # + app_variant_id=str(app_variant_db.id), + # + parameters=serialized_data, + # commit_message="...", + ) + + # Deserialize the data back to return application revision + application_revision_data = None + + if serialized_data and WORKFLOW_MARKER_KEY in serialized_data: + data_copy = serialized_data.copy() + del data_copy[WORKFLOW_MARKER_KEY] + try: + application_revision_data = LegacyApplicationData(**data_copy) + application_revision_data.version = str(app_variant_db.revision) # type: ignore + except ValidationError as e: + log.warning(f"Failed to deserialize application data: {e}") + + legacy_application = LegacyApplication( + id=app_db.id, # type: ignore + slug=app_db.app_name, # type: ignore + # + name=app_db.app_name, # type: ignore + # + created_at=app_db.created_at, # type: ignore + updated_at=app_db.updated_at, # type: ignore + created_by_id=app_db.modified_by_id, # type: ignore + # + tags={"type": app_db.app_type}, # type: ignore + # + data=application_revision_data, + ) + + return legacy_application + + async def retrieve( + self, + *, + project_id: UUID, + # + application_ref: Optional[Reference] = None, + application_variant_ref: Optional[Reference] = None, + application_revision_ref: Optional[Reference] = None, + ) -> Optional[ApplicationRevision]: + if ( + application_ref + and not application_ref.id + or application_variant_ref + and not application_variant_ref.id + or application_revision_ref + and not application_revision_ref.id + ): + return None + + if application_revision_ref and application_revision_ref.id: + # Fetch application revision details -------------------------------------------------- + variant_revision_db = await db_manager.fetch_app_variant_revision_by_id( + variant_revision_id=str(application_revision_ref.id) + ) + if not variant_revision_db: + return None + + # Fetch application variant details --------------------------------------------------- + app_variant_db = await db_manager.fetch_app_variant_by_id( + app_variant_id=str(variant_revision_db.variant_id) + ) + if not app_variant_db: + return None + + # Fetch application details ---------------------------------------------------------- + app_db = await db_manager.fetch_app_by_id(app_id=str(app_variant_db.app_id)) + if not app_db: + return None + + elif application_ref and application_ref.id: + # Fetch application details ---------------------------------------------------------- + app_db = await db_manager.fetch_app_by_id(app_id=str(application_ref.id)) + if not app_db: + return None + + # Fetch application variant details --------------------------------------------------- + app_variant_db = await db_manager.fetch_latest_app_variant( + app_id=str(app_db.id) + ) + if not app_variant_db: + return None + + # Fetch application revision details -------------------------------------------------- + variant_revision_db = await db_manager.fetch_app_variant_revision( + app_variant=str(app_variant_db.id), + revision_number=app_variant_db.revision, # type: ignore + ) + if not variant_revision_db: + return None + + elif application_variant_ref and application_variant_ref.id: + # Fetch application variant details --------------------------------------------------- + app_variant_db = await db_manager.fetch_app_variant_by_id( + app_variant_id=str(application_variant_ref.id) + ) + if not app_variant_db: + return None + + # Fetch application details ---------------------------------------------------------- + app_db = await db_manager.fetch_app_by_id(app_id=str(app_variant_db.app_id)) + if not app_db: + return None + + # Fetch application revision details ------------------------------------------------- + variant_revision_db = await db_manager.fetch_app_variant_revision( + app_variant=str(app_variant_db.id), + revision_number=app_variant_db.revision, # type: ignore + ) + if not variant_revision_db: + return None + + application = Application( + id=app_db.id, # type: ignore + slug=app_db.app_name, # type: ignore + name=app_db.app_name, # type: ignore + created_at=app_db.created_at, # type: ignore + updated_at=app_db.updated_at, # type: ignore + created_by_id=app_db.modified_by_id, # type: ignore + tags={"type": app_db.app_type}, # type: ignore + ) + + application_variant_slug = get_slug_from_name_and_id( + str(app_variant_db.variant_name), + UUID(str(app_variant_db.id)), + ) + + application_variant = ApplicationVariant( + id=app_variant_db.id, # type: ignore + slug=application_variant_slug, # type: ignore + name=app_variant_db.variant_name, + created_at=app_variant_db.created_at, # type: ignore + updated_at=app_variant_db.updated_at, # type: ignore + deleted_at=app_variant_db.updated_at if app_variant_db.hidden else None, # type: ignore + created_by_id=app_variant_db.modified_by_id, # type: ignore + updated_by_id=( + app_variant_db.modified_by_id # type: ignore + if app_variant_db.updated_at # type: ignore + else None + ), + deleted_by_id=( + app_variant_db.modified_by_id # type: ignore + if app_variant_db.hidden # type: ignore + else None + ), + tags=application.tags, + application_id=application.id, + ) + + application_revision_slug = get_slug_from_name_and_id( + str(variant_revision_db.config_name), + UUID(str(variant_revision_db.id)), + ) + + application_revision = ApplicationRevision( + id=variant_revision_db.id, # type: ignore + slug=application_revision_slug, # type: ignore + name=variant_revision_db.config_name, # type: ignore + created_at=variant_revision_db.created_at, # type: ignore + updated_at=variant_revision_db.updated_at, # type: ignore + deleted_at=( + variant_revision_db.updated_at # type: ignore + if variant_revision_db.hidden # type: ignore + else None + ), + created_by_id=variant_revision_db.modified_by_id, # type: ignore + updated_by_id=( + variant_revision_db.modified_by_id # type: ignore + if variant_revision_db.updated_at # type: ignore + else None + ), + deleted_by_id=( + variant_revision_db.modified_by_id # type: ignore + if variant_revision_db.hidden # type: ignore + else None + ), + tags=application_variant.tags, + application_id=application.id, + application_variant_id=application_variant.id, + ) + + # Deserialize data if marked as workflow + application_revision_data: Optional[ApplicationRevisionData] = None + + if isinstance(variant_revision_db.config_parameters, dict): + wf_data = self._deserialize_workflow_data( + variant_revision_db.config_parameters + ) + if wf_data is not None: + try: + application_revision_data = ApplicationRevisionData( + **wf_data.model_dump(mode="json") + ) + application_revision_data.version = str(app_variant_db.revision) # type: ignore + except ValidationError as e: + log.warning( + f"Failed to cast workflow data to ApplicationRevisionData: {e}" + ) + + # Set the data field if we have deserialized data + if application_revision_data: + application_revision.data = application_revision_data + else: + application_revision.data = ApplicationRevisionData() + + return application_revision + + def _is_workflow_data( + self, + config_parameters: Dict[str, Any], + ) -> bool: + """ + Check if the config_parameters contains workflow data (has marker key). + """ + + return ( + isinstance(config_parameters, dict) + and config_parameters.get(WORKFLOW_MARKER_KEY) is True + ) + + def _serialize_workflow_data( + self, + workflow_data: WorkflowRevisionData, + ) -> Dict[str, Any]: + """ + Serialize workflow revision data with marker for legacy storage. + """ + + serialized = workflow_data.model_dump(mode="json") + serialized[WORKFLOW_MARKER_KEY] = True + + return serialized + + def _deserialize_workflow_data( + self, + config_parameters: Dict[str, Any], + ) -> Optional[WorkflowRevisionData]: + """ + Deserialize workflow revision data from legacy storage. + Returns None if not workflow data or if deserialization fails. + """ + + if not self._is_workflow_data(config_parameters): + return None + + try: + data_copy = config_parameters.copy() + del data_copy[WORKFLOW_MARKER_KEY] + + return WorkflowRevisionData(**data_copy) + + except ValidationError as e: + log.warning(f"Failed to deserialize workflow data: {e}") + return None diff --git a/api/oss/src/core/blobs/utils.py b/api/oss/src/core/blobs/utils.py index 6fe0b9ab6c..1ebeaf20c4 100644 --- a/api/oss/src/core/blobs/utils.py +++ b/api/oss/src/core/blobs/utils.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Dict, Any from uuid import UUID from hashlib import blake2b from json import dumps @@ -6,11 +6,19 @@ def compute_blob_id( *, - blob_data: dict, + blob_data: Optional[Dict[str, Any]] = None, set_id: Optional[UUID] = None, ) -> UUID: # Deterministically serialize the blob data - json_blob_data = dumps(blob_data, sort_keys=True, separators=(",", ":")) + json_blob_data = ( + dumps( + blob_data, + sort_keys=True, + separators=(",", ":"), + ) + if blob_data + else "" + ) # Combine with set_id unhashed = f"{str(set_id)}{json_blob_data}".encode("utf-8") diff --git a/api/oss/src/core/evaluations/interfaces.py b/api/oss/src/core/evaluations/interfaces.py index 8dd163646b..65c7f8974d 100644 --- a/api/oss/src/core/evaluations/interfaces.py +++ b/api/oss/src/core/evaluations/interfaces.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional, List, Tuple from uuid import UUID from abc import ABC, abstractmethod @@ -12,14 +12,14 @@ EvaluationScenarioCreate, EvaluationScenarioEdit, EvaluationScenarioQuery, - EvaluationStep, - EvaluationStepCreate, - EvaluationStepEdit, - EvaluationStepQuery, - EvaluationMetric, - EvaluationMetricCreate, - EvaluationMetricEdit, - EvaluationMetricQuery, + EvaluationResult, + EvaluationResultCreate, + EvaluationResultEdit, + EvaluationResultQuery, + EvaluationMetrics, + EvaluationMetricsCreate, + EvaluationMetricsEdit, + EvaluationMetricsQuery, EvaluationQueue, EvaluationQueueCreate, EvaluationQueueEdit, @@ -120,7 +120,7 @@ async def delete_runs( raise NotImplementedError @abstractmethod - async def archive_run( + async def close_run( self, *, project_id: UUID, @@ -131,18 +131,18 @@ async def archive_run( raise NotImplementedError @abstractmethod - async def archive_runs( + async def close_runs( self, *, project_id: UUID, user_id: UUID, # - run_ids: Optional[List[UUID]] = None, + run_ids: List[UUID], ) -> List[EvaluationRun]: raise NotImplementedError @abstractmethod - async def unarchive_run( + async def open_run( self, *, project_id: UUID, @@ -153,50 +153,34 @@ async def unarchive_run( raise NotImplementedError @abstractmethod - async def unarchive_runs( + async def open_runs( self, *, project_id: UUID, user_id: UUID, # - run_ids: Optional[List[UUID]] = None, + run_ids: List[UUID], ) -> List[EvaluationRun]: raise NotImplementedError @abstractmethod - async def close_run( + async def query_runs( self, *, project_id: UUID, - user_id: UUID, # - run_id: UUID, - ) -> Optional[EvaluationRun]: - raise NotImplementedError - - @abstractmethod - async def close_runs( - self, - *, - project_id: UUID, - user_id: UUID, + run: Optional[EvaluationRunQuery] = None, # - run_ids: List[UUID], - ) -> List[UUID]: + windowing: Optional[Windowing] = None, + ) -> List[EvaluationRun]: raise NotImplementedError @abstractmethod - async def query_runs( + async def fetch_live_runs( self, *, - project_id: UUID, - # - run: EvaluationRunQuery, - # - include_archived: Optional[bool] = None, - # windowing: Optional[Windowing] = None, - ) -> List[EvaluationRun]: + ) -> List[Tuple[UUID, EvaluationRun]]: raise NotImplementedError # - EVALUATION SCENARIO ---------------------------------------------------- @@ -291,124 +275,111 @@ async def query_scenarios( *, project_id: UUID, # - scenario: EvaluationScenarioQuery, - # - include_archived: Optional[bool] = None, + scenario: Optional[EvaluationScenarioQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationScenario]: raise NotImplementedError - # - EVALUATION STEP -------------------------------------------------------- + # - EVALUATION RESULT ------------------------------------------------------ @abstractmethod - async def create_step( + async def create_result( self, *, project_id: UUID, user_id: UUID, # - step: EvaluationStepCreate, - ) -> Optional[EvaluationStep]: + result: EvaluationResultCreate, + ) -> Optional[EvaluationResult]: raise NotImplementedError @abstractmethod - async def create_steps( + async def create_results( self, *, project_id: UUID, user_id: UUID, # - steps: List[EvaluationStepCreate], - ) -> List[EvaluationStep]: + results: List[EvaluationResultCreate], + ) -> List[EvaluationResult]: raise NotImplementedError @abstractmethod - async def fetch_step( + async def fetch_result( self, *, project_id: UUID, # - step_id: UUID, - ) -> Optional[EvaluationStep]: + result_id: UUID, + ) -> Optional[EvaluationResult]: raise NotImplementedError @abstractmethod - async def fetch_steps( + async def fetch_results( self, *, project_id: UUID, # - step_ids: List[UUID], - ) -> List[EvaluationStep]: + result_ids: List[UUID], + ) -> List[EvaluationResult]: raise NotImplementedError @abstractmethod - async def edit_step( + async def edit_result( self, *, project_id: UUID, user_id: UUID, # - step: EvaluationStepEdit, - ) -> Optional[EvaluationStep]: + result: EvaluationResultEdit, + ) -> Optional[EvaluationResult]: raise NotImplementedError @abstractmethod - async def edit_steps( + async def edit_results( self, *, project_id: UUID, user_id: UUID, # - steps: List[EvaluationStepEdit], - ) -> List[EvaluationStep]: + results: List[EvaluationResultEdit], + ) -> List[EvaluationResult]: raise NotImplementedError @abstractmethod - async def delete_step( + async def delete_result( self, *, project_id: UUID, # - step_id: UUID, + result_id: UUID, ) -> Optional[UUID]: raise NotImplementedError @abstractmethod - async def delete_steps( + async def delete_results( self, *, project_id: UUID, # - step_ids: List[UUID], + result_ids: List[UUID], ) -> List[UUID]: raise NotImplementedError @abstractmethod - async def query_steps( + async def query_results( self, *, project_id: UUID, # - step: EvaluationStepQuery, + result: Optional[EvaluationResultQuery] = None, # windowing: Optional[Windowing] = None, - ) -> List[EvaluationStep]: + ) -> List[EvaluationResult]: raise NotImplementedError - # - EVALUATION METRIC ------------------------------------------------------ - - @abstractmethod - async def create_metric( - self, - *, - project_id: UUID, - user_id: UUID, - # - metric: EvaluationMetricCreate, - ) -> Optional[EvaluationMetric]: - raise NotImplementedError + # - EVALUATION METRICS ----------------------------------------------------- @abstractmethod async def create_metrics( @@ -417,18 +388,8 @@ async def create_metrics( project_id: UUID, user_id: UUID, # - metrics: List[EvaluationMetricCreate], - ) -> List[EvaluationMetric]: - raise NotImplementedError - - @abstractmethod - async def fetch_metric( - self, - *, - project_id: UUID, - # - metric_id: UUID, - ) -> Optional[EvaluationMetric]: + metrics: List[EvaluationMetricsCreate], + ) -> List[EvaluationMetrics]: raise NotImplementedError @abstractmethod @@ -437,19 +398,8 @@ async def fetch_metrics( *, project_id: UUID, # - metric_ids: List[UUID], - ) -> List[EvaluationMetric]: - raise NotImplementedError - - @abstractmethod - async def edit_metric( - self, - *, - project_id: UUID, - user_id: UUID, - # - metric: EvaluationMetricEdit, - ) -> Optional[EvaluationMetric]: + metrics_ids: List[UUID], + ) -> List[EvaluationMetrics]: raise NotImplementedError @abstractmethod @@ -459,18 +409,8 @@ async def edit_metrics( project_id: UUID, user_id: UUID, # - metrics: List[EvaluationMetricEdit], - ) -> List[EvaluationMetric]: - raise NotImplementedError - - @abstractmethod - async def delete_metric( - self, - *, - project_id: UUID, - # - metric_id: UUID, - ) -> Optional[UUID]: + metrics: List[EvaluationMetricsEdit], + ) -> List[EvaluationMetrics]: raise NotImplementedError @abstractmethod @@ -479,7 +419,7 @@ async def delete_metrics( *, project_id: UUID, # - metric_ids: Optional[List[UUID]] = None, + metrics_ids: Optional[List[UUID]] = None, ) -> List[UUID]: raise NotImplementedError @@ -489,10 +429,10 @@ async def query_metrics( *, project_id: UUID, # - metric: EvaluationMetricQuery, + metric: Optional[EvaluationMetricsQuery] = None, # windowing: Optional[Windowing] = None, - ) -> List[EvaluationMetric]: + ) -> List[EvaluationMetrics]: raise NotImplementedError # - EVALUATION QUEUE ------------------------------------------------------- @@ -587,7 +527,7 @@ async def query_queues( *, project_id: UUID, # - queue: EvaluationQueueQuery, + queue: Optional[EvaluationQueueQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationQueue]: diff --git a/api/oss/src/core/evaluations/service.py b/api/oss/src/core/evaluations/service.py index efff842b92..159a1b928b 100644 --- a/api/oss/src/core/evaluations/service.py +++ b/api/oss/src/core/evaluations/service.py @@ -1,52 +1,221 @@ -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Dict, Any from uuid import UUID +from asyncio import sleep +from copy import deepcopy +from datetime import datetime, timedelta, timezone +from celery import current_app as celery_dispatch + +from oss.src.utils.common import is_ee from oss.src.utils.logging import get_module_logger -from oss.src.core.shared.dtos import Windowing +from oss.src.core.shared.dtos import Reference, Windowing, Tags, Meta, Data from oss.src.core.evaluations.interfaces import EvaluationsDAOInterface -from oss.src.core.evaluations.types import EvaluationRunFlags from oss.src.core.evaluations.types import ( + EvaluationStatus, + # EVALUATION RUN + EvaluationRunFlags, + EvaluationRunDataMappingColumn, + EvaluationRunDataMappingStep, + EvaluationRunDataMapping, + EvaluationRunDataStepInput, + EvaluationRunDataStep, + EvaluationRunData, EvaluationRun, EvaluationRunCreate, EvaluationRunEdit, EvaluationRunQuery, + # EVALUATION SCENARIO EvaluationScenario, EvaluationScenarioCreate, EvaluationScenarioEdit, EvaluationScenarioQuery, - EvaluationStep, - EvaluationStepCreate, - EvaluationStepEdit, - EvaluationStepQuery, - EvaluationMetric, - EvaluationMetricCreate, - EvaluationMetricEdit, - EvaluationMetricQuery, + # EVALUATION RESULT + EvaluationResult, + EvaluationResultCreate, + EvaluationResultEdit, + EvaluationResultQuery, + # EVALUATION METRICS + EvaluationMetrics, + EvaluationMetricsCreate, + EvaluationMetricsEdit, + EvaluationMetricsQuery, + # EVALUATION QUEUE EvaluationQueue, EvaluationQueueCreate, EvaluationQueueEdit, EvaluationQueueQuery, ) +from oss.src.core.evaluations.types import ( + Target, + Origin, + # + SimpleEvaluationFlags, + SimpleEvaluationData, + SimpleEvaluationStatus, + # + SimpleEvaluation, + SimpleEvaluationCreate, + SimpleEvaluationEdit, + SimpleEvaluationQuery, +) + +from oss.src.core.tracing.dtos import ( + TracingQuery, + Filtering, + Condition, + ListOperator, + MetricsBucket, + MetricSpec, + MetricType, +) +from oss.src.core.tracing.service import TracingService + +from oss.src.core.evaluators.service import EvaluatorsService +from oss.src.core.queries.dtos import QueryRevision +from oss.src.core.testcases.dtos import Testcase +from oss.src.core.testsets.dtos import TestsetRevision +from oss.src.core.evaluators.dtos import EvaluatorRevision +from oss.src.core.queries.service import QueriesService +from oss.src.core.testsets.service import TestsetsService +from oss.src.core.testsets.service import SimpleTestsetsService +from oss.src.core.evaluators.service import EvaluatorsService +from oss.src.core.evaluators.service import SimpleEvaluatorsService + +from oss.src.core.evaluations.utils import filter_scenario_ids + +from oss.src.models.db_models import AppVariantRevisionsDB + +from oss.src.services.db_manager import ( + fetch_app_by_id, + fetch_app_variant_by_id, + fetch_app_variant_revision_by_id, +) +from oss.src.utils.helpers import get_slug_from_name_and_id +from oss.src.core.evaluations.utils import get_metrics_keys_from_schema log = get_module_logger(__name__) -# Divides cleanly into 1, 2, 3, 4, 5, 6, 8, 10, ... -BLOCKS = 1 * 2 * 3 * 4 * 5 + +SAFE_CLOSE_DELAY = 1 # seconds +DEFAULT_ORIGIN_QUERIES = "custom" +DEFAULT_ORIGIN_TESTSETS = "custom" +DEFAULT_ORIGIN_APPLICATIONS = "custom" +DEFAULT_ORIGIN_EVALUATORS = "custom" +DEFAULT_ORIGIN = dict( + queries=DEFAULT_ORIGIN_QUERIES, + testsets=DEFAULT_ORIGIN_TESTSETS, + applications=DEFAULT_ORIGIN_APPLICATIONS, + evaluators=DEFAULT_ORIGIN_EVALUATORS, +) + +DEFAULT_METRICS = [ + { + "path": "attributes.ag.metrics.duration.cumulative", + "type": "numeric/continuous", + }, + { + "path": "attributes.ag.metrics.errors.cumulative", + "type": "numeric/continuous", + }, + { + "path": "attributes.ag.metrics.costs.cumulative.total", + "type": "numeric/continuous", + }, + { + "path": "attributes.ag.metrics.tokens.cumulative.total", + "type": "numeric/continuous", + }, +] + +DEFAULT_REFRESH_INTERVAL = 1 # minute(s) class EvaluationsService: def __init__( self, evaluations_dao: EvaluationsDAOInterface, + tracing_service: TracingService, + queries_service: QueriesService, + testsets_service: TestsetsService, + evaluators_service: EvaluatorsService, ): self.evaluations_dao = evaluations_dao + self.tracing_service = tracing_service + self.queries_service = queries_service + self.testsets_service = testsets_service + self.evaluators_service = evaluators_service + ### CRUD # - EVALUATION RUN --------------------------------------------------------- + async def refresh_runs( + self, + *, + timestamp: datetime, + interval: int = DEFAULT_REFRESH_INTERVAL, + ) -> bool: + log.info(f"[LIVE] Refreshing runs at {timestamp} every {interval} minute(s)") + + if not timestamp: + return False + + newest = timestamp + timedelta(minutes=interval or 0) + oldest = timestamp + + try: + ext_runs = await self.fetch_live_runs() + except Exception as e: + log.error(f"[LIVE] Error fetching live runs: {e}", exc_info=True) + log.error(e, exc_info=True) + return False + + for project_id, run in ext_runs: + user_id = run.created_by_id + + try: + log.info( + "[LIVE]", + project_id=project_id, + run_id=run.id, + # + newest=newest, + oldest=oldest, + ) + + if is_ee(): + celery_dispatch.send_task( # type: ignore + "src.tasks.evaluations.live.evaluate", + kwargs=dict( + project_id=project_id, + user_id=user_id, + # + run_id=run.id, + # + newest=newest, + oldest=oldest, + ), + ) + + except Exception as e: # pylint: disable=broad-exception-caught + log.error(f"[LIVE] Error refreshing run {run.id}: {e}", exc_info=True) + + return True + + async def fetch_live_runs( + self, + *, + windowing: Optional[Windowing] = None, + ) -> List[Tuple[UUID, EvaluationRun]]: + ext_runs = await self.evaluations_dao.fetch_live_runs( + windowing=windowing, + ) + + return ext_runs + async def create_run( self, *, @@ -159,37 +328,7 @@ async def delete_runs( run_ids=run_ids, ) - async def archive_run( - self, - *, - project_id: UUID, - user_id: UUID, - # - run_id: UUID, - ) -> Optional[EvaluationRun]: - return await self.evaluations_dao.archive_run( - project_id=project_id, - user_id=user_id, - # - run_id=run_id, - ) - - async def archive_runs( - self, - *, - project_id: UUID, - user_id: UUID, - # - run_ids: List[UUID], - ) -> List[EvaluationRun]: - return await self.evaluations_dao.archive_runs( - project_id=project_id, - user_id=user_id, - # - run_ids=run_ids, - ) - - async def unarchive_run( + async def close_run( self, *, project_id: UUID, @@ -197,14 +336,14 @@ async def unarchive_run( # run_id: UUID, ) -> Optional[EvaluationRun]: - return await self.evaluations_dao.unarchive_run( + return await self.evaluations_dao.close_run( project_id=project_id, user_id=user_id, # run_id=run_id, ) - async def unarchive_runs( + async def close_runs( self, *, project_id: UUID, @@ -212,14 +351,14 @@ async def unarchive_runs( # run_ids: List[UUID], ) -> List[EvaluationRun]: - return await self.evaluations_dao.unarchive_runs( + return await self.evaluations_dao.close_runs( project_id=project_id, user_id=user_id, # run_ids=run_ids, ) - async def close_run( + async def open_run( self, *, project_id: UUID, @@ -227,14 +366,14 @@ async def close_run( # run_id: UUID, ) -> Optional[EvaluationRun]: - return await self.evaluations_dao.close_run( + return await self.evaluations_dao.open_run( project_id=project_id, user_id=user_id, # run_id=run_id, ) - async def close_runs( + async def open_runs( self, *, project_id: UUID, @@ -242,7 +381,7 @@ async def close_runs( # run_ids: List[UUID], ) -> List[EvaluationRun]: - return await self.evaluations_dao.close_runs( + return await self.evaluations_dao.open_runs( project_id=project_id, user_id=user_id, # @@ -254,9 +393,7 @@ async def query_runs( *, project_id: UUID, # - run: EvaluationRunQuery, - # - include_archived: Optional[bool] = None, + run: Optional[EvaluationRunQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationRun]: @@ -265,8 +402,6 @@ async def query_runs( # run=run, # - include_archived=include_archived, - # windowing=windowing, ) @@ -387,7 +522,7 @@ async def query_scenarios( *, project_id: UUID, # - scenario: EvaluationScenarioQuery, + scenario: Optional[EvaluationScenarioQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationScenario]: @@ -399,162 +534,147 @@ async def query_scenarios( windowing=windowing, ) - # - EVALUATION STEP -------------------------------------------------------- + # - EVALUATION RESULT ------------------------------------------------------ - async def create_step( + async def create_result( self, *, project_id: UUID, user_id: UUID, # - step: EvaluationStepCreate, - ) -> Optional[EvaluationStep]: - return await self.evaluations_dao.create_step( + result: EvaluationResultCreate, + ) -> Optional[EvaluationResult]: + return await self.evaluations_dao.create_result( project_id=project_id, user_id=user_id, # - step=step, + result=result, ) - async def create_steps( + async def create_results( self, *, project_id: UUID, user_id: UUID, # - steps: List[EvaluationStepCreate], - ) -> List[EvaluationStep]: - return await self.evaluations_dao.create_steps( + results: List[EvaluationResultCreate], + ) -> List[EvaluationResult]: + return await self.evaluations_dao.create_results( project_id=project_id, user_id=user_id, # - steps=steps, + results=results, ) - async def fetch_step( + async def fetch_result( self, *, project_id: UUID, # - step_id: UUID, - ) -> Optional[EvaluationStep]: - return await self.evaluations_dao.fetch_step( + result_id: UUID, + ) -> Optional[EvaluationResult]: + return await self.evaluations_dao.fetch_result( project_id=project_id, # - step_id=step_id, + result_id=result_id, ) - async def fetch_steps( + async def fetch_results( self, *, project_id: UUID, # - step_ids: List[UUID], - ) -> List[EvaluationStep]: - return await self.evaluations_dao.fetch_steps( + result_ids: List[UUID], + ) -> List[EvaluationResult]: + return await self.evaluations_dao.fetch_results( project_id=project_id, # - step_ids=step_ids, + result_ids=result_ids, ) - async def edit_step( + async def edit_result( self, *, project_id: UUID, user_id: UUID, # - step: EvaluationStepEdit, - ) -> Optional[EvaluationStep]: - return await self.evaluations_dao.edit_step( + result: EvaluationResultEdit, + ) -> Optional[EvaluationResult]: + return await self.evaluations_dao.edit_result( project_id=project_id, user_id=user_id, # - step=step, + result=result, ) - async def edit_steps( + async def edit_results( self, *, project_id: UUID, user_id: UUID, # - steps: List[EvaluationStepEdit], - ) -> List[EvaluationStep]: - return await self.evaluations_dao.edit_steps( + results: List[EvaluationResultEdit], + ) -> List[EvaluationResult]: + return await self.evaluations_dao.edit_results( project_id=project_id, user_id=user_id, # - steps=steps, + results=results, ) - async def delete_step( + async def delete_result( self, *, project_id: UUID, # - step_id: UUID, + result_id: UUID, ) -> Optional[UUID]: - return await self.evaluations_dao.delete_step( + return await self.evaluations_dao.delete_result( project_id=project_id, # - step_id=step_id, + result_id=result_id, ) - async def delete_steps( + async def delete_results( self, *, project_id: UUID, # - step_ids: List[UUID], + result_ids: List[UUID], ) -> List[UUID]: - return await self.evaluations_dao.delete_steps( + return await self.evaluations_dao.delete_results( project_id=project_id, # - step_ids=step_ids, + result_ids=result_ids, ) - async def query_steps( + async def query_results( self, *, project_id: UUID, # - step: EvaluationStepQuery, + result: Optional[EvaluationResultQuery] = None, # windowing: Optional[Windowing] = None, - ) -> List[EvaluationStep]: - return await self.evaluations_dao.query_steps( + ) -> List[EvaluationResult]: + return await self.evaluations_dao.query_results( project_id=project_id, # - step=step, + result=result, # windowing=windowing, ) # - EVALUATION METRIC ------------------------------------------------------ - async def create_metric( - self, - *, - project_id: UUID, - user_id: UUID, - # - metric: EvaluationMetricCreate, - ) -> Optional[EvaluationMetric]: - return await self.evaluations_dao.create_metric( - project_id=project_id, - user_id=user_id, - # - metric=metric, - ) - async def create_metrics( self, *, project_id: UUID, user_id: UUID, # - metrics: List[EvaluationMetricCreate], - ) -> List[EvaluationMetric]: + metrics: List[EvaluationMetricsCreate], + ) -> List[EvaluationMetrics]: return await self.evaluations_dao.create_metrics( project_id=project_id, user_id=user_id, @@ -562,45 +682,17 @@ async def create_metrics( metrics=metrics, ) - async def fetch_metric( - self, - *, - project_id: UUID, - # - metric_id: UUID, - ) -> Optional[EvaluationMetric]: - return await self.evaluations_dao.fetch_metric( - project_id=project_id, - # - metric_id=metric_id, - ) - async def fetch_metrics( self, *, project_id: UUID, # - metric_ids: List[UUID], - ) -> List[EvaluationMetric]: + metrics_ids: List[UUID], + ) -> List[EvaluationMetrics]: return await self.evaluations_dao.fetch_metrics( project_id=project_id, # - metric_ids=metric_ids, - ) - - async def edit_metric( - self, - *, - project_id: UUID, - user_id: UUID, - # - metric: EvaluationMetricEdit, - ) -> Optional[EvaluationMetric]: - return await self.evaluations_dao.edit_metric( - project_id=project_id, - user_id=user_id, - # - metric=metric, + metrics_ids=metrics_ids, ) async def edit_metrics( @@ -609,8 +701,8 @@ async def edit_metrics( project_id: UUID, user_id: UUID, # - metrics: List[EvaluationMetricEdit], - ) -> List[EvaluationMetric]: + metrics: List[EvaluationMetricsEdit], + ) -> List[EvaluationMetrics]: return await self.evaluations_dao.edit_metrics( project_id=project_id, user_id=user_id, @@ -618,30 +710,17 @@ async def edit_metrics( metrics=metrics, ) - async def delete_metric( - self, - *, - project_id: UUID, - # - metric_id: UUID, - ) -> Optional[UUID]: - return await self.evaluations_dao.delete_metric( - project_id=project_id, - # - metric_id=metric_id, - ) - async def delete_metrics( self, *, project_id: UUID, # - metric_ids: List[UUID], + metrics_ids: List[UUID], ) -> List[UUID]: return await self.evaluations_dao.delete_metrics( project_id=project_id, # - metric_ids=metric_ids, + metrics_ids=metrics_ids, ) async def query_metrics( @@ -649,10 +728,10 @@ async def query_metrics( *, project_id: UUID, # - metric: EvaluationMetricQuery, + metric: Optional[EvaluationMetricsQuery] = None, # windowing: Optional[Windowing] = None, - ) -> List[EvaluationMetric]: + ) -> List[EvaluationMetrics]: return await self.evaluations_dao.query_metrics( project_id=project_id, # @@ -661,6 +740,172 @@ async def query_metrics( windowing=windowing, ) + async def refresh_metrics( + self, + *, + project_id: UUID, + user_id: UUID, + # + run_id: UUID, + scenario_id: Optional[UUID] = None, + timestamp: Optional[datetime] = None, + interval: Optional[int] = None, + ) -> List[EvaluationMetrics]: + metrics_data: Dict[str, Any] = dict() + + run = await self.fetch_run( + project_id=project_id, + # + run_id=run_id, + ) + + if not run or not run.data or not run.data.steps: + log.warning("[WARN] run or run.data or run.data.steps not found") + return [] + + steps_metrics_keys: Dict[str, List[Dict[str, str]]] = dict() + + for step in run.data.steps: + steps_metrics_keys[step.key] = DEFAULT_METRICS + + if step.type == "annotation": + evaluator_revision_ref = step.references.get("evaluator_revision") + + if not evaluator_revision_ref: + log.warning("[WARN] Evaluator revision reference not found") + continue + + evaluator_revision = ( + await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + evaluator_revision_ref=evaluator_revision_ref, + ) + ) + + if not evaluator_revision: + log.warning("[WARN] Evaluator revision not found") + continue + + if evaluator_revision.data and evaluator_revision.data.schemas: + metrics_keys = get_metrics_keys_from_schema( + schema=(evaluator_revision.data.schemas.get("outputs")), + ) + + steps_metrics_keys[step.key] += [ + { + "path": "ag.data.outputs." + metric_key.get("path", ""), + "type": metric_key.get("type", ""), + } + for metric_key in metrics_keys + ] + + if not steps_metrics_keys: + log.warning("[WARN] No steps metrics keys found") + return [] + + step_keys = list(steps_metrics_keys.keys()) + + steps_trace_ids: Dict[str, List[str]] = dict() + + for step_key in step_keys: + results = await self.query_results( + project_id=project_id, + result=EvaluationResultQuery( + run_id=run_id, + scenario_id=scenario_id, + step_key=step_key, + timestamp=timestamp, + interval=interval, + ), + ) + + if not results: + # log.warning("[WARN] No results found") + continue + + trace_ids = [result.trace_id for result in results if result.trace_id] + + if trace_ids: + steps_trace_ids[step_key] = trace_ids + + for step_key in steps_metrics_keys.keys() & steps_trace_ids.keys(): + step_metrics_keys = steps_metrics_keys[step_key] + step_trace_ids = steps_trace_ids[step_key] + + try: + query = TracingQuery( + filtering=Filtering( + conditions=[ + Condition( + field="trace_id", + operator=ListOperator.IN, + value=step_trace_ids, + ) + ] + ) + ) + + specs = [ + MetricSpec( + type=MetricType(metric.get("type")), + path=metric.get("path") or "*", + ) + for metric in step_metrics_keys + ] + [ + MetricSpec( + type=MetricType.JSON, + path="atttributes.ag", + ) + ] + + buckets = await self.tracing_service.analytics( + project_id=project_id, + # + query=query, + specs=specs, + ) + + if len(buckets) != 1: + log.warning("[WARN] There should be one and only one bucket") + continue + + bucket = buckets[0] + + if not bucket.metrics: + log.warning("[WARN] Bucket metrics should not be empty") + continue + + metrics_data |= {step_key: bucket.metrics} + + except Exception as e: + log.error(e, exc_info=True) + + if not metrics_data: + # log.warning("[WARN] No metrics data: no metrics will be stored") + return [] + + metrics_create = [ + EvaluationMetricsCreate( + run_id=run_id, + scenario_id=scenario_id, + timestamp=timestamp, + interval=interval, + # + status=EvaluationStatus.SUCCESS, + # + data=metrics_data, + ) + ] + + metrics = await self.create_metrics( + project_id=project_id, + user_id=user_id, + # + metrics=metrics_create, + ) + + return metrics + # - EVALUATION QUEUE ------------------------------------------------------- async def create_queue( @@ -780,7 +1025,7 @@ async def query_queues( *, project_id: UUID, # - queue: EvaluationQueueQuery, + queue: Optional[EvaluationQueueQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationQueue]: @@ -796,11 +1041,11 @@ async def fetch_queue_scenarios( self, *, project_id: UUID, - user_id: UUID, + user_id: Optional[UUID] = None, # queue_id: UUID, # - ) -> List[Optional[List[UUID]]]: + ) -> List[List[UUID]]: queue = await self.fetch_queue( project_id=project_id, queue_id=queue_id, @@ -815,26 +1060,23 @@ async def fetch_queue_scenarios( project_id=project_id, scenario=EvaluationScenarioQuery( run_id=queue.run_id, - scenario_ids=queue_scenario_ids, + ids=queue_scenario_ids, ), ) run_scenario_ids = [scenario.id for scenario in scenarios] + run_scenario_ids = [id for id in run_scenario_ids if id is not None] - del scenarios - - user_ids = queue.data.user_ids if queue.data else None + queue_user_ids = queue.data.user_ids if queue.data else None - if not user_ids: + if not queue_user_ids: return [run_scenario_ids] - is_sequential = queue.flags and queue.flags.is_sequential - - del queue + is_sequential = queue.flags and queue.flags.is_sequential or False user_scenario_ids = filter_scenario_ids( user_id, - user_ids, + queue_user_ids, run_scenario_ids, is_sequential, ) @@ -842,77 +1084,1387 @@ async def fetch_queue_scenarios( return user_scenario_ids -def filter_scenario_ids( - user_id: UUID, - user_ids: List[List[UUID]], - scenario_ids: List[UUID], - is_sequential: bool, - offset: int = 0, -) -> List[List[UUID]]: - user_scenario_ids = [] +class SimpleEvaluationsService: + def __init__( + self, + queries_service: QueriesService, + testsets_service: TestsetsService, + evaluators_service: EvaluatorsService, + evaluations_service: EvaluationsService, + simple_testsets_service: SimpleTestsetsService, + simple_evaluators_service: SimpleEvaluatorsService, + ): + self.queries_service = queries_service + self.testsets_service = testsets_service + self.evaluators_service = evaluators_service + self.evaluations_service = evaluations_service + self.simple_testsets_service = simple_testsets_service + self.simple_evaluators_service = simple_evaluators_service + + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluation: SimpleEvaluationCreate, + ) -> Optional[SimpleEvaluation]: + evaluation.flags = evaluation.flags or SimpleEvaluationFlags( + is_closed=False, + is_live=False, + is_active=True, + ) + + if not evaluation.data: + log.info("[EVAL] [failure] missing simple evaluation data") + return None + + # ---------------------------------------------------------------------- + log.info("[EVAL] [create]") + log.info("[EVAL] [scope] ", project_id=project_id, user_id=user_id) + log.info("[EVAL] [flags] ", ids=evaluation.flags.model_dump(mode="json")) + log.info("[EVAL] [status] ", ids=evaluation.data.status) + log.info("[EVAL] [queries] ", ids=evaluation.data.query_steps) + log.info("[EVAL] [testsets] ", ids=evaluation.data.testset_steps) + log.info("[EVAL] [applications]", ids=evaluation.data.application_steps) + log.info("[EVAL] [evaluators] ", ids=evaluation.data.evaluator_steps) + log.info("[EVAL] [repeats] ", repeats=evaluation.data.repeats) + # ---------------------------------------------------------------------- + + try: + run_flags = await self._make_evaluation_run_flags( + is_closed=False, + is_live=evaluation.flags.is_live, + is_active=False, + ) + + if not run_flags: + log.info("[EVAL] [failure] invalid simple evaluation flags") + return None + + run_data = await self._make_evaluation_run_data( + project_id=project_id, + user_id=user_id, + # + query_steps=evaluation.data.query_steps, + testset_steps=evaluation.data.testset_steps, + application_steps=evaluation.data.application_steps, + evaluator_steps=evaluation.data.evaluator_steps, + # + repeats=evaluation.data.repeats, + # + jit_migration=True, + ) + + if not run_data: + log.error("[EVAL] [failure] missing or invalid simple evaluation data") + return None + + run_create = EvaluationRunCreate( + name=evaluation.name, + description=evaluation.description, + # + flags=run_flags, + tags=evaluation.tags, + meta=evaluation.meta, + # + status=evaluation.data.status or EvaluationStatus.PENDING, + # + data=run_data, + ) + + run = await self.evaluations_service.create_run( + project_id=project_id, + user_id=user_id, + # + run=run_create, + ) + + if not run or not run.id: + log.error("[EVAL] [failure] could not create evaluation run") + return None + + log.info("[EVAL] [run] ", id=run.id) + + log.info("[EVAL] [start] ", id=run.id) + + _evaluation = await self.start( + project_id=project_id, + user_id=user_id, + evaluation_id=run.id, + # + just_created=True, + ) + + if not _evaluation: + log.error("[EVAL] [failure] could not start evaluation run") + return None + + log.info("[EVAL] [success] ", id=run.id) + return _evaluation + + except: # pylint: disable=bare-except + log.error("[EVAL] [failure] ", exc_info=True) + return None + + async def fetch( + self, + *, + project_id: UUID, + # + evaluation_id: UUID, + ) -> Optional[SimpleEvaluation]: + run = await self.evaluations_service.fetch_run( + project_id=project_id, + # + run_id=evaluation_id, + ) + + if not run: + return None + + evaluation = await self._parse_evaluation_run(run=run) + + return evaluation + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluation: SimpleEvaluationEdit, + ) -> Optional[SimpleEvaluation]: + evaluation.flags = evaluation.flags or SimpleEvaluationFlags( + is_closed=False, + is_live=False, + is_active=True, + ) + + if not evaluation.id: + log.info("[EVAL] [failure] missing simple evaluation id") + return None + + if not evaluation.flags: + log.info("") + return None + + if not evaluation.data: + log.info("[EVAL] [failure] missing simple evaluation data") + return None + + # ---------------------------------------------------------------------- + log.info("[EVAL] [edit] ", run_id=evaluation.id) + log.info("[EVAL] [scope] ", project_id=project_id, user_id=user_id) + log.info("[EVAL] [flags] ", ids=evaluation.flags.model_dump(mode="json")) + log.info("[EVAL] [queries] ", ids=evaluation.data.query_steps) + log.info("[EVAL] [testsets] ", ids=evaluation.data.testset_steps) + log.info("[EVAL] [applications]", ids=evaluation.data.application_steps) + log.info("[EVAL] [evaluators] ", ids=evaluation.data.evaluator_steps) + log.info("[EVAL] [repeats] ", repeats=evaluation.data.repeats) + # ---------------------------------------------------------------------- + + try: + _evaluation = await self.fetch( + project_id=project_id, + # + evaluation_id=evaluation.id, + ) + + if not _evaluation or not _evaluation.id: + log.error("[EVAL] [failure] could not find evaluation run") + return None + + if not _evaluation.flags: + _evaluation.flags = SimpleEvaluationFlags() + + if not _evaluation.data: + _evaluation.data = SimpleEvaluationData() + + if _evaluation.flags.is_closed: + log.error("[EVAL] [failure] cannot edit closed evaluation run") + return None + + was_active = _evaluation.flags.is_active + + run_status = _evaluation.data.status + + if was_active: + log.info("[EVAL] [stop] ", run_id=_evaluation.id) + _evaluation = await self.stop( + project_id=project_id, + user_id=user_id, + # + evaluation_id=_evaluation.id, + ) - MOD = min(len(scenario_ids), BLOCKS) + if not _evaluation or not _evaluation.id: + log.error("[EVAL] [failure] could not stop evaluation run") + return None - for repeat_user_ids in user_ids: - if not repeat_user_ids: - user_scenario_ids.append([]) + if not _evaluation.flags: + _evaluation.flags = SimpleEvaluationFlags() - else: - repeat_user_bounds = get_bounds( - repeat_user_ids, - user_id, - MOD, + if not _evaluation.data: + _evaluation.data = SimpleEvaluationData() + + await sleep(SAFE_CLOSE_DELAY) + + run_flags = await self._make_evaluation_run_flags( + is_closed=_evaluation.flags.is_closed, + is_live=_evaluation.flags.is_live, + is_active=_evaluation.flags.is_active, + ) + + run_data = await self._make_evaluation_run_data( + project_id=project_id, + user_id=user_id, + # + query_steps=evaluation.data.query_steps, + testset_steps=evaluation.data.testset_steps, + application_steps=evaluation.data.application_steps, + evaluator_steps=evaluation.data.evaluator_steps, + # + repeats=_evaluation.data.repeats, + ) + + run_edit = EvaluationRunEdit( + id=_evaluation.id, + name=evaluation.name, + description=evaluation.description, + # + flags=run_flags, + tags=evaluation.tags, + meta=evaluation.meta, + # + status=run_status, + # + data=run_data, ) - if not repeat_user_bounds: - user_scenario_ids.append([]) + run = await self.evaluations_service.edit_run( + project_id=project_id, + user_id=user_id, + # + run=run_edit, + ) + + if not run: + log.error("[EVAL] [failure] could not edit evaluation run") + return None + + if was_active: + log.info("[EVAL] [start] ", run_id=_evaluation.id) + + _evaluation = await self.start( + project_id=project_id, + user_id=user_id, + # + evaluation_id=_evaluation.id, + ) + + if not _evaluation or not _evaluation.id: + log.error("[EVAL] [failure] could not start evaluation run") + return None else: - repeat_scenario_ids = [] - for scenario_idx, scenario_id in enumerate(scenario_ids): - mod = ( - (offset + scenario_idx) if is_sequential else int(scenario_id) - ) % MOD - - if any( - lower <= mod < upper for (lower, upper) in repeat_user_bounds - ): - repeat_scenario_ids.append(scenario_id) + _evaluation = await self.fetch( + project_id=project_id, + # + evaluation_id=_evaluation.id, + ) + + if not _evaluation or not _evaluation.id: + log.error("[EVAL] [failure] could not find evaluation run") + return None + + log.info("[EVAL] [success] ", run_id=_evaluation.id) + + return _evaluation + + except: # pylint: disable=bare-except + log.error("[EVAL] [failure] ", exc_info=True) + return None + + async def delete( + self, + *, + project_id: UUID, + # + evaluation_id: UUID, + ) -> Optional[UUID]: + await self.evaluations_service.delete_run( + project_id=project_id, + # + run_id=evaluation_id, + ) + + return evaluation_id + + async def query( + self, + *, + project_id: UUID, + # + query: Optional[SimpleEvaluationQuery] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[SimpleEvaluation]: + run_query = await self._make_evaluation_run_query( + is_closed=query.flags.is_closed if query and query.flags else None, + is_live=query.flags.is_live if query and query.flags else None, + is_active=query.flags.is_active if query and query.flags else None, + # + tags=query.tags if query else None, + meta=query.meta if query else None, + ) + + runs = await self.evaluations_service.query_runs( + project_id=project_id, + # + run=run_query, + # + windowing=windowing, + ) + + _evaluations = [ + _evaluation + for _evaluation in [ + await self._parse_evaluation_run(run=run) for run in runs if run + ] + if _evaluation + ] - if not repeat_scenario_ids: - user_scenario_ids.append([]) + return _evaluations + async def start( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluation_id: UUID, + # + just_created: Optional[bool] = None, + ) -> Optional[SimpleEvaluation]: + try: + _evaluation = await self.fetch( + project_id=project_id, + # + evaluation_id=evaluation_id, + ) + + if not _evaluation or not _evaluation.id: + return None + + if not _evaluation.flags: + _evaluation.flags = SimpleEvaluationFlags() + + if not _evaluation.data: + _evaluation.data = SimpleEvaluationData() + + if _evaluation.flags.is_live and _evaluation.data.query_steps: + run = await self._activate_evaluation_run( + project_id=project_id, + user_id=user_id, + # + run_id=_evaluation.id, + # + just_created=just_created, + ) + + if not run or not run.id: + log.error( + "[EVAL] [start] [failure] could not activate evaluation run" + ) + return None + + _evaluation = await self._parse_evaluation_run(run=run) + + elif ( + not _evaluation.flags.is_live + and _evaluation.data.evaluator_steps + and (_evaluation.data.query_steps or _evaluation.data.testset_steps) + ): + run = await self._activate_evaluation_run( + project_id=project_id, + user_id=user_id, + # + run_id=_evaluation.id, + # + just_created=just_created, + ) + + if not run or not run.id: + log.error( + "[EVAL] [start] [failure] could not activate evaluation run" + ) + return None + + if _evaluation.data.query_steps: + if is_ee(): + celery_dispatch.send_task( # type: ignore + "src.tasks.evaluations.batch.evaluate_queries", + kwargs=dict( + project_id=project_id, + user_id=user_id, + # + run_id=run.id, + ), + ) + + elif _evaluation.data.testset_steps: + if is_ee(): + # TODO: Fix typing ? + celery_dispatch.send_task( # type: ignore + "src.tasks.evaluations.batch.evaluate_testsets", + kwargs=dict( + project_id=project_id, + user_id=user_id, + # + run_id=run.id, + ), + ) + + return _evaluation + + log.info("[EVAL] [start] [success]") + + return _evaluation + + except: # pylint: disable=bare-except + log.error("[EVAL] [start] [failure]", exc_info=True) + return None + + async def stop( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluation_id: UUID, + ) -> Optional[SimpleEvaluation]: + run = await self._deactivate_evaluation_run( + project_id=project_id, + user_id=user_id, + # + run_id=evaluation_id, + ) + + if not run or not run.id: + log.error("[EVAL] [stop] [failure] could not stop evaluation run") + return None + + _evaluation = await self._parse_evaluation_run(run=run) + + return _evaluation + + async def close( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluation_id: UUID, + ) -> Optional[SimpleEvaluation]: + run = await self.evaluations_service.close_run( + project_id=project_id, + user_id=user_id, + # + run_id=evaluation_id, + ) + + if not run or not run.id: + log.error("[EVAL] [close] [failure] could not close evaluation run") + return None + + evaluation = await self._parse_evaluation_run(run=run) + + return evaluation + + async def open( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluation_id: UUID, + ) -> Optional[SimpleEvaluation]: + run = await self.evaluations_service.open_run( + project_id=project_id, + user_id=user_id, + # + run_id=evaluation_id, + ) + + if not run or not run.id: + log.error("[EVAL] [open] [failure] could not open evaluation run") + return None + + evaluation = await self._parse_evaluation_run(run=run) + + return evaluation + + async def _make_evaluation_run_data( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_steps: Optional[Target] = None, + testset_steps: Optional[Target] = None, + application_steps: Optional[Target] = None, + evaluator_steps: Optional[Target] = None, + # + repeats: Optional[int] = None, + # + jit_migration: bool = False, + ) -> Optional[EvaluationRunData]: + # IMPLICIT FLAG: is_multivariate=False + # IMPLICIT FLAG: all_inputs=True + # IMPLICIT FLAG: full_references=True + + try: + # fetch queries ---------------------------------------------------- + query_input_steps_keys: List[str] = list() + query_references: Dict[str, Dict[str, Reference]] = dict() + query_revisions: Dict[str, QueryRevision] = dict() + query_origins: Dict[str, Origin] = dict() + + if isinstance(query_steps, list): + query_steps = { + query_revision_id: DEFAULT_ORIGIN_QUERIES + for query_revision_id in query_steps + } + + for query_revision_id, origin in (query_steps or {}).items(): + query_revision_ref = Reference(id=query_revision_id) + + query_revision = await self.queries_service.fetch_query_revision( + project_id=project_id, + # + query_revision_ref=query_revision_ref, + ) + + if not query_revision or not query_revision.slug: + log.warn( + "[EVAL] [run] [make] [failure] could not find query revision", + id=query_revision_ref.id, + ) + return None + + query_variant_ref = Reference(id=query_revision.variant_id) + + query_variant = await self.queries_service.fetch_query_variant( + project_id=project_id, + # + query_variant_ref=query_variant_ref, + ) + + if not query_variant: + log.warn( + "[EVAL] [run] [make] [failure] could not find query variant", + id=query_variant_ref.id, + ) + return None + + query_ref = Reference(id=query_variant.query_id) + + query = await self.queries_service.fetch_query( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query: + log.warn( + "[EVAL] [run] [make] [failure] could not find query", + id=query_ref.id, + ) + return None + + step_key = "query-" + query_revision.slug + + query_input_steps_keys.append(step_key) + + query_references[step_key] = dict( + query=Reference( + id=query.id, + slug=query.slug, + ), + query_variant=Reference( + id=query_variant.id, + slug=query_variant.slug, + ), + query_revision=Reference( + id=query_revision.id, + slug=query_revision.slug, + version=query_revision.version, + ), + ) + + query_revisions[step_key] = query_revision + + query_origins[step_key] = origin + + # ------------------------------------------------------------------ + + # fetch testsets --------------------------------------------------- + testset_input_steps_keys: List[str] = list() + testset_references: Dict[str, Dict[str, Reference]] = dict() + testset_revisions: Dict[str, TestsetRevision] = dict() + testset_origins: Dict[str, Origin] = dict() + testcases: Dict[str, List[Testcase]] = dict() + + if isinstance(testset_steps, list): + testset_steps = { + testset_revision_id: DEFAULT_ORIGIN_TESTSETS + for testset_revision_id in testset_steps + } + + # JIT MIGRATION ================================================== # + _testset_steps = deepcopy(testset_steps or {}) + testset_steps = dict() + + if jit_migration: + for testset_id, origin in _testset_steps.items(): + testset_ref = Reference(id=testset_id) + + simple_testset = await self.simple_testsets_service.transfer( + project_id=project_id, + user_id=user_id, + # + testset_id=testset_id, + ) + + if ( + not simple_testset + or not simple_testset.id + or not simple_testset.slug + ): + log.warn( + "[EVAL] [run] [make] [failure] could not transfer simple testset", + id=testset_ref.id, + ) + return None + + testset_revision = await self.testsets_service.fetch_testset_revision( + project_id=project_id, + # + testset_ref=testset_ref, + ) + + if ( + not testset_revision + or not testset_revision.id + or not testset_revision.slug + ): + log.warn( + "[EVAL] [run] [make] [failure] could not find testset revision", + id=testset_ref.id, + ) + return None + + testset_steps[testset_revision.id] = origin + # ================================================================ # + + for testset_revision_id, origin in (testset_steps or {}).items(): + testset_revision_ref = Reference(id=testset_revision_id) + + testset_revision = await self.testsets_service.fetch_testset_revision( + project_id=project_id, + # + testset_revision_ref=testset_revision_ref, + ) + + if not testset_revision or not testset_revision.slug: + log.warn( + "[EVAL] [run] [make] [failure] could not find testset revision", + id=testset_revision_ref.id, + ) + return None + + if not testset_revision.data or not testset_revision.data.testcases: + log.warn( + "[EVAL] [run] [make] [failure] invalid testset revision", + id=testset_revision_ref.id, + ) + return None + + testset_variant_ref = Reference(id=testset_revision.variant_id) + + testset_variant = await self.testsets_service.fetch_testset_variant( + project_id=project_id, + # + testset_variant_ref=testset_variant_ref, + ) + + if not testset_variant: + log.warn( + "[EVAL] [run] [make] [failure] could not find testset variant", + id=testset_variant_ref.id, + ) + return None + + testset_ref = Reference(id=testset_variant.testset_id) + + testset = await self.testsets_service.fetch_testset( + project_id=project_id, + # + testset_ref=testset_ref, + ) + + if not testset: + log.warn( + "[EVAL] [run] [make] [failure] could not find testset", + id=testset_ref.id, + ) + return None + + step_key = "testset-" + testset_revision.slug + + testset_input_steps_keys.append(step_key) + + testset_references[step_key] = dict( + testset=Reference( + id=testset.id, + slug=testset.slug, + ), + testset_variant=Reference( + id=testset_variant.id, + slug=testset_variant.slug, + ), + testset_revision=Reference( + id=testset_revision.id, + slug=testset_revision.slug, + version=testset_revision.version, + ), + ) + + testset_revisions[step_key] = testset_revision + + testset_origins[step_key] = origin + + testcases[step_key] = testset_revision.data.testcases + + if any(not testcase.data for testcase in testcases[step_key]): + log.warn( + "[EVAL] [run] [make] [failure] invalid testset revision", + id=testset_revision_ref.id, + ) + return None + # ------------------------------------------------------------------ + + # fetch applications ----------------------------------------------- + application_invocation_steps_keys: List[str] = list() + application_references: Dict[str, Dict[str, Reference]] = dict() + application_revisions: Dict[str, AppVariantRevisionsDB] = dict() + application_origins: Dict[str, Origin] = dict() + + if isinstance(application_steps, list): + application_steps = { + application_revision_id: DEFAULT_ORIGIN_APPLICATIONS + for application_revision_id in application_steps + } + + for application_revision_id, origin in (application_steps or {}).items(): + application_revision_ref = Reference(id=application_revision_id) + + application_revision = await fetch_app_variant_revision_by_id( + variant_revision_id=str(application_revision_ref.id), + ) + + if not application_revision: + log.warn( + "[EVAL] [run] [make] [failure] could not find application revision", + id=application_revision_ref.id, + ) + return None + + application_variant_ref = Reference( + id=UUID(str(application_revision.variant_id)) + ) + + application_variant = await fetch_app_variant_by_id( + app_variant_id=str(application_variant_ref.id), + ) + + if not application_variant: + log.warn( + "[EVAL] [run] [make] [failure] could not find application variant", + id=application_variant_ref.id, + ) + return None + + application_ref = Reference(id=UUID(str(application_variant.app_id))) + + application = await fetch_app_by_id( + app_id=str(application_ref.id), + ) + + if not application: + log.warn( + "[EVAL] [run] [make] [failure] could not find application", + id=application_ref.id, + ) + return None + + application_revision_slug = get_slug_from_name_and_id( + str(application_revision.config_name), + UUID(str(application_revision.id)), + ) + + step_key = "application-" + application_revision_slug + + application_invocation_steps_keys.append(step_key) + + application_references[step_key] = dict( + application=Reference( + id=application_ref.id, + slug=str(application.app_name), + ), + application_variant=Reference( + id=application_variant_ref.id, + slug=str(application_variant.variant_name), + ), + application_revision=Reference( + id=application_revision_ref.id, + slug=str(application_revision.config_name), + version=str(application_revision.revision), + ), + ) + + application_revisions[step_key] = application_revision + + application_origins[step_key] = origin + + # ------------------------------------------------------------------ + + # fetch evaluators ------------------------------------------------- + evaluator_annotation_steps_keys: List[str] = list() + evaluator_references: Dict[str, Dict[str, Reference]] = dict() + evaluator_revisions: Dict[str, EvaluatorRevision] = dict() + evaluator_origins: Dict[str, Origin] = dict() + evaluator_metrics_keys: Dict[str, List[Dict[str, str]]] = dict() + + if isinstance(evaluator_steps, list): + evaluator_steps = { + evaluator_revision_id: DEFAULT_ORIGIN_EVALUATORS + for evaluator_revision_id in evaluator_steps + } + + # JIT MIGRATION ================================================== # + _evaluator_steps = deepcopy(evaluator_steps or {}) + evaluator_steps = dict() + + if jit_migration: + for evaluator_id, origin in _evaluator_steps.items(): + evaluator_ref = Reference(id=evaluator_id) + + simple_evaluator = await self.simple_evaluators_service.transfer( + project_id=project_id, + user_id=user_id, + # + evaluator_id=evaluator_id, + ) + + if ( + not simple_evaluator + or not simple_evaluator.id + or not simple_evaluator.slug + ): + log.warn( + "[EVAL] [run] [make] [failure] could not transfer simple evaluator", + id=evaluator_ref.id, + ) + return None + + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if ( + not evaluator_revision + or not evaluator_revision.id + or not evaluator_revision.slug + ): + log.warn( + "[EVAL] [run] [make] [failure] could not find evaluator revision", + id=evaluator_ref.id, + ) + return None + + evaluator_steps[evaluator_revision.id] = origin + # ================================================================ # + + for evaluator_revision_id, origin in (evaluator_steps or {}).items(): + evaluator_revision_ref = Reference(id=evaluator_revision_id) + + evaluator_revision = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_revision_ref=evaluator_revision_ref, + ) + + if not evaluator_revision or not evaluator_revision.slug: + log.warn( + "[EVAL] [run] [make] [failure] could not find evaluator revision", + id=evaluator_revision_ref.id, + ) + return None + + if not evaluator_revision.data: + log.warn( + "[EVAL] [run] [make] [failure] invalid evaluator revision", + id=evaluator_revision_ref.id, + ) + return None + + evaluator_variant_ref = Reference(id=evaluator_revision.variant_id) + + evaluator_variant = await self.evaluators_service.fetch_evaluator_variant( + project_id=project_id, + # + evaluator_variant_ref=evaluator_variant_ref, + ) + + if not evaluator_variant: + log.warn( + "[EVAL] [run] [make] [failure] could not find evaluator variant", + id=evaluator_variant_ref.id, + ) + return None + + evaluator_ref = Reference(id=evaluator_variant.evaluator_id) + + evaluator = await self.evaluators_service.fetch_evaluator( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if not evaluator: + log.warn( + "[EVAL] [run] [make] [failure] could not find evaluator", + id=evaluator_ref.id, + ) + return None + + step_key = "evaluator-" + evaluator_revision.slug + + evaluator_annotation_steps_keys.append(step_key) + + evaluator_references[step_key] = dict( + evaluator=Reference( + id=evaluator.id, + slug=evaluator.slug, + ), + evaluator_variant=Reference( + id=evaluator_variant.id, + slug=evaluator_variant.slug, + ), + evaluator_revision=Reference( + id=evaluator_revision.id, + slug=evaluator_revision.slug, + version=evaluator_revision.version, + ), + ) + + evaluator_revisions[step_key] = evaluator_revision + + evaluator_origins[step_key] = origin + + if evaluator_revision.data.schemas: + metrics_keys = get_metrics_keys_from_schema( + schema=(evaluator_revision.data.schemas.get("outputs")), + ) + + evaluator_metrics_keys[step_key] = [ + { + "path": metric_key.get("path", ""), + "type": metric_key.get("type", ""), + } + for metric_key in metrics_keys + ] else: - user_scenario_ids.append(repeat_scenario_ids) + evaluator_metrics_keys[step_key] = [ + { + "path": "outputs", + "type": "json", + } + ] + # ------------------------------------------------------------------ + + # make run steps --------------------------------------------------- + query_inputs_steps: List[EvaluationRunDataStep] = [ + EvaluationRunDataStep( + key=step_key, + type="input", + origin=query_origins[step_key], + # IMPLICIT FLAG: full_references=True + references=query_references[step_key], + ) + for step_key in query_input_steps_keys + ] + + testset_inputs_steps: List[EvaluationRunDataStep] = [ + EvaluationRunDataStep( + key=step_key, + type="input", + origin=testset_origins[step_key], + # IMPLICIT FLAG: full_references=True + references=testset_references[step_key], + ) + for step_key in testset_input_steps_keys + ] + + application_invocation_steps: List[EvaluationRunDataStep] = [ + EvaluationRunDataStep( + key=step_key, + type="invocation", + origin=application_origins[step_key], + references=application_references[step_key], + inputs=[ + # IMPLICIT FLAG: all_inputs=True + EvaluationRunDataStepInput(key="__all_inputs__"), + ], + ) + for step_key in application_invocation_steps_keys + ] + + evaluator_annotation_steps: List[EvaluationRunDataStep] = [ + EvaluationRunDataStep( + key=step_key, + type="annotation", + origin=evaluator_origins[step_key], + references=evaluator_references[step_key], + inputs=( + [ + # IMPLICIT FLAG: is_multivariate=False + EvaluationRunDataStepInput(key="__all_invocations__"), + # IMPLICIT FLAG: all_inputs=True + EvaluationRunDataStepInput(key="__all_inputs__"), + ] + if not query_steps + else [ + # IMPLICIT FLAG: all_inputs=True + EvaluationRunDataStepInput(key="__all_inputs__"), + ] + ), + ) + for step_key in evaluator_annotation_steps_keys + ] + + steps: List[EvaluationRunDataStep] = ( + query_inputs_steps + + testset_inputs_steps + + application_invocation_steps + + evaluator_annotation_steps + ) + # ------------------------------------------------------------------ + + # make run mappings ------------------------------------------------ + query_input_mappings: List[EvaluationRunDataMapping] = list( # type: ignore + EvaluationRunDataMapping( + column=EvaluationRunDataMappingColumn( + kind="query", + name="data", + ), + step=EvaluationRunDataMappingStep( + key=step_key, + path="attributes.ag.data", + ), + ) + for step_key in query_input_steps_keys + ) - return user_scenario_ids + testset_input_mappings: List[EvaluationRunDataMapping] = list( # type: ignore + EvaluationRunDataMapping( + column=EvaluationRunDataMappingColumn( + kind="testset", + name=key, + ), + step=EvaluationRunDataMappingStep( + key=step_key, + path=f"data.{key}", + ), + ) + for step_key in testset_input_steps_keys + for key in testcases[step_key][0].data.keys() # type: ignore + ) + application_invocation_mappings: List[EvaluationRunDataMapping] = list( # type: ignore + EvaluationRunDataMapping( + column=EvaluationRunDataMappingColumn( + kind="invocation", + name="outputs", + ), + step=EvaluationRunDataMappingStep( + key=step_key, + path="attributes.ag.data.outputs", + ), + ) + for step_key in application_invocation_steps_keys + ) -def get_bounds( - assignments: List[int], - target: int, - blocks: int, -) -> List[Tuple[int, int]]: - bounds = [] + evaluator_annotation_mappings: List[EvaluationRunDataMapping] = list( # type: ignore + EvaluationRunDataMapping( + column=EvaluationRunDataMappingColumn( + kind="annotation", + name=metric_key.get("path", ""), + ), + step=EvaluationRunDataMappingStep( + key=step_key, + path=f"attributes.ag.data.outputs{('.' + metric_key.get('path', '')) if metric_key.get('path') else ''}", + ), + ) + for step_key in evaluator_annotation_steps_keys + for metric_key in evaluator_metrics_keys[step_key] + ) - n = len(assignments) + mappings: List[EvaluationRunDataMapping] = ( + query_input_mappings + + testset_input_mappings + + application_invocation_mappings + + evaluator_annotation_mappings + ) + # ------------------------------------------------------------------ - if n == 0 or blocks <= 0: - return bounds + return EvaluationRunData( + steps=steps, + mappings=mappings, + repeats=repeats or 1, + ) - q, r = divmod(blocks, n) # base size and remainder + except: # pylint: disable=bare-except + log.error("[EVAL] [run] [make] [failure]", exc_info=True) - block_sizes = [q + 1 if i < r else q for i in range(n)] + return None - start = 0 + async def _make_evaluation_run_flags( + self, + *, + is_closed: Optional[bool] = None, + is_live: Optional[bool] = None, + is_active: Optional[bool] = None, + ) -> EvaluationRunFlags: + return EvaluationRunFlags( + is_closed=is_closed, + is_live=is_live, + is_active=is_active, + ) + + async def _make_evaluation_run_query( + self, + *, + is_closed: Optional[bool] = None, + is_live: Optional[bool] = None, + is_active: Optional[bool] = None, + # + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + ): + run_flags = await self._make_evaluation_run_flags( + is_closed=is_closed, + is_live=is_live, + is_active=is_active, + ) - for i, size in enumerate(block_sizes): - end = start + size - 1 + run_query = EvaluationRunQuery( + flags=run_flags, + tags=tags, + meta=meta, + ) - if str(assignments[i]) == str(target): - bounds.append((start, end + 1)) # half-open bounds [start, end) + return run_query - start = end + 1 # next block starts here + async def _activate_evaluation_run( + self, + *, + project_id: UUID, + user_id: UUID, + # + run_id: UUID, + # + just_created: Optional[bool] = None, + ) -> Optional[EvaluationRun]: + run = await self.evaluations_service.fetch_run( + project_id=project_id, + # + run_id=run_id, + ) + + if not run or not run.id: + log.error("[EVAL] [activate] [failure] could not find evaluation run") + return None + + if not run.flags: + run.flags = EvaluationRunFlags() + + run.flags.is_active = True + + run = await self.evaluations_service.edit_run( + project_id=project_id, + user_id=user_id, + # + run=EvaluationRunEdit( + id=run.id, + # + name=run.name, + description=run.description, + # + flags=run.flags, + tags=run.tags, + meta=run.meta, + # + status=EvaluationStatus.RUNNING if just_created else run.status, + # + data=run.data, + ), + ) + + return run + + async def _deactivate_evaluation_run( + self, + *, + project_id: UUID, + user_id: UUID, + # + run_id: UUID, + ) -> Optional[EvaluationRun]: + run = await self.evaluations_service.fetch_run( + project_id=project_id, + # + run_id=run_id, + ) + + if not run or not run.id: + log.error("[EVAL] [deactivate] [failure] could not find evaluation run") + return None + + if not run.flags: + run.flags = EvaluationRunFlags() + + run.flags.is_active = False + + run = await self.evaluations_service.edit_run( + project_id=project_id, + user_id=user_id, + # + run=EvaluationRunEdit( + id=run.id, + # + name=run.name, + description=run.description, + # + flags=run.flags, + tags=run.tags, + meta=run.meta, + # + status=run.status, + # + data=run.data, + ), + ) + + return run + + async def _parse_evaluation_run( + self, + *, + run: EvaluationRun, + ) -> Optional[SimpleEvaluation]: + try: + if not run: + return None + + if not run.flags: + run.flags = EvaluationRunFlags() + + if not run.data: + run.data = EvaluationRunData() + + steps = run.data.steps if run.data.steps else [] + + query_steps: Target = dict() + testset_steps: Target = dict() + application_steps: Target = dict() + evaluator_steps: Target = dict() + + repeats = run.data.repeats if run.data and run.data.repeats else None + + for step in steps: + step_type = step.type + step_origin = step.origin + step_references = step.references + step_id = None + + if step_type == "input": + if "query_revision" in step_references: + step_ref = step_references["query_revision"] + if not isinstance(step_ref, Reference): + continue + step_id = step_ref.id + query_steps[step_id] = step_origin # type: ignore + elif "testset_revision" in step_references: + step_ref = step_references["testset_revision"] + if not isinstance(step_ref, Reference): + continue + step_id = step_ref.id + testset_steps[step_id] = step_origin # type: ignore + elif step_type == "invocation": + if "application_revision" in step_references: + step_ref = step_references["application_revision"] + if not isinstance(step_ref, Reference): + continue + step_id = step_ref.id + application_steps[step_id] = step_origin # type: ignore + elif step_type == "annotation": + if "evaluator_revision" in step_references: + step_ref = step_references["evaluator_revision"] + if not isinstance(step_ref, Reference): + continue + step_id = step_ref.id + evaluator_steps[step_id] = step_origin # type: ignore + + evaluation_flags = SimpleEvaluationFlags(**run.flags.model_dump()) + + evaluation_status = SimpleEvaluationStatus(run.status) + + evaluation_data = SimpleEvaluationData( + status=evaluation_status, + # + query_steps=query_steps, + testset_steps=testset_steps, + application_steps=application_steps, + evaluator_steps=evaluator_steps, + # + repeats=repeats, + ) + + return SimpleEvaluation( + id=run.id, + # + name=run.name, + description=run.description, + # + created_at=run.created_at, + updated_at=run.updated_at, + deleted_at=run.deleted_at, + created_by_id=run.created_by_id, + updated_by_id=run.updated_by_id, + deleted_by_id=run.deleted_by_id, + # + flags=evaluation_flags, + tags=run.tags, + meta=run.meta, + # + data=evaluation_data, + ) - return bounds - # -------------------------------------------------------------------------- + except: # pylint: disable=bare-except + log.error("[EVAL] [run] [parse] [failure]", exc_info=True) + return None diff --git a/api/oss/src/core/evaluations/types.py b/api/oss/src/core/evaluations/types.py index 5dd6a733ff..af3cd780b4 100644 --- a/api/oss/src/core/evaluations/types.py +++ b/api/oss/src/core/evaluations/types.py @@ -1,17 +1,21 @@ -from typing import Optional, Dict, List +from typing import Optional, Dict, List, Union, Literal from enum import Enum from uuid import UUID from datetime import datetime -from pydantic import BaseModel +from pydantic import BaseModel, field_validator + +from oss.src.core.tracing.dtos import ( + MetricSpec, + TracingQuery, +) from oss.src.core.shared.dtos import ( + Version, Identifier, Lifecycle, - Flags, - Tags, - Meta, Header, + Metadata, Data, Reference, Link, @@ -20,6 +24,10 @@ References = Dict[str, Reference] Links = Dict[str, Link] +Type = Literal["input", "invocation", "annotation"] +Origin = Literal["custom", "human", "auto"] +Target = Union[List[UUID], Dict[UUID, Origin]] + class EvaluationStatus(str, Enum): PENDING = "pending" @@ -39,16 +47,16 @@ def __init__( message: str = "Cannot modify a closed evaluation.", run_id: Optional[UUID] = None, scenario_id: Optional[UUID] = None, - step_id: Optional[UUID] = None, - metric_id: Optional[UUID] = None, + result_id: Optional[UUID] = None, + metrics_id: Optional[UUID] = None, ): super().__init__(message) self.message = message self.run_id = run_id self.scenario_id = scenario_id - self.step_id = step_id - self.metric_id = metric_id + self.result_id = result_id + self.metrics_id = metrics_id def __str__(self): _message = self.message @@ -57,266 +65,270 @@ def __str__(self): _message += f" run_id={self.run_id}" if self.scenario_id: _message += f" scenario_id={self.scenario_id}" - if self.step_id: - _message += f" step_id={self.step_id}" - if self.metric_id: - _message += f" metric_id={self.metric_id}" + if self.result_id: + _message += f" result_id={self.result_id}" + if self.metrics_id: + _message += f" metrics_id={self.metrics_id}" return _message +# - EVALUATION RUN ------------------------------------------------------------- + + class EvaluationRunFlags(BaseModel): - is_closed: Optional[bool] = None + is_closed: Optional[bool] = None # Indicates if the run is modifiable + is_live: Optional[bool] = None # Indicates if the run has live queries + is_active: Optional[bool] = None # Indicates if the run is currently active -class EvaluationRunData(BaseModel): - steps: Optional[List[Data]] = None - mappings: Optional[List[Data]] = None - repeats: Optional[int] = None +class EvaluationRunDataStepInput(BaseModel): + key: str -class EvaluationRun(Identifier, Header, Lifecycle): - flags: Optional[EvaluationRunFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationRunDataStep(BaseModel): + key: str + type: Type + origin: Origin + references: Dict[str, Reference] + inputs: Optional[List[EvaluationRunDataStepInput]] = None - status: Optional[EvaluationStatus] = None - data: Optional[EvaluationRunData] = None +class EvaluationRunDataMappingColumn(BaseModel): + kind: str + name: str + + +class EvaluationRunDataMappingStep(BaseModel): + key: str + path: str -class EvaluationRunCreate(Header): - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationRunDataMapping(BaseModel): + column: EvaluationRunDataMappingColumn + step: EvaluationRunDataMappingStep + + +class EvaluationRunData(BaseModel): + steps: Optional[List[EvaluationRunDataStep]] = None + repeats: Optional[int] = 1 + mappings: Optional[List[EvaluationRunDataMapping]] = None + + @field_validator("repeats") + def set_repeats(cls, v): + if v is None: + return 1 + return v + + +class EvaluationRun(Version, Identifier, Lifecycle, Header, Metadata): + flags: Optional[EvaluationRunFlags] = None # type: ignore status: Optional[EvaluationStatus] = EvaluationStatus.PENDING data: Optional[EvaluationRunData] = None -class EvaluationRunEdit(Identifier, Header): - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationRunCreate(Header, Metadata): + version: str = "2025.07.14" + + flags: Optional[EvaluationRunFlags] = None # type: ignore status: Optional[EvaluationStatus] = None data: Optional[EvaluationRunData] = None -from typing import Any, Dict, List, Optional, Union - +class EvaluationRunEdit(Version, Identifier, Header, Metadata): + flags: Optional[EvaluationRunFlags] = None # type: ignore -class EvaluationRunQuery(BaseModel): - flags: Optional[EvaluationRunFlags] = None - tags: Optional[Tags] = None - # meta can be a dict (AND filter) or a list of dicts (OR filter) - meta: Optional[Union[Dict[str, Any], List[Dict[str, Any]]]] = None + status: Optional[EvaluationStatus] = None data: Optional[EvaluationRunData] = None + +class EvaluationRunQuery(Header, Metadata): + flags: Optional[EvaluationRunFlags] = None # type: ignore + status: Optional[EvaluationStatus] = None statuses: Optional[List[EvaluationStatus]] = None - ids: Optional[List[UUID]] = None + references: Optional[List[References]] = None - # Search term for case-insensitive partial matching on name field - search: Optional[str] = None + ids: Optional[List[UUID]] = None # - EVALUATION SCENARIO -------------------------------------------------------- -class EvaluationScenario(Identifier, Lifecycle): - # flags: Optional[Flags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - status: Optional[EvaluationStatus] = None +class EvaluationScenario(Version, Identifier, Lifecycle, Metadata): + status: Optional[EvaluationStatus] = EvaluationStatus.PENDING + interval: Optional[int] = None + timestamp: Optional[datetime] = None run_id: UUID - run: Optional[EvaluationRun] = None - # idx: int # new : Optional / Migration - # rnd_idx: int # new : Optional / Migration - # seq_idx: int # new : Optional / Migration +class EvaluationScenarioCreate(Metadata): + version: str = "2025.07.14" -class EvaluationScenarioCreate(BaseModel): - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - status: Optional[EvaluationStatus] = EvaluationStatus.PENDING + status: Optional[EvaluationStatus] = None + interval: Optional[int] = None + timestamp: Optional[datetime] = None run_id: UUID -class EvaluationScenarioEdit(Identifier): - tags: Optional[Tags] = None - meta: Optional[Meta] = None - +class EvaluationScenarioEdit(Version, Identifier, Metadata): status: Optional[EvaluationStatus] = None -class EvaluationScenarioQuery(BaseModel): - # flags: Optional[Flags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - +class EvaluationScenarioQuery(Metadata): status: Optional[EvaluationStatus] = None statuses: Optional[List[EvaluationStatus]] = None + interval: Optional[int] = None + intervals: Optional[List[int]] = None + + timestamp: Optional[datetime] = None + timestamps: Optional[List[datetime]] = None + run_id: Optional[UUID] = None run_ids: Optional[List[UUID]] = None ids: Optional[List[UUID]] = None - # idxs: Optional[List[int]] = None # new : Optional / Migration - # rnd_idxs: Optional[List[int]] = None # new : Optional / Migration - # sea_idxs: Optional[List[int]] = None # new : Optional / Migration - - -# - EVALUATION STEP ------------------------------------------------------------ - -class EvaluationStep(Identifier, Lifecycle): - # flags: Optional[Flags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - status: Optional[EvaluationStatus] = None - timestamp: Optional[datetime] = None +# - EVALUATION RESULT ---------------------------------------------------------- - key: str - # repeat_idx: int # new : Optional / Migration - repeat_id: UUID - retry_id: UUID +class EvaluationResult(Version, Identifier, Lifecycle, Metadata): hash_id: Optional[UUID] = None trace_id: Optional[str] = None testcase_id: Optional[UUID] = None error: Optional[Data] = None - scenario_id: UUID - scenario: Optional[EvaluationScenario] = None + status: Optional[EvaluationStatus] = EvaluationStatus.PENDING + interval: Optional[int] = None + timestamp: Optional[datetime] = None + repeat_idx: Optional[int] = 0 + step_key: str + scenario_id: UUID run_id: UUID - run: Optional[EvaluationRun] = None - -class EvaluationStepCreate(BaseModel): - tags: Optional[Tags] = None - meta: Optional[Meta] = None - status: Optional[EvaluationStatus] = EvaluationStatus.PENDING - - key: str - repeat_idx: Optional[int] = None # new : Optional - repeat_id: Optional[UUID] = None - retry_id: Optional[UUID] = None +class EvaluationResultCreate(Metadata): + version: str = "2025.07.14" hash_id: Optional[UUID] = None trace_id: Optional[str] = None testcase_id: Optional[UUID] = None error: Optional[Data] = None + status: Optional[EvaluationStatus] = None + + interval: Optional[int] = None + timestamp: Optional[datetime] = None + repeat_idx: Optional[int] = 0 + step_key: str scenario_id: UUID run_id: UUID -class EvaluationStepEdit(Identifier): - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - status: Optional[EvaluationStatus] = None - +class EvaluationResultEdit(Version, Identifier, Metadata): hash_id: Optional[UUID] = None trace_id: Optional[str] = None testcase_id: Optional[UUID] = None error: Optional[Data] = None + status: Optional[EvaluationStatus] = None -class EvaluationStepQuery(BaseModel): - # flags: Optional[Flags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - - key: Optional[str] = None - keys: Optional[List[str]] = None - repeat_idx: Optional[int] = None # new : Optional - repeat_idxs: Optional[List[int]] = None # new : Optional - repeat_id: Optional[UUID] = None - repeat_ids: Optional[List[UUID]] = None - retry_id: Optional[UUID] = None - retry_ids: Optional[List[UUID]] = None +class EvaluationResultQuery(Metadata): status: Optional[EvaluationStatus] = None statuses: Optional[List[EvaluationStatus]] = None + + interval: Optional[int] = None + intervals: Optional[List[int]] = None + timestamp: Optional[datetime] = None + timestamps: Optional[List[datetime]] = None + + repeat_idx: Optional[int] = None + repeat_idxs: Optional[List[int]] = None + + step_key: Optional[str] = None + step_keys: Optional[List[str]] = None - run_id: Optional[UUID] = None - run_ids: Optional[List[UUID]] = None scenario_id: Optional[UUID] = None scenario_ids: Optional[List[UUID]] = None - ids: Optional[List[UUID]] = None + run_id: Optional[UUID] = None + run_ids: Optional[List[UUID]] = None + ids: Optional[List[UUID]] = None -# - EVALUATION METRIC ---------------------------------------------------------- +# - EVALUATION METRICS --------------------------------------------------------- -class EvaluationMetric(Identifier, Lifecycle): - # flags: Optional[Flags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - status: Optional[EvaluationStatus] = None +class EvaluationMetrics(Version, Identifier, Lifecycle, Metadata): + status: Optional[EvaluationStatus] = EvaluationStatus.PENDING data: Optional[Data] = None + interval: Optional[int] = None + timestamp: Optional[datetime] = None scenario_id: Optional[UUID] = None - scenario: Optional[EvaluationScenario] = None - run_id: UUID - run: Optional[EvaluationRun] = None -class EvaluationMetricCreate(BaseModel): - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationMetricsCreate(Metadata): + version: str = "2025.07.14" - status: Optional[EvaluationStatus] = EvaluationStatus.PENDING + status: Optional[EvaluationStatus] = None data: Optional[Data] = None + interval: Optional[int] = None + timestamp: Optional[datetime] = None scenario_id: Optional[UUID] = None run_id: UUID -class EvaluationMetricEdit(Identifier): - tags: Optional[Tags] = None - meta: Optional[Meta] = None - +class EvaluationMetricsEdit(Version, Identifier, Metadata): status: Optional[EvaluationStatus] = None data: Optional[Data] = None -class EvaluationMetricQuery(BaseModel): - # flags: Optional[Flags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None - +class EvaluationMetricsQuery(Metadata): status: Optional[EvaluationStatus] = None statuses: Optional[List[EvaluationStatus]] = None + interval: Optional[int] = None + intervals: Optional[List[int]] = None + + timestamp: Optional[datetime] = None + timestamps: Optional[List[datetime]] = None + scenario_id: Optional[UUID] = None scenario_ids: Optional[List[UUID]] = None + run_id: Optional[UUID] = None run_ids: Optional[List[UUID]] = None ids: Optional[List[UUID]] = None +class EvaluationMetricsRefresh(BaseModel): + query: Optional[TracingQuery] = None + specs: Optional[List[MetricSpec]] = None + + ids: Optional[List[UUID]] = None + + # - EVALUATION QUEUE ----------------------------------------------------------- @@ -327,45 +339,91 @@ class EvaluationQueueFlags(BaseModel): class EvaluationQueueData(BaseModel): user_ids: Optional[List[List[UUID]]] = None scenario_ids: Optional[List[UUID]] = None + step_keys: Optional[List[str]] = None -class EvaluationQueue(Identifier, Lifecycle): - flags: Optional[EvaluationQueueFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationQueue(Version, Identifier, Lifecycle, Header, Metadata): + flags: Optional[EvaluationQueueFlags] = None # type: ignore - status: Optional[EvaluationStatus] = None + status: Optional[EvaluationStatus] = EvaluationStatus.PENDING data: Optional[EvaluationQueueData] = None run_id: UUID - run: Optional[EvaluationRun] = None -class EvaluationQueueCreate(BaseModel): - flags: Optional[EvaluationQueueFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationQueueCreate(Header, Metadata): + version: str = "2025.07.14" + + flags: Optional[EvaluationQueueFlags] = None # type: ignore + + status: Optional[EvaluationStatus] = None data: Optional[EvaluationQueueData] = None run_id: UUID -class EvaluationQueueEdit(Identifier): - flags: Optional[EvaluationQueueFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationQueueEdit(Version, Identifier, Header, Metadata): + flags: Optional[EvaluationQueueFlags] = None # type: ignore + + status: Optional[EvaluationStatus] = None data: Optional[EvaluationQueueData] = None -class EvaluationQueueQuery(BaseModel): - flags: Optional[EvaluationQueueFlags] = None - tags: Optional[Tags] = None - meta: Optional[Meta] = None +class EvaluationQueueQuery(Header, Metadata): + flags: Optional[EvaluationQueueFlags] = None # type: ignore + + user_id: Optional[UUID] = None + user_ids: Optional[List[UUID]] = None run_id: Optional[UUID] = None run_ids: Optional[List[UUID]] = None ids: Optional[List[UUID]] = None + + +# - SIMPLE EVALUATION ---------------------------------------------------------- + + +SimpleEvaluationFlags = EvaluationRunFlags + +SimpleEvaluationStatus = EvaluationStatus + + +class SimpleEvaluationData(BaseModel): + status: Optional[SimpleEvaluationStatus] = None + + query_steps: Optional[Target] = None + testset_steps: Optional[Target] = None + application_steps: Optional[Target] = None + evaluator_steps: Optional[Target] = None + + repeats: Optional[int] = None + + +class SimpleEvaluation(Version, Identifier, Lifecycle, Header, Metadata): + flags: Optional[SimpleEvaluationFlags] = None # type: ignore + + data: Optional[SimpleEvaluationData] = None + + +class SimpleEvaluationCreate(Header, Metadata): + version: str = "2025.07.14" + + flags: Optional[SimpleEvaluationFlags] = None # type: ignore + + data: Optional[SimpleEvaluationData] = None + + +class SimpleEvaluationEdit(Version, Identifier, Header, Metadata): + flags: Optional[SimpleEvaluationFlags] = None # type: ignore + + data: Optional[SimpleEvaluationData] = None + + +class SimpleEvaluationQuery(Header, Metadata): + flags: Optional[SimpleEvaluationFlags] = None # type: ignore + + ids: Optional[List[UUID]] = None diff --git a/api/oss/src/core/evaluations/utils.py b/api/oss/src/core/evaluations/utils.py new file mode 100644 index 0000000000..6d2f722423 --- /dev/null +++ b/api/oss/src/core/evaluations/utils.py @@ -0,0 +1,123 @@ +from typing import List, Tuple, Dict +from uuid import UUID + +# Divides cleanly into 1, 2, 3, 4, 5, 6, 8, 10, ... +BLOCKS = 1 * 2 * 3 * 4 * 5 + + +def filter_scenario_ids( + user_id: UUID, + user_ids: List[List[UUID]], + scenario_ids: List[UUID], + is_sequential: bool, + offset: int = 0, +) -> List[List[UUID]]: + user_scenario_ids: List[List[UUID]] = [] + + MOD = min(len(scenario_ids), BLOCKS) + + for repeat_user_ids in user_ids: + if not repeat_user_ids: + user_scenario_ids.append([]) + + else: + repeat_user_bounds = _get_bounds( + repeat_user_ids, + user_id, + MOD, + ) + + if not repeat_user_bounds: + user_scenario_ids.append([]) + + else: + repeat_scenario_ids = [] + for scenario_idx, scenario_id in enumerate(scenario_ids): + mod = ( + (offset + scenario_idx) if is_sequential else int(scenario_id) + ) % MOD + + if any( + lower <= mod < upper for (lower, upper) in repeat_user_bounds + ): + repeat_scenario_ids.append(scenario_id) + + if not repeat_scenario_ids: + user_scenario_ids.append([]) + + else: + user_scenario_ids.append(repeat_scenario_ids) + + return user_scenario_ids + + +def _get_bounds( + assignments: List[UUID], + target: UUID, + blocks: int, +) -> List[Tuple[int, int]]: + bounds: List[Tuple[int, int]] = [] + + n = len(assignments) + + if n == 0 or blocks <= 0: + return bounds + + q, r = divmod(blocks, n) # base size and remainder + + block_sizes = [q + 1 if i < r else q for i in range(n)] + + start = 0 + + for i, size in enumerate(block_sizes): + end = start + size - 1 + + if str(assignments[i]) == str(target): + bounds.append((start, end + 1)) # half-open bounds [start, end) + + start = end + 1 # next block starts here + + return bounds + # -------------------------------------------------------------------------- + + +def get_metrics_keys_from_schema( + schema=None, + path=(), +) -> List[Dict[str, str]]: + metrics: List[Dict[str, str]] = list() + + if not isinstance(schema, dict) or "type" not in schema: + return metrics + + metric_type = None + + t = schema["type"] + + if t == "object": + if "properties" in schema: + for key, prop in schema["properties"].items(): + metrics.extend(get_metrics_keys_from_schema(prop, path + (key,))) + else: + metric_type = "json" + + elif t == "array" and "items" in schema: + if schema["items"].get("type") == "string" and "enum" in schema["items"]: + metric_type = "categorical/multiple" + + elif t == "boolean": + metric_type = "binary" + + elif t == "string": + metric_type = "categorical/single" if "enum" in schema else "string" + + elif t == "number": + metric_type = "numeric/continuous" + + elif t == "integer": + metric_type = "numeric/discrete" + + if metric_type: + metrics.append({"path": ".".join(path), "type": metric_type}) + + return metrics diff --git a/api/oss/src/core/evaluators/dtos.py b/api/oss/src/core/evaluators/dtos.py new file mode 100644 index 0000000000..6e4465f84c --- /dev/null +++ b/api/oss/src/core/evaluators/dtos.py @@ -0,0 +1,297 @@ +from typing import Optional +from uuid import UUID + +from pydantic import Field + +from oss.src.core.shared.dtos import sync_alias, AliasConfig +from oss.src.core.shared.dtos import ( + Identifier, + Slug, + Lifecycle, + Header, + Metadata, +) +from oss.src.core.workflows.dtos import ( + ArtifactFork, + VariantFork, + RevisionFork, + # + Workflow, + WorkflowCreate, + WorkflowEdit, + WorkflowQuery, + WorkflowFlags, + WorkflowFork, + # + WorkflowVariant, + WorkflowVariantCreate, + WorkflowVariantEdit, + WorkflowVariantQuery, + WorkflowVariantFork, + # + WorkflowRevisionData, + # + WorkflowRevision, + WorkflowRevisionCreate, + WorkflowRevisionEdit, + WorkflowRevisionQuery, + WorkflowRevisionFork, + WorkflowRevisionCommit, + WorkflowRevisionsLog, +) + + +# aliases ---------------------------------------------------------------------- + + +class EvaluatorIdAlias(AliasConfig): + evaluator_id: Optional[UUID] = None + workflow_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="evaluator_id", + ) + + +class EvaluatorVariantIdAlias(AliasConfig): + evaluator_variant_id: Optional[UUID] = None + workflow_variant_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="evaluator_variant_id", + ) + + +class EvaluatorRevisionIdAlias(AliasConfig): + evaluator_revision_id: Optional[UUID] = None + workflow_revision_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="evaluator_revision_id", + ) + + +# globals ---------------------------------------------------------------------- + + +class EvaluatorFlags(WorkflowFlags): + def __init__(self, **data): + data["is_evaluator"] = True + + super().__init__(**data) + + +# evaluators ------------------------------------------------------------------- + + +class Evaluator(Workflow): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorCreate(WorkflowCreate): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorEdit(WorkflowEdit): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorQuery(WorkflowQuery): + flags: Optional[EvaluatorFlags] = None + + +# evaluator variants ----------------------------------------------------------- + + +class EvaluatorVariant( + WorkflowVariant, + EvaluatorIdAlias, +): + flags: Optional[EvaluatorFlags] = None + + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "workflow_id", self) + + +class EvaluatorVariantCreate( + WorkflowVariantCreate, + EvaluatorIdAlias, +): + flags: Optional[EvaluatorFlags] = None + + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "workflow_id", self) + + +class EvaluatorVariantEdit(WorkflowVariantEdit): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorVariantQuery(WorkflowVariantQuery): + flags: Optional[EvaluatorFlags] = None + + +# evaluator revisions ---------------------------------------------------------- + + +class EvaluatorRevisionData(WorkflowRevisionData): + pass + + +class EvaluatorRevision( + WorkflowRevision, + EvaluatorIdAlias, + EvaluatorVariantIdAlias, +): + flags: Optional[EvaluatorFlags] = None + + data: Optional[EvaluatorRevisionData] = None + + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "workflow_id", self) + sync_alias("evaluator_variant_id", "workflow_variant_id", self) + + +class EvaluatorRevisionCreate( + WorkflowRevisionCreate, + EvaluatorIdAlias, + EvaluatorVariantIdAlias, +): + flags: Optional[EvaluatorFlags] = None + + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "workflow_id", self) + sync_alias("evaluator_variant_id", "workflow_variant_id", self) + + +class EvaluatorRevisionEdit(WorkflowRevisionEdit): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorRevisionQuery(WorkflowRevisionQuery): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorRevisionCommit( + WorkflowRevisionCommit, + EvaluatorIdAlias, + EvaluatorVariantIdAlias, +): + flags: Optional[EvaluatorFlags] = None + + data: Optional[EvaluatorRevisionData] = None + + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "workflow_id", self) + sync_alias("evaluator_variant_id", "workflow_variant_id", self) + + +class EvaluatorRevisionsLog( + WorkflowRevisionsLog, + EvaluatorIdAlias, + EvaluatorVariantIdAlias, + EvaluatorRevisionIdAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "workflow_id", self) + sync_alias("evaluator_variant_id", "workflow_variant_id", self) + sync_alias("evaluator_revision_id", "workflow_revision_id", self) + + +# forks ------------------------------------------------------------------------ + + +class EvaluatorRevisionFork(WorkflowRevisionFork): + flags: Optional[EvaluatorFlags] = None + + data: Optional[EvaluatorRevisionData] = None + + +class EvaluatorVariantFork(WorkflowVariantFork): + flags: Optional[EvaluatorFlags] = None + + +class EvaluatorRevisionForkAlias(AliasConfig): + evaluator_revision: Optional[EvaluatorRevisionFork] = None + + revision: Optional[RevisionFork] = Field( + default=None, + exclude=True, + alias="evaluator_revision", + ) + + +class EvaluatorVariantForkAlias(AliasConfig): + evaluator_variant: Optional[EvaluatorVariantFork] = None + + variant: Optional[VariantFork] = Field( + default=None, + exclude=True, + alias="evaluator_variant", + ) + + +class EvaluatorFork( + WorkflowFork, + EvaluatorIdAlias, + EvaluatorVariantIdAlias, + EvaluatorVariantForkAlias, + EvaluatorRevisionIdAlias, + EvaluatorRevisionForkAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("evaluator_id", "artifact_id", self) + sync_alias("evaluator_variant_id", "variant_id", self) + sync_alias("evaluator_variant", "variant", self) + sync_alias("evaluator_revision_id", "revision_id", self) + sync_alias("evaluator_revision", "revision", self) + + +# evaluator services ----------------------------------------------------------- + +# TODO: Implement ? + +# simple evaluators ------------------------------------------------------------ + + +class SimpleEvaluatorFlags(EvaluatorFlags): + pass + + +class SimpleEvaluatorData(EvaluatorRevisionData): + pass + + +class SimpleEvaluator(Identifier, Slug, Lifecycle, Header, Metadata): + flags: Optional[SimpleEvaluatorFlags] = None + + data: Optional[SimpleEvaluatorData] = None + + +class SimpleEvaluatorCreate(Slug, Header, Metadata): + flags: Optional[SimpleEvaluatorFlags] = None + + data: Optional[SimpleEvaluatorData] = None + + +class SimpleEvaluatorEdit(Identifier, Header, Metadata): + flags: Optional[SimpleEvaluatorFlags] = None + + data: Optional[SimpleEvaluatorData] = None + + +class SimpleEvaluatorQuery(Metadata): + flags: Optional[SimpleEvaluatorFlags] = None + + +class SimpleEvaluatorQQuery( + Identifier, + Slug, + Lifecycle, + Header, + SimpleEvaluatorQuery, +): + data: Optional[SimpleEvaluatorData] = None + + +# ------------------------------------------------------------------------------ diff --git a/api/oss/src/core/evaluators/service.py b/api/oss/src/core/evaluators/service.py new file mode 100644 index 0000000000..3cddb0d88c --- /dev/null +++ b/api/oss/src/core/evaluators/service.py @@ -0,0 +1,1467 @@ +from typing import Optional, List +from uuid import UUID, uuid4 + +from oss.src.utils.helpers import get_slug_from_name_and_id +from oss.src.services.db_manager import fetch_evaluator_config +from oss.src.core.workflows.dtos import ( + WorkflowFlags, + # + WorkflowCreate, + WorkflowEdit, + WorkflowQuery, + WorkflowFork, + # + WorkflowVariantCreate, + WorkflowVariantEdit, + WorkflowVariantQuery, + # + WorkflowRevisionData, + # + WorkflowRevisionCreate, + WorkflowRevisionEdit, + WorkflowRevisionCommit, + WorkflowRevisionQuery, + WorkflowRevisionsLog, + # +) +from oss.src.core.shared.dtos import Windowing +from oss.src.core.workflows.service import WorkflowsService +from oss.src.core.evaluators.dtos import ( + SimpleEvaluatorData, + SimpleEvaluator, + SimpleEvaluatorCreate, + SimpleEvaluatorEdit, + SimpleEvaluatorQuery, + SimpleEvaluatorQQuery, + SimpleEvaluatorFlags, + # + Evaluator, + EvaluatorFlags, + EvaluatorQuery, + EvaluatorRevisionsLog, + EvaluatorCreate, + EvaluatorEdit, + EvaluatorFork, + # + EvaluatorVariant, + EvaluatorVariantCreate, + EvaluatorVariantEdit, + EvaluatorVariantQuery, + # + EvaluatorRevision, + EvaluatorRevisionCreate, + EvaluatorRevisionData, + EvaluatorRevisionEdit, + EvaluatorRevisionCommit, + EvaluatorRevisionQuery, +) +from oss.src.core.shared.dtos import Reference +from oss.src.utils.logging import get_module_logger +from oss.src.models.db_models import EvaluatorConfigDB + + +log = get_module_logger(__name__) + + +class EvaluatorsService: + def __init__( + self, + workflows_service: WorkflowsService, + ): + self.workflows_service = workflows_service + + # evaluators --------------------------------------------------------------- + + async def create_evaluator( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_create: EvaluatorCreate, + # + evaluator_id: Optional[UUID] = None, + ) -> Optional[Evaluator]: + workflow_create = WorkflowCreate( + **evaluator_create.model_dump( + mode="json", + ), + ) + + workflow = await self.workflows_service.create_workflow( + project_id=project_id, + user_id=user_id, + # + workflow_create=workflow_create, + # + workflow_id=evaluator_id, + ) + + if not workflow: + return None + + evaluator = Evaluator( + **workflow.model_dump( + mode="json", + ) + ) + + return evaluator + + async def fetch_evaluator( + self, + *, + project_id: UUID, + # + evaluator_ref: Reference, + ) -> Optional[Evaluator]: + workflow = await self.workflows_service.fetch_workflow( + project_id=project_id, + # + workflow_ref=evaluator_ref, + ) + + if not workflow: + return None + + evaluator = Evaluator( + **workflow.model_dump( + mode="json", + ) + ) + + return evaluator + + async def edit_evaluator( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_edit: EvaluatorEdit, + ) -> Optional[Evaluator]: + workflow_edit = WorkflowEdit( + **evaluator_edit.model_dump( + mode="json", + ), + ) + + workflow = await self.workflows_service.edit_workflow( + project_id=project_id, + user_id=user_id, + # + workflow_edit=workflow_edit, + ) + + if not workflow: + return None + + evaluator = Evaluator( + **workflow.model_dump( + mode="json", + ) + ) + + return evaluator + + async def archive_evaluator( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_id: UUID, + ) -> Optional[Evaluator]: + workflow = await self.workflows_service.archive_workflow( + project_id=project_id, + user_id=user_id, + # + workflow_id=evaluator_id, + ) + + if not workflow: + return None + + evaluator = Evaluator( + **workflow.model_dump( + mode="json", + ) + ) + + return evaluator + + async def unarchive_evaluator( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_id: UUID, + ) -> Optional[Evaluator]: + workflow = await self.workflows_service.unarchive_workflow( + project_id=project_id, + user_id=user_id, + # + workflow_id=evaluator_id, + ) + + if not workflow: + return None + + evaluator = Evaluator( + **workflow.model_dump( + mode="json", + ) + ) + + return evaluator + + async def query_evaluators( + self, + *, + project_id: UUID, + # + evaluator_query: Optional[EvaluatorQuery] = None, + # + evaluator_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[Evaluator]: + workflow_query = ( + WorkflowQuery( + **evaluator_query.model_dump( + mode="json", + ), + ) + if evaluator_query + else WorkflowQuery() + ) + + workflows = await self.workflows_service.query_workflows( + project_id=project_id, + # + workflow_query=workflow_query, + # + workflow_refs=evaluator_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + evaluators = [ + Evaluator( + **workflow.model_dump( + mode="json", + ), + ) + for workflow in workflows + ] + + return evaluators + + # evaluator variants ------------------------------------------------------- + + async def create_evaluator_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_variant_create: EvaluatorVariantCreate, + ) -> Optional[EvaluatorVariant]: + workflow_variant_create = WorkflowVariantCreate( + **evaluator_variant_create.model_dump( + mode="json", + ), + ) + + workflow_variant = await self.workflows_service.create_workflow_variant( + project_id=project_id, + user_id=user_id, + # + workflow_variant_create=workflow_variant_create, + ) + + if not workflow_variant: + return None + + evaluator_variant = EvaluatorVariant( + **workflow_variant.model_dump( + mode="json", + ) + ) + + return evaluator_variant + + async def fetch_evaluator_variant( + self, + *, + project_id: UUID, + # + evaluator_ref: Optional[Reference] = None, + evaluator_variant_ref: Optional[Reference] = None, + ) -> Optional[EvaluatorVariant]: + workflow_variant = await self.workflows_service.fetch_workflow_variant( + project_id=project_id, + # + workflow_ref=evaluator_ref, + workflow_variant_ref=evaluator_variant_ref, + ) + + if not workflow_variant: + return None + + evaluator_variant = EvaluatorVariant( + **workflow_variant.model_dump( + mode="json", + ) + ) + + return evaluator_variant + + async def edit_evaluator_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_variant_edit: EvaluatorVariantEdit, + ) -> Optional[EvaluatorVariant]: + workflow_variant_edit = WorkflowVariantEdit( + **evaluator_variant_edit.model_dump( + mode="json", + ) + ) + + evaluator_variant = await self.workflows_service.edit_workflow_variant( + project_id=project_id, + user_id=user_id, + # + workflow_variant_edit=workflow_variant_edit, + ) + + if not evaluator_variant: + return None + + evaluator_variant = EvaluatorVariant( + **evaluator_variant.model_dump( + mode="json", + ) + ) + + return evaluator_variant + + async def archive_evaluator_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_variant_id: UUID, + ) -> Optional[EvaluatorVariant]: + workflow_variant = await self.workflows_service.archive_workflow_variant( + project_id=project_id, + user_id=user_id, + # + workflow_variant_id=evaluator_variant_id, + ) + + if not workflow_variant: + return None + + evaluator_variant = EvaluatorVariant( + **workflow_variant.model_dump( + mode="json", + ) + ) + + return evaluator_variant + + async def unarchive_evaluator_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_variant_id: UUID, + ) -> Optional[EvaluatorVariant]: + workflow_variant = await self.workflows_service.unarchive_workflow_variant( + project_id=project_id, + user_id=user_id, + # + workflow_variant_id=evaluator_variant_id, + ) + + if not workflow_variant: + return None + + evaluator_variant = EvaluatorVariant( + **workflow_variant.model_dump( + mode="json", + ) + ) + + return evaluator_variant + + async def query_evaluator_variants( + self, + *, + project_id: UUID, + # + evaluator_variant_query: Optional[EvaluatorVariantQuery] = None, + # + evaluator_refs: Optional[List[Reference]] = None, + evaluator_variant_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[EvaluatorVariant]: + workflow_variant_query = ( + WorkflowVariantQuery( + **evaluator_variant_query.model_dump( + mode="json", + ) + ) + if evaluator_variant_query + else WorkflowVariantQuery() + ) + + workflow_variants = await self.workflows_service.query_workflow_variants( + project_id=project_id, + # + workflow_variant_query=workflow_variant_query, + # + workflow_refs=evaluator_refs, + workflow_variant_refs=evaluator_variant_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + if not workflow_variants: + return [] + + evaluator_variants = [ + EvaluatorVariant( + **workflow_variant.model_dump( + mode="json", + ) + ) + for workflow_variant in workflow_variants + ] + + return evaluator_variants + + async def fork_evaluator_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_fork: EvaluatorFork, + ) -> Optional[EvaluatorVariant]: + workflow_fork = WorkflowFork( + **evaluator_fork.model_dump( + mode="json", + ) + ) + + workflow_variant = await self.workflows_service.fork_workflow_variant( + project_id=project_id, + user_id=user_id, + # + workflow_fork=workflow_fork, + ) + + if not workflow_variant: + return None + + evaluator_variant = EvaluatorVariant( + **workflow_variant.model_dump( + mode="json", + ) + ) + + return evaluator_variant + + # evaluator revisions ------------------------------------------------------ + + async def create_evaluator_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_revision_create: EvaluatorRevisionCreate, + ) -> Optional[EvaluatorRevision]: + workflow_revision_create = WorkflowRevisionCreate( + **evaluator_revision_create.model_dump( + mode="json", + ) + ) + + workflow_revision = await self.workflows_service.create_workflow_revision( + project_id=project_id, + user_id=user_id, + # + workflow_revision_create=workflow_revision_create, + ) + + if not workflow_revision: + return None + + evaluator_revision = EvaluatorRevision( + **workflow_revision.model_dump( + mode="json", + ) + ) + + return evaluator_revision + + async def fetch_evaluator_revision( + self, + *, + project_id: UUID, + # + evaluator_ref: Optional[Reference] = None, + evaluator_variant_ref: Optional[Reference] = None, + evaluator_revision_ref: Optional[Reference] = None, + ) -> Optional[EvaluatorRevision]: + workflow_revision = await self.workflows_service.fetch_workflow_revision( + project_id=project_id, + # + workflow_ref=evaluator_ref, + workflow_variant_ref=evaluator_variant_ref, + workflow_revision_ref=evaluator_revision_ref, + ) + + if not workflow_revision: + return None + + evaluator_revision = EvaluatorRevision( + **workflow_revision.model_dump( + mode="json", + ) + ) + + return evaluator_revision + + async def edit_evaluator_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_revision_edit: EvaluatorRevisionEdit, + ) -> Optional[EvaluatorRevision]: + workflow_revision_edit = WorkflowRevisionEdit( + **evaluator_revision_edit.model_dump( + mode="json", + ) + ) + + workflow_revision = await self.workflows_service.edit_workflow_revision( + project_id=project_id, + user_id=user_id, + # + workflow_revision_edit=workflow_revision_edit, + ) + + if not workflow_revision: + return None + + evaluator_revision = EvaluatorRevision( + **workflow_revision.model_dump( + mode="json", + ) + ) + + return evaluator_revision + + async def archive_evaluator_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_revision_id: UUID, + ) -> Optional[EvaluatorRevision]: + workflow_revision = await self.workflows_service.archive_workflow_revision( + project_id=project_id, + user_id=user_id, + # + workflow_revision_id=evaluator_revision_id, + ) + + if not workflow_revision: + return None + + evaluator_revision = EvaluatorRevision( + **workflow_revision.model_dump( + mode="json", + ) + ) + + return evaluator_revision + + async def unarchive_evaluator_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_revision_id: UUID, + ) -> Optional[EvaluatorRevision]: + workflow_revision = await self.workflows_service.unarchive_workflow_revision( + project_id=project_id, + user_id=user_id, + # + workflow_revision_id=evaluator_revision_id, + ) + + if not workflow_revision: + return None + + evaluator_revision = EvaluatorRevision( + **workflow_revision.model_dump( + mode="json", + ) + ) + + return evaluator_revision + + async def query_evaluator_revisions( + self, + *, + project_id: UUID, + # + evaluator_revision_query: Optional[EvaluatorRevisionQuery] = None, + # + evaluator_refs: Optional[List[Reference]] = None, + evaluator_variant_refs: Optional[List[Reference]] = None, + evaluator_revision_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[EvaluatorRevision]: + workflow_revision_query = ( + WorkflowRevisionQuery( + **evaluator_revision_query.model_dump( + mode="json", + ) + ) + if evaluator_revision_query + else WorkflowRevisionQuery() + ) + + workflow_revisions = await self.workflows_service.query_workflow_revisions( + project_id=project_id, + # + workflow_revision_query=workflow_revision_query, + # + workflow_refs=evaluator_refs, + workflow_variant_refs=evaluator_variant_refs, + workflow_revision_refs=evaluator_revision_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + if not workflow_revisions: + return [] + + evaluator_revisions = [ + EvaluatorRevision( + **revision.model_dump( + mode="json", + ) + ) + for revision in workflow_revisions + ] + + return evaluator_revisions + + async def commit_evaluator_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_revision_commit: EvaluatorRevisionCommit, + ) -> Optional[EvaluatorRevision]: + workflow_revision_commit = WorkflowRevisionCommit( + **evaluator_revision_commit.model_dump( + mode="json", + ) + ) + + workflow_revision = await self.workflows_service.commit_workflow_revision( + project_id=project_id, + user_id=user_id, + # + workflow_revision_commit=workflow_revision_commit, + ) + + if not workflow_revision: + return None + + evaluator_revision = EvaluatorRevision( + **workflow_revision.model_dump( + mode="json", + ) + ) + + return evaluator_revision + + async def log_evaluator_revisions( + self, + *, + project_id: UUID, + # + evaluator_revisions_log: EvaluatorRevisionsLog, + ) -> List[EvaluatorRevision]: + workflow_revisions_log = WorkflowRevisionsLog( + **evaluator_revisions_log.model_dump( + mode="json", + ) + ) + + workflow_revisions = await self.workflows_service.log_workflow_revisions( + project_id=project_id, + # + workflow_revisions_log=workflow_revisions_log, + ) + + if not workflow_revisions: + return [] + + evaluator_revisions = [ + EvaluatorRevision( + **revision.model_dump( + mode="json", + ) + ) + for revision in workflow_revisions + ] + + return evaluator_revisions + + # evaluator services ------------------------------------------------------- + + # TODO: Implement ? + + # -------------------------------------------------------------------------- + + +class SimpleEvaluatorsService: + def __init__( + self, + *, + evaluators_service: EvaluatorsService, + ): + self.evaluators_service = evaluators_service + + # public ------------------------------------------------------------------- + + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + simple_evaluator_create: SimpleEvaluatorCreate, + # + evaluator_id: Optional[UUID] = None, + ) -> Optional[SimpleEvaluator]: + simple_evaluator_flags = ( + SimpleEvaluatorFlags( + **( + simple_evaluator_create.flags.model_dump( + mode="json", + ) + ) + ) + if simple_evaluator_create.flags + else SimpleEvaluatorFlags( + is_custom=False, + is_human=False, + is_evaluator=True, + ) + ) + + evaluator_flags = EvaluatorFlags( + **simple_evaluator_flags.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ), + ) + + evaluator_create = EvaluatorCreate( + slug=simple_evaluator_create.slug, + # + name=simple_evaluator_create.name, + description=simple_evaluator_create.description, + # + flags=evaluator_flags, + meta=simple_evaluator_create.meta, + tags=simple_evaluator_create.tags, + ) + + evaluator: Optional[Evaluator] = await self.evaluators_service.create_evaluator( + project_id=project_id, + user_id=user_id, + # + evaluator_create=evaluator_create, + # + evaluator_id=evaluator_id, + ) + + if evaluator is None: + return None + + evaluator_variant_slug = uuid4().hex + + evaluator_variant_create = EvaluatorVariantCreate( + slug=evaluator_variant_slug, + # + name=evaluator_create.name, + description=evaluator_create.description, + # + flags=evaluator_flags, + tags=evaluator_create.tags, + meta=evaluator_create.meta, + # + evaluator_id=evaluator.id, + ) + + evaluator_variant: Optional[ + EvaluatorVariant + ] = await self.evaluators_service.create_evaluator_variant( + project_id=project_id, + user_id=user_id, + # + evaluator_variant_create=evaluator_variant_create, + ) + + if evaluator_variant is None: + return None + + evaluator_revision_slug = uuid4().hex + + evaluator_revision_commit = EvaluatorRevisionCommit( + slug=evaluator_revision_slug, + # + name=evaluator_create.name, + description=evaluator_create.description, + # + flags=evaluator_flags, + tags=evaluator_create.tags, + meta=evaluator_create.meta, + # + data=simple_evaluator_create.data, + # + evaluator_id=evaluator.id, + evaluator_variant_id=evaluator_variant.id, + ) + + evaluator_revision: Optional[ + EvaluatorRevision + ] = await self.evaluators_service.commit_evaluator_revision( + project_id=project_id, + user_id=user_id, + evaluator_revision_commit=evaluator_revision_commit, + ) + + if evaluator_revision is None: + return None + + simple_evaluator_data = SimpleEvaluatorData( + **( + evaluator_revision.data.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + if evaluator_revision.data + else {} + ), + ) + + simple_evaluator = SimpleEvaluator( + id=evaluator.id, + slug=evaluator.slug, + # + created_at=evaluator.created_at, + updated_at=evaluator.updated_at, + deleted_at=evaluator.deleted_at, + created_by_id=evaluator.created_by_id, + updated_by_id=evaluator.updated_by_id, + deleted_by_id=evaluator.deleted_by_id, + # + name=evaluator.name, + description=evaluator.description, + # + flags=simple_evaluator_flags, + tags=evaluator.tags, + meta=evaluator.meta, + # + data=simple_evaluator_data, + ) + + return simple_evaluator + + async def fetch( + self, + *, + project_id: UUID, + # + evaluator_id: UUID, + ) -> Optional[SimpleEvaluator]: + evaluator_ref = Reference( + id=evaluator_id, + ) + + evaluator: Optional[Evaluator] = await self.evaluators_service.fetch_evaluator( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if evaluator is None: + return None + + evaluator_variant: Optional[ + EvaluatorVariant + ] = await self.evaluators_service.fetch_evaluator_variant( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if evaluator_variant is None: + return None + + evaluator_variant_ref = Reference( + id=evaluator_variant.id, + ) + + evaluator_revision: Optional[ + EvaluatorRevision + ] = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_variant_ref=evaluator_variant_ref, + ) + + if evaluator_revision is None: + return None + + simple_evaluator_flags = SimpleEvaluatorFlags( + **( + evaluator.flags.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + if evaluator.flags + else {} + ) + ) + + simple_evaluator_data = SimpleEvaluatorData( + **( + evaluator_revision.data.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + if evaluator_revision.data + else {} + ), + ) + + simple_evaluator = SimpleEvaluator( + id=evaluator.id, + slug=evaluator.slug, + # + created_at=evaluator.created_at, + updated_at=evaluator.updated_at, + deleted_at=evaluator.deleted_at, + created_by_id=evaluator.created_by_id, + updated_by_id=evaluator.updated_by_id, + deleted_by_id=evaluator.deleted_by_id, + # + name=evaluator.name, + description=evaluator.description, + # + flags=simple_evaluator_flags, + tags=evaluator.tags, + meta=evaluator.meta, + # + data=simple_evaluator_data, + ) + return simple_evaluator + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + simple_evaluator_edit: SimpleEvaluatorEdit, + ) -> Optional[SimpleEvaluator]: + simple_evaluator_flags = ( + SimpleEvaluatorFlags( + **( + simple_evaluator_edit.flags.model_dump( + mode="json", + ) + ) + ) + if simple_evaluator_edit.flags + else SimpleEvaluatorFlags() + ) + + evaluator_flags = EvaluatorFlags( + **simple_evaluator_flags.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + ) + + evaluator_ref = Reference( + id=simple_evaluator_edit.id, + ) + + evaluator: Optional[Evaluator] = await self.evaluators_service.fetch_evaluator( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if evaluator is None: + return None + + evaluator_edit = EvaluatorEdit( + id=evaluator.id, + # + name=simple_evaluator_edit.name, + description=simple_evaluator_edit.description, + # + flags=evaluator_flags, + tags=simple_evaluator_edit.tags, + meta=simple_evaluator_edit.meta, + ) + + evaluator = await self.evaluators_service.edit_evaluator( + project_id=project_id, + user_id=user_id, + # + evaluator_edit=evaluator_edit, + ) + + if evaluator is None: + return None + + evaluator_variant: Optional[ + EvaluatorVariant + ] = await self.evaluators_service.fetch_evaluator_variant( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if evaluator_variant is None: + return None + + evaluator_variant_edit = EvaluatorVariantEdit( + id=evaluator_variant.id, + # + name=evaluator_edit.name, + description=evaluator_edit.description, + # + flags=evaluator_flags, + tags=evaluator_edit.tags, + meta=evaluator_edit.meta, + # + ) + + evaluator_variant = await self.evaluators_service.edit_evaluator_variant( + project_id=project_id, + user_id=user_id, + # + evaluator_variant_edit=evaluator_variant_edit, + ) + + if evaluator_variant is None: + return None + + evaluator_revision_slug = uuid4().hex + + evaluator_revision_commit = EvaluatorRevisionCommit( + slug=evaluator_revision_slug, + # + name=evaluator_edit.name, + description=evaluator_edit.description, + # + flags=evaluator_flags, + tags=evaluator_edit.tags, + meta=evaluator_edit.meta, + # + data=simple_evaluator_edit.data, + # + evaluator_id=evaluator.id, + evaluator_variant_id=evaluator_variant.id, + ) + + evaluator_revision: Optional[ + EvaluatorRevision + ] = await self.evaluators_service.commit_evaluator_revision( + project_id=project_id, + user_id=user_id, + # + evaluator_revision_commit=evaluator_revision_commit, + ) + + if evaluator_revision is None: + return None + + simple_evaluator_data = SimpleEvaluatorData( + **( + evaluator_revision.data.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + if evaluator_revision.data + else {} + ), + ) + + simple_evaluator = SimpleEvaluator( + id=evaluator.id, + slug=evaluator.slug, + # + created_at=evaluator.created_at, + updated_at=evaluator.updated_at, + deleted_at=evaluator.deleted_at, + created_by_id=evaluator.created_by_id, + updated_by_id=evaluator.updated_by_id, + deleted_by_id=evaluator.deleted_by_id, + # + name=evaluator.name, + description=evaluator.description, + # + flags=simple_evaluator_flags, + tags=evaluator.tags, + meta=evaluator.meta, + # + data=simple_evaluator_data, + ) + + return simple_evaluator + + async def transfer( + self, + *, + project_id: UUID, + user_id: UUID, + # + evaluator_id: UUID, + ) -> Optional[SimpleEvaluator]: + old_evaluator = await fetch_evaluator_config( + evaluator_config_id=str(evaluator_id), + ) + + if old_evaluator is None: + return None + + evaluator_revision_data = self._transfer_evaluator_revision_data( + old_evaluator=old_evaluator, + ) + + evaluator_ref = Reference(id=evaluator_id) + + new_evaluator = await self.evaluators_service.fetch_evaluator( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if new_evaluator is None: + name = str(old_evaluator.name) + slug = get_slug_from_name_and_id( + name=name, + id=evaluator_id, + ) + + evaluator_create = SimpleEvaluatorCreate( + slug=slug, + name=name, + description=None, + flags=SimpleEvaluatorFlags( + is_custom=False, + is_human=False, + is_evaluator=True, + ), + tags=None, + meta=None, + data=SimpleEvaluatorData( + **evaluator_revision_data.model_dump( + mode="json", + ) + ), + ) + simple_evaluator = await self.create( + project_id=project_id, + user_id=user_id, + simple_evaluator_create=evaluator_create, + evaluator_id=evaluator_id, + ) + + return simple_evaluator + + evaluator_edit = SimpleEvaluatorEdit( + id=evaluator_id, + name=new_evaluator.name, + description=new_evaluator.description, + flags=( + SimpleEvaluatorFlags( + **new_evaluator.flags.model_dump( + mode="json", + ) + ) + if new_evaluator.flags + else None + ), + tags=new_evaluator.tags, + meta=new_evaluator.meta, + data=SimpleEvaluatorData( + **evaluator_revision_data.model_dump( + mode="json", + ) + ), + ) + + simple_evaluator = await self.edit( + project_id=project_id, + user_id=user_id, + simple_evaluator_edit=evaluator_edit, + ) + + return simple_evaluator + + async def query( + self, + *, + project_id: UUID, + # + simple_evaluator_query: Optional[SimpleEvaluatorQuery] = None, + # + simple_evaluator_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[SimpleEvaluatorQQuery]: + evaluator_query = EvaluatorQuery( + **( + simple_evaluator_query.model_dump( + mode="json", + ) + if simple_evaluator_query + else {} + ), + ) + + evaluator_queries = await self.evaluators_service.query_evaluators( + project_id=project_id, + # + evaluator_query=evaluator_query, + # + evaluator_refs=simple_evaluator_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + if not evaluator_queries: + return [] + + simple_evaluators_qqueries: List[SimpleEvaluatorQQuery] = [] + + for evaluator_query in evaluator_queries: + evaluator_ref = Reference( + id=evaluator_query.id, + ) + + evaluator_variant: Optional[ + EvaluatorVariant + ] = await self.evaluators_service.fetch_evaluator_variant( + project_id=project_id, + # + evaluator_ref=evaluator_ref, + ) + + if not evaluator_variant: + continue + + evaluator_variant_ref = Reference( + id=evaluator_variant.id, + ) + + evaluator_revision: Optional[ + EvaluatorRevision + ] = await self.evaluators_service.fetch_evaluator_revision( + project_id=project_id, + # + evaluator_variant_ref=evaluator_variant_ref, + ) + + if not evaluator_revision: + continue + + simple_evaluator_flags = SimpleEvaluatorFlags( + **( + evaluator_query.flags.model_dump( + mode="json", + ) + if evaluator_query.flags + else {} + ), + ) + + simple_evaluator_data = SimpleEvaluatorData( + **( + evaluator_revision.data.model_dump( + mode="json", + ) + if evaluator_revision.data + else {} + ), + ) + + evaluator_query = SimpleEvaluatorQQuery( + id=evaluator_query.id, + slug=evaluator_query.slug, + # + created_at=evaluator_query.created_at, + updated_at=evaluator_query.updated_at, + deleted_at=evaluator_query.deleted_at, + created_by_id=evaluator_query.created_by_id, + updated_by_id=evaluator_query.updated_by_id, + deleted_by_id=evaluator_query.deleted_by_id, + # + name=evaluator_query.name, + description=evaluator_query.description, + # + flags=simple_evaluator_flags, + tags=evaluator_query.tags, + meta=evaluator_query.meta, + # + data=simple_evaluator_data, + ) + + simple_evaluators_qqueries.append(evaluator_query) + + return simple_evaluators_qqueries + + # internals ---------------------------------------------------------------- + + def _transfer_evaluator_revision_data( + self, + old_evaluator: EvaluatorConfigDB, + ) -> EvaluatorRevisionData: + version = "2025.07.14" + uri = f"agenta:built-in:{old_evaluator.evaluator_key}:v0" + url = ( + old_evaluator.settings_values.get("webhook_url", None) + if old_evaluator.evaluator_key == "auto_webhook_test" # type: ignore + else None + ) + headers = None + mappings = None + properties = ( + {"score": {"type": "number"}, "success": {"type": "boolean"}} + if old_evaluator.evaluator_key + in ( + "auto_levenshtein_distance", + "auto_semantic_similarity", + "auto_similarity_match", + "auto_json_diff", + "auto_webhook_test", + "auto_custom_code_run", + "auto_ai_critique", + "rag_faithfulness", + "rag_context_relevancy", + ) + else {"success": {"type": "boolean"}} + ) + schemas = { + "outputs": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": properties, + "required": ( + list(properties.keys()) + if old_evaluator.evaluator_key + not in ( + "auto_levenshtein_distance", + "auto_semantic_similarity", + "auto_similarity_match", + "auto_json_diff", + "auto_webhook_test", + "auto_custom_code_run", + "auto_ai_critique", + "rag_faithfulness", + "rag_context_relevancy", + ) + else [] + ), + "additionalProperties": False, + } + } + script = ( + old_evaluator.settings_values.get("code", None) + if old_evaluator.evaluator_key == "auto_custom_code_run" # type: ignore + else None + ) + parameters = old_evaluator.settings_values + service = { + "agenta": "0.1.0", + "format": { + "type": "object", + "$schema": "http://json-schema.org/schema#", + "required": ["outputs"], + "properties": { + "outputs": schemas["outputs"], + }, + }, + } + configuration = parameters + + return EvaluatorRevisionData( + version=version, + uri=uri, + url=url, + headers=headers, + mappings=mappings, + schemas=schemas, + script=script, + parameters=parameters, # type: ignore + # LEGACY + service=service, + configuration=configuration, # type: ignore + ) + + # -------------------------------------------------------------------------- diff --git a/api/oss/src/core/git/dtos.py b/api/oss/src/core/git/dtos.py index 125fc1e823..ff49f5ba89 100644 --- a/api/oss/src/core/git/dtos.py +++ b/api/oss/src/core/git/dtos.py @@ -12,22 +12,11 @@ Header, Metadata, Data, - Reference, + Commit, ) -class Commit(BaseModel): - author: Optional[UUID] = None - date: Optional[datetime] = None - message: Optional[str] = None - - -class CommitCreate(BaseModel): - message: Optional[str] = None - - -class CommitFetch(BaseModel): - authors: Optional[List[UUID]] = None +# artifacts -------------------------------------------------------------------- class Artifact(Identifier, Slug, Lifecycle, Header, Metadata): @@ -42,10 +31,13 @@ class ArtifactEdit(Identifier, Header, Metadata): pass -class ArtifactQuery(Metadata): +class ArtifactQuery(Header, Metadata): pass +# variants --------------------------------------------------------------------- + + class Variant(Identifier, Slug, Lifecycle, Header, Metadata): artifact_id: Optional[UUID] = None @@ -58,10 +50,13 @@ class VariantEdit(Identifier, Header, Metadata): pass -class VariantQuery(Metadata): +class VariantQuery(Header, Metadata): pass +# revisions -------------------------------------------------------------------- + + class Revision(Identifier, Slug, Version, Lifecycle, Header, Metadata, Commit): data: Optional[Data] = None @@ -78,32 +73,49 @@ class RevisionEdit(Identifier, Header, Metadata): pass -class RevisionQuery(Metadata, CommitFetch): - pass - +class RevisionQuery(Header, Metadata): + author: Optional[UUID] = None + authors: Optional[List[UUID]] = None -class RevisionCommit(Slug, Header, Metadata, CommitCreate): - data: Optional[Data] = None + date: Optional[datetime] = None + dates: Optional[List[datetime]] = None - artifact_id: Optional[UUID] = None - variant_id: Optional[UUID] = None + message: Optional[str] = None -class RevisionFork(Slug, Header, Metadata, CommitCreate): +class RevisionCommit(Slug, Header, Metadata): data: Optional[Data] = None + message: Optional[str] = None -class VariantFork(Slug, Header, Metadata): - pass + artifact_id: Optional[UUID] = None + variant_id: Optional[UUID] = None -class ArtifactLog(BaseModel): +class RevisionsLog(BaseModel): + artifact_id: Optional[UUID] = None variant_id: Optional[UUID] = None revision_id: Optional[UUID] = None depth: Optional[int] = None -class ArtifactFork(ArtifactLog): +# forks ------------------------------------------------------------------------ + + +class RevisionFork(Slug, Header, Metadata): + data: Optional[Data] = None + + message: Optional[str] = None + + +class VariantFork(Slug, Header, Metadata): + pass + + +class ArtifactFork(RevisionsLog): variant: Optional[VariantFork] = None revision: Optional[RevisionFork] = None + + +# ------------------------------------------------------------------------------ diff --git a/api/oss/src/core/git/interfaces.py b/api/oss/src/core/git/interfaces.py index cf7a088cff..e2bb39957d 100644 --- a/api/oss/src/core/git/interfaces.py +++ b/api/oss/src/core/git/interfaces.py @@ -9,7 +9,7 @@ ArtifactCreate, ArtifactEdit, ArtifactQuery, - ArtifactLog, + RevisionsLog, ArtifactFork, Variant, VariantCreate, @@ -297,7 +297,7 @@ async def log_revisions( *, project_id: UUID, # - artifact_log: ArtifactLog, + revisions_log: RevisionsLog, ) -> List[Revision]: raise NotImplementedError diff --git a/api/oss/src/core/invocations/__init__.py b/api/oss/src/core/invocations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/core/invocations/service.py b/api/oss/src/core/invocations/service.py new file mode 100644 index 0000000000..cf73839fd5 --- /dev/null +++ b/api/oss/src/core/invocations/service.py @@ -0,0 +1,1059 @@ +from typing import Optional, List +from uuid import UUID, uuid4 + +from fastapi import Request + +from oss.src.utils.logging import get_module_logger + +from oss.src.core.applications.service import LegacyApplicationsService + +from oss.src.core.shared.dtos import ( + Tags, + Meta, + Data, + Reference, + Link, + Windowing, +) +from oss.src.core.tracing.dtos import ( + Focus, + Format, + TracingQuery, + Formatting, + Condition, + Filtering, + OTelReference, + OTelLink, + LogicalOperator, + ComparisonOperator, + ListOperator, + TraceType, + SpanType, +) +from oss.src.core.applications.dtos import ( + LegacyApplicationFlags, + LegacyApplicationData, +) + + +from oss.src.core.tracing.utils import ( + parse_into_attributes, + parse_from_attributes, +) +from oss.src.core.invocations.types import ( + InvocationOrigin, + InvocationKind, + InvocationChannel, + InvocationReferences, + InvocationLinks, + InvocationFlags, + # + Invocation, + InvocationCreate, + InvocationEdit, + InvocationQuery, +) + +from oss.src.apis.fastapi.tracing.router import TracingRouter +from oss.src.apis.fastapi.tracing.models import ( + OTelFlatSpan, + OTelTracingRequest, + OTelTracingResponse, +) +from oss.src.apis.fastapi.applications.models import LegacyApplicationCreate + + +log = get_module_logger(__name__) + + +class InvocationsService: + def __init__( + self, + *, + legacy_applications_service: LegacyApplicationsService, + tracing_router: TracingRouter, + ): + self.legacy_applications_service = legacy_applications_service + self.tracing_router = tracing_router + + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + invocation_create: InvocationCreate, + ) -> Optional[Invocation]: + legacy_application_slug = ( + invocation_create.references.application.slug + if invocation_create.references.application + else None + ) or uuid4().hex + + legacy_application_flags = LegacyApplicationFlags() + + application_revision = await self.legacy_applications_service.retrieve( + project_id=project_id, + # + application_ref=invocation_create.references.application, + application_variant_ref=invocation_create.references.application_variant, + application_revision_ref=invocation_create.references.application_revision, + ) + + if not application_revision: + legacy_application_create = LegacyApplicationCreate( + slug=legacy_application_slug, + # + name=legacy_application_slug, + # + flags=legacy_application_flags, + ) + + legacy_application = await self.legacy_applications_service.create( + project_id=project_id, + user_id=user_id, + # + legacy_application_create=legacy_application_create, + ) + + if legacy_application: + application_revision = await self.legacy_applications_service.retrieve( + project_id=project_id, + # + application_ref=Reference(id=legacy_application.id), + ) + + if not application_revision or not application_revision.data: + return None + + if application_revision: + invocation_create.references.application = Reference( + id=application_revision.application_id, + slug=( + invocation_create.references.application.slug + if invocation_create.references.application + else None + ), + ) + + invocation_create.references.application_variant = Reference( + id=application_revision.application_variant_id, + slug=( + invocation_create.references.application_variant.slug + if invocation_create.references.application_variant + else None + ), + ) + + invocation_create.references.application_revision = Reference( + id=application_revision.id, + slug=application_revision.slug, + version=application_revision.version, + ) + + invocation_flags = InvocationFlags( + is_sdk=invocation_create.channel == InvocationChannel.SDK, + is_web=invocation_create.channel == InvocationChannel.WEB, + is_evaluation=invocation_create.kind == InvocationKind.EVAL, + ) + + invocation_references = InvocationReferences( + **invocation_create.references.model_dump(), + ) + + invocation_link = await self._create_invocation( + project_id=project_id, + user_id=user_id, + # + name=legacy_application.name, + # + flags=invocation_flags, + tags=invocation_create.tags, + meta=invocation_create.meta, + # + data=invocation_create.data, + # + references=invocation_references, + links=invocation_create.links, + ) + + if invocation_link is None: + return None + + invocation = await self._fetch_invocation( + project_id=project_id, + # + invocation_link=invocation_link, + ) + + return invocation + + async def fetch( + self, + *, + project_id: UUID, + # + trace_id: str, + span_id: Optional[str] = None, + ): + invocation_link = Link( + trace_id=trace_id, + span_id=span_id, + ) + + invocation: Optional[Invocation] = await self._fetch_invocation( + project_id=project_id, + # + invocation_link=invocation_link, + ) + return invocation + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + trace_id: str, + span_id: Optional[str] = None, + # + invocation_edit: InvocationEdit, + ): + invocation_link = Link( + trace_id=trace_id, + span_id=span_id, + ) + + invocation: Optional[Invocation] = await self._fetch_invocation( + project_id=project_id, + # + invocation_link=invocation_link, + ) + + if invocation is None: + return None + + legacy_application_slug = ( + invocation.references.application.slug + if invocation.references.application + else None + ) or uuid4().hex + + legacy_application_flags = LegacyApplicationFlags() + + application_revision = await self.legacy_applications_service.retrieve( + project_id=project_id, + # + application_ref=invocation.references.application, + application_variant_ref=invocation.references.application_variant, + application_revision_ref=invocation.references.application_revision, + ) + + if not application_revision: + legacy_application_create = LegacyApplicationCreate( + slug=legacy_application_slug, + # + name=legacy_application_slug, + # + flags=legacy_application_flags, + ) + + legacy_application = await self.legacy_applications_service.create( + project_id=project_id, + user_id=user_id, + # + legacy_application_create=legacy_application_create, + ) + + if legacy_application: + application_revision = await self.legacy_applications_service.retrieve( + project_id=project_id, + # + application_ref=Reference(id=legacy_application.id), + ) + + if not application_revision or not application_revision.data: + return None + + if application_revision: + invocation.references.application = Reference( + id=application_revision.application_id, + slug=( + invocation.references.application.slug + if invocation.references.application + else None + ), + ) + + invocation.references.application_variant = Reference( + id=application_revision.application_variant_id, + slug=( + invocation.references.application_variant.slug + if invocation.references.application_variant + else None + ), + ) + + invocation.references.application_revision = Reference( + id=application_revision.id, + slug=application_revision.slug, + version=application_revision.version, + ) + + invocation_flags = InvocationFlags( + is_sdk=invocation.channel == InvocationChannel.SDK, + is_web=invocation.channel == InvocationChannel.WEB, + is_evaluation=invocation.kind == InvocationKind.EVAL, + ) + + invocation_references = InvocationReferences( + **invocation.references.model_dump(), + ) + + invocation_link = await self._edit_invocation( + project_id=project_id, + user_id=user_id, + # + invocation=invocation, + # + flags=invocation_flags, + tags=invocation_edit.tags, + meta=invocation_edit.meta, + # + data=invocation_edit.data, + # + references=invocation_references, + links=invocation.links, + ) + + if invocation_link is None: + return None + + invocation = await self._fetch_invocation( + project_id=project_id, + # + invocation_link=invocation_link, + ) + + return invocation + + async def delete( + self, + *, + project_id: UUID, + user_id: UUID, + # + trace_id: str, + span_id: Optional[str] = None, + ): + invocation_link = Link( + trace_id=trace_id, + span_id=span_id, + ) + + invocation_link = await self._delete_invocation( + project_id=project_id, + user_id=user_id, + # + invocation_link=invocation_link, + ) + + return invocation_link + + async def query( + self, + *, + project_id: UUID, + # + invocation_query: Optional[InvocationQuery] = None, + # + invocation_links: Optional[List[Link]] = None, + # + windowing: Optional[Windowing] = None, + ): + invocation = invocation_query if invocation_query else None + invocation_flags = InvocationFlags(is_evaluator=True) + + if invocation: + if invocation.channel: + invocation_flags.is_sdk = invocation.channel == InvocationChannel.SDK + invocation_flags.is_web = invocation.channel == InvocationChannel.WEB + + if invocation.kind: + invocation_flags.is_evaluation = invocation.kind == InvocationKind.EVAL + + invocation_tags = invocation.tags if invocation else None + invocation_meta = invocation.meta if invocation else None + + invocation_references = ( + InvocationReferences( + **invocation.references.model_dump(), + ) + if invocation and invocation.references + else None + ) + + _invocation_links = invocation.links if invocation else None + + invocations = await self._query_invocation( + project_id=project_id, + # + flags=invocation_flags, + tags=invocation_tags, + meta=invocation_meta, + # + references=invocation_references, + links=_invocation_links, + # + invocation_links=invocation_links, + # + windowing=windowing, + ) + return invocations + + # -------- Internal Functions ------------------------------------------------------------------- + + async def _create_invocation( + self, + *, + project_id: UUID, + user_id: UUID, + # + name: Optional[str], + # + flags: InvocationFlags, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + # + data: Data, + # + references: InvocationReferences, + links: Optional[InvocationLinks], + ) -> Optional[Link]: + trace_id = uuid4().hex + trace_type = TraceType.INVOCATION + + span_id = uuid4().hex[16:] + span_type = SpanType.TASK + span_name = name or references.application.slug or "invocation" + + _references = references.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + + if isinstance(links, dict): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": key}, # type: ignore + ) + for key, link in links.items() + if link.trace_id and link.span_id + ] + elif isinstance(links, list): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": "key"}, # type: ignore + ) + for link in links + if link.trace_id and link.span_id + ] + else: + _links = None + + _flags = flags.model_dump(mode="json", exclude_none=True) + + _type = { + "trace": "invocation", + "span": "task", + } + + _attributes = parse_into_attributes( + type=_type, + flags=_flags, + tags=tags, + meta=meta, + data=data, + references=_references, + ) + + trace_request = OTelTracingRequest( + spans=[ + OTelFlatSpan( + trace_id=trace_id, + trace_type=trace_type, + span_id=span_id, + span_type=span_type, + span_name=span_name, + attributes=_attributes, + links=_links, + ) + ] + ) + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + + _links_response = await self.tracing_router.create_trace( + request=request, + # + trace_request=trace_request, + ) + + _link = ( + Link( + trace_id=_links_response.links[0].trace_id, + span_id=_links_response.links[0].span_id, + ) + if _links_response.links and len(_links_response.links) > 0 + else None + ) + + return _link + + async def _fetch_invocation( + self, + *, + project_id: UUID, + user_id: Optional[UUID] = None, + # + invocation_link: Link, + ) -> Optional[Invocation]: + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) if user_id else None + + if not invocation_link.trace_id: + return None + + trace_response: OTelTracingResponse = await self.tracing_router.fetch_trace( + request=request, + # + trace_id=invocation_link.trace_id, + ) + + if not trace_response or not trace_response.traces: + return None + + traces = list(trace_response.traces.values()) + trace = traces[0] if traces else None + + if not trace or not trace.spans: + return None + + spans = list(trace.spans.values()) + root_span = spans[0] if spans else None + + if not root_span or isinstance(root_span, list): + return None + + ( + type, + flags, + tags, + meta, + data, + references, + ) = parse_from_attributes(root_span.attributes or {}) + + if not data: + return None + + _references = InvocationReferences( + **{ + key: Reference( + id=ref.get("id"), + slug=ref.get("slug"), + version=ref.get("version"), + ) + for key, ref in (references or {}).items() + } + ) + + _links = dict( + **{ + str(link.attributes["key"]): Link( + trace_id=link.trace_id, + span_id=link.span_id, + ) + for link in root_span.links or [] + if link.attributes and "key" in link.attributes + } + ) + + _origin = InvocationOrigin.CUSTOM + + _kind = ( + (flags.get("is_evaluation") and InvocationKind.EVAL or InvocationKind.ADHOC) + if flags + else InvocationKind.ADHOC + ) + + _channel = ( + ( + flags.get("is_sdk") + and InvocationChannel.SDK + or flags.get("is_web") + and InvocationChannel.WEB + or InvocationChannel.API + ) + if flags + else InvocationChannel.API + ) + + invocation = Invocation( + trace_id=root_span.trace_id, + span_id=root_span.span_id, + # + created_at=root_span.created_at, + updated_at=root_span.updated_at, + deleted_at=root_span.deleted_at, + created_by_id=root_span.created_by_id, + updated_by_id=root_span.updated_by_id, + deleted_by_id=root_span.deleted_by_id, + # + origin=_origin, + kind=_kind, + channel=_channel, + # + tags=tags, + meta=meta, + # + data=data, + # + references=_references, + links=_links, + ) + + return invocation + + async def _edit_invocation( + self, + *, + project_id: UUID, + user_id: UUID, + # + invocation: Invocation, + # + flags: InvocationFlags, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + # + data: Data, + # + references: InvocationReferences, + links: InvocationLinks, + ) -> Optional[Link]: + if not invocation.trace_id or not invocation.span_id: + return None + + _references = references.model_dump( + mode="json", + exclude_none=True, + exclude_unset=True, + ) + + if isinstance(links, dict): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": key}, # type: ignore + ) + for key, link in links.items() + if link.trace_id and link.span_id + ] + elif isinstance(links, list): + _links = [ + OTelLink( + trace_id=link.trace_id, + span_id=link.span_id, + attributes={"key": "key"}, # type: ignore + ) + for link in links + if link.trace_id and link.span_id + ] + else: + _links = None + + _flags = flags.model_dump(mode="json", exclude_none=True) + + _type = { + "trace": "invocation", + "span": "task", + } + + _attributes = parse_into_attributes( + type=_type, + flags=_flags, + tags=tags, + meta=meta, + data=data, + references=_references, + ) + + trace_request = OTelTracingRequest( + spans=[ + OTelFlatSpan( + trace_id=invocation.trace_id, + span_id=invocation.span_id, + attributes=_attributes, + links=_links, + ) + ] + ) + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + + _links_response = await self.tracing_router.edit_trace( + request=request, + # + trace_id=invocation.trace_id, + # + trace_request=trace_request, + ) + + _link = ( + Link( + trace_id=_links_response.links[0].trace_id, + span_id=_links_response.links[0].span_id, + ) + if _links_response.links and len(_links_response.links) > 0 + else None + ) + + return _link + + async def _delete_invocation( + self, + *, + project_id: UUID, + user_id: UUID, + # + invocation_link: Link, + ) -> Optional[Link]: + if not invocation_link.trace_id: + return None + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + + link_response = await self.tracing_router.delete_trace( + request=request, + # + trace_id=invocation_link.trace_id, + ) + + link = link_response.links[0] if link_response.links else None + + if not link or not link.trace_id or not link.span_id: + return None + + invocation_link = Link( + trace_id=link.trace_id, + span_id=link.span_id, + ) + + return invocation_link + + async def _query_invocation( + self, + *, + project_id: UUID, + user_id: Optional[UUID] = None, + # + flags: Optional[InvocationFlags] = None, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + # + references: Optional[InvocationReferences] = None, + links: Optional[InvocationLinks] = None, + # + invocation_links: Optional[List[Link]] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[Invocation]: + formatting = Formatting( + focus=Focus.TRACE, + format=Format.AGENTA, + ) + + filtering = Filtering() + + conditions: List[Condition | Filtering] = [ + Condition( + field="attributes", + key="ag.type.trace", + value="invocation", + operator=ComparisonOperator.IS, + ) + ] + + trace_ids = ( + [invocation_link.trace_id for invocation_link in invocation_links] + if invocation_links + else None + ) + + # span_ids = ( + # [invocation_link.span_id for invocation_link in invocation_links] + # if invocation_links + # else None + # ) + + if trace_ids: + conditions.append( + Condition( + field="trace_id", + value=trace_ids, + operator=ListOperator.IN, + ) + ) + + # if span_ids: + # conditions.append( + # Condition( + # field="span_id", + # value=span_ids, + # operator=ListOperator.IN, + # ) + # ) + + if flags: + for key, value in flags.model_dump(mode="json", exclude_none=True).items(): + conditions.append( + Condition( + field="attributes", + key=f"ag.flags.{key}", + value=value, + operator=ComparisonOperator.IS, + ) + ) + + if tags: + for key, value in tags.items(): + conditions.append( + Condition( + field="attributes", + key=f"ag.tags.{key}", + value=value, # type:ignore + operator=ComparisonOperator.IS, + ) + ) + + if meta: + for key, value in meta.items(): + conditions.append( + Condition( + field="attributes", + key=f"ag.meta.{key}", + value=value, # type:ignore + operator=ComparisonOperator.IS, + ) + ) + + if references: + for _, reference in references.model_dump(mode="json").items(): + if reference: + ref_id = str(reference.get("id")) if reference.get("id") else None + ref_slug = ( + str(reference.get("slug")) if reference.get("slug") else None + ) + conditions.append( + Condition( + field="references", + value=[ + {"id": ref_id, "slug": ref_slug}, + ], + operator=ListOperator.IN, + ) + ) + + if links: + if isinstance(links, dict): + for _, link in links.items(): + if link: + conditions.append( + Condition( + field="links", + value=[ + { + "trace_id": link.trace_id, + "span_id": link.span_id, + }, + ], + operator=ListOperator.IN, + ) + ) + elif isinstance(links, list): + _conditions = [] + for link in links: + if link: + _conditions.append( + Condition( + field="links", + value=[ + { + "trace_id": link.trace_id, + "span_id": link.span_id, + }, + ], + operator=ListOperator.IN, + ) + ) + if _conditions: + conditions.append( + Filtering( + operator=LogicalOperator.OR, + conditions=_conditions, + ) + ) + + if conditions: + filtering = Filtering( + operator=LogicalOperator.AND, + conditions=conditions, + ) + + query = TracingQuery( + formatting=formatting, + filtering=filtering, + windowing=windowing, + ) + + request = Request( + scope={"type": "http", "http_version": "1.1", "scheme": "http"} + ) + + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) if user_id else None + + spans_response: OTelTracingResponse = await self.tracing_router.query_spans( + request=request, + # + query=query, + ) + + if not spans_response or not spans_response.traces: + return [] + + traces = list(spans_response.traces.values()) + + invocations = [] + + for trace in traces: + if not trace or not trace.spans: + continue + + spans = list(trace.spans.values()) + root_span = spans[0] if spans else None + + if not root_span or isinstance(root_span, list): + continue + + ( + __type, + __flags, + __tags, + __meta, + __data, + __references, + ) = parse_from_attributes(root_span.attributes or {}) + + if not __data: + continue + + _references = InvocationReferences( + **{ + key: Reference( + id=ref.get("id"), + slug=ref.get("slug"), + version=ref.get("version"), + ) + for key, ref in (__references or {}).items() + } + ) + + _links = dict( + **{ + str(link.attributes["key"]): Link( + trace_id=link.trace_id, + span_id=link.span_id, + ) + for link in root_span.links or [] + if link.attributes and "key" in link.attributes + } + ) + + _origin = InvocationOrigin.CUSTOM + + _kind = ( + ( + __flags.get("is_evaluation") + and InvocationKind.EVAL + or InvocationKind.ADHOC + ) + if __flags + else InvocationKind.ADHOC + ) + + _channel = ( + ( + __flags.get("is_sdk") + and InvocationChannel.SDK + or __flags.get("is_web") + and InvocationChannel.WEB + or InvocationChannel.API + ) + if __flags + else InvocationChannel.API + ) + + invocation = Invocation( + trace_id=root_span.trace_id, + span_id=root_span.span_id, + # + created_at=root_span.created_at, + updated_at=root_span.updated_at, + deleted_at=root_span.deleted_at, + created_by_id=root_span.created_by_id, + updated_by_id=root_span.updated_by_id, + deleted_by_id=root_span.deleted_by_id, + # + origin=_origin, + kind=_kind, + channel=_channel, + # + tags=__tags, + meta=__meta, + # + data=__data, + # + references=_references, + links=_links, + ) + + invocations.append(invocation) + + return invocations diff --git a/api/oss/src/core/invocations/types.py b/api/oss/src/core/invocations/types.py new file mode 100644 index 0000000000..575855fca1 --- /dev/null +++ b/api/oss/src/core/invocations/types.py @@ -0,0 +1,48 @@ +from typing import Optional + +from oss.src.core.shared.dtos import Reference +from oss.src.core.workflows.dtos import WorkflowFlags +from oss.src.core.tracing.dtos import ( + SimpleTraceOrigin, + SimpleTraceKind, + SimpleTraceChannel, + SimpleTraceReferences, + SimpleTraceLinks, + # + SimpleTrace, + SimpleTraceCreate, + SimpleTraceEdit, + SimpleTraceQuery, +) + + +InvocationOrigin = SimpleTraceOrigin +InvocationKind = SimpleTraceKind +InvocationChannel = SimpleTraceChannel +InvocationLinks = SimpleTraceLinks + + +class InvocationFlags(WorkflowFlags): + is_sdk: Optional[bool] = None + is_web: Optional[bool] = None + is_evaluation: Optional[bool] = None + + +class InvocationReferences(SimpleTraceReferences): + application: Reference # type: ignore + + +class Invocation(SimpleTrace): + links: Optional[InvocationLinks] + + +class InvocationCreate(SimpleTraceCreate): + links: Optional[InvocationLinks] + + +class InvocationEdit(SimpleTraceEdit): + pass + + +class InvocationQuery(SimpleTraceQuery): + pass diff --git a/api/oss/src/core/invocations/utils.py b/api/oss/src/core/invocations/utils.py new file mode 100644 index 0000000000..0ad3df24d2 --- /dev/null +++ b/api/oss/src/core/invocations/utils.py @@ -0,0 +1,57 @@ +from jsonschema import ( + Draft202012Validator, + Draft7Validator, + Draft4Validator, + Draft6Validator, + Draft201909Validator, +) + +from fastapi import status, HTTPException + + +def _get_jsonschema_validator( + format: dict, # pylint: disable=redefined-builtin +): + schema_uri = format.get( + "$schema", + "https://json-schema.org/draft/2020-12/schema", + ) + + if "2020-12" in schema_uri: + return Draft202012Validator + elif "2019-09" in schema_uri: + return Draft201909Validator + elif "draft-07" in schema_uri: + return Draft7Validator + elif "draft-06" in schema_uri: + return Draft6Validator + elif "draft-04" in schema_uri: + return Draft4Validator + return Draft202012Validator # fallback + + +def validate_data_against_schema( + data: dict, + schema: dict, # pylint: disable=redefined-builtin +): + validator_class = _get_jsonschema_validator(schema) + validator = validator_class(schema) + + errors = list(validator.iter_errors(data)) + + if errors: + details = [] + for e in errors: + loc = list(e.absolute_path) + msg = e.message + details.append( + { + "loc": ["body", "invocation", "data"] + loc, + "msg": msg, + "type": "value_error.json_schema", + } + ) + + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=details + ) diff --git a/api/oss/src/core/observability/dtos.py b/api/oss/src/core/observability/dtos.py index 0f4a96f165..bce3b2cdcf 100644 --- a/api/oss/src/core/observability/dtos.py +++ b/api/oss/src/core/observability/dtos.py @@ -5,7 +5,7 @@ from pydantic import BaseModel -from oss.src.core.shared.dtos import LegacyLifecycleDTO +from oss.src.core.shared.dtos import LegacyLifecycleDTO, Windowing ## --- SUB-ENTITIES --- ## @@ -16,10 +16,10 @@ class RootDTO(BaseModel): class TreeType(Enum): - # --- VARIANTS --- # INVOCATION = "invocation" ANNOTATION = "annotation" - # --- VARIANTS --- # + # + UNKNOWN = "unknown" class TreeDTO(BaseModel): @@ -233,10 +233,8 @@ class OTelSpanDTO(BaseModel): ## --- QUERY --- ## -class WindowingDTO(BaseModel): - oldest: Optional[datetime] = None - newest: Optional[datetime] = None - window: Optional[int] = None +class WindowingDTO(Windowing): + pass class LogicalOperator(Enum): @@ -361,6 +359,6 @@ def plus(self, other: "MetricsDTO") -> "MetricsDTO": class BucketDTO(BaseModel): timestamp: datetime - window: int + interval: int total: MetricsDTO error: MetricsDTO diff --git a/api/oss/src/core/observability/utils.py b/api/oss/src/core/observability/utils.py index cb78cd3192..01e72c7825 100644 --- a/api/oss/src/core/observability/utils.py +++ b/api/oss/src/core/observability/utils.py @@ -158,7 +158,7 @@ def parse_ingest_value( ) -> None: try: attributes[key] = to_type(attributes[key]) - except ValueError: + except (ValueError, TypeError): print_exc() log.warn( "Failed to parse attributes:", diff --git a/api/oss/src/core/queries/__init__.py b/api/oss/src/core/queries/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/core/queries/dtos.py b/api/oss/src/core/queries/dtos.py new file mode 100644 index 0000000000..da88948794 --- /dev/null +++ b/api/oss/src/core/queries/dtos.py @@ -0,0 +1,245 @@ +from typing import Optional +from uuid import UUID + +from pydantic import BaseModel, Field + + +from oss.src.core.tracing.dtos import Filtering, Windowing +from oss.src.core.shared.dtos import ( + Identifier, + Slug, + Lifecycle, + Header, + Flags, + Tags, + Meta, +) +from oss.src.core.shared.dtos import sync_alias, AliasConfig +from oss.src.core.git.dtos import ( + Artifact, + ArtifactCreate, + ArtifactEdit, + ArtifactQuery, + ArtifactFork, + RevisionsLog, + Variant, + VariantCreate, + VariantEdit, + VariantQuery, + VariantFork, + Revision, + RevisionCreate, + RevisionEdit, + RevisionQuery, + RevisionCommit, + RevisionFork, +) + + +class QueryIdAlias(AliasConfig): + query_id: Optional[UUID] = None + artifact_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="query_id", + ) + + +class QueryVariantIdAlias(AliasConfig): + query_variant_id: Optional[UUID] = None + variant_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="query_variant_id", + ) + + +class QueryRevisionIdAlias(AliasConfig): + query_revision_id: Optional[UUID] = None + revision_id: Optional[UUID] = Field( + default=None, + exclude=True, + alias="query_revision_id", + ) + + +class Query(Artifact): + pass + + +class QueryCreate(ArtifactCreate): + pass + + +class QueryEdit(ArtifactEdit): + pass + + +class QueryQuery(ArtifactQuery): + pass + + +class QueryVariant( + Variant, + QueryIdAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("query_id", "artifact_id", self) + + +class QueryVariantCreate( + VariantCreate, + QueryIdAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("query_id", "artifact_id", self) + + +class QueryVariantEdit(VariantEdit): + pass + + +class QueryVariantQuery(VariantQuery): + pass + + +class QueryRevisionData(BaseModel): + windowing: Optional[Windowing] = None + filtering: Optional[Filtering] = None + + +class QueryRevision( + Revision, + QueryIdAlias, + QueryVariantIdAlias, +): + data: Optional[QueryRevisionData] = None + + def model_post_init(self, __context) -> None: + sync_alias("query_id", "artifact_id", self) + sync_alias("query_variant_id", "variant_id", self) + + +class QueryRevisionCreate( + RevisionCreate, + QueryIdAlias, + QueryVariantIdAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("query_id", "artifact_id", self) + sync_alias("query_variant_id", "variant_id", self) + + +class QueryRevisionEdit(RevisionEdit): + pass + + +class QueryRevisionQuery(RevisionQuery): + pass + + +class QueryRevisionCommit( + RevisionCommit, + QueryIdAlias, + QueryVariantIdAlias, +): + data: Optional[QueryRevisionData] = None + + def model_post_init(self, __context) -> None: + sync_alias("query_id", "artifact_id", self) + sync_alias("query_variant_id", "variant_id", self) + + +class QueryRevisionsLog( + RevisionsLog, + QueryIdAlias, + QueryVariantIdAlias, + QueryRevisionIdAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("query_id", "artifact_id", self) + sync_alias("query_variant_id", "variant_id", self) + sync_alias("query_revision_id", "revision_id", self) + + +class QueryRevisionFork(RevisionFork): + data: Optional[QueryRevisionData] = None + + +class QueryVariantFork(VariantFork): + pass + + +class QueryVariantForkAlias(AliasConfig): + query_variant: Optional[QueryVariantFork] = None + + variant: Optional[VariantFork] = Field( + default=None, + exclude=True, + alias="query_variant", + ) + + +class QueryRevisionForkAlias(AliasConfig): + query_revision: Optional[QueryRevisionFork] = None + + revision: Optional[RevisionFork] = Field( + default=None, + exclude=True, + alias="query_revision", + ) + + +class QueryFork( + ArtifactFork, + QueryVariantIdAlias, + QueryRevisionIdAlias, + QueryVariantForkAlias, + QueryRevisionForkAlias, +): + def model_post_init(self, __context) -> None: + sync_alias("query_variant", "variant", self) + sync_alias("query_revision", "revision", self) + sync_alias("query_variant_id", "variant_id", self) + sync_alias("query_revision_id", "revision_id", self) + + +class SimpleQuery( + Identifier, + Slug, + Lifecycle, + Header, +): + flags: Optional[Flags] = None + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + data: Optional[QueryRevisionData] = None + + +class SimpleQueryCreate( + Slug, + Header, +): + flags: Optional[Flags] = None + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + data: Optional[QueryRevisionData] = None + + +class SimpleQueryEdit( + Identifier, + Header, +): + flags: Optional[Flags] = None + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + data: Optional[QueryRevisionData] = None + + +class SimpleQueryQuery(BaseModel): + flags: Optional[Flags] = None + tags: Optional[Tags] = None + meta: Optional[Meta] = None diff --git a/api/oss/src/core/queries/service.py b/api/oss/src/core/queries/service.py new file mode 100644 index 0000000000..06189d1ac1 --- /dev/null +++ b/api/oss/src/core/queries/service.py @@ -0,0 +1,1324 @@ +from typing import Optional, List +from uuid import UUID, uuid4 + +from oss.src.utils.logging import get_module_logger + +from oss.src.core.git.interfaces import GitDAOInterface +from oss.src.core.shared.dtos import Reference, Windowing +from oss.src.core.git.dtos import ( + ArtifactCreate, + ArtifactEdit, + ArtifactQuery, + ArtifactFork, + RevisionsLog, + # + VariantCreate, + VariantEdit, + VariantQuery, + # + RevisionCreate, + RevisionEdit, + RevisionQuery, + RevisionCommit, +) +from oss.src.core.queries.dtos import ( + Query, + QueryCreate, + QueryEdit, + QueryQuery, + QueryFork, + # + QueryVariant, + QueryVariantCreate, + QueryVariantEdit, + QueryVariantQuery, + # + QueryRevision, + QueryRevisionCreate, + QueryRevisionEdit, + QueryRevisionQuery, + QueryRevisionCommit, + QueryRevisionsLog, + # + SimpleQuery, + SimpleQueryCreate, + SimpleQueryEdit, + SimpleQueryQuery, +) + + +log = get_module_logger(__name__) + + +class QueriesService: + def __init__( + self, + *, + queries_dao: GitDAOInterface, + ): + self.queries_dao = queries_dao + + ## -- artifacts ------------------------------------------------------------ + + async def create_query( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_create: QueryCreate, + # + query_id: Optional[UUID] = None, + ) -> Optional[Query]: + _artifact_create = ArtifactCreate( + **query_create.model_dump(mode="json"), + ) + + artifact = await self.queries_dao.create_artifact( + project_id=project_id, + user_id=user_id, + # + artifact_create=_artifact_create, + # + artifact_id=query_id, + ) + + if not artifact: + return None + + _query = Query(**artifact.model_dump(mode="json")) + + return _query + + async def fetch_query( + self, + *, + project_id: UUID, + # + query_ref: Reference, + ) -> Optional[Query]: + artifact = await self.queries_dao.fetch_artifact( + project_id=project_id, + # + artifact_ref=query_ref, + ) + + if not artifact: + return None + + _query = Query(**artifact.model_dump(mode="json")) + + return _query + + async def edit_query( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_edit: QueryEdit, + ) -> Optional[Query]: + _artifact_edit = ArtifactEdit( + **query_edit.model_dump(mode="json"), + ) + + artifact = await self.queries_dao.edit_artifact( + project_id=project_id, + user_id=user_id, + # + artifact_edit=_artifact_edit, + ) + + if not artifact: + return None + + _query = Query(**artifact.model_dump(mode="json")) + + return _query + + async def archive_query( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_id: UUID, + ) -> Optional[Query]: + artifact = await self.queries_dao.archive_artifact( + project_id=project_id, + user_id=user_id, + # + artifact_id=query_id, + ) + + if not artifact: + return None + + _query = Query(**artifact.model_dump(mode="json")) + + return _query + + async def unarchive_query( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_id: UUID, + ) -> Optional[Query]: + artifact = await self.queries_dao.unarchive_artifact( + project_id=project_id, + user_id=user_id, + # + artifact_id=query_id, + ) + + if not artifact: + return None + + _query = Query(**artifact.model_dump(mode="json")) + + return _query + + async def query_queries( + self, + *, + project_id: UUID, + # + query: Optional[QueryQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[Query]: + _artifact_query = ( + ArtifactQuery( + **query.model_dump(mode="json", exclude_none=True), + ) + if query + else ArtifactQuery() + ) + + artifacts = await self.queries_dao.query_artifacts( + project_id=project_id, + # + artifact_query=_artifact_query, + # + artifact_refs=query_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + _queries = [ + Query( + **artifact.model_dump(mode="json"), + ) + for artifact in artifacts + ] + + return _queries + + ## ------------------------------------------------------------------------- + + ## -- variants ------------------------------------------------------------- + + async def create_query_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_variant_create: QueryVariantCreate, + ) -> Optional[QueryVariant]: + _variant_create = VariantCreate( + **query_variant_create.model_dump(mode="json"), + ) + + variant = await self.queries_dao.create_variant( + project_id=project_id, + user_id=user_id, + # + variant_create=_variant_create, + ) + + if not variant: + return None + + _query_variant = QueryVariant( + **variant.model_dump(mode="json"), + ) + + return _query_variant + + async def fetch_query_variant( + self, + *, + project_id: UUID, + # + query_ref: Optional[Reference] = None, + query_variant_ref: Optional[Reference] = None, + ) -> Optional[QueryVariant]: + variant = await self.queries_dao.fetch_variant( + project_id=project_id, + # + artifact_ref=query_ref, + variant_ref=query_variant_ref, + ) + + if not variant: + return None + + _query_variant = QueryVariant( + **variant.model_dump(mode="json"), + ) + + return _query_variant + + async def edit_query_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_variant_edit: QueryVariantEdit, + ) -> Optional[QueryVariant]: + _variant_edit = VariantEdit( + **query_variant_edit.model_dump(mode="json"), + ) + + variant = await self.queries_dao.edit_variant( + project_id=project_id, + user_id=user_id, + # + variant_edit=_variant_edit, + ) + + if not variant: + return None + + _query_variant = QueryVariant( + **variant.model_dump(mode="json"), + ) + + return _query_variant + + async def archive_query_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_variant_id: UUID, + ) -> Optional[QueryVariant]: + variant = await self.queries_dao.archive_variant( + project_id=project_id, + user_id=user_id, + # + variant_id=query_variant_id, + ) + + if not variant: + return None + + _query_variant = QueryVariant( + **variant.model_dump(mode="json"), + ) + + return _query_variant + + async def unarchive_query_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_variant_id: UUID, + ) -> Optional[QueryVariant]: + variant = await self.queries_dao.unarchive_variant( + project_id=project_id, + user_id=user_id, + # + variant_id=query_variant_id, + ) + + if not variant: + return None + + _workdlow_variant = QueryVariant( + **variant.model_dump(mode="json"), + ) + + return _workdlow_variant + + async def query_query_variants( + self, + *, + project_id: UUID, + # + query_variant_query: Optional[QueryVariantQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + query_variant_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[QueryVariant]: + _variant_query = ( + VariantQuery( + **query_variant_query.model_dump(mode="json", exclude_none=True), + ) + if query_variant_query + else VariantQuery() + ) + + variants = await self.queries_dao.query_variants( + project_id=project_id, + # + variant_query=_variant_query, + # + artifact_refs=query_refs, + variant_refs=query_variant_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + _query_variants = [ + QueryVariant( + **variant.model_dump(mode="json"), + ) + for variant in variants + ] + + return _query_variants + + ## ......................................................................... + + async def fork_query_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_fork: QueryFork, + ) -> Optional[QueryVariant]: + _artifact_fork = ArtifactFork( + **query_fork.model_dump(mode="json"), + ) + + variant = await self.queries_dao.fork_variant( + project_id=project_id, + user_id=user_id, + # + artifact_fork=_artifact_fork, + ) + + if not variant: + return None + + _query_variant = QueryVariant( + **variant.model_dump(mode="json"), + ) + + return _query_variant + + ## ------------------------------------------------------------------------- + + ## -- revisions ------------------------------------------------------------ + + async def create_query_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_revision_create: QueryRevisionCreate, + ) -> Optional[QueryRevision]: + _revision_create = RevisionCreate( + **query_revision_create.model_dump(mode="json"), + ) + + revision = await self.queries_dao.create_revision( + project_id=project_id, + user_id=user_id, + # + revision_create=_revision_create, + ) + + if not revision: + return None + + _query_revision = QueryRevision( + **revision.model_dump(mode="json"), + ) + + return _query_revision + + async def fetch_query_revision( + self, + *, + project_id: UUID, + # + query_ref: Optional[Reference] = None, + query_variant_ref: Optional[Reference] = None, + query_revision_ref: Optional[Reference] = None, + ) -> Optional[QueryRevision]: + if not query_ref and not query_variant_ref and not query_revision_ref: + return None + + if query_ref and not query_variant_ref and not query_revision_ref: + query_variant = await self.query_query_variants( + project_id=project_id, + # + query_refs=[query_ref], + # + windowing=Windowing(limit=1, order="descending"), + ) + + if not query_variant: + return None + + query_variant_ref = Reference( + id=query_variant[0].id, + slug=query_variant[0].slug, + ) + + revision = await self.queries_dao.fetch_revision( + project_id=project_id, + # + variant_ref=query_variant_ref, + revision_ref=query_revision_ref, + ) + + if not revision: + return None + + _query_revision = QueryRevision( + **revision.model_dump(mode="json"), + ) + + return _query_revision + + async def edit_query_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_revision_edit: QueryRevisionEdit, + ) -> Optional[QueryRevision]: + _query_revision_edit = RevisionEdit( + **query_revision_edit.model_dump(mode="json"), + ) + + revision = await self.queries_dao.edit_revision( + project_id=project_id, + user_id=user_id, + # + revision_edit=_query_revision_edit, + ) + + if not revision: + return None + + _query_revision = QueryRevision( + **revision.model_dump(mode="json"), + ) + + return _query_revision + + async def archive_query_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_revision_id: UUID, + ) -> Optional[QueryRevision]: + revision = await self.queries_dao.archive_revision( + project_id=project_id, + user_id=user_id, + # + revision_id=query_revision_id, + ) + + if not revision: + return None + + _query_revision = QueryRevision( + **revision.model_dump(mode="json"), + ) + + return _query_revision + + async def unarchive_query_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_revision_id: UUID, + ) -> Optional[QueryRevision]: + revision = await self.queries_dao.unarchive_revision( + project_id=project_id, + user_id=user_id, + # + revision_id=query_revision_id, + ) + + if not revision: + return None + + _query_revision = QueryRevision( + **revision.model_dump(mode="json"), + ) + + return _query_revision + + async def query_query_revisions( + self, + *, + project_id: UUID, + # + query_revision: Optional[QueryRevisionQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + query_variant_refs: Optional[List[Reference]] = None, + query_revision_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[QueryRevision]: + _revision_query = ( + RevisionQuery( + **query_revision.model_dump(mode="json", exclude_none=True), + ) + if query_revision + else RevisionQuery() + ) + + revisions = await self.queries_dao.query_revisions( + project_id=project_id, + # + revision_query=_revision_query, + # + artifact_refs=query_refs, + variant_refs=query_variant_refs, + revision_refs=query_revision_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + _query_revisions = [ + QueryRevision( + **revision.model_dump(mode="json"), + ) + for revision in revisions + ] + + return _query_revisions + + ## ......................................................................... + + async def commit_query_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_revision_commit: QueryRevisionCommit, + ) -> Optional[QueryRevision]: + _revision_commit = RevisionCommit( + **query_revision_commit.model_dump(mode="json"), + ) + + if not _revision_commit.artifact_id: + if not _revision_commit.variant_id: + return None + + variant = await self.queries_dao.fetch_variant( + project_id=project_id, + # + variant_ref=Reference(id=_revision_commit.variant_id), + ) + + if not variant: + return None + + _revision_commit.artifact_id = variant.artifact_id + + revision = await self.queries_dao.commit_revision( + project_id=project_id, + user_id=user_id, + # + revision_commit=_revision_commit, + ) + + if not revision: + return None + + _query_revision = QueryRevision( + **revision.model_dump(mode="json"), + ) + + return _query_revision + + async def log_query_revisions( + self, + *, + project_id: UUID, + # + query_revisions_log: QueryRevisionsLog, + # + ) -> List[QueryRevision]: + _revisions_log = RevisionsLog( + **query_revisions_log.model_dump(mode="json"), + ) + + revisions = await self.queries_dao.log_revisions( + project_id=project_id, + # + revisions_log=_revisions_log, + ) + + _query_revisions = [ + QueryRevision( + **revision.model_dump(mode="json"), + ) + for revision in revisions + ] + + return _query_revisions + + ## ------------------------------------------------------------------------- + + +class SimpleQueriesService: + def __init__( + self, + queries_service: QueriesService, + ): + self.queries_service = queries_service + + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + simple_query_create: SimpleQueryCreate, + # + query_id: Optional[UUID] = None, + ) -> Optional[SimpleQuery]: + # ---------------------------------------------------------------------- + # Query + # ---------------------------------------------------------------------- + _query_create = QueryCreate( + slug=simple_query_create.slug, + # + name=simple_query_create.name, + description=simple_query_create.description, + # + flags=simple_query_create.flags, + tags=simple_query_create.tags, + meta=simple_query_create.meta, + ) + + query: Optional[Query] = await self.queries_service.create_query( + project_id=project_id, + user_id=user_id, + # + query_create=_query_create, + # + query_id=query_id, + ) + + if not query: + return None + + # ---------------------------------------------------------------------- + # Query variant + # ---------------------------------------------------------------------- + query_variant_slug = uuid4().hex + + _query_variant_create = QueryVariantCreate( + slug=query_variant_slug, + # + name=simple_query_create.name, + description=simple_query_create.description, + # + flags=simple_query_create.flags, + tags=simple_query_create.tags, + meta=simple_query_create.meta, + # + query_id=query.id, # type: ignore[arg-type] + ) + + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.create_query_variant( + project_id=project_id, + user_id=user_id, + # + query_variant_create=_query_variant_create, + ) + + if not query_variant: + return None + + # ---------------------------------------------------------------------- + # Query revision + # ---------------------------------------------------------------------- + query_revision_slug = uuid4().hex + + _query_revision_commit = QueryRevisionCommit( + slug=query_revision_slug, + # + name=simple_query_create.name, + description=simple_query_create.description, + # + flags=simple_query_create.flags, + tags=simple_query_create.tags, + meta=simple_query_create.meta, + # + data=simple_query_create.data, + # + query_id=query.id, + query_variant_id=query_variant.id, + ) + + query_revision: Optional[ + QueryRevision + ] = await self.queries_service.commit_query_revision( + project_id=project_id, + user_id=user_id, + query_revision_commit=_query_revision_commit, + ) + + if not query_revision: + return None + + # ---------------------------------------------------------------------- + # Simple Query + # ---------------------------------------------------------------------- + simple_query = SimpleQuery( + id=query.id, + slug=query.slug, + # + created_at=query.created_at, + updated_at=query.updated_at, + deleted_at=query.deleted_at, + created_by_id=query.created_by_id, + updated_by_id=query.updated_by_id, + deleted_by_id=query.deleted_by_id, + # + name=query.name, + description=query.description, + # + flags=query.flags, + tags=query.tags, + meta=query.meta, + # + data=query_revision.data, + ) + + return simple_query + + async def fetch( + self, + *, + project_id: UUID, + # + query_id: Optional[UUID] = None, + ) -> Optional[SimpleQuery]: + # ---------------------------------------------------------------------- + # Query + # ---------------------------------------------------------------------- + query_ref = Reference( + id=query_id, + ) + + query: Optional[Query] = await self.queries_service.fetch_query( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query: + return None + + # ---------------------------------------------------------------------- + # Query variant + # ---------------------------------------------------------------------- + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.fetch_query_variant( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query_variant: + return None + + # ---------------------------------------------------------------------- + # Query + # ---------------------------------------------------------------------- + + query_variant_ref = Reference( + id=query_variant.id, + ) + query_revision: Optional[ + QueryRevision + ] = await self.queries_service.fetch_query_revision( + project_id=project_id, + # + query_variant_ref=query_variant_ref, + ) + + if not query_revision: + return None + + # ---------------------------------------------------------------------- + # Simple Query + # ---------------------------------------------------------------------- + + simple_query = SimpleQuery( + id=query.id, + slug=query.slug, + # + created_at=query.created_at, + updated_at=query.updated_at, + deleted_at=query.deleted_at, + created_by_id=query.created_by_id, + updated_by_id=query.updated_by_id, + deleted_by_id=query.deleted_by_id, + # + name=query.name, + description=query.description, + # + flags=query.flags, + tags=query.tags, + meta=query.meta, + # + data=query_revision.data, + ) + + return simple_query + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + simple_query_edit: SimpleQueryEdit, + # + query_id: UUID, + ) -> Optional[SimpleQuery]: + if str(query_id) != str(simple_query_edit.id): + return None + + # ---------------------------------------------------------------------- + # Query + # ---------------------------------------------------------------------- + query_ref = Reference(id=simple_query_edit.id) + + query: Optional[Query] = await self.queries_service.fetch_query( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query: + return None + + _query_edit = QueryEdit( + id=query.id, + # + name=simple_query_edit.name, + description=simple_query_edit.description, + # + flags=simple_query_edit.flags, + tags=simple_query_edit.tags, + meta=simple_query_edit.meta, + ) + + query = await self.queries_service.edit_query( + project_id=project_id, + user_id=user_id, + # + query_edit=_query_edit, + ) + + if not query: + return None + + # ---------------------------------------------------------------------- + # Query variant + # ---------------------------------------------------------------------- + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.fetch_query_variant( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query_variant: + return None + + _query_variant_edit = QueryVariantEdit( + id=query_variant.id, + # + name=simple_query_edit.name, + description=simple_query_edit.description, + # + flags=simple_query_edit.flags, + tags=simple_query_edit.tags, + meta=simple_query_edit.meta, + ) + + query_variant = await self.queries_service.edit_query_variant( + project_id=project_id, + user_id=user_id, + # + query_variant_edit=_query_variant_edit, + ) + + if not query_variant: + return None + + # ---------------------------------------------------------------------- + # Query revision + # ---------------------------------------------------------------------- + query_revision_slug = uuid4().hex + + _query_revision_commit = QueryRevisionCommit( + slug=query_revision_slug, + # + name=simple_query_edit.name, + description=simple_query_edit.description, + # + flags=simple_query_edit.flags, + tags=simple_query_edit.tags, + meta=simple_query_edit.meta, + # + data=simple_query_edit.data, + # + query_id=query.id, + query_variant_id=query_variant.id, + ) + + query_revision: Optional[ + QueryRevision + ] = await self.queries_service.commit_query_revision( + project_id=project_id, + user_id=user_id, + # + query_revision_commit=_query_revision_commit, + ) + + if not query_revision: + return None + + # ---------------------------------------------------------------------- + # Simple Query + # ---------------------------------------------------------------------- + simple_query = SimpleQuery( + id=query.id, + slug=query.slug, + # + created_at=query.created_at, + updated_at=query.updated_at, + deleted_at=query.deleted_at, + created_by_id=query.created_by_id, + updated_by_id=query.updated_by_id, + deleted_by_id=query.deleted_by_id, + # + name=query.name, + description=query.description, + # + flags=query.flags, + tags=query.tags, + meta=query.meta, + # + data=query_revision.data, + ) + + return simple_query + + async def query( + self, + *, + project_id: UUID, + # + query: Optional[SimpleQueryQuery] = None, + # + query_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[SimpleQuery]: + # ---------------------------------------------------------------------- + # Queries + # ---------------------------------------------------------------------- + queries: List[Query] = await self.queries_service.query_queries( + project_id=project_id, + # + query=query, + # + query_refs=query_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + if not queries: + return [] + + simple_queries: List[SimpleQuery] = [] + + for query in queries: + # ------------------------------------------------------------------ + # Query variants + # ------------------------------------------------------------------ + query_ref = Reference( + id=query.id, + ) + + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.fetch_query_variant( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query_variant: + continue + + # ------------------------------------------------------------------ + # Query revisions + # ------------------------------------------------------------------ + query_variant_ref = Reference( + id=query_variant.id, + ) + + query_revision: Optional[ + QueryRevision + ] = await self.queries_service.fetch_query_revision( + project_id=project_id, + # + query_variant_ref=query_variant_ref, + ) + + if not query_revision: + continue + + # ------------------------------------------------------------------ + # Simple Queries + # ------------------------------------------------------------------ + simple_query = SimpleQuery( + id=query.id, + slug=query.slug, + # + created_at=query.created_at, + updated_at=query.updated_at, + deleted_at=query.deleted_at, + created_by_id=query.created_by_id, + updated_by_id=query.updated_by_id, + deleted_by_id=query.deleted_by_id, + # + name=query.name, + description=query.description, + # + flags=query.flags, + tags=query.tags, + meta=query.meta, + # + data=query_revision.data, + ) + + simple_queries.append(simple_query) + + return simple_queries + + async def archive( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_id: Optional[UUID] = None, + ) -> Optional[SimpleQuery]: + # ---------------------------------------------------------------------- + # Query + # ---------------------------------------------------------------------- + query_ref = Reference( + id=query_id, + ) + + query: Optional[Query] = await self.queries_service.fetch_query( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query: + return None + + query: Optional[Query] = await self.queries_service.archive_query( + project_id=project_id, + user_id=user_id, + # + query_id=query_id, + ) + + if not query: + return None + + # ---------------------------------------------------------------------- + # Query variant + # ---------------------------------------------------------------------- + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.fetch_query_variant( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query_variant: + return None + + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.archive_query_variant( + project_id=project_id, + user_id=user_id, + # + query_variant_id=query_variant.id, + ) + + if not query_variant: + return None + + # ---------------------------------------------------------------------- + # Simple Query + # ---------------------------------------------------------------------- + simple_query = SimpleQuery( + id=query.id, + slug=query.slug, + # + created_at=query.created_at, + updated_at=query.updated_at, + deleted_at=query.deleted_at, + created_by_id=query.created_by_id, + updated_by_id=query.updated_by_id, + deleted_by_id=query.deleted_by_id, + # + name=query.name, + description=query.description, + # + flags=query.flags, + tags=query.tags, + meta=query.meta, + ) + + return simple_query + + async def unarchive( + self, + *, + project_id: UUID, + user_id: UUID, + # + query_id: Optional[UUID] = None, + ) -> Optional[SimpleQuery]: + # ---------------------------------------------------------------------- + # Query + # ---------------------------------------------------------------------- + query_ref = Reference( + id=query_id, + ) + + query: Optional[Query] = await self.queries_service.fetch_query( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query: + return None + + query: Optional[Query] = await self.queries_service.unarchive_query( + project_id=project_id, + user_id=user_id, + # + query_id=query_id, + ) + + if not query: + return None + + # ---------------------------------------------------------------------- + # Query variant + # ---------------------------------------------------------------------- + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.fetch_query_variant( + project_id=project_id, + # + query_ref=query_ref, + ) + + if not query_variant: + return None + + query_variant: Optional[ + QueryVariant + ] = await self.queries_service.unarchive_query_variant( + project_id=project_id, + user_id=user_id, + # + query_variant_id=query_variant.id, + ) + + if not query_variant: + return None + + # ---------------------------------------------------------------------- + # Simple Query + # ---------------------------------------------------------------------- + simple_query = SimpleQuery( + id=query.id, + slug=query.slug, + # + created_at=query.created_at, + updated_at=query.updated_at, + deleted_at=query.deleted_at, + created_by_id=query.created_by_id, + updated_by_id=query.updated_by_id, + deleted_by_id=query.deleted_by_id, + # + name=query.name, + description=query.description, + # + flags=query.flags, + tags=query.tags, + meta=query.meta, + ) + + return simple_query diff --git a/api/oss/src/core/secrets/utils.py b/api/oss/src/core/secrets/utils.py index 02019e2898..9edc02035f 100644 --- a/api/oss/src/core/secrets/utils.py +++ b/api/oss/src/core/secrets/utils.py @@ -53,11 +53,11 @@ async def get_user_llm_providers_secrets(project_id: str) -> Dict[str, Any]: return readable_secrets -async def get_llm_providers_secrets(provider_id: str) -> Dict[str, Any]: +async def get_llm_providers_secrets(project_id: str) -> Dict[str, Any]: """ Fetches LLM providers secrets from system and vault. """ system_llm_secrets = await get_system_llm_providers_secrets() - user_llm_secrets = await get_user_llm_providers_secrets(provider_id) + user_llm_secrets = await get_user_llm_providers_secrets(project_id) return {**system_llm_secrets, **user_llm_secrets} diff --git a/api/oss/src/core/services/__init__.py b/api/oss/src/core/services/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/core/services/registry.py b/api/oss/src/core/services/registry.py new file mode 100644 index 0000000000..2b87657ba9 --- /dev/null +++ b/api/oss/src/core/services/registry.py @@ -0,0 +1,45 @@ +from oss.src.core.services.v0 import ( + auto_exact_match_v0, + auto_regex_test_v0, + field_match_test_v0, + auto_webhook_test_v0, + auto_custom_code_run_v0, + auto_ai_critique_v0, + auto_starts_with_v0, + auto_ends_with_v0, + auto_contains_v0, + auto_contains_any_v0, + auto_contains_all_v0, + auto_contains_json_v0, + auto_json_diff_v0, + rag_faithfulness_v0, + rag_context_relevancy_v0, + auto_levenshtein_distance_v0, + auto_similarity_match_v0, + auto_semantic_similarity_v0, +) + +REGISTRY = { + "agenta": { + "built-in": { + "auto_exact_match": {"v0": auto_exact_match_v0}, + "auto_regex_test": {"v0": auto_regex_test_v0}, + "field_match_test": {"v0": field_match_test_v0}, + "auto_webhook_test": {"v0": auto_webhook_test_v0}, + "auto_custom_code_run": {"v0": auto_custom_code_run_v0}, + "auto_ai_critique": {"v0": auto_ai_critique_v0}, + "auto_starts_with": {"v0": auto_starts_with_v0}, + "auto_ends_with": {"v0": auto_ends_with_v0}, + "auto_contains": {"v0": auto_contains_v0}, + "auto_contains_any": {"v0": auto_contains_any_v0}, + "auto_contains_all": {"v0": auto_contains_all_v0}, + "auto_contains_json": {"v0": auto_contains_json_v0}, + "auto_json_diff": {"v0": auto_json_diff_v0}, + "rag_faithfulness": {"v0": rag_faithfulness_v0}, + "rag_context_relevancy": {"v0": rag_context_relevancy_v0}, + "auto_levenshtein_distance": {"v0": auto_levenshtein_distance_v0}, + "auto_similarity_match": {"v0": auto_similarity_match_v0}, + "auto_semantic_similarity": {"v0": auto_semantic_similarity_v0}, + }, + }, +} diff --git a/api/oss/src/core/services/utils.py b/api/oss/src/core/services/utils.py new file mode 100644 index 0000000000..32fcee38b1 --- /dev/null +++ b/api/oss/src/core/services/utils.py @@ -0,0 +1,17 @@ +from typing import Optional, Tuple + + +async def parse_service_uri( + uri: str, +) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]: + if not uri or not uri.strip(): + return None, None, None, None + + # uri ~ [|empty|'custom']:'built-in'|'custom'::[|'latest'|empty] + + parts = uri.split(":") + + if len(parts) != 4: + return None, None, None, None + + return (parts[0], parts[1], parts[2], parts[3]) diff --git a/api/oss/src/core/workflows/utils.py b/api/oss/src/core/services/v0.py similarity index 93% rename from api/oss/src/core/workflows/utils.py rename to api/oss/src/core/services/v0.py index 68334d5854..0c25956a28 100644 --- a/api/oss/src/core/workflows/utils.py +++ b/api/oss/src/core/services/v0.py @@ -21,7 +21,7 @@ from oss.src.utils.logging import get_module_logger -from oss.src.apis.fastapi.workflows.models import ( +from oss.src.core.workflows.dtos import ( WorkflowServiceRequest, WorkflowRevision, ) @@ -30,7 +30,6 @@ Data, Trace, Tree, - VersionedTree, ) from oss.src.core.workflows.errors import ( @@ -119,11 +118,13 @@ async def auto_exact_match_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -173,11 +174,13 @@ async def auto_regex_test_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -242,11 +245,13 @@ async def field_match_test_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -314,11 +319,13 @@ async def auto_webhook_test_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -433,11 +440,13 @@ async def auto_custom_code_run_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -521,16 +530,20 @@ async def auto_ai_critique_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # secrets: Optional[dict] = None, ) -> Data: + # return {"score": 0.75, "success": True} + """ AI critique evaluator for using an LLM to evaluate trace_outputs. @@ -609,8 +622,12 @@ async def auto_ai_critique_v0( context = { **inputs, + # "prediction": trace_outputs, "ground_truth": correct_answer, + # + "trace_inputs": trace_inputs, + "trace_outputs": trace_outputs, } try: @@ -675,11 +692,13 @@ async def auto_starts_with_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -736,11 +755,13 @@ async def auto_ends_with_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -797,11 +818,13 @@ async def auto_contains_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -858,11 +881,13 @@ async def auto_contains_any_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -928,11 +953,13 @@ async def auto_contains_all_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -998,11 +1025,13 @@ async def auto_contains_json_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -1049,11 +1078,13 @@ async def auto_json_diff_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -1145,11 +1176,13 @@ async def rag_faithfulness_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -1188,7 +1221,7 @@ async def rag_faithfulness_v0( # Check for SDK version compatibility if tree is not None and isinstance(tree, str): raise InvalidParametersV0Error( - expected="VersionedTree or None", got="str (outdated SDK version)" + expected="Tree or None", got="str (outdated SDK version)" ) # Extract trace version and structure @@ -1220,7 +1253,7 @@ def extract_spans_from_v2_trace(trace: Trace): # Process trace into trace tree nodes = ( trace.model_dump() - if isinstance(trace, VersionedTree) + if isinstance(trace, Tree) else ( [span.model_dump() for span in extract_spans_from_v2_trace(trace)] if isinstance(trace, Trace) # type: ignore @@ -1287,11 +1320,13 @@ async def rag_context_relevancy_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -1329,7 +1364,7 @@ async def rag_context_relevancy_v0( # Check for SDK version compatibility if tree is not None and isinstance(tree, str): raise InvalidParametersV0Error( - expected="VersionedTree or None", got="str (outdated SDK version)" + expected="Tree or None", got="str (outdated SDK version)" ) # Extract trace version and structure @@ -1361,7 +1396,7 @@ def extract_spans_from_v2_trace(trace: Trace): # Process trace into trace tree nodes = ( trace.model_dump() - if isinstance(trace, VersionedTree) + if isinstance(trace, Tree) else ( [span.model_dump() for span in extract_spans_from_v2_trace(trace)] if isinstance(trace, Trace) # type: ignore @@ -1428,11 +1463,13 @@ async def auto_levenshtein_distance_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -1539,11 +1576,13 @@ async def auto_similarity_match_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # @@ -1635,11 +1674,13 @@ async def auto_semantic_similarity_v0( revision: WorkflowRevision, request: WorkflowServiceRequest, # - parameters: Data, - inputs: Data, - trace_outputs: Data | str, + parameters: Optional[Data] = None, + inputs: Optional[Data] = None, # trace_parameters: Optional[Data] = None, + trace_inputs: Optional[Data] = None, + trace_outputs: Optional[Data | str] = None, + # trace: Optional[Trace] = None, tree: Optional[Tree] = None, # diff --git a/api/oss/src/core/shared/dtos.py b/api/oss/src/core/shared/dtos.py index e368568e88..8749f13b41 100644 --- a/api/oss/src/core/shared/dtos.py +++ b/api/oss/src/core/shared/dtos.py @@ -8,42 +8,42 @@ from typing_extensions import TypeAliasType -BoolJson: TypeAliasType = TypeAliasType( +BoolJson: TypeAliasType = TypeAliasType( # type: ignore "BoolJson", - Union[bool, Dict[str, "BoolJson"]], + Union[bool, Dict[str, "BoolJson"]], # type: ignore ) -StringJson: TypeAliasType = TypeAliasType( +StringJson: TypeAliasType = TypeAliasType( # type: ignore "StringJson", - Union[str, Dict[str, "StringJson"]], + Union[str, Dict[str, "StringJson"]], # type: ignore ) -FullJson: TypeAliasType = TypeAliasType( +FullJson: TypeAliasType = TypeAliasType( # type: ignore "FullJson", - Union[str, int, float, bool, None, Dict[str, "FullJson"], List["FullJson"]], + Union[str, int, float, bool, None, Dict[str, "FullJson"], List["FullJson"]], # type: ignore ) -NumericJson: TypeAliasType = TypeAliasType( +NumericJson: TypeAliasType = TypeAliasType( # type: ignore "NumericJson", - Union[int, float, Dict[str, "NumericJson"]], + Union[int, float, Dict[str, "NumericJson"]], # type: ignore ) -NoListJson: TypeAliasType = TypeAliasType( +NoListJson: TypeAliasType = TypeAliasType( # type: ignore "NoListJson", - Union[str, int, float, bool, None, Dict[str, "NoListJson"]], + Union[str, int, float, bool, None, Dict[str, "NoListJson"]], # type: ignore ) -Json = Dict[str, FullJson] +Json = Dict[str, FullJson] # type: ignore -Data = Dict[str, FullJson] +Data = Dict[str, FullJson] # type: ignore -Meta = Dict[str, FullJson] +Flags = Dict[str, bool | str] # type: ignore -Tags = Dict[str, NoListJson] +Tags = Dict[str, NoListJson] # type: ignore -Flags = Dict[str, bool | str] +Meta = Dict[str, FullJson] # type: ignore -Hashes = Dict[str, StringJson] +Hashes = Dict[str, StringJson] # type: ignore class Metadata(BaseModel): @@ -53,11 +53,31 @@ class Metadata(BaseModel): class Windowing(BaseModel): + # RANGE + newest: Optional[datetime] = None + oldest: Optional[datetime] = None + # TOKEN next: Optional[UUID] = None - start: Optional[datetime] = None - stop: Optional[datetime] = None + # LIMIT limit: Optional[int] = None + # ORDER order: Optional[Literal["ascending", "descending"]] = None + # BUCKETS + interval: Optional[int] = None + # SAMPLES + rate: Optional[float] = None + + @field_validator("rate") + def check_rate(cls, v): + if v is not None and (v < 0.0 or v > 1.0): + raise ValueError("Sampling rate must be between 0.0 and 1.0.") + return v + + @field_validator("interval") + def check_interval(cls, v): + if v is not None and v <= 0: + raise ValueError("Bucket interval must be a positive integer.") + return v class Lifecycle(BaseModel): @@ -86,7 +106,7 @@ class Slug(BaseModel): slug: Optional[str] = None @field_validator("slug") - def check_url_safety(cls, v): # pylint: disable=no-self-argument + def check_url_safety(cls, v): if v is not None: if not match(r"^[a-zA-Z0-9_-]+$", v): raise ValueError("slug must be URL-safe.") @@ -102,6 +122,12 @@ class Header(BaseModel): description: Optional[str] = None +class Commit(BaseModel): + author: Optional[UUID] = None + date: Optional[datetime] = None + message: Optional[str] = None + + class Reference(Identifier, Slug, Version): pass @@ -129,7 +155,7 @@ class AliasConfig(BaseModel): # LEGACY ----------------------------------------------------------------------- -Metrics = Dict[str, NumericJson] +Metrics = Dict[str, NumericJson] # type: ignore class LegacyLifecycleDTO(BaseModel): @@ -152,4 +178,4 @@ class Status(BaseModel): Mappings = Dict[str, str] -Schema = Dict[str, FullJson] +Schema = Dict[str, FullJson] # type: ignore diff --git a/api/oss/src/core/testcases/service.py b/api/oss/src/core/testcases/service.py index 3d7b24d982..3afd298dd4 100644 --- a/api/oss/src/core/testcases/service.py +++ b/api/oss/src/core/testcases/service.py @@ -4,7 +4,7 @@ from oss.src.utils.logging import get_module_logger from oss.src.core.blobs.interfaces import BlobsDAOInterface -from oss.src.core.blobs.dtos import BlobCreate, BlobQuery +from oss.src.core.blobs.dtos import BlobCreate, BlobQuery, Windowing from oss.src.core.testcases.dtos import Testcase log = get_module_logger(__name__) @@ -14,11 +14,11 @@ class TestcasesService: def __init__( self, *, - blobs_dao: BlobsDAOInterface, + testcases_dao: BlobsDAOInterface, ): - self.blobs_dao = blobs_dao + self.testcases_dao = testcases_dao - async def add_testcases( + async def create_testcases( self, *, project_id: UUID, @@ -33,7 +33,7 @@ async def add_testcases( for testcase in testcases ] - blobs = await self.blobs_dao.add_blobs( + blobs = await self.testcases_dao.add_blobs( project_id=project_id, user_id=user_id, # @@ -61,7 +61,7 @@ async def fetch_testcases( # testset_id: Optional[UUID] = None, # - windowing: Optional[bool] = False, + windowing: Optional[Windowing] = None, ) -> List[Testcase]: _blob_query = ( BlobQuery( @@ -72,7 +72,7 @@ async def fetch_testcases( else BlobQuery() ) - blobs = await self.blobs_dao.query_blobs( + blobs = await self.testcases_dao.query_blobs( project_id=project_id, # blob_query=_blob_query, diff --git a/api/oss/src/core/testsets/dtos.py b/api/oss/src/core/testsets/dtos.py index 3e44ca7ad2..df0915f2b2 100644 --- a/api/oss/src/core/testsets/dtos.py +++ b/api/oss/src/core/testsets/dtos.py @@ -3,8 +3,17 @@ from pydantic import BaseModel, Field -# from oss.src.core.shared.dtos import Link -from oss.src.core.shared.dtos import sync_alias, AliasConfig +from oss.src.core.shared.dtos import ( + sync_alias, + AliasConfig, +) +from oss.src.core.shared.dtos import ( + Identifier, + Slug, + Lifecycle, + Header, + Metadata, +) from oss.src.core.git.dtos import ( Artifact, ArtifactCreate, @@ -17,13 +26,15 @@ VariantQuery, # Revision, + RevisionsLog, RevisionCreate, RevisionEdit, RevisionQuery, RevisionCommit, ) - -from oss.src.core.testcases.dtos import Testcase +from oss.src.core.testcases.dtos import ( + Testcase, +) class TestsetIdAlias(AliasConfig): @@ -53,6 +64,18 @@ class TestsetRevisionIdAlias(AliasConfig): ) +class TestsetLog( + RevisionsLog, + TestsetVariantIdAlias, + TestsetRevisionIdAlias, +): + testset_variant_id: Optional[UUID] = None + + def model_post_init(self, _context) -> None: + sync_alias("testset_variant_id", "variant_id", self) + sync_alias("testset_revision_id", "revision_id", self) + + class TestsetFlags(BaseModel): has_testcases: Optional[bool] = None has_traces: Optional[bool] = None @@ -157,3 +180,25 @@ class TestsetRevisionCommit( def model_post_init(self, __context) -> None: sync_alias("testset_id", "artifact_id", self) sync_alias("testset_variant_id", "variant_id", self) + + +class SimpleTestset(Identifier, Slug, Lifecycle, Header, Metadata): + flags: Optional[TestsetFlags] = None # type: ignore + + data: Optional[TestsetRevisionData] = None + + +class SimpleTestsetCreate(Slug, Header, Metadata): + flags: Optional[TestsetFlags] = None # type: ignore + + data: Optional[TestsetRevisionData] = None + + +class SimpleTestsetEdit(Identifier, Header, Metadata): + flags: Optional[TestsetFlags] = None # type: ignore + + data: Optional[TestsetRevisionData] = None + + +class SimpleTestsetQuery(Metadata): + flags: Optional[TestsetFlags] = None # type: ignore diff --git a/api/oss/src/core/testsets/service.py b/api/oss/src/core/testsets/service.py index 8d15a39fbe..348e18bd2a 100644 --- a/api/oss/src/core/testsets/service.py +++ b/api/oss/src/core/testsets/service.py @@ -1,5 +1,5 @@ from typing import Optional, List -from uuid import UUID +from uuid import UUID, uuid4 from oss.src.utils.logging import get_module_logger from oss.src.core.git.interfaces import GitDAOInterface @@ -20,11 +20,24 @@ RevisionQuery, RevisionCommit, ) +from oss.src.models.db_models import TestSetDB +from oss.src.core.testcases.dtos import Testcase +from oss.src.services.db_manager import fetch_testset_by_id +from oss.src.utils.helpers import get_slug_from_name_and_id +from oss.src.apis.fastapi.testsets.models import ( + SimpleTestset, + SimpleTestsetCreate, + SimpleTestsetEdit, + # + SimpleTestsetCreateRequest, + SimpleTestsetEditRequest, +) from oss.src.core.testsets.dtos import ( Testset, TestsetCreate, TestsetEdit, TestsetQuery, + TestsetLog, # TestsetVariant, TestsetVariantCreate, @@ -32,11 +45,22 @@ TestsetVariantQuery, # TestsetRevision, + TestsetRevisionData, TestsetRevisionCreate, TestsetRevisionEdit, TestsetRevisionQuery, TestsetRevisionCommit, ) +from oss.src.apis.fastapi.testsets.utils import ( + csv_file_to_json_array, + json_file_to_json_array, + json_array_to_json_object, + json_array_to_csv_file, + json_array_to_json_file, + validate_testset_limits, + TESTSETS_SIZE_EXCEPTION, + TESTSETS_SIZE_LIMIT, +) log = get_module_logger(__name__) @@ -63,15 +87,17 @@ async def create_testset( # testset_id: Optional[UUID] = None, ) -> Optional[Testset]: - _artifact_create = ArtifactCreate( - **testset_create.model_dump(mode="json"), + artifact_create = ArtifactCreate( + **testset_create.model_dump( + mode="json", + ), ) artifact = await self.testsets_dao.create_artifact( project_id=project_id, user_id=user_id, # - artifact_create=_artifact_create, + artifact_create=artifact_create, # artifact_id=testset_id, ) @@ -79,11 +105,13 @@ async def create_testset( if not artifact: return None - _testset = Testset( - **artifact.model_dump(mode="json"), + testset = Testset( + **artifact.model_dump( + mode="json", + ), ) - return _testset + return testset async def fetch_testset( self, @@ -101,11 +129,13 @@ async def fetch_testset( if not artifact: return None - _testset = Testset( - **artifact.model_dump(mode="json"), + testset = Testset( + **artifact.model_dump( + mode="json", + ), ) - return _testset + return testset async def edit_testset( self, @@ -115,25 +145,29 @@ async def edit_testset( # testset_edit: TestsetEdit, ) -> Optional[Testset]: - _artifact_edit = ArtifactEdit( - **testset_edit.model_dump(mode="json"), + artifact_edit = ArtifactEdit( + **testset_edit.model_dump( + mode="json", + ), ) artifact = await self.testsets_dao.edit_artifact( project_id=project_id, user_id=user_id, # - artifact_edit=_artifact_edit, + artifact_edit=artifact_edit, ) if not artifact: return None - _testset = Testset( - **artifact.model_dump(mode="json"), + testset = Testset( + **artifact.model_dump( + mode="json", + ), ) - return _testset + return testset async def archive_testset( self, @@ -153,11 +187,13 @@ async def archive_testset( if not artifact: return None - _testset = Testset( - **artifact.model_dump(mode="json"), + testset = Testset( + **artifact.model_dump( + mode="json", + ), ) - return _testset + return testset async def unarchive_testset( self, @@ -177,18 +213,20 @@ async def unarchive_testset( if not artifact: return None - _testset = Testset( - **artifact.model_dump(mode="json"), + testset = Testset( + **artifact.model_dump( + mode="json", + ), ) - return _testset + return testset async def query_testsets( self, *, project_id: UUID, # - testset_query: TestsetQuery, + testset_query: Optional[TestsetQuery] = None, # testset_refs: Optional[List[Reference]] = None, # @@ -196,9 +234,11 @@ async def query_testsets( # windowing: Optional[Windowing] = None, ) -> List[Testset]: - _artifact_query = ( + artifact_query = ( ArtifactQuery( - **testset_query.model_dump(mode="json"), + **testset_query.model_dump( + mode="json", + ), ) if testset_query else ArtifactQuery() @@ -207,7 +247,7 @@ async def query_testsets( artifacts = await self.testsets_dao.query_artifacts( project_id=project_id, # - artifact_query=_artifact_query, + artifact_query=artifact_query, # artifact_refs=testset_refs, # @@ -216,14 +256,16 @@ async def query_testsets( windowing=windowing, ) - _testsets = [ + testsets = [ Testset( - **artifact.model_dump(mode="json"), + **artifact.model_dump( + mode="json", + ), ) for artifact in artifacts ] - return _testsets + return testsets ## ------------------------------------------------------------------------- @@ -237,25 +279,29 @@ async def create_testset_variant( # testset_variant_create: TestsetVariantCreate, ) -> Optional[TestsetVariant]: - _variant_create = VariantCreate( - **testset_variant_create.model_dump(mode="json"), + variant_create = VariantCreate( + **testset_variant_create.model_dump( + mode="json", + ), ) variant = await self.testsets_dao.create_variant( project_id=project_id, user_id=user_id, # - variant_create=_variant_create, + variant_create=variant_create, ) if not variant: return None - _testset_variant = TestsetVariant( - **variant.model_dump(mode="json"), + testset_variant = TestsetVariant( + **variant.model_dump( + mode="json", + ), ) - return _testset_variant + return testset_variant async def fetch_testset_variant( self, @@ -275,11 +321,13 @@ async def fetch_testset_variant( if not variant: return None - _testset_variant = TestsetVariant( - **variant.model_dump(mode="json"), + testset_variant = TestsetVariant( + **variant.model_dump( + mode="json", + ), ) - return _testset_variant + return testset_variant async def edit_testset_variant( self, @@ -289,25 +337,29 @@ async def edit_testset_variant( # testset_variant_edit: TestsetVariantEdit, ) -> Optional[TestsetVariant]: - _variant_edit = VariantEdit( - **testset_variant_edit.model_dump(mode="json"), + variant_edit = VariantEdit( + **testset_variant_edit.model_dump( + mode="json", + ), ) variant = await self.testsets_dao.edit_variant( project_id=project_id, user_id=user_id, # - variant_edit=_variant_edit, + variant_edit=variant_edit, ) if not variant: return None - _testset_variant = TestsetVariant( - **variant.model_dump(mode="json"), + testset_variant = TestsetVariant( + **variant.model_dump( + mode="json", + ), ) - return _testset_variant + return testset_variant async def archive_testset_variant( self, @@ -327,11 +379,13 @@ async def archive_testset_variant( if not variant: return None - _testset_variant = TestsetVariant( - **variant.model_dump(mode="json"), + testset_variant = TestsetVariant( + **variant.model_dump( + mode="json", + ), ) - return _testset_variant + return testset_variant async def unarchive_testset_variant( self, @@ -351,45 +405,55 @@ async def unarchive_testset_variant( if not variant: return None - _testset_variant = TestsetVariant( - **variant.model_dump(mode="json"), + testset_variant = TestsetVariant( + **variant.model_dump( + mode="json", + ), ) - return _testset_variant + return testset_variant async def query_variants( self, *, project_id: UUID, # - testset_variant_query: TestsetVariantQuery, + testset_variant_query: Optional[TestsetVariantQuery] = None, # include_archived: Optional[bool] = None, # windowing: Optional[Windowing] = None, ) -> List[TestsetVariant]: - _testset_variant_query = VariantQuery( - **testset_variant_query.model_dump(mode="json"), + variant_query = ( + VariantQuery( + **testset_variant_query.model_dump( + mode="json", + ), + ) + if testset_variant_query + else VariantQuery() ) variants = await self.testsets_dao.query_variants( project_id=project_id, # - variant_query=_testset_variant_query, + variant_query=variant_query, # include_archived=include_archived, # windowing=windowing, ) - _testset_variants = [ + testset_variants = [ TestsetVariant( - **variant.model_dump(mode="json"), + **variant.model_dump( + mode="json", + ), ) for variant in variants ] - return _testset_variants + return testset_variants ## ------------------------------------------------------------------------- @@ -403,32 +467,36 @@ async def create_testset_revision( # testset_revision_create: TestsetRevisionCreate, ) -> Optional[TestsetRevision]: - _revision_create = RevisionCreate( - **testset_revision_create.model_dump(mode="json"), + revision_create = RevisionCreate( + **testset_revision_create.model_dump( + mode="json", + ), ) revision = await self.testsets_dao.create_revision( project_id=project_id, user_id=user_id, # - revision_create=_revision_create, + revision_create=revision_create, ) if not revision: return None - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - if _testset_revision.data and _testset_revision.data.testcase_ids: - _testset_revision.data.testcases = await self.testcases_service.fetch_testcases( + if testset_revision.data and testset_revision.data.testcase_ids: + testset_revision.data.testcases = await self.testcases_service.fetch_testcases( project_id=project_id, # - testcase_ids=_testset_revision.data.testcase_ids, + testcase_ids=testset_revision.data.testcase_ids, ) - return _testset_revision + return testset_revision async def fetch_testset_revision( self, @@ -439,6 +507,24 @@ async def fetch_testset_revision( testset_variant_ref: Optional[Reference] = None, testset_revision_ref: Optional[Reference] = None, ) -> Optional[TestsetRevision]: + if not testset_ref and not testset_variant_ref and not testset_revision_ref: + return None + + if testset_ref and not testset_variant_ref and not testset_revision_ref: + testset_variant = await self.fetch_testset_variant( + project_id=project_id, + # + testset_ref=testset_ref, + ) + + if not testset_variant: + return None + + testset_variant_ref = Reference( + id=testset_variant.id, + slug=testset_variant.slug, + ) + revision = await self.testsets_dao.fetch_revision( project_id=project_id, # @@ -449,18 +535,20 @@ async def fetch_testset_revision( if not revision: return None - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - if _testset_revision.data and _testset_revision.data.testcase_ids: - _testset_revision.data.testcases = await self.testcases_service.fetch_testcases( + if testset_revision.data and testset_revision.data.testcase_ids: + testset_revision.data.testcases = await self.testcases_service.fetch_testcases( project_id=project_id, # - testcase_ids=_testset_revision.data.testcase_ids, + testcase_ids=testset_revision.data.testcase_ids, ) - return _testset_revision + return testset_revision async def edit_testset_revision( self, @@ -470,32 +558,36 @@ async def edit_testset_revision( # testset_revision_edit: TestsetRevisionEdit, ) -> Optional[TestsetRevision]: - _revision_edit = TestsetRevisionEdit( - **testset_revision_edit.model_dump(mode="json"), + revision_edit = TestsetRevisionEdit( + **testset_revision_edit.model_dump( + mode="json", + ), ) revision = await self.testsets_dao.edit_revision( project_id=project_id, user_id=user_id, # - revision_edit=_revision_edit, + revision_edit=revision_edit, ) if not revision: return None - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - if _testset_revision.data and _testset_revision.data.testcase_ids: - _testset_revision.data.testcases = await self.testcases_service.fetch_testcases( + if testset_revision.data and testset_revision.data.testcase_ids: + testset_revision.data.testcases = await self.testcases_service.fetch_testcases( project_id=project_id, # - testcase_ids=_testset_revision.data.testcase_ids, + testcase_ids=testset_revision.data.testcase_ids, ) - return _testset_revision + return testset_revision async def archive_testset_revision( self, @@ -515,11 +607,13 @@ async def archive_testset_revision( if not revision: return None - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - return _testset_revision + return testset_revision async def unarchive_testset_revision( self, @@ -539,49 +633,59 @@ async def unarchive_testset_revision( if not revision: return None - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - return _testset_revision + return testset_revision async def query_testset_revisions( self, *, project_id: UUID, # - testset_revision_query: TestsetRevisionQuery, + testset_revision_query: Optional[TestsetRevisionQuery], ) -> List[TestsetRevision]: - _revision_query = RevisionQuery( - **testset_revision_query.model_dump(mode="json"), + revision_query = ( + RevisionQuery( + **testset_revision_query.model_dump( + mode="json", + ), + ) + if testset_revision_query + else RevisionQuery() ) revisions = await self.testsets_dao.query_revisions( project_id=project_id, # - revision_query=_revision_query, + revision_query=revision_query, ) if not revisions: return [] - _testset_revisions = [] + testset_revisions = [] for revision in revisions: - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - if _testset_revision.data and _testset_revision.data.testcase_ids: - _testset_revision.data.testcases = await self.testcases_service.fetch_testcases( + if testset_revision.data and testset_revision.data.testcase_ids: + testset_revision.data.testcases = await self.testcases_service.fetch_testcases( project_id=project_id, # - testcase_ids=_testset_revision.data.testcase_ids, + testcase_ids=testset_revision.data.testcase_ids, ) - _testset_revisions.append(_testset_revision) + testset_revisions.append(testset_revision) - return _testset_revisions + return testset_revisions ## ......................................................................... @@ -598,7 +702,7 @@ async def commit_testset_revision( for testcase in testset_revision_commit.data.testcases: testcase.set_id = testset_revision_commit.testset_id - testcases = await self.testcases_service.add_testcases( + testcases = await self.testcases_service.create_testcases( project_id=project_id, user_id=user_id, # @@ -611,7 +715,7 @@ async def commit_testset_revision( testset_revision_commit.data.testcases = None - _revision_commit = RevisionCommit( + revision_commit = RevisionCommit( **testset_revision_commit.model_dump(mode="json", exclude_none=True), ) @@ -619,61 +723,533 @@ async def commit_testset_revision( project_id=project_id, user_id=user_id, # - revision_commit=_revision_commit, + revision_commit=revision_commit, ) if not revision: return None - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - if _testset_revision.data and _testset_revision.data.testcase_ids: - _testset_revision.data.testcases = await self.testcases_service.fetch_testcases( + if testset_revision.data and testset_revision.data.testcase_ids: + testset_revision.data.testcases = await self.testcases_service.fetch_testcases( project_id=project_id, # - testcase_ids=_testset_revision.data.testcase_ids, + testcase_ids=testset_revision.data.testcase_ids, ) - return _testset_revision + return testset_revision async def log_testset_revisions( self, *, project_id: UUID, # - testset_variant_ref: Optional[Reference] = None, - testset_revision_ref: Optional[Reference] = None, + testset_log: TestsetLog, depth: Optional[int] = None, ) -> List[TestsetRevision]: revisions = await self.testsets_dao.log_revisions( project_id=project_id, # - variant_ref=testset_variant_ref, - revision_ref=testset_revision_ref, - depth=depth, + revisions_log=testset_log, ) if not revisions: return [] - _testset_revisions = [] + testset_revisions = [] for revision in revisions: - _testset_revision = TestsetRevision( - **revision.model_dump(mode="json"), + testset_revision = TestsetRevision( + **revision.model_dump( + mode="json", + ), ) - if _testset_revision.data and _testset_revision.data.testcase_ids: - _testset_revision.data.testcases = await self.testcases_service.fetch_testcases( + if testset_revision.data and testset_revision.data.testcase_ids: + testset_revision.data.testcases = await self.testcases_service.fetch_testcases( project_id=project_id, # - testcase_ids=_testset_revision.data.testcase_ids, + testcase_ids=testset_revision.data.testcase_ids, ) - _testset_revisions.append(_testset_revision) + testset_revisions.append(testset_revision) - return _testset_revisions + return testset_revisions ## ------------------------------------------------------------------------- + + +class SimpleTestsetsService: + def __init__( + self, + *, + testsets_service: TestsetsService, + ): + self.testsets_service = testsets_service + + async def create( + self, + *, + project_id: UUID, + user_id: UUID, + # + simple_testset_create_request: SimpleTestsetCreateRequest, + # + testset_id: Optional[UUID] = None, + ): + try: + testcases = simple_testset_create_request.testset.data.testcases + + testcases_data = [testcase.data for testcase in testcases] + + testcases_data = json_array_to_json_object( + data=testcases_data, + ) + + validate_testset_limits(testcases_data) + + for i, testcase_data in enumerate(testcases_data.values()): + simple_testset_create_request.testset.data.testcases[ + i + ].data = testcase_data + + except Exception as e: + return None + + try: + testset_revision_data = TestsetRevisionData( + testcases=simple_testset_create_request.testset.data.testcases, + ) + + except Exception as e: + return None + + testset_create = TestsetCreate( + slug=simple_testset_create_request.testset.slug, + # + name=simple_testset_create_request.testset.name, + description=simple_testset_create_request.testset.description, + # + # flags = + tags=simple_testset_create_request.testset.tags, + meta=simple_testset_create_request.testset.meta, + ) + + testset: Optional[Testset] = await self.testsets_service.create_testset( + project_id=project_id, + user_id=user_id, + # + testset_create=testset_create, + # + testset_id=testset_id, + ) + + if testset is None: + return None + + testset_variant_slug = uuid4().hex + + testset_variant_create = TestsetVariantCreate( + slug=testset_variant_slug, + # + name=simple_testset_create_request.testset.name, + description=simple_testset_create_request.testset.description, + # + # flags = + tags=simple_testset_create_request.testset.tags, + meta=simple_testset_create_request.testset.meta, + # + testset_id=testset.id, + ) + + testset_variant: Optional[ + TestsetVariant + ] = await self.testsets_service.create_testset_variant( + project_id=project_id, + user_id=user_id, + # + testset_variant_create=testset_variant_create, + ) + + if testset_variant is None: + return None + + testset_revision_slug = uuid4().hex + + testset_revision_create = TestsetRevisionCreate( + slug=testset_revision_slug, + # + name=simple_testset_create_request.testset.name, + description=simple_testset_create_request.testset.description, + # + # flags = + tags=simple_testset_create_request.testset.tags, + meta=simple_testset_create_request.testset.meta, + # + testset_id=testset.id, + testset_variant_id=testset_variant.id, + ) + + testset_revision: Optional[ + TestsetRevision + ] = await self.testsets_service.create_testset_revision( + project_id=project_id, + user_id=user_id, + # + testset_revision_create=testset_revision_create, + ) + + if testset_revision is None: + return None + + testset_revision_slug = uuid4().hex + + testset_revision_commit = TestsetRevisionCommit( + slug=testset_revision_slug, + # + name=simple_testset_create_request.testset.name, + description=simple_testset_create_request.testset.description, + # + # flags = + tags=simple_testset_create_request.testset.tags, + meta=simple_testset_create_request.testset.meta, + # + data=testset_revision_data, + # + testset_id=testset.id, + testset_variant_id=testset_variant.id, + ) + + testset_revision = await self.testsets_service.commit_testset_revision( + project_id=project_id, + user_id=user_id, + # + testset_revision_commit=testset_revision_commit, + ) + + if testset_revision is None: + return None + + simple_testset = SimpleTestset( + id=testset.id, + slug=testset.slug, + # + created_at=testset.created_at, + updated_at=testset.updated_at, + deleted_at=testset.deleted_at, + created_by_id=testset.created_by_id, + updated_by_id=testset.updated_by_id, + deleted_by_id=testset.deleted_by_id, + # + name=testset.name, + description=testset.description, + # + # flags = + tags=testset.tags, + meta=testset.meta, + # + data=testset_revision.data, + ) + + return simple_testset + + async def edit( + self, + *, + project_id: UUID, + user_id: UUID, + # + simple_testset_edit_request: SimpleTestsetEditRequest, + ) -> Optional[SimpleTestset]: + try: + testcases = simple_testset_edit_request.testset.data.testcases + + testcases_data = [testcase.data for testcase in testcases] + + testcases_data = json_array_to_json_object( + data=testcases_data, + ) + + validate_testset_limits(testcases_data) + + for i, testcase_data in enumerate(testcases_data.values()): + simple_testset_edit_request.testset.data.testcases[ + i + ].data = testcase_data + + except Exception as e: + return None + + try: + testset_revision_data = TestsetRevisionData( + testcases=testcases, + ) + + except Exception as e: + return None + + testset_ref = Reference( + id=simple_testset_edit_request.testset.id, + ) + + testset: Optional[Testset] = await self.testsets_service.fetch_testset( + project_id=project_id, + # + testset_ref=testset_ref, + ) + + if testset is None: + return None + + has_changes = ( + testset.name != simple_testset_edit_request.testset.name + or testset.description != simple_testset_edit_request.testset.description + or testset.tags != simple_testset_edit_request.testset.tags + or testset.meta != simple_testset_edit_request.testset.meta + ) + + if has_changes: + testset_edit = TestsetEdit( + id=testset.id, + # + name=simple_testset_edit_request.testset.name, + description=simple_testset_edit_request.testset.description, + # + # flags = + tags=simple_testset_edit_request.testset.tags, + meta=simple_testset_edit_request.testset.meta, + ) + + testset: Optional[Testset] = await self.testsets_service.edit_testset( # type: ignore + project_id=project_id, + user_id=user_id, + # + testset_edit=testset_edit, + ) + + if testset is None: + return None + + testset_variant: Optional[ + TestsetVariant + ] = await self.testsets_service.fetch_testset_variant( + project_id=project_id, + # + testset_ref=testset_ref, + ) + + if testset_variant is None: + return None + + has_changes = ( + testset_variant.name != simple_testset_edit_request.testset.name + or testset_variant.description + != simple_testset_edit_request.testset.description + or testset_variant.tags != simple_testset_edit_request.testset.tags + or testset_variant.meta != simple_testset_edit_request.testset.meta + ) + + if has_changes: + testset_variant_edit = TestsetVariant( + id=testset_variant.id, + # + name=simple_testset_edit_request.testset.name, + description=simple_testset_edit_request.testset.description, + # + # flags = + tags=simple_testset_edit_request.testset.tags, + meta=simple_testset_edit_request.testset.meta, + ) + + testset_variant: Optional[TestsetVariant] = ( # type: ignore + await self.testsets_service.edit_testset_variant( + project_id=project_id, + user_id=user_id, + # + testset_variant_edit=testset_variant_edit, + ) + ) + + if testset_variant is None: + return None + + testset_variant_ref = Reference( + id=testset_variant.id, + ) + + testset_revision: Optional[ + TestsetRevision + ] = await self.testsets_service.fetch_testset_revision( + project_id=project_id, + # + testset_variant_ref=testset_variant_ref, + ) + + if testset_revision is None: + return None + + old_testcase_ids = [ + testcase.data for testcase in testset_revision.data.testcases + ] + + new_testcase_ids = [ + testcase.data + for testcase in simple_testset_edit_request.testset.data.testcases + ] + + has_changes = ( + testset_revision.name != simple_testset_edit_request.testset.name + or testset_revision.description + != simple_testset_edit_request.testset.description + or testset_revision.tags != simple_testset_edit_request.testset.tags + or testset_revision.meta != simple_testset_edit_request.testset.meta + or old_testcase_ids != new_testcase_ids + ) + + if has_changes: + testset_revision_slug = uuid4().hex + + testset_revision_commit = TestsetRevisionCommit( + slug=testset_revision_slug, + # + name=simple_testset_edit_request.testset.name, + description=simple_testset_edit_request.testset.description, + # + # flags = + tags=simple_testset_edit_request.testset.tags, + meta=simple_testset_edit_request.testset.meta, + # + data=testset_revision_data, + # + testset_id=testset.id, + testset_variant_id=testset_variant.id, + ) + + testset_revision: Optional[TestsetRevision] = ( # type: ignore + await self.testsets_service.commit_testset_revision( + project_id=project_id, + user_id=user_id, + # + testset_revision_commit=testset_revision_commit, + ) + ) + + if testset_revision is None: + return None + + simple_testset = SimpleTestset( + id=testset.id, + slug=testset.slug, + # + created_at=testset.created_at, + updated_at=testset.updated_at, + deleted_at=testset.deleted_at, + created_by_id=testset.created_by_id, + updated_by_id=testset.updated_by_id, + deleted_by_id=testset.deleted_by_id, + # + name=testset.name, + description=testset.description, + # + # flags = + tags=testset.tags, + meta=testset.meta, + # + data=testset_revision.data, + ) + + return simple_testset + + async def transfer( + self, + *, + project_id: UUID, + user_id: UUID, + # + testset_id: UUID, + ): + old_testset = await fetch_testset_by_id( + testset_id=str(testset_id), + ) + + if old_testset is None: + return None + + testset_revision_data = self._transfer_simple_testset_revision_data( + old_testset=old_testset, + ) + + new_testset = await self.testsets_service.fetch_testset( + project_id=project_id, + # + testset_ref=Reference(id=testset_id), + ) + + if not new_testset: + name = str(old_testset.name) + slug = get_slug_from_name_and_id( + name=name, + id=testset_id, + ) + + simple_testset_create_request = SimpleTestsetCreateRequest( + testset=SimpleTestsetCreate( + slug=slug, + name=name, + description=None, + # flags=None, + tags=None, + meta=None, + data=testset_revision_data, + ) + ) + + testset = await self.create( + project_id=project_id, + user_id=user_id, + # + simple_testset_create_request=simple_testset_create_request, + # + testset_id=testset_id, + ) + + else: + simple_testset_edit_request = SimpleTestsetEditRequest( + testset=SimpleTestsetEdit( + id=testset_id, + # + name=new_testset.name, + description=new_testset.description, + # + # flags=new_testset.flags, + tags=new_testset.tags, + meta=new_testset.meta, + # + data=testset_revision_data, + ) + ) + + testset = await self.edit( + project_id=project_id, + user_id=user_id, + # + simple_testset_edit_request=simple_testset_edit_request, + ) + + return testset + + def _transfer_simple_testset_revision_data( + self, + *, + old_testset: TestSetDB, + ) -> TestsetRevisionData: + return TestsetRevisionData( + testcases=[ + Testcase(data=testcase_data) for testcase_data in old_testset.csvdata + ], + ) diff --git a/api/oss/src/core/tracing/dtos.py b/api/oss/src/core/tracing/dtos.py index e54dea8794..fa838cfa8d 100644 --- a/api/oss/src/core/tracing/dtos.py +++ b/api/oss/src/core/tracing/dtos.py @@ -16,6 +16,9 @@ Data, Reference, Hashes, + Windowing, + FullJson, + Link, ) @@ -326,7 +329,7 @@ class Condition(BaseModel): class Filtering(BaseModel): - operator: Optional[LogicalOperator] = LogicalOperator.AND + operator: LogicalOperator = LogicalOperator.AND conditions: List[Union[Condition, "Filtering"]] = list() @@ -340,19 +343,12 @@ class Format(str, Enum): OPENTELEMETRY = "opentelemetry" -class Windowing(BaseModel): - oldest: Optional[datetime] = None - newest: Optional[datetime] = None - limit: Optional[int] = None - window: Optional[int] = None - - class Formatting(BaseModel): - focus: Optional[Focus] = Focus.SPAN - format: Optional[Format] = Format.AGENTA + focus: Optional[Focus] = None + format: Optional[Format] = None -class Query(BaseModel): +class TracingQuery(BaseModel): formatting: Optional[Formatting] = None windowing: Optional[Windowing] = None filtering: Optional[Filtering] = None @@ -387,12 +383,128 @@ def plus(self, other: "Analytics") -> "Analytics": class Bucket(BaseModel): timestamp: datetime - window: int + interval: int total: Analytics errors: Analytics +class MetricType(str, Enum): + NUMERIC_CONTINUOUS = "numeric/continuous" + NUMERIC_DISCRETE = "numeric/discrete" + BINARY = "binary" + CATEGORICAL_SINGLE = "categorical/single" + CATEGORICAL_MULTIPLE = "categorical/multiple" + STRING = "string" + JSON = "json" + NONE = "none" + WILDCARD = "*" + + +class MetricSpec(BaseModel): + type: MetricType = MetricType.NONE + path: str = "*" + # OPTS + bins: Optional[int] = None + vmin: Optional[float] = None + vmax: Optional[float] = None + edge: Optional[bool] = None + + +class MetricsBucket(BaseModel): + timestamp: datetime + interval: int + metrics: Optional[Dict[str, FullJson]] = None + + # WORKFLOWS -------------------------------------------------------------------- -Trace = OTelTraceTree +Trace = OTelSpansTree + + +# SIMPLE TRACE: INVOCATIONS & ANNOTATIONS -------------------------------------- + + +class SimpleTraceOrigin(str, Enum): + CUSTOM = "custom" # custom + HUMAN = "human" # human + AUTO = "auto" # automatic + + +class SimpleTraceKind(str, Enum): + ADHOC = "adhoc" # adhoc + EVAL = "eval" # evaluation + PLAY = "play" # playground + + +class SimpleTraceChannel(str, Enum): + OTLP = "otlp" # otlp + WEB = "web" # react + SDK = "sdk" # python vs typescript ? + API = "api" # http + + +class SimpleTraceReferences(BaseModel): + query: Optional[Reference] = None + query_variant: Optional[Reference] = None + query_revision: Optional[Reference] = None + testset: Optional[Reference] = None + testset_variant: Optional[Reference] = None + testset_revision: Optional[Reference] = None + application: Optional[Reference] = None + application_variant: Optional[Reference] = None + application_revision: Optional[Reference] = None + evaluator: Optional[Reference] = None + evaluator_variant: Optional[Reference] = None + evaluator_revision: Optional[Reference] = None + testcase: Optional[Reference] = None + + +SimpleTraceLinks = Union[Dict[str, Link], List[Link]] + + +class SimpleTrace(Link, Lifecycle): + origin: SimpleTraceOrigin = SimpleTraceOrigin.CUSTOM + kind: SimpleTraceKind = SimpleTraceKind.ADHOC + channel: SimpleTraceChannel = SimpleTraceChannel.API + + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + data: Data + + references: SimpleTraceReferences + links: SimpleTraceLinks + + +class SimpleTraceCreate(BaseModel): + origin: SimpleTraceOrigin = SimpleTraceOrigin.CUSTOM + kind: SimpleTraceKind = SimpleTraceKind.ADHOC + channel: SimpleTraceChannel = SimpleTraceChannel.API + + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + data: Data + + references: SimpleTraceReferences + links: SimpleTraceLinks + + +class SimpleTraceEdit(BaseModel): + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + data: Data + + +class SimpleTraceQuery(BaseModel): + origin: Optional[SimpleTraceOrigin] = None + kind: Optional[SimpleTraceKind] = None + channel: Optional[SimpleTraceChannel] = None + + tags: Optional[Tags] = None + meta: Optional[Meta] = None + + references: Optional[SimpleTraceReferences] = None + links: Optional[SimpleTraceLinks] = None diff --git a/api/oss/src/core/tracing/interfaces.py b/api/oss/src/core/tracing/interfaces.py index 0e22d05b5a..05aeda2c63 100644 --- a/api/oss/src/core/tracing/interfaces.py +++ b/api/oss/src/core/tracing/interfaces.py @@ -1,8 +1,15 @@ +from typing import List, Optional, Dict, Any from uuid import UUID -from typing import List, Optional from abc import ABC, abstractmethod -from oss.src.core.tracing.dtos import OTelLink, OTelFlatSpan, Query, Bucket +from oss.src.core.tracing.dtos import ( + OTelLink, + OTelFlatSpan, + TracingQuery, + Bucket, + MetricSpec, + MetricsBucket, +) class TracingDAOInterface(ABC): @@ -145,18 +152,29 @@ async def query( *, project_id: UUID, # - query: Query, + query: TracingQuery, ) -> List[OTelFlatSpan]: raise NotImplementedError ### ANALYTICS @abstractmethod - async def analytics( + async def legacy_analytics( self, *, project_id: UUID, # - query: Query, + query: TracingQuery, ) -> List[Bucket]: raise NotImplementedError + + @abstractmethod + async def analytics( + self, + *, + project_id: UUID, + # + query: TracingQuery, + specs: List[MetricSpec], + ) -> List[MetricsBucket]: + raise NotImplementedError diff --git a/api/oss/src/core/tracing/service.py b/api/oss/src/core/tracing/service.py index d098c4e7bd..193dda6a62 100644 --- a/api/oss/src/core/tracing/service.py +++ b/api/oss/src/core/tracing/service.py @@ -1,12 +1,19 @@ -from typing import List, Optional +from typing import List, Optional, Dict, Any from uuid import UUID from oss.src.utils.logging import get_module_logger from oss.src.core.tracing.interfaces import TracingDAOInterface -from oss.src.core.tracing.dtos import OTelLink, OTelFlatSpan, Query, Bucket from oss.src.core.tracing.utils import parse_query, parse_ingest +from oss.src.core.tracing.dtos import ( + OTelLink, + OTelFlatSpan, + TracingQuery, + Bucket, + MetricSpec, + MetricsBucket, +) from oss.src.core.tracing.utils import ( parse_span_dtos_to_span_idx, @@ -202,7 +209,7 @@ async def query( *, project_id: UUID, # - query: Query, + query: TracingQuery, ) -> List[OTelFlatSpan]: parse_query(query) @@ -214,19 +221,38 @@ async def query( return span_dtos - async def analytics( + async def legacy_analytics( self, *, project_id: UUID, # - query: Query, + query: TracingQuery, ) -> List[Bucket]: parse_query(query) + bucket_dtos = await self.tracing_dao.legacy_analytics( + project_id=project_id, + # + query=query, + ) + + return bucket_dtos + + async def analytics( + self, + *, + project_id: UUID, + # + query: TracingQuery, + specs: List[MetricSpec], + ) -> List[MetricsBucket]: + parse_query(query) + bucket_dtos = await self.tracing_dao.analytics( project_id=project_id, # query=query, + specs=specs, ) return bucket_dtos diff --git a/api/oss/src/core/tracing/utils.py b/api/oss/src/core/tracing/utils.py index cd9eef411e..36e7ccf12a 100644 --- a/api/oss/src/core/tracing/utils.py +++ b/api/oss/src/core/tracing/utils.py @@ -1,4 +1,4 @@ -from typing import Dict, Union, Any, List +from typing import Dict, Union, Any, Optional, Tuple, List from uuid import UUID from datetime import datetime from collections import OrderedDict @@ -8,16 +8,25 @@ from oss.src.utils.logging import get_module_logger -from oss.src.core.shared.dtos import Reference, Link +from oss.src.core.shared.dtos import ( + Flags, + Tags, + Meta, + Data, + Reference, + Link, +) from oss.src.core.tracing.dtos import ( + Attributes, OTelSpanKind, OTelStatusCode, OTelSpan, + OTelReference, OTelFlatSpan, OTelLink, OTelFlatSpans, - Query, + TracingQuery, FilteringException, Filtering, Condition, @@ -799,7 +808,9 @@ def parse_value_to_enum(value: str, enum: type) -> type: ) from e -def parse_timestamp_to_datetime(ts): +def parse_timestamp_to_datetime( + ts: Optional[Union[str, int, datetime]], +) -> Optional[datetime]: if isinstance(ts, datetime): return ts @@ -807,7 +818,7 @@ def parse_timestamp_to_datetime(ts): try: ts = int(ts) except ValueError: - return datetime.fromisoformat(ts) + return datetime.fromisoformat(str(ts)) if isinstance(ts, int): digits = len(str(ts)) @@ -1196,7 +1207,12 @@ def _parse_fts_field_condition(condition: Condition) -> None: # FILTERING / CONDITION -def parse_filtering(filtering: Filtering) -> None: +def parse_filtering( + filtering: Optional[Filtering] = None, +) -> None: + if filtering is None: + return + for condition in filtering.conditions: if isinstance(condition, Filtering): parse_filtering(condition) @@ -1208,7 +1224,12 @@ def parse_filtering(filtering: Filtering) -> None: ) -def parse_condition(condition: Condition) -> None: +def parse_condition( + condition: Optional[Condition] = None, +) -> None: + if condition is None: + return + if condition.field == Fields.TRACE_ID: _parse_trace_id_condition(condition) elif condition.field == Fields.TRACE_TYPE: @@ -1267,5 +1288,64 @@ def parse_ingest(span_dtos: OTelFlatSpans) -> None: pass -def parse_query(query: Query) -> None: +def parse_query(query: TracingQuery) -> None: parse_filtering(query.filtering) + + +# INVOCATIONS / ANNOTATIONS + + +def parse_into_attributes( + *, + type: Optional[Dict[str, str]] = None, + flags: Optional[Flags] = None, + tags: Optional[Tags] = None, + meta: Optional[Meta] = None, + data: Optional[Data] = None, + references: Optional[Dict[str, Dict[str, Any]]] = None, +) -> Attributes: + attributes = dict( + ag=( + dict( + type=type, + flags=flags, + tags=tags, + meta=meta, + data=data, + references=references, + ) + if type or flags or tags or meta or data or references + else None + ) + ) + + return attributes # type: ignore + + +def parse_from_attributes( + attributes: Attributes, +) -> Tuple[ + Optional[Dict[str, str]], # type + Optional[Flags], # flags + Optional[Tags], # tags + Optional[Meta], # meta + Optional[Data], # data + Optional[Dict[str, Dict[str, Any]]], # references +]: + # TODO - add error handling + ag: dict = attributes.get("ag", {}) # type: ignore + type: dict = ag.get("type", {}) # type: ignore + flags: dict = ag.get("flags") # type: ignore + tags: dict = ag.get("tags") # type: ignore + meta: dict = ag.get("meta") # type: ignore + data: dict = ag.get("data") # type: ignore + references = ag.get("references") # type: ignore + + return ( + type, + flags, + tags, + meta, + data, + references, + ) diff --git a/api/oss/src/core/workflows/dtos.py b/api/oss/src/core/workflows/dtos.py index acf891dc3a..8badacda96 100644 --- a/api/oss/src/core/workflows/dtos.py +++ b/api/oss/src/core/workflows/dtos.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict, Any, Union +from typing import Optional, Dict, Any, Callable from uuid import UUID, uuid4 from urllib.parse import urlparse @@ -18,28 +18,29 @@ ) from jsonschema.exceptions import SchemaError -from oss.src.core.shared.dtos import sync_alias, AliasConfig - from oss.src.core.git.dtos import ( Artifact, ArtifactCreate, ArtifactEdit, ArtifactQuery, ArtifactFork, - ArtifactLog, + # Variant, VariantCreate, VariantEdit, VariantQuery, VariantFork, + # Revision, RevisionCreate, RevisionEdit, RevisionQuery, - RevisionCommit, RevisionFork, + RevisionCommit, + RevisionsLog, ) +from oss.src.core.shared.dtos import sync_alias, AliasConfig from oss.src.core.shared.dtos import ( Identifier, Slug, @@ -56,14 +57,11 @@ # Secret, ) -from oss.src.core.tracing.dtos import ( - Trace, -) +from oss.src.core.tracing.dtos import Trace +from oss.src.apis.fastapi.observability.models import AgentaVersionedTreeDTO as Tree -from oss.src.apis.fastapi.observability.models import ( - AgentaNodesDTO as Tree, - AgentaVersionedTreeDTO as VersionedTree, -) + +# aliases ---------------------------------------------------------------------- class WorkflowIdAlias(AliasConfig): @@ -93,12 +91,18 @@ class WorkflowRevisionIdAlias(AliasConfig): ) +# globals ---------------------------------------------------------------------- + + class WorkflowFlags(BaseModel): is_custom: Optional[bool] = None is_evaluator: Optional[bool] = None is_human: Optional[bool] = None +# workflows -------------------------------------------------------------------- + + class Workflow(Artifact): flags: Optional[WorkflowFlags] = None @@ -115,6 +119,9 @@ class WorkflowQuery(ArtifactQuery): flags: Optional[WorkflowFlags] = None +# workflow variants ------------------------------------------------------------ + + class WorkflowVariant( Variant, WorkflowIdAlias, @@ -143,6 +150,9 @@ class WorkflowVariantQuery(VariantQuery): flags: Optional[WorkflowFlags] = None +# workflow revisions ----------------------------------------------------------- + + class WorkflowServiceVersion(BaseModel): version: Optional[str] = None @@ -151,6 +161,7 @@ class WorkflowServiceInterface(WorkflowServiceVersion): uri: Optional[str] = None # str (Enum) w/ validation url: Optional[str] = None # str w/ validation headers: Optional[Dict[str, Reference | str]] = None # either hardcoded or a secret + # handler: Optional[Callable] = None schemas: Optional[Dict[str, Schema]] = None # json-schema instead of pydantic mappings: Optional[Mappings] = None # used in the workflow interface @@ -284,24 +295,37 @@ def model_post_init(self, __context) -> None: sync_alias("workflow_variant_id", "variant_id", self) -class WorkflowLog( - ArtifactLog, +class WorkflowRevisionsLog( + RevisionsLog, + WorkflowIdAlias, WorkflowVariantIdAlias, WorkflowRevisionIdAlias, ): - workflow_variant_id: Optional[UUID] = None - def model_post_init(self, __context) -> None: + sync_alias("workflow_id", "artifact_id", self) sync_alias("workflow_variant_id", "variant_id", self) sync_alias("workflow_revision_id", "revision_id", self) +# forks ------------------------------------------------------------------------ + + class WorkflowRevisionFork(RevisionFork): flags: Optional[WorkflowFlags] = None data: Optional[WorkflowRevisionData] = None +class WorkflowRevisionForkAlias(AliasConfig): + workflow_revision: Optional[WorkflowRevisionFork] = None + + revision: Optional[RevisionFork] = Field( + default=None, + exclude=True, + alias="workflow_revision", + ) + + class WorkflowVariantFork(VariantFork): flags: Optional[WorkflowFlags] = None @@ -316,58 +340,46 @@ class WorkflowVariantForkAlias(AliasConfig): ) -class WorkflowRevisionForkAlias(AliasConfig): - workflow_revision: Optional[WorkflowRevisionFork] = None - - revision: Optional[RevisionFork] = Field( - default=None, - exclude=True, - alias="workflow_revision", - ) - - class WorkflowFork( ArtifactFork, + WorkflowIdAlias, WorkflowVariantIdAlias, - WorkflowRevisionIdAlias, WorkflowVariantForkAlias, + WorkflowRevisionIdAlias, WorkflowRevisionForkAlias, ): def model_post_init(self, __context) -> None: - sync_alias("workflow_variant", "variant", self) - sync_alias("workflow_revision", "revision", self) + sync_alias("workflow_id", "artifact_id", self) sync_alias("workflow_variant_id", "variant_id", self) + sync_alias("workflow_variant", "variant", self) sync_alias("workflow_revision_id", "revision_id", self) + sync_alias("workflow_revision", "revision", self) -# WORKFLOWS -------------------------------------------------------------------- +# workflow services ------------------------------------------------------------ class WorkflowServiceData(BaseModel): + parameters: Optional[Data] = None inputs: Optional[Data] = None - # outputs: Optional[Data | str] = None # - traces: Optional[Dict[str, Trace]] = None - # - trace: Optional[Trace] = None + trace_parameters: Optional[Data] = None trace_inputs: Optional[Data] = None trace_outputs: Optional[Data | str] = None - trace_parameters: Optional[Data] = None - # LEGACY - tree: Optional[VersionedTree] = None # used for workflow execution traces + # + trace: Optional[Trace] = None + # LEGACY -- used for workflow execution traces + tree: Optional[Tree] = None class WorkflowServiceRequest(Version, Metadata): data: Optional[WorkflowServiceData] = None - # path: Optional[str] = "/" - # method: Optional[str] = "invoke" - references: Optional[Dict[str, Reference]] = None links: Optional[Dict[str, Link]] = None - credentials: Optional[str] = None + credentials: Optional[str] = None # Fix typing secrets: Optional[Dict[str, Any]] = None # Fix typing @@ -383,3 +395,6 @@ def __init__(self, **data): self.id = uuid4() if not self.id else self.id self.version = "2025.07.14" if not self.version else self.version + + +# ------------------------------------------------------------------------------ diff --git a/api/oss/src/core/workflows/service.py b/api/oss/src/core/workflows/service.py index 5c52371df7..2ca1d83836 100644 --- a/api/oss/src/core/workflows/service.py +++ b/api/oss/src/core/workflows/service.py @@ -1,12 +1,8 @@ from typing import Optional, List, Tuple -from uuid import UUID, uuid4 +from uuid import UUID from oss.src.utils.logging import get_module_logger -from oss.src.apis.fastapi.tracing.router import ( - TracingRouter, -) # change to TracingRouterInterface - from oss.src.core.git.interfaces import GitDAOInterface from oss.src.core.shared.dtos import Reference, Windowing, Status from oss.src.core.git.dtos import ( @@ -14,7 +10,6 @@ ArtifactEdit, ArtifactQuery, ArtifactFork, - ArtifactLog, # VariantCreate, VariantEdit, @@ -24,6 +19,7 @@ RevisionEdit, RevisionQuery, RevisionCommit, + RevisionsLog, ) from oss.src.core.workflows.dtos import ( Workflow, @@ -31,7 +27,7 @@ WorkflowEdit, WorkflowQuery, WorkflowFork, - WorkflowLog, + WorkflowRevisionsLog, # WorkflowVariant, WorkflowVariantCreate, @@ -60,56 +56,11 @@ ErrorStatus, ) +from oss.src.core.services.registry import REGISTRY +from oss.src.core.services.utils import parse_service_uri log = get_module_logger(__name__) -# - REGISTRY ------------------------------------------------------------------- - -from oss.src.core.workflows.utils import ( - auto_exact_match_v0, - auto_regex_test_v0, - field_match_test_v0, - auto_webhook_test_v0, - auto_custom_code_run_v0, - auto_ai_critique_v0, - auto_starts_with_v0, - auto_ends_with_v0, - auto_contains_v0, - auto_contains_any_v0, - auto_contains_all_v0, - auto_contains_json_v0, - auto_json_diff_v0, - rag_faithfulness_v0, - rag_context_relevancy_v0, - auto_levenshtein_distance_v0, - auto_similarity_match_v0, - auto_semantic_similarity_v0, -) - -REGISTRY = { - "agenta": { - "built-in": { - "auto_exact_match": {"v0": auto_exact_match_v0}, - "auto_regex_test": {"v0": auto_regex_test_v0}, - "field_match_test": {"v0": field_match_test_v0}, - "auto_webhook_test": {"v0": auto_webhook_test_v0}, - "auto_custom_code_run": {"v0": auto_custom_code_run_v0}, - "auto_ai_critique": {"v0": auto_ai_critique_v0}, - "auto_starts_with": {"v0": auto_starts_with_v0}, - "auto_ends_with": {"v0": auto_ends_with_v0}, - "auto_contains": {"v0": auto_contains_v0}, - "auto_contains_any": {"v0": auto_contains_any_v0}, - "auto_contains_all": {"v0": auto_contains_all_v0}, - "auto_contains_json": {"v0": auto_contains_json_v0}, - "auto_json_diff": {"v0": auto_json_diff_v0}, - "rag_faithfulness": {"v0": rag_faithfulness_v0}, - "rag_context_relevancy": {"v0": rag_context_relevancy_v0}, - "auto_levenshtein_distance": {"v0": auto_levenshtein_distance_v0}, - "auto_similarity_match": {"v0": auto_similarity_match_v0}, - "auto_semantic_similarity": {"v0": auto_semantic_similarity_v0}, - }, - }, -} # ------------------------------------------------------------------------------ @@ -119,11 +70,10 @@ def __init__( self, *, workflows_dao: GitDAOInterface, - tracing_router: TracingRouter, ): self.workflows_dao = workflows_dao - ## -- artifacts ------------------------------------------------------------ + # workflows ---------------------------------------------------------------- async def create_workflow( self, @@ -135,7 +85,7 @@ async def create_workflow( # workflow_id: Optional[UUID] = None, ) -> Optional[Workflow]: - _artifact_create = ArtifactCreate( + artifact_create = ArtifactCreate( **workflow_create.model_dump(mode="json"), ) @@ -143,7 +93,7 @@ async def create_workflow( project_id=project_id, user_id=user_id, # - artifact_create=_artifact_create, + artifact_create=artifact_create, # artifact_id=workflow_id, ) @@ -151,9 +101,9 @@ async def create_workflow( if not artifact: return None - _workflow = Workflow(**artifact.model_dump(mode="json")) + workflow = Workflow(**artifact.model_dump(mode="json")) - return _workflow + return workflow async def fetch_workflow( self, @@ -171,9 +121,9 @@ async def fetch_workflow( if not artifact: return None - _workflow = Workflow(**artifact.model_dump(mode="json")) + workflow = Workflow(**artifact.model_dump(mode="json")) - return _workflow + return workflow async def edit_workflow( self, @@ -183,7 +133,7 @@ async def edit_workflow( # workflow_edit: WorkflowEdit, ) -> Optional[Workflow]: - _artifact_edit = ArtifactEdit( + artifact_edit = ArtifactEdit( **workflow_edit.model_dump(mode="json"), ) @@ -191,15 +141,57 @@ async def edit_workflow( project_id=project_id, user_id=user_id, # - artifact_edit=_artifact_edit, + artifact_edit=artifact_edit, ) if not artifact: return None - _workflow = Workflow(**artifact.model_dump(mode="json")) + workflow = Workflow(**artifact.model_dump(mode="json")) - return _workflow + return workflow + + async def query_workflows( + self, + *, + project_id: UUID, + # + workflow_query: Optional[WorkflowQuery] = None, + # + workflow_refs: Optional[List[Reference]] = None, + # + include_archived: Optional[bool] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[Workflow]: + artifact_query = ( + ArtifactQuery( + **workflow_query.model_dump(mode="json", exclude_none=True), + ) + if workflow_query + else ArtifactQuery() + ) + + artifacts = await self.workflows_dao.query_artifacts( + project_id=project_id, + # + artifact_query=artifact_query, + # + artifact_refs=workflow_refs, + # + include_archived=include_archived, + # + windowing=windowing, + ) + + workflows = [ + Workflow( + **artifact.model_dump(mode="json"), + ) + for artifact in artifacts + ] + + return workflows async def archive_workflow( self, @@ -245,51 +237,7 @@ async def unarchive_workflow( return _workflow - async def query_workflows( - self, - *, - project_id: UUID, - # - workflow_query: Optional[WorkflowQuery] = None, - # - workflow_refs: Optional[List[Reference]] = None, - # - include_archived: Optional[bool] = None, - # - windowing: Optional[Windowing] = None, - ) -> List[Workflow]: - _artifact_query = ( - ArtifactQuery( - **workflow_query.model_dump(mode="json", exclude_none=True), - ) - if workflow_query - else ArtifactQuery() - ) - - artifacts = await self.workflows_dao.query_artifacts( - project_id=project_id, - # - artifact_query=_artifact_query, - # - artifact_refs=workflow_refs, - # - include_archived=include_archived, - # - windowing=windowing, - ) - - _workflows = [ - Workflow( - **artifact.model_dump(mode="json"), - ) - for artifact in artifacts - ] - - return _workflows - - ## ------------------------------------------------------------------------- - - ## -- variants ------------------------------------------------------------- + # workflow variants -------------------------------------------------------- async def create_workflow_variant( self, @@ -371,54 +319,6 @@ async def edit_workflow_variant( return _workflow_variant - async def archive_workflow_variant( - self, - *, - project_id: UUID, - user_id: UUID, - # - workflow_variant_id: UUID, - ) -> Optional[WorkflowVariant]: - variant = await self.workflows_dao.archive_variant( - project_id=project_id, - user_id=user_id, - # - variant_id=workflow_variant_id, - ) - - if not variant: - return None - - _workflow_variant = WorkflowVariant( - **variant.model_dump(mode="json"), - ) - - return _workflow_variant - - async def unarchive_workflow_variant( - self, - *, - project_id: UUID, - user_id: UUID, - # - workflow_variant_id: UUID, - ) -> Optional[WorkflowVariant]: - variant = await self.workflows_dao.unarchive_variant( - project_id=project_id, - user_id=user_id, - # - variant_id=workflow_variant_id, - ) - - if not variant: - return None - - _workdlow_variant = WorkflowVariant( - **variant.model_dump(mode="json"), - ) - - return _workdlow_variant - async def query_workflow_variants( self, *, @@ -463,8 +363,6 @@ async def query_workflow_variants( return _workflow_variants - ## ......................................................................... - async def fork_workflow_variant( self, *, @@ -493,9 +391,55 @@ async def fork_workflow_variant( return _workflow_variant - ## ------------------------------------------------------------------------- + async def archive_workflow_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + workflow_variant_id: UUID, + ) -> Optional[WorkflowVariant]: + variant = await self.workflows_dao.archive_variant( + project_id=project_id, + user_id=user_id, + # + variant_id=workflow_variant_id, + ) + + if not variant: + return None + + _workflow_variant = WorkflowVariant( + **variant.model_dump(mode="json"), + ) + + return _workflow_variant + + async def unarchive_workflow_variant( + self, + *, + project_id: UUID, + user_id: UUID, + # + workflow_variant_id: UUID, + ) -> Optional[WorkflowVariant]: + variant = await self.workflows_dao.unarchive_variant( + project_id=project_id, + user_id=user_id, + # + variant_id=workflow_variant_id, + ) + + if not variant: + return None + + _workdlow_variant = WorkflowVariant( + **variant.model_dump(mode="json"), + ) - ## -- revisions ------------------------------------------------------------ + return _workdlow_variant + + # workflow revisions ------------------------------------------------------- async def create_workflow_revision( self, @@ -598,54 +542,6 @@ async def edit_workflow_revision( return _workflow_revision - async def archive_workflow_revision( - self, - *, - project_id: UUID, - user_id: UUID, - # - workflow_revision_id: UUID, - ) -> Optional[WorkflowRevision]: - revision = await self.workflows_dao.archive_revision( - project_id=project_id, - user_id=user_id, - # - revision_id=workflow_revision_id, - ) - - if not revision: - return None - - _workflow_revision = WorkflowRevision( - **revision.model_dump(mode="json"), - ) - - return _workflow_revision - - async def unarchive_workflow_revision( - self, - *, - project_id: UUID, - user_id: UUID, - # - workflow_revision_id: UUID, - ) -> Optional[WorkflowRevision]: - revision = await self.workflows_dao.unarchive_revision( - project_id=project_id, - user_id=user_id, - # - revision_id=workflow_revision_id, - ) - - if not revision: - return None - - _workflow_revision = WorkflowRevision( - **revision.model_dump(mode="json"), - ) - - return _workflow_revision - async def query_workflow_revisions( self, *, @@ -692,8 +588,6 @@ async def query_workflow_revisions( return _workflow_revisions - ## ......................................................................... - async def commit_workflow_revision( self, *, @@ -742,16 +636,16 @@ async def log_workflow_revisions( *, project_id: UUID, # - workflow_log: WorkflowLog, + workflow_revisions_log: WorkflowRevisionsLog, ) -> List[WorkflowRevision]: - _artifact_log = ArtifactLog( - **workflow_log.model_dump(mode="json"), + _revisions_log = RevisionsLog( + **workflow_revisions_log.model_dump(mode="json"), ) revisions = await self.workflows_dao.log_revisions( project_id=project_id, # - artifact_log=_artifact_log, + revisions_log=_revisions_log, ) _workflow_revisions = [ @@ -763,7 +657,55 @@ async def log_workflow_revisions( return _workflow_revisions - ## ------------------------------------------------------------------------- + async def archive_workflow_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + workflow_revision_id: UUID, + ) -> Optional[WorkflowRevision]: + revision = await self.workflows_dao.archive_revision( + project_id=project_id, + user_id=user_id, + # + revision_id=workflow_revision_id, + ) + + if not revision: + return None + + _workflow_revision = WorkflowRevision( + **revision.model_dump(mode="json"), + ) + + return _workflow_revision + + async def unarchive_workflow_revision( + self, + *, + project_id: UUID, + user_id: UUID, + # + workflow_revision_id: UUID, + ) -> Optional[WorkflowRevision]: + revision = await self.workflows_dao.unarchive_revision( + project_id=project_id, + user_id=user_id, + # + revision_id=workflow_revision_id, + ) + + if not revision: + return None + + _workflow_revision = WorkflowRevision( + **revision.model_dump(mode="json"), + ) + + return _workflow_revision + + # workflow services -------------------------------------------------------- async def invoke_workflow( self, @@ -809,8 +751,9 @@ async def invoke_workflow( parameters=revision.data.parameters, inputs=request.data.inputs, # - trace_outputs=request.data.trace_outputs, trace_parameters=request.data.trace_parameters, + trace_inputs=request.data.trace_inputs, + trace_outputs=request.data.trace_outputs, # trace=request.data.trace, tree=request.data.tree, @@ -859,20 +802,4 @@ async def inspect_workflow( ) -> WorkflowServiceInterface: pass - ## ------------------------------------------------------------------------- - - -async def parse_service_uri( - uri: str, -) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]: - if not uri or not uri.strip(): - return None, None, None, None - - # uri ~ [|empty|'custom']:::[|'latest'|empty] - - parts = uri.split(":") - - if len(parts) != 4: - return None, None, None, None - - return tuple(parts) + # -------------------------------------------------------------------------- diff --git a/api/oss/src/dbs/postgres/blobs/dao.py b/api/oss/src/dbs/postgres/blobs/dao.py index e7bd57483a..6ca13bb348 100644 --- a/api/oss/src/dbs/postgres/blobs/dao.py +++ b/api/oss/src/dbs/postgres/blobs/dao.py @@ -1,12 +1,11 @@ from typing import Optional, List, TypeVar, Type from uuid import UUID -from json import dumps from datetime import datetime, timezone -from hashlib import blake2b from sqlalchemy import select from oss.src.utils.logging import get_module_logger +from oss.src.utils.exceptions import suppress_exceptions from oss.src.core.shared.exceptions import EntityCreationConflict from oss.src.core.shared.dtos import Windowing @@ -14,8 +13,8 @@ from oss.src.core.blobs.interfaces import BlobsDAOInterface from oss.src.core.blobs.utils import compute_blob_id +from oss.src.dbs.postgres.shared.utils import apply_windowing from oss.src.dbs.postgres.shared.exceptions import check_entity_creation_conflict -from oss.src.utils.exceptions import suppress_exceptions from oss.src.dbs.postgres.shared.engine import engine from oss.src.dbs.postgres.blobs.mappings import map_dbe_to_dto, map_dto_to_dbe @@ -67,6 +66,9 @@ async def add_blob( async with engine.core_session() as session: stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore + ) + + stmt = select(self.BlobDBE).filter( self.BlobDBE.id == blob.id, # type: ignore ) @@ -87,7 +89,7 @@ async def add_blob( return await self.fetch_blob( project_id=project_id, # - blob_id=blob.id, + blob_id=blob.id, # type: ignore ) except Exception as e: @@ -105,15 +107,15 @@ async def fetch_blob( blob_id: UUID, ) -> Optional[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.BlobDBE.id == blob_id) # type: ignore + stmt = stmt.filter(self.BlobDBE.id == blob_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) blob_dbe = result.scalar_one_or_none() @@ -137,15 +139,15 @@ async def edit_blob( blob_edit: BlobEdit, ) -> Optional[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.BlobDBE.id == blob_edit.id) # type: ignore + stmt = stmt.filter(self.BlobDBE.id == blob_edit.id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) blob_dbe = result.scalar_one_or_none() @@ -155,8 +157,8 @@ async def edit_blob( for key, value in blob_edit.model_dump(exclude_unset=True).items(): setattr(blob_dbe, key, value) - blob_dbe.updated_at = datetime.now(timezone.utc) - blob_dbe.updated_by_id = user_id + blob_dbe.updated_at = datetime.now(timezone.utc) # type: ignore + blob_dbe.updated_by_id = user_id # type: ignore await session.commit() @@ -178,15 +180,15 @@ async def remove_blob( blob_id: UUID, ) -> Optional[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.BlobDBE.id == blob_id) # type: ignore + stmt = stmt.filter(self.BlobDBE.id == blob_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) blob_dbe = result.scalar_one_or_none() @@ -239,15 +241,18 @@ async def add_blobs( try: async with engine.core_session() as session: stmt = select(self.BlobDBE).filter( - self.BlobDBE.project_id == project_id, - self.BlobDBE.id.in_(blob_ids), + self.BlobDBE.project_id == project_id, # type: ignore + ) + + stmt = select(self.BlobDBE).filter( + self.BlobDBE.id.in_(blob_ids), # type: ignore ) existing = await session.execute(stmt) existing_dbes = existing.scalars().all() - existing_ids = {b.id for b in existing_dbes} + existing_ids = {b.id for b in existing_dbes} # type: ignore new_blobs = list( { @@ -271,7 +276,7 @@ async def add_blobs( all_blobs = await self.fetch_blobs( project_id=project_id, # - blob_ids=blob_ids, + blob_ids=blob_ids, # type: ignore ) return all_blobs @@ -291,13 +296,13 @@ async def fetch_blobs( blob_ids: List[UUID], ) -> List[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.BlobDBE.id.in_(blob_ids)) # type: ignore + stmt = stmt.filter(self.BlobDBE.id.in_(blob_ids)) # type: ignore - result = await session.execute(query) + result = await session.execute(stmt) blob_dbes = result.scalars().all() @@ -305,7 +310,7 @@ async def fetch_blobs( return [] _blobs = { - blob_dbe.id: map_dbe_to_dto( + blob_dbe.id: map_dbe_to_dto( # type: ignore DTO=Blob, dbe=blob_dbe, # type: ignore ) @@ -327,17 +332,17 @@ async def edit_blobs( blob_edits: List[BlobEdit], ) -> List[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) blob_ids = [blob_edit.id for blob_edit in blob_edits] - query = query.filter(self.BlobDBE.id.in_(blob_ids)) + stmt = stmt.filter(self.BlobDBE.id.in_(blob_ids)) # type: ignore - query = query.limit(len(blob_edits)) + stmt = stmt.limit(len(blob_edits)) - result = await session.execute(query) + result = await session.execute(stmt) blob_dbes = result.scalars().all() @@ -346,12 +351,12 @@ async def edit_blobs( for blob_dbe in blob_dbes: for blob_edit in blob_edits: - if blob_dbe.id == blob_edit.id: + if blob_dbe.id == blob_edit.id: # type: ignore for key, value in blob_edit.model_dump().items(): setattr(blob_dbe, key, value) - blob_dbe.updated_at = datetime.now(timezone.utc) - blob_dbe.updated_by_id = user_id + blob_dbe.updated_at = datetime.now(timezone.utc) # type: ignore + blob_dbe.updated_by_id = user_id # type: ignore await session.commit() @@ -377,15 +382,15 @@ async def remove_blobs( blob_ids: List[UUID], ) -> List[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.BlobDBE.id.in_(blob_ids)) # type: ignore + stmt = stmt.filter(self.BlobDBE.id.in_(blob_ids)) # type: ignore - query = query.limit(len(blob_ids)) + stmt = stmt.limit(len(blob_ids)) - result = await session.execute(query) + result = await session.execute(stmt) blob_dbes = result.scalars().all() @@ -418,64 +423,41 @@ async def query_blobs( windowing: Optional[Windowing] = None, ) -> List[Blob]: async with engine.core_session() as session: - query = select(self.BlobDBE).filter( + stmt = select(self.BlobDBE).filter( self.BlobDBE.project_id == project_id, # type: ignore ) if blob_query.set_ids: - query = query.filter(self.BlobDBE.set_id.in_(blob_query.set_ids)) # type: ignore + stmt = stmt.filter(self.BlobDBE.set_id.in_(blob_query.set_ids)) # type: ignore if blob_query.blob_ids: - query = query.filter(self.BlobDBE.id.in_(blob_query.blob_ids)) # type: ignore + stmt = stmt.filter(self.BlobDBE.id.in_(blob_query.blob_ids)) # type: ignore if blob_query.flags: - query = query.filter( + stmt = stmt.filter( self.BlobDBE.flags.contains(blob_query.flags), # type: ignore ) if blob_query.tags: - query = query.filter( + stmt = stmt.filter( self.BlobDBE.tags.contains(blob_query.tags), # type: ignore ) if blob_query.meta: - query = query.filter( + stmt = stmt.filter( self.BlobDBE.meta.contains(blob_query.meta), # type: ignore ) if windowing: - if windowing.next is not None: - query = query.filter( - self.BlobDBE.id > windowing.next, # type: ignore - ) - if windowing.start: - query = query.filter( - self.BlobDBE.created_at > windowing.start, # type: ignore - ) - - if windowing.stop: - query = query.filter( - self.BlobDBE.created_at <= windowing.stop, # type: ignore - ) - - if windowing: - if windowing.order: - if windowing.order.lower() == "descending": - query = query.order_by(self.BlobDBE.created_at.desc()) - elif windowing.order.lower() == "ascending": - query = query.order_by(self.BlobDBE.created_at.asc()) - else: - query = query.order_by(self.BlobDBE.created_at.asc()) - else: - query = query.order_by(self.BlobDBE.created_at.asc()) - else: - query = query.order_by(self.BlobDBE.created_at.asc()) - - if windowing: - if windowing.limit: - query = query.limit(windowing.limit) + stmt = apply_windowing( + stmt=stmt, + DBE=self.BlobDBE, + attribute="created_at", # UUID4 + order="ascending", # data-style + windowing=windowing, + ) - result = await session.execute(query) + result = await session.execute(stmt) blob_dbes = result.scalars().all() diff --git a/api/oss/src/dbs/postgres/evaluations/dao.py b/api/oss/src/dbs/postgres/evaluations/dao.py index 46ecfcee03..90450f4928 100644 --- a/api/oss/src/dbs/postgres/evaluations/dao.py +++ b/api/oss/src/dbs/postgres/evaluations/dao.py @@ -1,17 +1,19 @@ -from typing import Optional, List -from uuid import UUID, uuid4 +from typing import Optional, List, Tuple, Dict +from uuid import UUID from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select -from sqlalchemy import func +from sqlalchemy import not_ import sqlalchemy +from sqlalchemy.orm.attributes import flag_modified + from oss.src.utils.logging import get_module_logger from oss.src.utils.exceptions import suppress_exceptions from oss.src.core.shared.exceptions import EntityCreationConflict -from oss.src.core.shared.dtos import Windowing +from oss.src.core.shared.dtos import Windowing, Reference from oss.src.core.evaluations.interfaces import EvaluationsDAOInterface from oss.src.core.evaluations.types import EvaluationClosedConflict from oss.src.core.evaluations.types import ( @@ -20,24 +22,29 @@ EvaluationRunCreate, EvaluationRunEdit, EvaluationRunQuery, + # EvaluationScenario, EvaluationScenarioCreate, EvaluationScenarioEdit, EvaluationScenarioQuery, - EvaluationStep, - EvaluationStepCreate, - EvaluationStepEdit, - EvaluationStepQuery, - EvaluationMetric, - EvaluationMetricCreate, - EvaluationMetricEdit, - EvaluationMetricQuery, + # + EvaluationResult, + EvaluationResultCreate, + EvaluationResultEdit, + EvaluationResultQuery, + # + EvaluationMetrics, + EvaluationMetricsCreate, + EvaluationMetricsEdit, + EvaluationMetricsQuery, + # EvaluationQueue, EvaluationQueueCreate, EvaluationQueueEdit, EvaluationQueueQuery, ) +from oss.src.dbs.postgres.shared.utils import apply_windowing from oss.src.dbs.postgres.shared.exceptions import check_entity_creation_conflict from oss.src.dbs.postgres.shared.engine import engine from oss.src.dbs.postgres.evaluations.mappings import ( @@ -48,8 +55,8 @@ from oss.src.dbs.postgres.evaluations.dbes import ( EvaluationRunDBE, EvaluationScenarioDBE, - EvaluationStepDBE, - EvaluationMetricDBE, + EvaluationResultDBE, + EvaluationMetricsDBE, EvaluationQueueDBE, ) @@ -72,31 +79,56 @@ async def create_run( # run: EvaluationRunCreate, ) -> Optional[EvaluationRun]: - now = datetime.now(timezone.utc) - run = EvaluationRun( - **run.model_dump(), - created_at=now, + _run = EvaluationRun( + **run.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) + run_references: List[dict] = [] + + if _run and _run.data and _run.data.steps: + _references: Dict[str, dict] = {} + + for step in _run.data.steps: + if not step.references: + continue + + for key, ref in step.references.items(): + _key = getattr(ref, "id", None) or key + _references[_key] = ref.model_dump( + mode="json", + exclude_none=True, + ) | {"key": str(key)} + + run_references = list(_references.values()) + run_dbe = create_dbe_from_dto( DBE=EvaluationRunDBE, project_id=project_id, - dto=run, + dto=_run, + # + references=run_references, ) + if _run.data: + run_dbe.data = _run.data.model_dump(mode="json") # type: ignore + try: async with engine.core_session() as session: session.add(run_dbe) await session.commit() - run = create_dto_from_dbe( + _run = create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, ) - return run + return _run except Exception as e: check_entity_creation_conflict(e) @@ -112,24 +144,56 @@ async def create_runs( # runs: List[EvaluationRunCreate], ) -> List[EvaluationRun]: - now = datetime.now(timezone.utc) - runs = [ + _runs = [ EvaluationRun( - **run.model_dump(), - created_at=now, + **run.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) for run in runs ] - run_dbes = [ - create_dbe_from_dto( + runs_references: List[List[dict]] = [] + + for _run in _runs: + run_references: List[dict] = [] + + if _run and _run.data and _run.data.steps: + _references: Dict[str, dict] = {} + + for step in _run.data.steps: + if not step.references: + continue + + for key, ref in step.references.items(): + _key = getattr(ref, "id", None) or key + _references[_key] = ref.model_dump( + mode="json", + exclude_none=True, + ) | {"key": str(key)} + + run_references = list(_references.values()) + + runs_references.append(run_references) + + run_dbes = [] + + for i, _run in enumerate(_runs): + run_dbe = create_dbe_from_dto( DBE=EvaluationRunDBE, project_id=project_id, - dto=run, + dto=_run, + # + references=runs_references[i], ) - for run in runs - ] + + if _run.data: + run_dbe.data = _run.data.model_dump(mode="json") # type: ignore + + run_dbes.append(run_dbe) try: async with engine.core_session() as session: @@ -137,7 +201,7 @@ async def create_runs( await session.commit() - runs = [ + _runs = [ create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, @@ -145,7 +209,7 @@ async def create_runs( for run_dbe in run_dbes ] - return runs + return _runs except Exception as e: check_entity_creation_conflict(e) @@ -163,24 +227,27 @@ async def fetch_run( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id == run_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbe = result.scalars().first() + run_dbe = res.scalars().first() if run_dbe is None: return None - run = create_dto_from_dbe( + _run = create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, ) - return run + return _run @suppress_exceptions(default=[]) async def fetch_runs( @@ -193,16 +260,19 @@ async def fetch_runs( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id.in_(run_ids), ) stmt = stmt.limit(len(run_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbes = result.scalars().all() + run_dbes = res.scalars().all() - runs = [ + _runs = [ create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, @@ -210,7 +280,7 @@ async def fetch_runs( for run_dbe in run_dbes ] - return runs + return _runs @suppress_exceptions() async def edit_run( @@ -224,14 +294,17 @@ async def edit_run( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id == run.id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbe = result.scalars().first() + run_dbe = res.scalars().first() if run_dbe is None: return None @@ -243,21 +316,43 @@ async def edit_run( run_id=run.id, ) + run_references: List[dict] = [] + + if run and run.data and run.data.steps: + _references: Dict[str, dict] = {} + + for step in run.data.steps: + if not step.references: + continue + + for key, ref in step.references.items(): + _key = getattr(ref, "id", None) or key + _references[_key] = ref.model_dump( + mode="json", + exclude_none=True, + ) | {"key": str(key)} + + run_references = list(_references.values()) + run_dbe = edit_dbe_from_dto( dbe=run_dbe, dto=run, updated_at=datetime.now(timezone.utc), updated_by_id=user_id, + references=run_references, ) + if run.data: + run_dbe.data = run.data.model_dump(mode="json") # type: ignore + await session.commit() - run = create_dto_from_dbe( + _run = create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, ) - return run + return _run @suppress_exceptions(default=[]) async def edit_runs( @@ -273,14 +368,17 @@ async def edit_runs( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id.in_(run_ids), ) stmt = stmt.limit(len(run_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbes = result.scalars().all() + run_dbes = res.scalars().all() if not run_dbes: return [] @@ -290,7 +388,7 @@ async def edit_runs( if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=run_dbe.id, + run_id=run_dbe.id, # type: ignore ) run = next( @@ -298,17 +396,41 @@ async def edit_runs( None, ) - if run is not None: - run_dbe = edit_dbe_from_dto( - dbe=run_dbe, - dto=run, - updated_at=datetime.now(timezone.utc), - updated_by_id=user_id, - ) + if run is None: + continue + + run_references: List[dict] = [] + + if run and run.data and run.data.steps: + _references: Dict[str, dict] = {} + + for step in run.data.steps: + if not step.references: + continue + + for key, ref in step.references.items(): + _key = getattr(ref, "id", None) or key + _references[_key] = ref.model_dump( + mode="json", + exclude_none=True, + ) | {"key": str(key)} + + run_references = list(_references.values()) + + run_dbe = edit_dbe_from_dto( + dbe=run_dbe, + dto=run, + updated_at=datetime.now(timezone.utc), + updated_by_id=user_id, + references=run_references, + ) + + if run.data: + run_dbe.data = run.data.model_dump(mode="json") # type: ignore await session.commit() - runs = [ + _runs = [ create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, @@ -316,7 +438,7 @@ async def edit_runs( for run_dbe in run_dbes ] - return runs + return _runs @suppress_exceptions() async def delete_run( @@ -329,14 +451,17 @@ async def delete_run( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id == run_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbe = result.scalars().first() + run_dbe = res.scalars().first() if run_dbe is None: return None @@ -358,14 +483,17 @@ async def delete_runs( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id.in_(run_ids), ) stmt = stmt.limit(len(run_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbes = result.scalars().all() + run_dbes = res.scalars().all() if not run_dbes: return [] @@ -375,12 +503,12 @@ async def delete_runs( await session.commit() - run_ids = [run_dbe.id for run_dbe in run_dbes] + run_ids = [run_dbe.id for run_dbe in run_dbes] # type: ignore return run_ids @suppress_exceptions() - async def archive_run( + async def close_run( self, *, project_id: UUID, @@ -391,40 +519,44 @@ async def archive_run( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id == run_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbe = result.scalars().first() + run_dbe = res.scalars().first() if run_dbe is None: return None - if not run_dbe.flags: - run_dbe.flags = EvaluationRunFlags().model_dump(mode="json") + if run_dbe.flags is None: + run_dbe.flags = EvaluationRunFlags().model_dump( # type: ignore + mode="json", + exclude_none=True, + ) - run_dbe.flags["is_closed"] = True + run_dbe.flags["is_closed"] = True # type: ignore + flag_modified(run_dbe, "flags") - now = datetime.now(timezone.utc) - run_dbe.updated_at = now - run_dbe.updated_by_id = user_id - run_dbe.deleted_at = now - run_dbe.deleted_by_id = user_id + run_dbe.updated_at = datetime.now(timezone.utc) # type: ignore + run_dbe.updated_by_id = user_id # type: ignore await session.commit() - run = create_dto_from_dbe( + _run = create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, ) - return run + return _run @suppress_exceptions(default=[]) - async def archive_runs( + async def close_runs( self, *, project_id: UUID, @@ -435,34 +567,37 @@ async def archive_runs( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id.in_(run_ids), ) stmt = stmt.limit(len(run_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbes = result.scalars().all() + run_dbes = res.scalars().all() if not run_dbes: return [] for run_dbe in run_dbes: - if not run_dbe.flags: - run_dbe.flags = EvaluationRunFlags().model_dump(mode="json") + if run_dbe.flags is None: + run_dbe.flags = EvaluationRunFlags().model_dump( # type: ignore + mode="json", + exclude_none=True, + ) - run_dbe.flags["is_closed"] = True + run_dbe.flags["is_closed"] = True # type: ignore + flag_modified(run_dbe, "flags") - now = datetime.now(timezone.utc) - for run_dbe in run_dbes: - run_dbe.updated_at = now - run_dbe.updated_by_id = user_id - run_dbe.deleted_at = now - run_dbe.deleted_by_id = user_id + run_dbe.updated_at = datetime.now(timezone.utc) # type: ignore + run_dbe.updated_by_id = user_id # type: ignore await session.commit() - runs = [ + _runs = [ create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, @@ -470,10 +605,10 @@ async def archive_runs( for run_dbe in run_dbes ] - return runs + return _runs @suppress_exceptions() - async def unarchive_run( + async def open_run( self, *, project_id: UUID, @@ -484,35 +619,44 @@ async def unarchive_run( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id == run_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbe = result.scalars().first() + run_dbe = res.scalars().first() if run_dbe is None: return None - now = datetime.now(timezone.utc) - run_dbe.updated_at = now - run_dbe.updated_by_id = user_id - run_dbe.deleted_at = None - run_dbe.deleted_by_id = None + if run_dbe.flags is None: + run_dbe.flags = EvaluationRunFlags().model_dump( # type: ignore + mode="json", + exclude_none=True, + ) + + run_dbe.flags["is_closed"] = False # type: ignore + flag_modified(run_dbe, "flags") + + run_dbe.updated_at = datetime.now(timezone.utc) # type: ignore + run_dbe.updated_by_id = user_id # type: ignore await session.commit() - run = create_dto_from_dbe( + _run = create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, ) - return run + return _run @suppress_exceptions(default=[]) - async def unarchive_runs( + async def open_runs( self, *, project_id: UUID, @@ -523,28 +667,37 @@ async def unarchive_runs( async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, + ) + + stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.id.in_(run_ids), ) stmt = stmt.limit(len(run_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - run_dbes = result.scalars().all() + run_dbes = res.scalars().all() if not run_dbes: return [] - now = datetime.now(timezone.utc) for run_dbe in run_dbes: - run_dbe.updated_at = now - run_dbe.updated_by_id = user_id - run_dbe.deleted_at = None - run_dbe.deleted_by_id = None + if run_dbe.flags is None: + run_dbe.flags = EvaluationRunFlags().model_dump( # type: ignore + mode="json", + exclude_none=True, + ) + + run_dbe.flags["is_closed"] = False # type: ignore + flag_modified(run_dbe, "flags") + + run_dbe.updated_at = datetime.now(timezone.utc) # type: ignore + run_dbe.updated_by_id = user_id # type: ignore await session.commit() - runs = [ + _runs = [ create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, @@ -552,87 +705,96 @@ async def unarchive_runs( for run_dbe in run_dbes ] - return runs + return _runs - @suppress_exceptions() - async def close_run( + @suppress_exceptions(default=[]) + async def query_runs( self, *, project_id: UUID, - user_id: UUID, # - run_id: UUID, - ) -> Optional[EvaluationRun]: + run: Optional[EvaluationRunQuery] = None, + # + windowing: Optional[Windowing] = None, + ) -> List[EvaluationRun]: async with engine.core_session() as session: stmt = select(EvaluationRunDBE).filter( EvaluationRunDBE.project_id == project_id, - EvaluationRunDBE.id == run_id, ) - stmt = stmt.limit(1) - - result = await session.execute(stmt) - - run_dbe = result.scalars().first() - - if run_dbe is None: - return None - - if not run_dbe.flags: - run_dbe.flags = EvaluationRunFlags().model_dump(mode="json") - - run_dbe.flags["is_closed"] = True + if run is not None: + if run.ids is not None: + stmt = stmt.filter( + EvaluationRunDBE.id.in_(run.ids), + ) - now = datetime.now(timezone.utc) - run_dbe.updated_at = now - run_dbe.updated_by_id = user_id + if run.references is not None: + run_references: List[dict] = list() - await session.commit() + for _references in run.references: + for key, ref in _references.items(): + # _key = getattr(ref, "id", None) or key + run_references.append( + ref.model_dump( + mode="json", + exclude_none=True, + ) + # | {"key": str(key)} + ) - run = create_dto_from_dbe( - DTO=EvaluationRun, - dbe=run_dbe, - ) + stmt = stmt.filter( + EvaluationRunDBE.references.contains(run_references), + ) - return run + if run.flags is not None: + stmt = stmt.filter( + EvaluationRunDBE.flags.contains(run.flags), + ) - @suppress_exceptions(default=[]) - async def close_runs( - self, - *, - project_id: UUID, - user_id: UUID, - # - run_ids: List[UUID], - ) -> List[UUID]: - async with engine.core_session() as session: - stmt = select(EvaluationRunDBE).filter( - EvaluationRunDBE.project_id == project_id, - EvaluationRunDBE.id.in_(run_ids), - ) + if run.tags is not None: + stmt = stmt.filter( + EvaluationRunDBE.tags.contains(run.tags), + ) - stmt = stmt.limit(len(run_ids)) + if run.meta is not None: + stmt = stmt.filter( + EvaluationRunDBE.meta.contains(run.meta), + ) - result = await session.execute(stmt) + if run.status is not None: + stmt = stmt.filter( + EvaluationRunDBE.status == run.status, + ) - run_dbes = result.scalars().all() + if run.statuses is not None: + stmt = stmt.filter( + EvaluationRunDBE.status.in_(run.statuses), + ) - if not run_dbes: - return [] + if run.name is not None: + stmt = stmt.filter( + EvaluationRunDBE.name.ilike(f"%{run.name}%"), + ) - now = datetime.now(timezone.utc) - for run_dbe in run_dbes: - if not run_dbe.flags: - run_dbe.flags = EvaluationRunFlags().model_dump(mode="json") + if run.description is not None: + stmt = stmt.filter( + EvaluationRunDBE.description.ilike(f"%{run.description}%"), + ) - run_dbe.flags["is_closed"] = True + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=EvaluationRunDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, + ) - run_dbe.updated_at = now - run_dbe.updated_by_id = user_id + res = await session.execute(stmt) - await session.commit() + run_dbes = res.scalars().all() - runs = [ + _runs = [ create_dto_from_dbe( DTO=EvaluationRun, dbe=run_dbe, @@ -640,122 +802,58 @@ async def close_runs( for run_dbe in run_dbes ] - return runs + return _runs @suppress_exceptions(default=[]) - async def query_runs( + async def fetch_live_runs( self, *, - project_id: UUID, - # - run: EvaluationRunQuery, - # - include_archived: Optional[bool] = None, - # windowing: Optional[Windowing] = None, - ) -> List[EvaluationRun]: + ) -> List[Tuple[UUID, EvaluationRun]]: async with engine.core_session() as session: - stmt = select(EvaluationRunDBE).filter( - EvaluationRunDBE.project_id == project_id, - ) + stmt = select(EvaluationRunDBE) - # data-based filtering: generic JSONB containment for any nested data filters - if run.data is not None: - data_dict = run.data.dict(exclude_none=True) - if data_dict: - stmt = stmt.filter(EvaluationRunDBE.data.contains(data_dict)) - - if run.flags is not None: - stmt = stmt.filter( - EvaluationRunDBE.flags.contains( - run.flags.model_dump(mode="json"), - ), - ) - - if run.tags is not None: - stmt = stmt.filter( - EvaluationRunDBE.tags.contains(run.tags), - ) + stmt = stmt.filter( + not_(EvaluationRunDBE.flags.contains({"is_closed": True})), + ) - if run.meta is not None: - # If meta is a list, OR across .contains() for each dict - if isinstance(run.meta, list): - or_filters = [ - EvaluationRunDBE.meta.contains(m) - for m in run.meta - if isinstance(m, dict) and m - ] - if or_filters: - stmt = stmt.filter(sqlalchemy.or_(*or_filters)) - # If meta is a dict, filter as before - elif isinstance(run.meta, dict): - stmt = stmt.filter(EvaluationRunDBE.meta.contains(run.meta)) - # Otherwise, ignore (invalid type) - - if run.status is not None: - stmt = stmt.filter( - EvaluationRunDBE.status == run.status, - ) + stmt = stmt.filter( + EvaluationRunDBE.flags.contains({"is_live": True}), + ) - if run.ids is not None: - stmt = stmt.filter( - EvaluationRunDBE.id.in_(run.ids), - ) + stmt = stmt.filter( + EvaluationRunDBE.flags.contains({"is_active": True}), + ) - if run.search is not None: - stmt = stmt.filter(EvaluationRunDBE.name.ilike(f"%{run.search}%")) + stmt = stmt.filter( + EvaluationRunDBE.status == "running", + ) - if include_archived is not True: - stmt = stmt.filter( - EvaluationRunDBE.deleted_at.is_(None), + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=EvaluationRunDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, ) - if windowing is not None: - if windowing.next is not None: - stmt = stmt.filter( - EvaluationRunDBE.id > windowing.next, - ) - - if windowing.start is not None: - stmt = stmt.filter( - EvaluationRunDBE.created_at > windowing.start, - ) - - if windowing.stop is not None: - stmt = stmt.filter( - EvaluationRunDBE.created_at <= windowing.stop, - ) - - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - stmt = stmt.order_by(EvaluationRunDBE.id.asc()) - elif windowing.order.lower() == "descending": - stmt = stmt.order_by(EvaluationRunDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationRunDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationRunDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationRunDBE.id.desc()) + res = await session.execute(stmt) - if windowing is not None: - if windowing.limit is not None: - stmt = stmt.limit(windowing.limit) + run_dbes = res.scalars().all() - result = await session.execute(stmt) - - run_dbes = result.scalars().all() - - runs = [ - create_dto_from_dbe( - DTO=EvaluationRun, - dbe=run_dbe, + _runs = [ + ( + UUID(str(run_dbe.project_id)), + create_dto_from_dbe( + DTO=EvaluationRun, + dbe=run_dbe, + ), ) for run_dbe in run_dbes ] - return runs + return _runs # - EVALUATION SCENARIO ---------------------------------------------------- @@ -778,17 +876,19 @@ async def create_scenario( run_id=scenario.run_id, ) - now = datetime.now(timezone.utc) - scenario = EvaluationScenario( - **scenario.model_dump(), - created_at=now, + _scenario = EvaluationScenario( + **scenario.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) scenario_dbe = create_dbe_from_dto( DBE=EvaluationScenarioDBE, project_id=project_id, - dto=scenario, + dto=_scenario, ) try: @@ -797,12 +897,12 @@ async def create_scenario( await session.commit() - scenario = create_dto_from_dbe( + _scenario = create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, ) - return scenario + return _scenario except Exception as e: check_entity_creation_conflict(e) @@ -829,11 +929,13 @@ async def create_scenarios( run_id=scenario.run_id, ) - now = datetime.now(timezone.utc) - scenarios = [ + _scenarios = [ EvaluationScenario( - **scenario.model_dump(), - created_at=now, + **scenario.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) for scenario in scenarios @@ -843,9 +945,9 @@ async def create_scenarios( create_dbe_from_dto( DBE=EvaluationScenarioDBE, project_id=project_id, - dto=scenario, + dto=_scenario, ) - for scenario in scenarios + for _scenario in _scenarios ] try: @@ -854,7 +956,7 @@ async def create_scenarios( await session.commit() - scenarios = [ + _scenarios = [ create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, @@ -862,7 +964,7 @@ async def create_scenarios( for scenario_dbe in scenario_dbes ] - return scenarios + return _scenarios except Exception as e: check_entity_creation_conflict(e) @@ -880,24 +982,27 @@ async def fetch_scenario( async with engine.core_session() as session: stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.project_id == project_id, + ) + + stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.id == scenario_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbe = result.scalars().first() + scenario_dbe = res.scalars().first() if scenario_dbe is None: return None - scenario = create_dto_from_dbe( + _scenario = create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, ) - return scenario + return _scenario @suppress_exceptions(default=[]) async def fetch_scenarios( @@ -910,16 +1015,19 @@ async def fetch_scenarios( async with engine.core_session() as session: stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.project_id == project_id, + ) + + stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.id.in_(scenario_ids), ) stmt = stmt.limit(len(scenario_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbes = result.scalars().all() + scenario_dbes = res.scalars().all() - scenarios = [ + _scenarios = [ create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, @@ -927,7 +1035,7 @@ async def fetch_scenarios( for scenario_dbe in scenario_dbes ] - return scenarios + return _scenarios @suppress_exceptions() async def edit_scenario( @@ -941,14 +1049,17 @@ async def edit_scenario( async with engine.core_session() as session: stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.project_id == project_id, + ) + + stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.id == scenario.id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbe = result.scalars().first() + scenario_dbe = res.scalars().first() if scenario_dbe is None: return None @@ -956,13 +1067,13 @@ async def edit_scenario( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=scenario_dbe.run_id, + run_id=scenario_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=scenario_dbe.run_id, - scenario_id=scenario_dbe.id, + run_id=scenario_dbe.run_id, # type: ignore + scenario_id=scenario_dbe.id, # type: ignore ) scenario_dbe = edit_dbe_from_dto( @@ -974,12 +1085,12 @@ async def edit_scenario( await session.commit() - scenario = create_dto_from_dbe( + _scenario = create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, ) - return scenario + return _scenario @suppress_exceptions(default=[]) async def edit_scenarios( @@ -995,14 +1106,17 @@ async def edit_scenarios( async with engine.core_session() as session: stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.project_id == project_id, + ) + + stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.id.in_(scenario_ids), ) stmt = stmt.limit(len(scenario_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbes = result.scalars().all() + scenario_dbes = res.scalars().all() if not scenario_dbes: return [] @@ -1011,13 +1125,13 @@ async def edit_scenarios( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=scenario_dbe.run_id, + run_id=scenario_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=scenario_dbe.run_id, - scenario_id=scenario_dbe.id, + run_id=scenario_dbe.run_id, # type: ignore + scenario_id=scenario_dbe.id, # type: ignore ) scenario = next( @@ -1035,7 +1149,7 @@ async def edit_scenarios( await session.commit() - scenarios = [ + _scenarios = [ create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, @@ -1043,7 +1157,7 @@ async def edit_scenarios( for scenario_dbe in scenario_dbes ] - return scenarios + return _scenarios @suppress_exceptions() async def delete_scenario( @@ -1056,28 +1170,31 @@ async def delete_scenario( async with engine.core_session() as session: stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.project_id == project_id, + ) + + stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.id == scenario_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbe = result.scalars().first() + scenario_dbe = res.scalars().first() - if scenario_dbe is None: + if not scenario_dbe: return None run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=scenario_dbe.run_id, + run_id=scenario_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=scenario_dbe.run_id, - scenario_id=scenario_dbe.id, + run_id=scenario_dbe.run_id, # type: ignore + scenario_id=scenario_dbe.id, # type: ignore ) await session.delete(scenario_dbe) @@ -1097,14 +1214,17 @@ async def delete_scenarios( async with engine.core_session() as session: stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.project_id == project_id, + ) + + stmt = select(EvaluationScenarioDBE).filter( EvaluationScenarioDBE.id.in_(scenario_ids), ) stmt = stmt.limit(len(scenario_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbes = result.scalars().all() + scenario_dbes = res.scalars().all() if not scenario_dbes: return [] @@ -1113,20 +1233,20 @@ async def delete_scenarios( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=scenario_dbe.run_id, + run_id=scenario_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=scenario_dbe.run_id, - scenario_id=scenario_dbe.id, + run_id=scenario_dbe.run_id, # type: ignore + scenario_id=scenario_dbe.id, # type: ignore ) await session.delete(scenario_dbe) await session.commit() - scenario_ids = [scenario_dbe.id for scenario_dbe in scenario_dbes] + scenario_ids = [scenario_dbe.id for scenario_dbe in scenario_dbes] # type: ignore return scenario_ids @@ -1136,7 +1256,7 @@ async def query_scenarios( *, project_id: UUID, # - scenario: EvaluationScenarioQuery, + scenario: Optional[EvaluationScenarioQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationScenario]: @@ -1145,81 +1265,81 @@ async def query_scenarios( EvaluationScenarioDBE.project_id == project_id, ) - if scenario.run_id is not None: - stmt = stmt.filter( - EvaluationScenarioDBE.run_id == scenario.run_id, - ) + if scenario is not None: + if scenario.ids is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.id.in_(scenario.ids), + ) - if scenario.run_ids is not None: - stmt = stmt.filter( - EvaluationScenarioDBE.run_id.in_(scenario.run_ids), - ) + if scenario.run_id is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.run_id == scenario.run_id, + ) - # if scenario.flags is not None: - # stmt = stmt.filter( - # EvaluationScenarioDBE.flags.contains( - # scenario.flags.model_dump(mode="json"), - # ), - # ) + if scenario.run_ids is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.run_id.in_(scenario.run_ids), + ) - if scenario.tags is not None: - stmt = stmt.filter( - EvaluationScenarioDBE.tags.contains(scenario.tags), - ) + if scenario.timestamp is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.timestamp == scenario.timestamp, + ) - if scenario.meta is not None: - stmt = stmt.filter( - EvaluationScenarioDBE.meta.contains(scenario.meta), - ) + if scenario.timestamps is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.timestamp.in_(scenario.timestamps), + ) - if scenario.status is not None: - stmt = stmt.filter( - EvaluationScenarioDBE.status == scenario.status, - ) + if scenario.interval is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.interval == scenario.interval, + ) - if scenario.statuses is not None: - stmt = stmt.filter( - EvaluationScenarioDBE.status.in_(scenario.statuses), - ) + if scenario.intervals is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.interval.in_(scenario.intervals), + ) - if windowing is not None: - if windowing.next is not None: + if scenario.flags is not None: stmt = stmt.filter( - EvaluationScenarioDBE.id > windowing.next, + EvaluationScenarioDBE.flags.contains(scenario.flags), ) - if windowing.start is not None: + if scenario.tags is not None: stmt = stmt.filter( - EvaluationScenarioDBE.created_at > windowing.start, + EvaluationScenarioDBE.tags.contains(scenario.tags), ) - if windowing.stop is not None: + if scenario.meta is not None: stmt = stmt.filter( - EvaluationScenarioDBE.created_at <= windowing.stop, + EvaluationScenarioDBE.meta.contains(scenario.meta), ) - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - stmt = stmt.order_by(EvaluationScenarioDBE.id.asc()) - elif windowing.order.lower() == "descending": - stmt = stmt.order_by(EvaluationScenarioDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationScenarioDBE.id.asc()) - else: - stmt = stmt.order_by(EvaluationScenarioDBE.id.asc()) - else: - stmt = stmt.order_by(EvaluationScenarioDBE.id.asc()) + if scenario.status is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.status == scenario.status, + ) + + if scenario.statuses is not None: + stmt = stmt.filter( + EvaluationScenarioDBE.status.in_(scenario.statuses), + ) - if windowing is not None: - if windowing.limit is not None: - stmt = stmt.limit(windowing.limit) + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=EvaluationScenarioDBE, + attribute="id", # UUID7 + order="ascending", # data-style + windowing=windowing, + ) - result = await session.execute(stmt) + res = await session.execute(stmt) - scenario_dbes = result.scalars().all() + scenario_dbes = res.scalars().all() - scenarios = [ + _scenarios = [ create_dto_from_dbe( DTO=EvaluationScenario, dbe=scenario_dbe, @@ -1227,61 +1347,56 @@ async def query_scenarios( for scenario_dbe in scenario_dbes ] - return scenarios + return _scenarios - # - EVALUATION STEP -------------------------------------------------------- + # - EVALUATION RESULT ------------------------------------------------------ @suppress_exceptions(exclude=[EntityCreationConflict]) - async def create_step( + async def create_result( self, *, project_id: UUID, user_id: UUID, # - step: EvaluationStepCreate, - ) -> Optional[EvaluationStep]: + result: EvaluationResultCreate, + ) -> Optional[EvaluationResult]: run_flags = await _get_run_flags( project_id=project_id, - run_id=step.run_id, + run_id=result.run_id, ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=step.run_id, + run_id=result.run_id, ) - now = datetime.now(timezone.utc) - step = EvaluationStep( - **step.model_dump(exclude={"repeat_idx", "repeat_id", "retry_id"}), - repeat_id=( - UUID(f"{step.scenario_id.hex[:-4]}{(step.repeat_idx or 0):04x}") - if step.repeat_id is None - else step.repeat_id + _result = EvaluationResult( + **result.model_dump( + mode="json", + exclude_none=True, ), - retry_id=step.retry_id or uuid4(), - created_at=now, + created_at=datetime.now(timezone.utc), created_by_id=user_id, - timestamp=now, ) - step_dbe = create_dbe_from_dto( - DBE=EvaluationStepDBE, + result_dbe = create_dbe_from_dto( + DBE=EvaluationResultDBE, project_id=project_id, - dto=step, + dto=_result, ) try: async with engine.core_session() as session: - session.add(step_dbe) + session.add(result_dbe) await session.commit() - step = create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + _result = create_dto_from_dbe( + DTO=EvaluationResult, + dbe=result_dbe, ) - return step + return _result except Exception as e: check_entity_creation_conflict(e) @@ -1289,569 +1404,472 @@ async def create_step( raise @suppress_exceptions(default=[], exclude=[EntityCreationConflict]) - async def create_steps( + async def create_results( self, *, project_id: UUID, user_id: UUID, # - steps: List[EvaluationStepCreate], - ) -> List[EvaluationStep]: - for step in steps: + results: List[EvaluationResultCreate], + ) -> List[EvaluationResult]: + for result in results: run_flags = await _get_run_flags( project_id=project_id, - run_id=step.run_id, + run_id=result.run_id, ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=step.run_id, + run_id=result.run_id, ) async with engine.core_session() as session: - now = datetime.now(timezone.utc) - steps = [ - EvaluationStep( - **step.model_dump(exclude={"repeat_idx", "repeat_id", "retry_id"}), - repeat_id=( - UUID(f"{step.scenario_id.hex[:-4]}{(step.repeat_idx or 0):04x}") - if step.repeat_id is None - else step.repeat_id + _results = [ + EvaluationResult( + **result.model_dump( + mode="json", + exclude_none=True, ), - retry_id=step.retry_id or uuid4(), - created_at=now, + created_at=datetime.now(timezone.utc), created_by_id=user_id, - timestamp=now, ) - for step in steps + for result in results ] - step_dbes = [ + result_dbes = [ create_dbe_from_dto( - DBE=EvaluationStepDBE, + DBE=EvaluationResultDBE, project_id=project_id, - dto=step, + dto=_result, ) - for step in steps + for _result in _results ] - session.add_all(step_dbes) + session.add_all(result_dbes) await session.commit() - steps = [ + _results = [ create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + DTO=EvaluationResult, + dbe=result_dbe, ) - for step_dbe in step_dbes + for result_dbe in result_dbes ] - return steps + return _results @suppress_exceptions() - async def fetch_step( + async def fetch_result( self, *, project_id: UUID, # - step_id: UUID, - ) -> Optional[EvaluationStep]: + result_id: UUID, + ) -> Optional[EvaluationResult]: async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, - EvaluationStepDBE.id == step_id, + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, + ) + + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.id == result_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - step_dbe = result.scalars().first() + result_dbe = res.scalars().first() - if step_dbe is None: + if result_dbe is None: return None - step = create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + _result = create_dto_from_dbe( + DTO=EvaluationResult, + dbe=result_dbe, ) - return step + return _result @suppress_exceptions(default=[]) - async def fetch_steps( + async def fetch_results( self, *, project_id: UUID, # - step_ids: List[UUID], - ) -> List[EvaluationStep]: + result_ids: List[UUID], + ) -> List[EvaluationResult]: async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, - EvaluationStepDBE.id.in_(step_ids), + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, + ) + + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.id.in_(result_ids), ) - stmt = stmt.limit(len(step_ids)) + stmt = stmt.limit(len(result_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - step_dbes = result.scalars().all() + result_dbes = res.scalars().all() - steps = [ + _results = [ create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + DTO=EvaluationResult, + dbe=result_dbe, ) - for step_dbe in step_dbes + for result_dbe in result_dbes ] - return steps + return _results @suppress_exceptions() - async def edit_step( + async def edit_result( self, *, project_id: UUID, user_id: UUID, # - step: EvaluationStepEdit, - ) -> Optional[EvaluationStep]: + result: EvaluationResultEdit, + ) -> Optional[EvaluationResult]: async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, - EvaluationStepDBE.id == step.id, + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, + ) + + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.id == result.id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - step_dbe = result.scalars().first() + result_dbe = res.scalars().first() - if step_dbe is None: + if result_dbe is None: return None run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=step_dbe.run_id, + run_id=result_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=step_dbe.run_id, - scenario_id=step_dbe.scenario_id, - step_id=step_dbe.id, + run_id=result_dbe.run_id, # type: ignore + scenario_id=result_dbe.scenario_id, # type: ignore + result_id=result_dbe.id, # type: ignore ) - step_dbe = edit_dbe_from_dto( - dbe=step_dbe, - dto=step, - timestamp=datetime.now(timezone.utc), + result_dbe = edit_dbe_from_dto( + dbe=result_dbe, + dto=result, updated_at=datetime.now(timezone.utc), updated_by_id=user_id, ) await session.commit() - step = create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + _result = create_dto_from_dbe( + DTO=EvaluationResult, + dbe=result_dbe, ) - return step + return _result @suppress_exceptions(default=[]) - async def edit_steps( + async def edit_results( self, *, project_id: UUID, user_id: UUID, # - steps: List[EvaluationStepEdit], - ) -> List[EvaluationStep]: - step_ids = [step.id for step in steps] + results: List[EvaluationResultEdit], + ) -> List[EvaluationResult]: + result_ids = [result.id for result in results] async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, - EvaluationStepDBE.id.in_(step_ids), + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, + ) + + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.id.in_(result_ids), ) - stmt = stmt.limit(len(step_ids)) + stmt = stmt.limit(len(result_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - step_dbes = result.scalars().all() + result_dbes = res.scalars().all() - if not step_dbes: + if not result_dbes: return [] - for step_dbe in step_dbes: + for result_dbe in result_dbes: run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=step_dbe.run_id, + run_id=result_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=step_dbe.run_id, - scenario_id=step_dbe.scenario_id, - step_id=step_dbe.id, + run_id=result_dbe.run_id, # type: ignore + scenario_id=result_dbe.scenario_id, # type: ignore + result_id=result_dbe.id, # type: ignore ) - step = next( - (s for s in steps if s.id == step_dbe.id), + result = next( + (s for s in results if s.id == result_dbe.id), None, ) - if step is not None: - step_dbe = edit_dbe_from_dto( - dbe=step_dbe, - dto=step, - timestamp=datetime.now(timezone.utc), + if result is not None: + result_dbe = edit_dbe_from_dto( + dbe=result_dbe, + dto=result, updated_at=datetime.now(timezone.utc), updated_by_id=user_id, ) await session.commit() - steps = [ + _results = [ create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + DTO=EvaluationResult, + dbe=result_dbe, ) - for step_dbe in step_dbes + for result_dbe in result_dbes ] - return steps + return _results @suppress_exceptions() - async def delete_step( + async def delete_result( self, *, project_id: UUID, # - step_id: UUID, + result_id: UUID, ) -> Optional[UUID]: async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, - EvaluationStepDBE.id == step_id, + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, + ) + + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.id == result_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - step_dbe = result.scalars().first() + result_dbe = res.scalars().first() - if step_dbe is None: + if result_dbe is None: return None run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=step_dbe.run_id, + run_id=result_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=step_dbe.run_id, - scenario_id=step_dbe.scenario_id, - step_id=step_dbe.id, + run_id=result_dbe.run_id, # type: ignore + scenario_id=result_dbe.scenario_id, # type: ignore + result_id=result_dbe.id, # type: ignore ) - await session.delete(step_dbe) + await session.delete(result_dbe) await session.commit() - return step_id + return result_id @suppress_exceptions(default=[]) - async def delete_steps( + async def delete_results( self, *, project_id: UUID, # - step_ids: List[UUID], + result_ids: List[UUID], ) -> List[UUID]: async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, - EvaluationStepDBE.id.in_(step_ids), + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, ) - stmt = stmt.limit(len(step_ids)) + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.id.in_(result_ids), + ) - result = await session.execute(stmt) + stmt = stmt.limit(len(result_ids)) - step_dbes = result.scalars().all() + res = await session.execute(stmt) - if not step_dbes: + result_dbes = res.scalars().all() + + if not result_dbes: return [] - for step_dbe in step_dbes: + for result_dbe in result_dbes: run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=step_dbe.run_id, + run_id=result_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=step_dbe.run_id, - scenario_id=step_dbe.scenario_id, - step_id=step_dbe.id, + run_id=result_dbe.run_id, # type: ignore + scenario_id=result_dbe.scenario_id, # type: ignore + result_id=result_dbe.id, # type: ignore ) - await session.delete(step_dbe) + await session.delete(result_dbe) await session.commit() - return step_ids + return result_ids @suppress_exceptions(default=[]) - async def query_steps( + async def query_results( self, *, project_id: UUID, # - step: EvaluationStepQuery, + result: Optional[EvaluationResultQuery] = None, # windowing: Optional[Windowing] = None, - ) -> List[EvaluationStep]: + ) -> List[EvaluationResult]: async with engine.core_session() as session: - stmt = select(EvaluationStepDBE).filter( - EvaluationStepDBE.project_id == project_id, + stmt = select(EvaluationResultDBE).filter( + EvaluationResultDBE.project_id == project_id, ) - if step.ids is not None: - stmt = stmt.filter( - EvaluationStepDBE.id.in_(step.ids), - ) - - if step.run_id is not None: - stmt = stmt.filter( - EvaluationStepDBE.run_id == step.run_id, - ) - - if step.run_ids is not None: - stmt = stmt.filter( - EvaluationStepDBE.run_id.in_(step.run_ids), - ) - - if step.scenario_id is not None: - stmt = stmt.filter( - EvaluationStepDBE.scenario_id == step.scenario_id, - ) - - if step.scenario_ids is not None: - stmt = stmt.filter( - EvaluationStepDBE.scenario_id.in_(step.scenario_ids), - ) - - # if step.flags is not None: - # stmt = stmt.filter( - # EvaluationStepDBE.flags.contains( - # step.flags.model_dump(mode="json"), - # ), - # ) - - if step.tags is not None: - stmt = stmt.filter( - EvaluationStepDBE.tags.contains(step.tags), - ) - - if step.meta is not None: - stmt = stmt.filter( - EvaluationStepDBE.meta.contains(step.meta), - ) - - if step.key is not None: - stmt = stmt.filter( - EvaluationStepDBE.key == step.key, - ) - - if step.keys is not None: - stmt = stmt.filter( - EvaluationStepDBE.key.in_(step.keys), - ) - - if step.repeat_id is not None: - stmt = stmt.filter( - EvaluationStepDBE.repeat_id == step.repeat_id, - ) - - if step.repeat_ids is not None: - stmt = stmt.filter( - EvaluationStepDBE.repeat_id.in_(step.repeat_ids), - ) + if result is not None: + if result.ids is not None: + stmt = stmt.filter( + EvaluationResultDBE.id.in_(result.ids), + ) - if step.repeat_idx is not None and step.scenario_id is not None: - repeat_id = UUID(f"{step.scenario_id.hex[:-4]}{step.repeat_idx:04x}") + if result.run_id is not None: + stmt = stmt.filter( + EvaluationResultDBE.run_id == result.run_id, + ) - stmt = stmt.filter( - EvaluationStepDBE.repeat_id == repeat_id, - ) + if result.run_ids is not None: + stmt = stmt.filter( + EvaluationResultDBE.run_id.in_(result.run_ids), + ) - if step.repeat_idxs is not None and step.scenario_id is not None: - repeat_ids = [ - UUID(f"{step.scenario_id.hex[:-4]}{idx:04x}") - for idx in step.repeat_idxs - ] + if result.scenario_id is not None: + stmt = stmt.filter( + EvaluationResultDBE.scenario_id == result.scenario_id, + ) - stmt = stmt.filter( - EvaluationStepDBE.repeat_id.in_(repeat_ids), - ) + if result.scenario_ids is not None: + stmt = stmt.filter( + EvaluationResultDBE.scenario_id.in_(result.scenario_ids), + ) - if step.repeat_idx is not None and step.scenario_ids is not None: - repeat_ids = [ - UUID(f"{scenario_id.hex[:-4]}{step.repeat_idx:04x}") - for scenario_id in step.scenario_ids - ] + if result.step_key is not None: + stmt = stmt.filter( + EvaluationResultDBE.step_key == result.step_key, + ) - stmt = stmt.filter( - EvaluationStepDBE.repeat_id.in_(repeat_ids), - ) + if result.step_keys is not None: + stmt = stmt.filter( + EvaluationResultDBE.step_key.in_(result.step_keys), + ) - if step.repeat_idxs is not None and step.scenario_ids is not None: - repeat_ids = [ - UUID(f"{scenario_id.hex[:-4]}{idx:04x}") - for scenario_id in step.scenario_ids - for idx in step.repeat_idxs - ] + if result.repeat_idx is not None: + stmt = stmt.filter( + EvaluationResultDBE.repeat_idx == result.repeat_idx, + ) - stmt = stmt.filter( - EvaluationStepDBE.repeat_id.in_(repeat_ids), - ) + if result.repeat_idxs is not None: + stmt = stmt.filter( + EvaluationResultDBE.repeat_idx.in_(result.repeat_idxs), + ) - if step.retry_id is not None: - stmt = stmt.filter( - EvaluationStepDBE.retry_id == step.retry_id, - ) + if result.timestamp is not None: + stmt = stmt.filter( + EvaluationResultDBE.timestamp == result.timestamp, + ) - if step.retry_ids is not None: - stmt = stmt.filter( - EvaluationStepDBE.retry_id.in_(step.retry_ids), - ) + if result.timestamps is not None: + stmt = stmt.filter( + EvaluationResultDBE.timestamp.in_(result.timestamps), + ) - if step.status is not None: - stmt = stmt.filter( - EvaluationStepDBE.status == step.status, - ) + if result.interval is not None: + stmt = stmt.filter( + EvaluationResultDBE.interval == result.interval, + ) - if step.statuses is not None: - stmt = stmt.filter( - EvaluationStepDBE.status.in_(step.statuses), - ) + if result.intervals is not None: + stmt = stmt.filter( + EvaluationResultDBE.interval.in_(result.intervals), + ) - if step.timestamp is not None: - stmt = stmt.filter( - EvaluationStepDBE.timestamp > step.timestamp, - ) + if result.flags is not None: + stmt = stmt.filter( + EvaluationResultDBE.flags.contains(result.flags), + ) - if windowing is not None: - if windowing.next is not None: + if result.tags is not None: stmt = stmt.filter( - EvaluationStepDBE.id > windowing.next, + EvaluationResultDBE.tags.contains(result.tags), ) - if windowing.start is not None: + if result.meta is not None: stmt = stmt.filter( - EvaluationStepDBE.created_at > windowing.start, + EvaluationResultDBE.meta.contains(result.meta), ) - if windowing.stop is not None: + if result.status is not None: stmt = stmt.filter( - EvaluationStepDBE.created_at <= windowing.stop, + EvaluationResultDBE.status == result.status, ) - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - stmt = stmt.order_by(EvaluationStepDBE.id.asc()) - elif windowing.order.lower() == "descending": - stmt = stmt.order_by(EvaluationStepDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationStepDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationStepDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationStepDBE.id.desc()) + if result.statuses is not None: + stmt = stmt.filter( + EvaluationResultDBE.status.in_(result.statuses), + ) - if windowing is not None: - if windowing.limit is not None: - stmt = stmt.limit(windowing.limit) + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=EvaluationResultDBE, + attribute="id", # UUID7 + order="ascending", # data-style + windowing=windowing, + ) - result = await session.execute(stmt) + res = await session.execute(stmt) - step_dbes = result.scalars().all() + result_dbes = res.scalars().all() - steps = [ + results = [ create_dto_from_dbe( - DTO=EvaluationStep, - dbe=step_dbe, + DTO=EvaluationResult, + dbe=result_dbe, ) - for step_dbe in step_dbes + for result_dbe in result_dbes ] - return steps - - # - EVALUATION METRIC ------------------------------------------------------ + return results - @suppress_exceptions(exclude=[EntityCreationConflict]) - async def create_metric( - self, - *, - project_id: UUID, - user_id: UUID, - # - metric: EvaluationMetricCreate, - ) -> Optional[EvaluationMetric]: - run_flags = await _get_run_flags( - project_id=project_id, - run_id=metric.run_id, - ) - - if run_flags.get("is_closed", False): - raise EvaluationClosedConflict( - run_id=metric.run_id, - ) - - now = datetime.now(timezone.utc) - metric = EvaluationMetric( - **metric.model_dump(), - created_at=now, - created_by_id=user_id, - ) - - metric_dbe = create_dbe_from_dto( - DBE=EvaluationMetricDBE, - project_id=project_id, - dto=metric, - ) - - try: - async with engine.core_session() as session: - session.add(metric_dbe) - - await session.commit() - - metric = create_dto_from_dbe( - DTO=EvaluationMetric, - dbe=metric_dbe, - ) - - return metric - - except Exception as e: - check_entity_creation_conflict(e) - - raise + # - EVALUATION METRICS ----------------------------------------------------- @suppress_exceptions(default=[], exclude=[EntityCreationConflict]) async def create_metrics( @@ -1860,8 +1878,8 @@ async def create_metrics( project_id: UUID, user_id: UUID, # - metrics: List[EvaluationMetricCreate], - ) -> List[EvaluationMetric]: + metrics: List[EvaluationMetricsCreate], + ) -> List[EvaluationMetrics]: for metric in metrics: run_flags = await _get_run_flags( project_id=project_id, @@ -1873,11 +1891,13 @@ async def create_metrics( run_id=metric.run_id, ) - now = datetime.now(timezone.utc) - metrics = [ - EvaluationMetric( - **metric.model_dump(), - created_at=now, + _metrics = [ + EvaluationMetrics( + **metric.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) for metric in metrics @@ -1885,11 +1905,11 @@ async def create_metrics( metric_dbes = [ create_dbe_from_dto( - DBE=EvaluationMetricDBE, + DBE=EvaluationMetricsDBE, project_id=project_id, - dto=metric, + dto=_metric, ) - for metric in metrics + for _metric in _metrics ] try: @@ -1898,133 +1918,53 @@ async def create_metrics( await session.commit() - metrics = [ + _metrics = [ create_dto_from_dbe( - DTO=EvaluationMetric, + DTO=EvaluationMetrics, dbe=metric_dbe, ) for metric_dbe in metric_dbes ] - return metrics + return _metrics except Exception as e: check_entity_creation_conflict(e) raise - @suppress_exceptions() - async def fetch_metric( - self, - *, - project_id: UUID, - # - metric_id: UUID, - ) -> Optional[EvaluationMetric]: - async with engine.core_session() as session: - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, - EvaluationMetricDBE.id == metric_id, - ) - - stmt = stmt.limit(1) - - result = await session.execute(stmt) - - metric_dbe = result.scalars().first() - - if metric_dbe is None: - return None - - metric = create_dto_from_dbe( - DTO=EvaluationMetric, - dbe=metric_dbe, - ) - - return metric - @suppress_exceptions(default=[]) async def fetch_metrics( self, *, project_id: UUID, # - metric_ids: List[UUID], - ) -> List[EvaluationMetric]: + metrics_ids: List[UUID], + ) -> List[EvaluationMetrics]: async with engine.core_session() as session: - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, - EvaluationMetricDBE.id.in_(metric_ids), + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.project_id == project_id, ) - stmt = stmt.limit(len(metric_ids)) + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.id.in_(metrics_ids), + ) - result = await session.execute(stmt) + stmt = stmt.limit(len(metrics_ids)) - metric_dbes = result.scalars().all() + res = await session.execute(stmt) - metrics = [ + metric_dbes = res.scalars().all() + + _metrics = [ create_dto_from_dbe( - DTO=EvaluationMetric, + DTO=EvaluationMetrics, dbe=metric_dbe, ) for metric_dbe in metric_dbes ] - return metrics - - @suppress_exceptions() - async def edit_metric( - self, - *, - project_id: UUID, - user_id: UUID, - # - metric: EvaluationMetricEdit, - ) -> Optional[EvaluationMetric]: - async with engine.core_session() as session: - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, - EvaluationMetricDBE.id == metric.id, - ) - - stmt = stmt.limit(1) - - result = await session.execute(stmt) - - metric_dbe = result.scalars().first() - - if metric_dbe is None: - return None - - run_flags = await _get_run_flags( - session=session, - project_id=project_id, - run_id=metric_dbe.run_id, - ) - - if run_flags.get("is_closed", False): - raise EvaluationClosedConflict( - run_id=metric_dbe.run_id, - scenario_id=metric_dbe.scenario_id, - metric_id=metric_dbe.id, - ) - - metric_dbe = edit_dbe_from_dto( - dbe=metric_dbe, - dto=metric, - updated_at=datetime.now(timezone.utc), - updated_by_id=user_id, - ) - - await session.commit() - - metric = create_dto_from_dbe( - DTO=EvaluationMetric, - dbe=metric_dbe, - ) - - return metric + return _metrics @suppress_exceptions(default=[]) async def edit_metrics( @@ -2033,21 +1973,24 @@ async def edit_metrics( project_id: UUID, user_id: UUID, # - metrics: List[EvaluationMetricEdit], - ) -> List[EvaluationMetric]: - metric_ids = [metric.id for metric in metrics] + metrics: List[EvaluationMetricsEdit], + ) -> List[EvaluationMetrics]: + metrics_ids = [metric.id for metric in metrics] async with engine.core_session() as session: - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, - EvaluationMetricDBE.id.in_(metric_ids), + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.project_id == project_id, + ) + + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.id.in_(metrics_ids), ) - stmt = stmt.limit(len(metric_ids)) + stmt = stmt.limit(len(metrics_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - metric_dbes = result.scalars().all() + metric_dbes = res.scalars().all() if not metric_dbes: return [] @@ -2056,14 +1999,14 @@ async def edit_metrics( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=metric_dbe.run_id, + run_id=metric_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=metric_dbe.run_id, - scenario_id=metric_dbe.scenario_id, - metric_id=metric_dbe.id, + run_id=metric_dbe.run_id, # type: ignore + scenario_id=metric_dbe.scenario_id, # type: ignore + metrics_id=metric_dbe.id, # type: ignore ) metric = next( @@ -2081,57 +2024,15 @@ async def edit_metrics( await session.commit() - metrics = [ + _metrics = [ create_dto_from_dbe( - DTO=EvaluationMetric, + DTO=EvaluationMetrics, dbe=metric_dbe, ) for metric_dbe in metric_dbes ] - return metrics - - @suppress_exceptions() - async def delete_metric( - self, - *, - project_id: UUID, - # - metric_id: UUID, - ) -> Optional[UUID]: - async with engine.core_session() as session: - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, - EvaluationMetricDBE.id == metric_id, - ) - - stmt = stmt.limit(1) - - result = await session.execute(stmt) - - metric_dbe = result.scalars().first() - - if metric_dbe is None: - return None - - run_flags = await _get_run_flags( - session=session, - project_id=project_id, - run_id=metric_dbe.run_id, - ) - - if run_flags.get("is_closed", False): - raise EvaluationClosedConflict( - run_id=metric_dbe.run_id, - scenario_id=metric_dbe.scenario_id, - metric_id=metric_dbe.id, - ) - - await session.delete(metric_dbe) - - await session.commit() - - return metric_id + return _metrics @suppress_exceptions(default=[]) async def delete_metrics( @@ -2139,22 +2040,25 @@ async def delete_metrics( *, project_id: UUID, # - metric_ids: Optional[List[UUID]] = None, + metrics_ids: Optional[List[UUID]] = None, ) -> List[UUID]: async with engine.core_session() as session: - if metric_ids is None: + if metrics_ids is None: return [] - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, - EvaluationMetricDBE.id.in_(metric_ids), + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.project_id == project_id, + ) + + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.id.in_(metrics_ids), ) - stmt = stmt.limit(len(metric_ids)) + stmt = stmt.limit(len(metrics_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - metric_dbes = result.scalars().all() + metric_dbes = res.scalars().all() if not metric_dbes: return [] @@ -2163,21 +2067,21 @@ async def delete_metrics( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=metric_dbe.run_id, + run_id=metric_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( - run_id=metric_dbe.run_id, - scenario_id=metric_dbe.scenario_id, - metric_id=metric_dbe.id, + run_id=metric_dbe.run_id, # type: ignore + scenario_id=metric_dbe.scenario_id, # type: ignore + metrics_id=metric_dbe.id, # type: ignore ) await session.delete(metric_dbe) await session.commit() - return metric_ids + return metrics_ids @suppress_exceptions(default=[]) async def query_metrics( @@ -2185,102 +2089,102 @@ async def query_metrics( *, project_id: UUID, # - metric: EvaluationMetricQuery, + metric: Optional[EvaluationMetricsQuery] = None, # windowing: Optional[Windowing] = None, - ) -> List[EvaluationMetric]: + ) -> List[EvaluationMetrics]: async with engine.core_session() as session: - stmt = select(EvaluationMetricDBE).filter( - EvaluationMetricDBE.project_id == project_id, + stmt = select(EvaluationMetricsDBE).filter( + EvaluationMetricsDBE.project_id == project_id, ) - if metric.run_id is not None: - stmt = stmt.filter( - EvaluationMetricDBE.run_id == metric.run_id, - ) + if metric is not None: + if metric.ids is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.id.in_(metric.ids), + ) - if metric.run_ids is not None: - stmt = stmt.filter( - EvaluationMetricDBE.run_id.in_(metric.run_ids), - ) + if metric.run_id is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.run_id == metric.run_id, + ) - if metric.scenario_id is not None: - stmt = stmt.filter( - EvaluationMetricDBE.scenario_id == metric.scenario_id, - ) + if metric.run_ids is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.run_id.in_(metric.run_ids), + ) - if metric.scenario_ids is not None: - stmt = stmt.filter( - EvaluationMetricDBE.scenario_id.in_(metric.scenario_ids), - ) + if metric.scenario_id is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.scenario_id == metric.scenario_id, + ) - # if metric.flags is not None: - # stmt = stmt.filter( - # EvaluationMetricDBE.flags.contains( - # metric.flags.model_dump(mode="json"), - # ), - # ) + if metric.scenario_ids is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.scenario_id.in_(metric.scenario_ids), + ) - if metric.tags is not None: - stmt = stmt.filter( - EvaluationMetricDBE.tags.contains(metric.tags), - ) + if metric.timestamp is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.timestamp == metric.timestamp, + ) - if metric.meta is not None: - stmt = stmt.filter( - EvaluationMetricDBE.meta.contains(metric.meta), - ) + if metric.timestamps is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.timestamp.in_(metric.timestamps), + ) - if metric.status is not None: - stmt = stmt.filter( - EvaluationMetricDBE.status == metric.status, - ) + if metric.interval is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.interval == metric.interval, + ) - if metric.statuses is not None: - stmt = stmt.filter( - EvaluationMetricDBE.status.in_(metric.statuses), - ) + if metric.intervals is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.interval.in_(metric.intervals), + ) + + if metric.flags is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.flags.contains(metric.flags), + ) - if windowing is not None: - if windowing.next is not None: + if metric.tags is not None: stmt = stmt.filter( - EvaluationMetricDBE.id > windowing.next, + EvaluationMetricsDBE.tags.contains(metric.tags), ) - if windowing.start is not None: + if metric.meta is not None: stmt = stmt.filter( - EvaluationMetricDBE.created_at > windowing.start, + EvaluationMetricsDBE.meta.contains(metric.meta), ) - if windowing.stop is not None: + if metric.status is not None: stmt = stmt.filter( - EvaluationMetricDBE.created_at <= windowing.stop, + EvaluationMetricsDBE.status == metric.status, ) - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - stmt = stmt.order_by(EvaluationMetricDBE.id.asc()) - elif windowing.order.lower() == "descending": - stmt = stmt.order_by(EvaluationMetricDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationMetricDBE.id.asc()) - else: - stmt = stmt.order_by(EvaluationMetricDBE.id.asc()) - else: - stmt = stmt.order_by(EvaluationMetricDBE.id.asc()) + if metric.statuses is not None: + stmt = stmt.filter( + EvaluationMetricsDBE.status.in_(metric.statuses), + ) - if windowing is not None: - if windowing.limit is not None: - stmt = stmt.limit(windowing.limit) + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=EvaluationMetricsDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, + ) - result = await session.execute(stmt) + res = await session.execute(stmt) - metric_dbes = result.scalars().all() + metric_dbes = res.scalars().all() metrics = [ create_dto_from_dbe( - DTO=EvaluationMetric, + DTO=EvaluationMetrics, dbe=metric_dbe, ) for metric_dbe in metric_dbes @@ -2309,17 +2213,19 @@ async def create_queue( run_id=queue.run_id, ) - now = datetime.now(timezone.utc) - queue = EvaluationQueue( - **queue.model_dump(), - created_at=now, + _queue = EvaluationQueue( + **queue.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) - if queue.data and queue.data.user_ids: - queue.data.user_ids = [ + if _queue.data and _queue.data.user_ids: + _queue.data.user_ids = [ # type: ignore [str(repeat_user_id) for repeat_user_id in repeat_user_ids] - for repeat_user_ids in queue.data.user_ids + for repeat_user_ids in _queue.data.user_ids ] queue_dbe = create_dbe_from_dto( @@ -2334,12 +2240,12 @@ async def create_queue( await session.commit() - queue = create_dto_from_dbe( + _queue = create_dto_from_dbe( DTO=EvaluationQueue, dbe=queue_dbe, ) - return queue + return _queue except Exception as e: check_entity_creation_conflict(e) @@ -2365,21 +2271,23 @@ async def create_queues( run_id=queue.run_id, ) - now = datetime.now(timezone.utc) - queues = [ + _queues = [ EvaluationQueue( - **queue.model_dump(), - created_at=now, + **queue.model_dump( + mode="json", + exclude_none=True, + ), + created_at=datetime.now(timezone.utc), created_by_id=user_id, ) for queue in queues ] - for queue in queues: - if queue.data and queue.data.user_ids: - queue.data.user_ids = [ + for _queue in _queues: + if _queue.data and _queue.data.user_ids: + _queue.data.user_ids = [ # type: ignore [str(repeat_user_id) for repeat_user_id in repeat_user_ids] - for repeat_user_ids in queue.data.user_ids + for repeat_user_ids in _queue.data.user_ids ] queue_dbes = [ @@ -2397,7 +2305,7 @@ async def create_queues( await session.commit() - queues = [ + _queues = [ create_dto_from_dbe( DTO=EvaluationQueue, dbe=queue_dbe, @@ -2405,7 +2313,7 @@ async def create_queues( for queue_dbe in queue_dbes ] - return queues + return _queues except Exception as e: check_entity_creation_conflict(e) @@ -2423,24 +2331,27 @@ async def fetch_queue( async with engine.core_session() as session: stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.project_id == project_id, + ) + + stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.id == queue_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbe = result.scalars().first() + queue_dbe = res.scalars().first() if queue_dbe is None: return None - queue = create_dto_from_dbe( + _queue = create_dto_from_dbe( DTO=EvaluationQueue, dbe=queue_dbe, ) - return queue + return _queue @suppress_exceptions(default=[]) async def fetch_queues( @@ -2453,16 +2364,19 @@ async def fetch_queues( async with engine.core_session() as session: stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.project_id == project_id, + ) + + stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.id.in_(queue_ids), ) stmt = stmt.limit(len(queue_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbes = result.scalars().all() + queue_dbes = res.scalars().all() - queues = [ + _queues = [ create_dto_from_dbe( DTO=EvaluationQueue, dbe=queue_dbe, @@ -2470,7 +2384,7 @@ async def fetch_queues( for queue_dbe in queue_dbes ] - return queues + return _queues @suppress_exceptions() async def edit_queue( @@ -2484,14 +2398,17 @@ async def edit_queue( async with engine.core_session() as session: stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.project_id == project_id, + ) + + stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.id == queue.id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbe = result.scalars().first() + queue_dbe = res.scalars().first() if queue_dbe is None: return None @@ -2499,13 +2416,13 @@ async def edit_queue( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=queue_dbe.run_id, + run_id=queue_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( run_id=queue_dbe.run_id, - queue_id=queue_dbe.id, + queue_id=queue_dbe.id, # type: ignore ) queue_dbe = edit_dbe_from_dto( @@ -2517,12 +2434,12 @@ async def edit_queue( await session.commit() - queue = create_dto_from_dbe( + _queue = create_dto_from_dbe( DTO=EvaluationQueue, dbe=queue_dbe, ) - return queue + return _queue @suppress_exceptions(default=[]) async def edit_queues( @@ -2537,14 +2454,17 @@ async def edit_queues( async with engine.core_session() as session: stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.project_id == project_id, + ) + + stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.id.in_(queue_ids), ) stmt = stmt.limit(len(queue_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbes = result.scalars().all() + queue_dbes = res.scalars().all() if not queue_dbes: return [] @@ -2553,13 +2473,13 @@ async def edit_queues( run_flags = await _get_run_flags( session=session, project_id=project_id, - run_id=queue_dbe.run_id, + run_id=queue_dbe.run_id, # type: ignore ) if run_flags.get("is_closed", False): raise EvaluationClosedConflict( run_id=queue_dbe.run_id, - queue_id=queue_dbe.id, + queue_id=queue_dbe.id, # type: ignore ) queue = next( @@ -2577,7 +2497,7 @@ async def edit_queues( await session.commit() - queues = [ + _queues = [ create_dto_from_dbe( DTO=EvaluationQueue, dbe=queue_dbe, @@ -2585,7 +2505,7 @@ async def edit_queues( for queue_dbe in queue_dbes ] - return queues + return _queues @suppress_exceptions() async def delete_queue( @@ -2598,14 +2518,17 @@ async def delete_queue( async with engine.core_session() as session: stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.project_id == project_id, + ) + + stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.id == queue_id, ) stmt = stmt.limit(1) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbe = result.scalars().first() + queue_dbe = res.scalars().first() if queue_dbe is None: return None @@ -2627,14 +2550,17 @@ async def delete_queues( async with engine.core_session() as session: stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.project_id == project_id, + ) + + stmt = select(EvaluationQueueDBE).filter( EvaluationQueueDBE.id.in_(queue_ids), ) stmt = stmt.limit(len(queue_ids)) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbes = result.scalars().all() + queue_dbes = res.scalars().all() if not queue_dbes: return [] @@ -2652,7 +2578,7 @@ async def query_queues( *, project_id: UUID, # - queue: EvaluationQueueQuery, + queue: Optional[EvaluationQueueQuery] = None, # windowing: Optional[Windowing] = None, ) -> List[EvaluationQueue]: @@ -2661,69 +2587,59 @@ async def query_queues( EvaluationQueueDBE.project_id == project_id, ) - if queue.run_id is not None: - stmt = stmt.filter( - EvaluationQueueDBE.run_id == queue.run_id, - ) - - if queue.run_ids is not None: - stmt = stmt.filter( - EvaluationQueueDBE.run_id.in_(queue.run_ids), - ) + if queue is not None: + if queue.ids is not None: + stmt = stmt.filter( + EvaluationQueueDBE.id.in_(queue.ids), + ) - if queue.flags is not None: - stmt = stmt.filter( - EvaluationQueueDBE.flags.contains( - queue.flags.model_dump(mode="json"), - ), - ) + if queue.run_id is not None: + stmt = stmt.filter( + EvaluationQueueDBE.run_id == queue.run_id, + ) - if queue.tags is not None: - stmt = stmt.filter( - EvaluationQueueDBE.tags.contains(queue.tags), - ) + if queue.run_ids is not None: + stmt = stmt.filter( + EvaluationQueueDBE.run_id.in_(queue.run_ids), + ) - if queue.meta is not None: - stmt = stmt.filter( - EvaluationQueueDBE.meta.contains(queue.meta), - ) + if queue.flags is not None: + stmt = stmt.filter( + EvaluationQueueDBE.flags.contains(queue.flags), + ) - if windowing is not None: - if windowing.next is not None: + if queue.tags is not None: stmt = stmt.filter( - EvaluationQueueDBE.id > windowing.next, + EvaluationQueueDBE.tags.contains(queue.tags), ) - if windowing.start is not None: + if queue.meta is not None: stmt = stmt.filter( - EvaluationQueueDBE.created_at > windowing.start, + EvaluationQueueDBE.meta.contains(queue.meta), ) - if windowing.stop is not None: + if queue.name is not None: stmt = stmt.filter( - EvaluationQueueDBE.created_at <= windowing.stop, + EvaluationQueueDBE.name.contains(queue.name), ) - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - stmt = stmt.order_by(EvaluationQueueDBE.id.asc()) - elif windowing.order.lower() == "descending": - stmt = stmt.order_by(EvaluationQueueDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationQueueDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationQueueDBE.id.desc()) - else: - stmt = stmt.order_by(EvaluationQueueDBE.id.desc()) + if queue.description is not None: + stmt = stmt.filter( + EvaluationQueueDBE.description.contains(queue.description), + ) - if windowing is not None: - if windowing.limit is not None: - stmt = stmt.limit(windowing.limit) + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=EvaluationQueueDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, + ) - result = await session.execute(stmt) + res = await session.execute(stmt) - queue_dbes = result.scalars().all() + queue_dbes = res.scalars().all() queues = [ create_dto_from_dbe( @@ -2754,9 +2670,13 @@ async def _get_run_flags( stmt = select(EvaluationRunDBE.flags).filter( EvaluationRunDBE.project_id == project_id, + ) + stmt = select(EvaluationRunDBE.flags).filter( EvaluationRunDBE.id == run_id, ) - result = await session.execute(stmt) - run_flags = result.scalars().first() + + res = await session.execute(stmt) + + run_flags = res.scalars().first() return run_flags or {} diff --git a/api/oss/src/dbs/postgres/evaluations/dbas.py b/api/oss/src/dbs/postgres/evaluations/dbas.py index 3a93a47224..85bd700721 100644 --- a/api/oss/src/dbs/postgres/evaluations/dbas.py +++ b/api/oss/src/dbs/postgres/evaluations/dbas.py @@ -1,7 +1,8 @@ from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy import Column, UUID, VARCHAR, TIMESTAMP, ARRAY +from sqlalchemy import Column, UUID, VARCHAR, TIMESTAMP, INTEGER from oss.src.dbs.postgres.shared.dbas import ( + VersionDBA, IdentifierDBA, LifecycleDBA, FlagsDBA, @@ -13,12 +14,13 @@ class EvaluationRunDBA( + VersionDBA, IdentifierDBA, LifecycleDBA, + HeaderDBA, FlagsDBA, TagsDBA, MetaDBA, - HeaderDBA, DataDBA, # steps, mappings ): __abstract__ = True @@ -28,8 +30,14 @@ class EvaluationRunDBA( nullable=False, ) + references = Column( + JSONB(none_as_null=True), + nullable=True, + ) + class EvaluationScenarioDBA( + VersionDBA, IdentifierDBA, LifecycleDBA, FlagsDBA, @@ -43,13 +51,22 @@ class EvaluationScenarioDBA( nullable=False, ) + interval = Column( + INTEGER, + nullable=True, + ) + timestamp = Column( + TIMESTAMP(timezone=True), + nullable=True, + ) run_id = Column( UUID(as_uuid=True), nullable=False, ) -class EvaluationStepDBA( +class EvaluationResultDBA( + VersionDBA, IdentifierDBA, LifecycleDBA, FlagsDBA, @@ -58,28 +75,6 @@ class EvaluationStepDBA( ): __abstract__ = True - status = Column( - VARCHAR, - nullable=False, - ) - timestamp = Column( - TIMESTAMP(timezone=True), - nullable=False, - ) - - key = Column( - VARCHAR, - nullable=False, - ) - repeat_id = Column( - UUID(as_uuid=True), - nullable=False, - ) - retry_id = Column( - UUID(as_uuid=True), - nullable=False, - ) - hash_id = Column( UUID(as_uuid=True), nullable=True, @@ -97,6 +92,27 @@ class EvaluationStepDBA( nullable=True, ) + status = Column( + VARCHAR, + nullable=False, + ) + + interval = Column( + INTEGER, + nullable=True, + ) + timestamp = Column( + TIMESTAMP(timezone=True), + nullable=True, + ) + repeat_idx = Column( + INTEGER, + nullable=True, + ) + step_key = Column( + VARCHAR, + nullable=False, + ) scenario_id = Column( UUID(as_uuid=True), nullable=False, @@ -107,7 +123,8 @@ class EvaluationStepDBA( ) -class EvaluationMetricDBA( +class EvaluationMetricsDBA( + VersionDBA, IdentifierDBA, LifecycleDBA, FlagsDBA, @@ -122,11 +139,18 @@ class EvaluationMetricDBA( nullable=False, ) + interval = Column( + INTEGER, + nullable=True, + ) + timestamp = Column( + TIMESTAMP(timezone=True), + nullable=True, + ) scenario_id = Column( UUID(as_uuid=True), nullable=True, ) - run_id = Column( UUID(as_uuid=True), nullable=False, @@ -134,8 +158,10 @@ class EvaluationMetricDBA( class EvaluationQueueDBA( + VersionDBA, IdentifierDBA, LifecycleDBA, + HeaderDBA, FlagsDBA, TagsDBA, MetaDBA, @@ -143,6 +169,11 @@ class EvaluationQueueDBA( ): __abstract__ = True + status = Column( + VARCHAR, + nullable=False, + ) + run_id = Column( UUID(as_uuid=True), nullable=False, diff --git a/api/oss/src/dbs/postgres/evaluations/dbes.py b/api/oss/src/dbs/postgres/evaluations/dbes.py index 2a2fc127df..2476d77ab4 100644 --- a/api/oss/src/dbs/postgres/evaluations/dbes.py +++ b/api/oss/src/dbs/postgres/evaluations/dbes.py @@ -10,8 +10,8 @@ from oss.src.dbs.postgres.evaluations.dbas import ( EvaluationRunDBA, EvaluationScenarioDBA, - EvaluationStepDBA, - EvaluationMetricDBA, + EvaluationResultDBA, + EvaluationMetricsDBA, EvaluationQueueDBA, ) @@ -37,6 +37,22 @@ class EvaluationRunDBE( "ix_evaluation_runs_project_id", "project_id", ), # for filtering + Index( + "ix_evaluation_runs_flags", + "flags", + postgresql_using="gin", + ), # for filtering§ + Index( + "ix_evaluation_runs_tags", + "tags", + postgresql_using="gin", + ), # for filtering + Index( + "ix_evaluation_runs_references", + "references", + postgresql_using="gin", + postgresql_ops={"references": "jsonb_path_ops"}, + ), # for filtering ) @@ -70,15 +86,30 @@ class EvaluationScenarioDBE( "ix_evaluation_scenarios_run_id", "run_id", ), # for filtering + Index( + "ix_evaluation_scenarios_timestamp_interval", + "timestamp", + "interval", + ), # for filtering + Index( + "ix_evaluation_scenarios_flags", + "flags", + postgresql_using="gin", + ), # for filtering + Index( + "ix_evaluation_scenarios_tags", + "tags", + postgresql_using="gin", + ), # for filtering ) -class EvaluationStepDBE( +class EvaluationResultDBE( Base, ProjectScopeDBA, - EvaluationStepDBA, + EvaluationResultDBA, ): - __tablename__ = "evaluation_steps" + __tablename__ = "evaluation_results" __table_args__ = ( PrimaryKeyConstraint( @@ -104,27 +135,51 @@ class EvaluationStepDBE( "project_id", "run_id", "scenario_id", - "key", + "step_key", + "repeat_idx", ), # for uniqueness Index( - "ix_evaluation_steps_project_id", + "ix_evaluation_results_project_id", "project_id", ), # for filtering Index( - "ix_evaluation_steps_run_id", + "ix_evaluation_results_run_id", "run_id", ), # for filtering Index( - "ix_evaluation_steps_scenario_id", + "ix_evaluation_results_scenario_id", "scenario_id", ), # for filtering + Index( + "ix_evaluation_results_step_key", + "step_key", + ), # for filtering + Index( + "ix_evaluation_results_repeat_idx", + "repeat_idx", + ), # for filtering + Index( + "ix_evaluation_results_timestamp_interval", + "timestamp", + "interval", + ), # for filtering + Index( + "ix_evaluation_results_flags", + "flags", + postgresql_using="gin", + ), # for filtering + Index( + "ix_evaluation_results_tags", + "tags", + postgresql_using="gin", + ), # for filtering ) -class EvaluationMetricDBE( +class EvaluationMetricsDBE( Base, ProjectScopeDBA, - EvaluationMetricDBA, + EvaluationMetricsDBA, ): __tablename__ = "evaluation_metrics" @@ -152,6 +207,7 @@ class EvaluationMetricDBE( "project_id", "run_id", "scenario_id", + "timestamp", ), # for uniqueness Index( "ix_evaluation_metrics_project_id", @@ -165,6 +221,21 @@ class EvaluationMetricDBE( "ix_evaluation_metrics_scenario_id", "scenario_id", ), # for filtering + Index( + "ix_evaluation_metrics_timestamp_interval", + "timestamp", + "interval", + ), # for filtering + Index( + "ix_evaluation_metrics_flags", + "flags", + postgresql_using="gin", + ), # for filtering + Index( + "ix_evaluation_metrics_tags", + "tags", + postgresql_using="gin", + ), # for filtering ) @@ -198,4 +269,14 @@ class EvaluationQueueDBE( "ix_evaluation_queues_run_id", "run_id", ), # for filtering + Index( + "ix_evaluation_queues_flags", + "flags", + postgresql_using="gin", + ), # for filtering + Index( + "ix_evaluation_queues_tags", + "tags", + postgresql_using="gin", + ), # for filtering ) diff --git a/api/oss/src/dbs/postgres/evaluations/mappings.py b/api/oss/src/dbs/postgres/evaluations/mappings.py index d0c2b62048..bad6183fcc 100644 --- a/api/oss/src/dbs/postgres/evaluations/mappings.py +++ b/api/oss/src/dbs/postgres/evaluations/mappings.py @@ -16,13 +16,17 @@ def create_dbe_from_dto( DBE: Type[DBE_T], project_id: UUID, dto: DTO_T, + **kwargs, ) -> DBE_T: """Map a Pydantic DTO instance to a SQLAlchemy DBE, with extra project_id.""" - attributes = dto.model_dump(exclude_none=True) + attributes = dto.model_dump( + # mode="json", + exclude_none=True, + ) attributes["project_id"] = project_id - dbe = DBE(**attributes) + dbe = DBE(**attributes, **kwargs) return dbe @@ -35,7 +39,9 @@ def edit_dbe_from_dto( ) -> DBE_T: """Edit a SQLAlchemy DBE instance with a Pydantic DTO.""" - for field, value in dto.model_dump().items(): + for field, value in dto.model_dump( + # mode="json", + ).items(): setattr(dbe, field, value) for field, value in kwargs.items(): diff --git a/api/oss/src/dbs/postgres/git/dao.py b/api/oss/src/dbs/postgres/git/dao.py index d9d16e7620..3b145bacb6 100644 --- a/api/oss/src/dbs/postgres/git/dao.py +++ b/api/oss/src/dbs/postgres/git/dao.py @@ -15,7 +15,7 @@ ArtifactEdit, ArtifactQuery, ArtifactFork, - ArtifactLog, + RevisionsLog, # Variant, VariantCreate, @@ -29,6 +29,7 @@ RevisionCommit, ) +from oss.src.dbs.postgres.shared.utils import apply_windowing from oss.src.dbs.postgres.shared.exceptions import check_entity_creation_conflict from oss.src.utils.exceptions import suppress_exceptions from oss.src.dbs.postgres.shared.engine import engine @@ -125,18 +126,18 @@ async def fetch_artifact( return None async with engine.core_session() as session: - query = select(self.ArtifactDBE).filter( + stmt = select(self.ArtifactDBE).filter( self.ArtifactDBE.project_id == project_id, # type: ignore ) if artifact_ref.id: - query = query.filter(self.ArtifactDBE.id == artifact_ref.id) # type: ignore + stmt = stmt.filter(self.ArtifactDBE.id == artifact_ref.id) # type: ignore elif artifact_ref.slug: - query = query.filter(self.ArtifactDBE.slug == artifact_ref.slug) # type: ignore + stmt = stmt.filter(self.ArtifactDBE.slug == artifact_ref.slug) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) artifact_dbe = result.scalars().first() @@ -160,15 +161,15 @@ async def edit_artifact( artifact_edit: ArtifactEdit, ) -> Optional[Artifact]: async with engine.core_session() as session: - query = select(self.ArtifactDBE).filter( + stmt = select(self.ArtifactDBE).filter( self.ArtifactDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.ArtifactDBE.id == artifact_edit.id) # type: ignore + stmt = stmt.filter(self.ArtifactDBE.id == artifact_edit.id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) artifact_dbe = result.scalars().first() @@ -207,15 +208,15 @@ async def archive_artifact( artifact_id: UUID, ) -> Optional[Artifact]: async with engine.core_session() as session: - query = select(self.ArtifactDBE).filter( + stmt = select(self.ArtifactDBE).filter( self.ArtifactDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.ArtifactDBE.id == artifact_id) # type: ignore + stmt = stmt.filter(self.ArtifactDBE.id == artifact_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) artifact_dbe = result.scalars().first() @@ -249,15 +250,15 @@ async def unarchive_artifact( artifact_id: UUID, ) -> Optional[Artifact]: async with engine.core_session() as session: - query = select(self.ArtifactDBE).filter( + stmt = select(self.ArtifactDBE).filter( self.ArtifactDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.ArtifactDBE.id == artifact_id) # type: ignore + stmt = stmt.filter(self.ArtifactDBE.id == artifact_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) artifact_dbe = result.scalars().first() @@ -296,7 +297,7 @@ async def query_artifacts( windowing: Optional[Windowing] = None, ) -> List[Artifact]: async with engine.core_session() as session: - query = select(self.ArtifactDBE).filter( + stmt = select(self.ArtifactDBE).filter( self.ArtifactDBE.project_id == project_id, # type: ignore ) @@ -306,7 +307,7 @@ async def query_artifacts( ] if artifact_ids: - query = query.filter( + stmt = stmt.filter( self.ArtifactDBE.id.in_(artifact_ids) # type: ignore ) @@ -315,63 +316,50 @@ async def query_artifacts( ] if artifact_slugs: - query = query.filter( + stmt = stmt.filter( self.ArtifactDBE.slug.in_(artifact_slugs) # type: ignore ) if artifact_query.flags: - query = query.filter( + stmt = stmt.filter( self.ArtifactDBE.flags.contains(artifact_query.flags) # type: ignore ) if artifact_query.tags: - query = query.filter( + stmt = stmt.filter( self.ArtifactDBE.tags.contains(artifact_query.tags) # type: ignore ) if artifact_query.meta: - query = query.filter( + stmt = stmt.filter( self.ArtifactDBE.meta.contains(artifact_query.meta) # type: ignore ) + if artifact_query.name: + stmt = stmt.filter( + self.ArtifactDBE.name.ilike(f"%{artifact_query.name}%"), # type: ignore + ) + + if artifact_query.description: + stmt = stmt.filter( + self.ArtifactDBE.description.ilike(f"%{artifact_query.description}%"), # type: ignore + ) + if include_archived is not True: - query = query.filter( + stmt = stmt.filter( self.ArtifactDBE.deleted_at.is_(None) # type: ignore ) if windowing: - if windowing.next is not None: - query = query.filter( - self.ArtifactDBE.id > windowing.next, # type: ignore - ) - if windowing.start: - query = query.filter( - self.ArtifactDBE.created_at > windowing.start, # type: ignore - ) - - if windowing.stop: - query = query.filter( - self.ArtifactDBE.created_at <= windowing.stop, # type: ignore - ) - - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - query = query.order_by(self.ArtifactDBE.created_at.asc()) - elif windowing.order.lower() == "descending": - query = query.order_by(self.ArtifactDBE.created_at.desc()) - else: - query = query.order_by(self.ArtifactDBE.created_at.desc()) - else: - query = query.order_by(self.ArtifactDBE.created_at.desc()) - else: - query = query.order_by(self.ArtifactDBE.created_at.desc()) - - if windowing is not None: - if windowing.limit is not None: - query = query.limit(windowing.limit) + stmt = apply_windowing( + stmt=stmt, + DBE=self.ArtifactDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, + ) - result = await session.execute(query) + result = await session.execute(stmt) artifact_dbes = result.scalars().all() @@ -456,22 +444,22 @@ async def fetch_variant( return None async with engine.core_session() as session: - query = select(self.VariantDBE).filter( + stmt = select(self.VariantDBE).filter( self.VariantDBE.project_id == project_id, # type: ignore ) if variant_ref: if variant_ref.id: - query = query.filter(self.VariantDBE.id == variant_ref.id) # type: ignore + stmt = stmt.filter(self.VariantDBE.id == variant_ref.id) # type: ignore elif variant_ref.slug: - query = query.filter(self.VariantDBE.slug == variant_ref.slug) # type: ignore + stmt = stmt.filter(self.VariantDBE.slug == variant_ref.slug) # type: ignore elif artifact_ref: if artifact_ref.id: - query = query.filter(self.VariantDBE.artifact_id == artifact_ref.id) # type: ignore + stmt = stmt.filter(self.VariantDBE.artifact_id == artifact_ref.id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) variant_dbe = result.scalars().first() @@ -495,15 +483,15 @@ async def edit_variant( variant_edit: VariantEdit, ) -> Optional[Variant]: async with engine.core_session() as session: - query = select(self.VariantDBE).filter( + stmt = select(self.VariantDBE).filter( self.VariantDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.VariantDBE.id == variant_edit.id) # type: ignore + stmt = stmt.filter(self.VariantDBE.id == variant_edit.id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) variant_dbe = result.scalars().first() @@ -542,15 +530,15 @@ async def archive_variant( variant_id: UUID, ) -> Optional[Variant]: async with engine.core_session() as session: - query = select(self.VariantDBE).filter( + stmt = select(self.VariantDBE).filter( self.VariantDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.VariantDBE.id == variant_id) # type: ignore + stmt = stmt.filter(self.VariantDBE.id == variant_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) variant_dbe = result.scalars().first() @@ -584,15 +572,15 @@ async def unarchive_variant( variant_id: UUID, ) -> Optional[Variant]: async with engine.core_session() as session: - query = select(self.VariantDBE).filter( + stmt = select(self.VariantDBE).filter( self.VariantDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.VariantDBE.id == variant_id) # type: ignore + stmt = stmt.filter(self.VariantDBE.id == variant_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) variant_dbe = result.scalars().first() @@ -632,7 +620,7 @@ async def query_variants( windowing: Optional[Windowing] = None, ) -> List[Variant]: async with engine.core_session() as session: - query = select(self.VariantDBE).filter( + stmt = select(self.VariantDBE).filter( self.VariantDBE.project_id == project_id, # type: ignore ) @@ -642,7 +630,7 @@ async def query_variants( ] if artifact_ids: - query = query.filter( + stmt = stmt.filter( self.VariantDBE.artifact_id.in_(artifact_ids) # type: ignore ) @@ -650,7 +638,7 @@ async def query_variants( variant_ids = [variant.id for variant in variant_refs if variant.id] if variant_ids: - query = query.filter( + stmt = stmt.filter( self.VariantDBE.id.in_(variant_ids) # type: ignore ) @@ -659,61 +647,50 @@ async def query_variants( ] if variant_slugs: - query = query.filter( + stmt = stmt.filter( self.VariantDBE.slug.in_(variant_slugs) # type: ignore ) if variant_query.flags: - query = query.filter( + stmt = stmt.filter( self.VariantDBE.flags.contains(variant_query.flags) # type: ignore ) if variant_query.tags: - query = query.filter( + stmt = stmt.filter( self.VariantDBE.tags.contains(variant_query.tags) # type: ignore ) if variant_query.meta: - query = query.filter( + stmt = stmt.filter( self.VariantDBE.meta.contains(variant_query.meta) # type: ignore ) - if include_archived is not True: - query = query.filter(self.VariantDBE.deleted_at.is_(None)) # type: ignore - - if windowing: - if windowing.next is not None: - query = query.filter( - self.VariantDBE.id > windowing.next, # type: ignore - ) - if windowing.start: - query = query.filter( - self.VariantDBE.created_at > windowing.start, # type: ignore - ) + if variant_query.name: + stmt = stmt.filter( + self.VariantDBE.name.ilike(f"%{variant_query.name}%"), # type: ignore + ) - if windowing.stop: - query = query.filter( - self.VariantDBE.created_at <= windowing.stop, # type: ignore - ) + if variant_query.description: + stmt = stmt.filter( + self.VariantDBE.description.ilike(f"%{variant_query.description}%"), # type: ignore + ) - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - query = query.order_by(self.VariantDBE.created_at.asc()) - elif windowing.order.lower() == "descending": - query = query.order_by(self.VariantDBE.created_at.desc()) - else: - query = query.order_by(self.VariantDBE.created_at.desc()) - else: - query = query.order_by(self.VariantDBE.created_at.desc()) - else: - query = query.order_by(self.VariantDBE.created_at.desc()) + if include_archived is not True: + stmt = stmt.filter( + self.VariantDBE.deleted_at.is_(None), # type: ignore + ) - if windowing is not None: - if windowing.limit is not None: - query = query.limit(windowing.limit) + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=self.VariantDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, + ) - result = await session.execute(query) + result = await session.execute(stmt) variant_dbes = result.scalars().all() @@ -741,7 +718,7 @@ async def fork_variant( source_revisions = await self.log_revisions( project_id=project_id, # - artifact_log=ArtifactLog( + revisions_log=RevisionsLog( variant_id=artifact_fork.variant_id, revision_id=artifact_fork.revision_id, depth=artifact_fork.depth, @@ -925,28 +902,28 @@ async def fetch_revision( return None async with engine.core_session() as session: - query = select(self.RevisionDBE).filter( + stmt = select(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) if revision_ref and not revision_ref.version: if revision_ref.id: - query = query.filter(self.RevisionDBE.id == revision_ref.id) # type: ignore + stmt = stmt.filter(self.RevisionDBE.id == revision_ref.id) # type: ignore elif revision_ref.slug: - query = query.filter(self.RevisionDBE.slug == revision_ref.slug) # type: ignore + stmt = stmt.filter(self.RevisionDBE.slug == revision_ref.slug) # type: ignore elif variant_ref: if variant_ref.id: - query = query.filter(self.RevisionDBE.variant_id == variant_ref.id) # type: ignore + stmt = stmt.filter(self.RevisionDBE.variant_id == variant_ref.id) # type: ignore if revision_ref and revision_ref.version: - query = query.filter(self.RevisionDBE.version == revision_ref.version) # type: ignore + stmt = stmt.filter(self.RevisionDBE.version == revision_ref.version) # type: ignore else: - query = query.order_by(self.RevisionDBE.created_at.desc()) # type: ignore - query = query.offset(0) + stmt = stmt.order_by(self.RevisionDBE.created_at.desc()) # type: ignore + stmt = stmt.offset(0) - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) revision_dbe = result.scalars().first() @@ -970,15 +947,15 @@ async def edit_revision( revision_edit: RevisionEdit, ) -> Optional[Revision]: async with engine.core_session() as session: - query = select(self.RevisionDBE).filter( + stmt = select(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.RevisionDBE.id == revision_edit.id) # type: ignore + stmt = stmt.filter(self.RevisionDBE.id == revision_edit.id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) revision_dbe = result.scalars().first() @@ -1017,15 +994,15 @@ async def archive_revision( revision_id: UUID, ) -> Optional[Revision]: async with engine.core_session() as session: - query = select(self.RevisionDBE).filter( + stmt = select(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.RevisionDBE.id == revision_id) # type: ignore + stmt = stmt.filter(self.RevisionDBE.id == revision_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) revision_dbe = result.scalars().first() @@ -1059,15 +1036,15 @@ async def unarchive_revision( revision_id: UUID, ) -> Optional[Revision]: async with engine.core_session() as session: - query = select(self.RevisionDBE).filter( + stmt = select(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.RevisionDBE.id == revision_id) # type: ignore + stmt = stmt.filter(self.RevisionDBE.id == revision_id) # type: ignore - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) revision_dbe = result.scalars().first() @@ -1108,7 +1085,7 @@ async def query_revisions( windowing: Optional[Windowing] = None, ) -> List[Revision]: async with engine.core_session() as session: - query = select(self.RevisionDBE).filter( + stmt = select(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) @@ -1118,7 +1095,7 @@ async def query_revisions( ] if artifact_ids: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.artifact_id.in_(artifact_ids) # type: ignore ) @@ -1126,7 +1103,7 @@ async def query_revisions( variant_ids = [variant.id for variant in variant_refs if variant.id] if variant_ids: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.variant_id.in_(variant_ids) # type: ignore ) @@ -1136,7 +1113,7 @@ async def query_revisions( ] if revision_ids: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.id.in_(revision_ids) # type: ignore ) @@ -1145,68 +1122,75 @@ async def query_revisions( ] if revision_slugs: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.slug.in_(revision_slugs) # type: ignore ) - if revision_query.authors: - query = query.filter( - self.RevisionDBE.author.in_(revision_query.authors) # type: ignore - ) - if revision_query.flags: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.flags.contains(revision_query.flags) # type: ignore ) if revision_query.tags: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.tags.contains(revision_query.tags) # type: ignore ) if revision_query.meta: - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.meta.contains(revision_query.meta) # type: ignore ) - if include_archived is not True: - query = query.filter( - self.RevisionDBE.deleted_at.is_(None), # type: ignore + if revision_query.author: + stmt = stmt.filter( + self.RevisionDBE.author == revision_query.author # type: ignore ) - if windowing: - if windowing.next is not None: - query = query.filter( - self.RevisionDBE.id > windowing.next, # type: ignore - ) - if windowing.start: - query = query.filter( - self.RevisionDBE.created_at > windowing.start, # type: ignore - ) + if revision_query.authors: + stmt = stmt.filter( + self.RevisionDBE.author.in_(revision_query.authors) # type: ignore + ) - if windowing.stop: - query = query.filter( - self.RevisionDBE.created_at <= windowing.stop, # type: ignore - ) + if revision_query.date: + stmt = stmt.filter( + self.RevisionDBE.date == revision_query.date # type: ignore + ) - if windowing is not None: - if windowing.order: - if windowing.order.lower() == "ascending": - query = query.order_by(self.RevisionDBE.created_at.asc()) - elif windowing.order.lower() == "descending": - query = query.order_by(self.RevisionDBE.created_at.desc()) - else: - query = query.order_by(self.RevisionDBE.created_at.desc()) - else: - query = query.order_by(self.RevisionDBE.created_at.desc()) - else: - query = query.order_by(self.RevisionDBE.created_at.desc()) + if revision_query.dates: + stmt = stmt.filter( + self.RevisionDBE.date.in_(revision_query.dates) # type: ignore + ) - if windowing is not None: - if windowing.limit is not None: - query = query.limit(windowing.limit) + if revision_query.message: + stmt = stmt.filter( + self.RevisionDBE.message.ilike(f"%{revision_query.message}%") # type: ignore + ) - result = await session.execute(query) + if revision_query.name: + stmt = stmt.filter( + self.RevisionDBE.name.ilike(f"%{revision_query.name}%") # type: ignore + ) + + if revision_query.description: + stmt = stmt.filter( + self.RevisionDBE.description.ilike(f"%{revision_query.description}%") # type: ignore + ) + + if include_archived is not True: + stmt = stmt.filter( + self.RevisionDBE.deleted_at.is_(None), # type: ignore + ) + + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=self.RevisionDBE, + attribute="id", # UUID7 + order="descending", # jobs-style + windowing=windowing, + ) + + result = await session.execute(stmt) revision_dbes = result.scalars().all() @@ -1303,23 +1287,23 @@ async def log_revisions( *, project_id: UUID, # - artifact_log: ArtifactLog, + revisions_log: RevisionsLog, ) -> List[Revision]: revision = await self.fetch_revision( # type: ignore project_id=project_id, # variant_ref=( Reference( - id=artifact_log.variant_id, + id=revisions_log.variant_id, ) - if artifact_log.variant_id + if revisions_log.variant_id else None ), revision_ref=( Reference( - id=artifact_log.revision_id, + id=revisions_log.revision_id, ) - if artifact_log.revision_id + if revisions_log.revision_id else None ), ) @@ -1327,7 +1311,7 @@ async def log_revisions( if not revision: return [] - depth = artifact_log.depth + depth = revisions_log.depth version = int(revision.version) if revision.version else 0 if depth is not None: @@ -1339,31 +1323,31 @@ async def log_revisions( offset = None limit = None - order_by = self.RevisionDBE.created_at.desc() # type: ignore + order_by = self.RevisionDBE.id.desc() # type: ignore if depth is None: offset = 0 limit = version + 1 - order_by = self.RevisionDBE.created_at.asc() # type: ignore + order_by = self.RevisionDBE.id.asc() # type: ignore elif depth is not None: offset = max(version - depth + 1, 0) limit = min(depth, version + 1) - order_by = self.RevisionDBE.created_at.asc() # type: ignore + order_by = self.RevisionDBE.id.asc() # type: ignore async with engine.core_session() as session: - query = select(self.RevisionDBE).filter( + stmt = select(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) - query = query.filter( + stmt = stmt.filter( self.RevisionDBE.variant_id == revision.variant_id, # type: ignore ) - query = query.order_by(order_by) - query = query.offset(offset) - query = query.limit(limit) + stmt = stmt.order_by(order_by) + stmt = stmt.offset(offset) + stmt = stmt.limit(limit) - result = await session.execute(query) + result = await session.execute(stmt) revision_dbes = result.scalars().all() @@ -1378,7 +1362,7 @@ async def log_revisions( for revision_dbe in revision_dbes ] - if order_by == self.RevisionDBE.created_at.asc(): # type: ignore + if order_by == self.RevisionDBE.id.asc(): # type: ignore revisions.reverse() return revisions @@ -1419,15 +1403,15 @@ async def _set_version( version: str, ) -> None: async with engine.core_session() as session: - query = update(self.RevisionDBE).filter( + stmt = update(self.RevisionDBE).filter( self.RevisionDBE.project_id == project_id, # type: ignore ) - query = query.filter(self.RevisionDBE.id == revision_id) # type: ignore + stmt = stmt.filter(self.RevisionDBE.id == revision_id) # type: ignore - query = query.values(version=version) # type: ignore + stmt = stmt.values(version=version) # type: ignore - await session.execute(query) + await session.execute(stmt) await session.commit() diff --git a/api/oss/src/dbs/postgres/observability/dao.py b/api/oss/src/dbs/postgres/observability/dao.py index 6710d643d8..100460fc93 100644 --- a/api/oss/src/dbs/postgres/observability/dao.py +++ b/api/oss/src/dbs/postgres/observability/dao.py @@ -4,7 +4,7 @@ from uuid import UUID from sqlalchemy.exc import DBAPIError -from sqlalchemy import and_, or_, not_, distinct, Column, func, cast, text +from sqlalchemy import and_, or_, not_, distinct, Column, func, cast, text, Select from sqlalchemy import TIMESTAMP, Enum, UUID as SQLUUID, Integer, Numeric from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.future import select @@ -51,24 +51,30 @@ log = get_module_logger(__name__) _DEFAULT_TIME_DELTA = timedelta(days=30) -_DEFAULT_WINDOW = 1440 # 1 day -_DEFAULT_WINDOW_TEXT = "1 day" +_DEFAULT_INTERVAL = 1440 # 1 day +_DEFAULT_INTERVAL_TEXT = "1 day" _MAX_ALLOWED_BUCKETS = 1024 _SUGGESTED_BUCKETS_LIST = [ - (1, "1 minute"), - (5, "5 minutes"), - (15, "15 minutes"), - (30, "30 minutes"), - (60, "1 hour"), - (720, "12 hours"), - (1440, "1 day"), + (1 * 1, "1 minute"), + (1 * 5, "5 minutes"), + (1 * 15, "15 minutes"), + (1 * 30, "30 minutes"), + (1 * 60 * 1, "1 hour"), + (1 * 60 * 3, "3 hours"), + (1 * 60 * 6, "6 hours"), + (1 * 60 * 12, "12 hours"), + (1 * 60 * 24 * 1, "1 day"), + (1 * 60 * 24 * 3, "3 days"), + (1 * 60 * 24 * 7, "7 days"), + (1 * 60 * 24 * 14, "14 days"), + (1 * 60 * 24 * 30, "30 days"), ] DEBUG_ARGS = { "dialect": postgresql.dialect(), "compile_kwargs": {"literal_binds": True}, } -STATEMENT_TIMEOUT = 60_000 # milliseconds +STATEMENT_TIMEOUT = 15_000 # milliseconds COLUMNS_TO_EXCLUDE = ["content"] COLUMNS_TO_INCLUDE = [ column @@ -90,11 +96,11 @@ async def query( ) -> Tuple[List[SpanDTO], Optional[int]]: try: async with engine.tracing_session() as session: - stmt = text(f"SET LOCAL statement_timeout = '{STATEMENT_TIMEOUT}'") - await session.execute(stmt) + _stmt = text(f"SET LOCAL statement_timeout = '{STATEMENT_TIMEOUT}'") + await session.execute(_stmt) # BASE (SUB-)QUERY - query = select(*COLUMNS_TO_INCLUDE) + stmt = select(*COLUMNS_TO_INCLUDE) # ---------------- # GROUPING @@ -107,14 +113,14 @@ async def query( grouping.focus.value + "_id", ) - query = select( + stmt = select( distinct(grouping_column).label("grouping_key"), NodesDBE.created_at, ) # -------- # SCOPING - query = query.filter_by( + stmt = stmt.filter_by( project_id=project_id, ) # ------- @@ -124,10 +130,10 @@ async def query( # --------- if windowing: if windowing.oldest: - query = query.filter(NodesDBE.created_at >= windowing.oldest) + stmt = stmt.filter(NodesDBE.created_at >= windowing.oldest) if windowing.newest: - query = query.filter(NodesDBE.created_at < windowing.newest) + stmt = stmt.filter(NodesDBE.created_at < windowing.newest) # --------- # FILTERING @@ -137,7 +143,7 @@ async def query( operator = filtering.operator conditions = filtering.conditions - query = query.filter( + stmt = stmt.filter( _combine( operator, _filters(conditions), @@ -146,58 +152,58 @@ async def query( # --------- # SORTING - query = query.order_by( + stmt = stmt.order_by( NodesDBE.created_at.desc(), ) # ------- # COUNTING // dangerous with large datasets - count_query = select( + count_stmt = select( func.count() # pylint: disable=E1102:not-callable - ).select_from(query.subquery()) + ).select_from(stmt.subquery()) - count = (await session.execute(count_query)).scalar() + count = (await session.execute(count_stmt)).scalar() # -------- # PAGINATION pagination = query_dto.pagination # ---------- if pagination: - query = _chunk( - query, + stmt = _chunk( + stmt, **pagination.model_dump(), ) # ---------- # GROUPING if grouping and grouping_column: - subquery = query.subquery() + substmt = stmt.subquery() - query = select(*COLUMNS_TO_INCLUDE) - query = query.filter( - grouping_column.in_(select(subquery.c["grouping_key"])) + stmt = select(*COLUMNS_TO_INCLUDE) + stmt = stmt.filter( + grouping_column.in_(select(substmt.c["grouping_key"])) ) # SORTING - query = query.order_by( + stmt = stmt.order_by( NodesDBE.created_at.desc(), NodesDBE.time_start.asc(), ) # ------- else: # SORTING - query = query.order_by( + stmt = stmt.order_by( NodesDBE.time_start.desc(), ) # ------- # -------- # DEBUGGING - # log.trace(str(query.compile(**DEBUG_ARGS)).replace("\n", " ")) + # log.trace(str(stmt.compile(**DEBUG_ARGS)).replace("\n", " ")) # --------- # QUERY EXECUTION - spans = (await session.execute(query)).all() + spans = (await session.execute(stmt)).all() # --------------- return [map_span_dbe_to_span_dto(span) for span in spans], count @@ -207,8 +213,8 @@ async def query( if "QueryCanceledError" in str(e.orig): raise FilteringException( - "Query execution was cancelled due to timeout. " - "Please try again with a smaller time window." + "TracingQuery execution was cancelled due to timeout. " + "Please try again with a smaller time range." ) from e raise e @@ -244,7 +250,7 @@ async def analytics( oldest = None newest = None - window_text = None + interval_text = None # --------- if analytics_dto.windowing: if analytics_dto.windowing.newest: @@ -260,36 +266,36 @@ async def analytics( else: oldest = newest - _DEFAULT_TIME_DELTA - if analytics_dto.windowing.window: - _desired_window = analytics_dto.windowing.window + if analytics_dto.windowing.interval: + _desired_interval = analytics_dto.windowing.interval else: - _desired_window = _DEFAULT_WINDOW + _desired_interval = _DEFAULT_INTERVAL - _window_minutes = (newest - oldest).total_seconds() // 60 + _interval_minutes = (newest - oldest).total_seconds() // 60 - _desired_buckets = _window_minutes // _desired_window + _desired_buckets = _interval_minutes // _desired_interval if _desired_buckets > _MAX_ALLOWED_BUCKETS: for ( _suggested_minutes, - _suggest_window_text, + _suggest_interval_text, ) in _SUGGESTED_BUCKETS_LIST: - _suggested_buckets = _window_minutes // _suggested_minutes + _suggested_buckets = _interval_minutes // _suggested_minutes if _suggested_buckets <= _MAX_ALLOWED_BUCKETS: - window_text = _suggest_window_text + interval_text = _suggest_interval_text break - if not window_text: - window_text = _SUGGESTED_BUCKETS_LIST[-1][1] + if not interval_text: + interval_text = _SUGGESTED_BUCKETS_LIST[-1][1] else: - window_text = f"{_desired_window} minute{'s' if _desired_window > 1 else ''}" + interval_text = f"{_desired_interval} minute{'s' if _desired_interval > 1 else ''}" else: newest = start_of_next_day oldest = newest - _DEFAULT_TIME_DELTA - window_text = _DEFAULT_WINDOW_TEXT + interval_text = _DEFAULT_INTERVAL_TEXT # --------- @@ -299,7 +305,7 @@ async def analytics( _cost = None _tokens = None _timestamp = func.date_bin( - text(f"'{window_text}'"), + text(f"'{interval_text}'"), NodesDBE.created_at, oldest, ).label("timestamp") @@ -355,7 +361,7 @@ async def analytics( # -------- # BASE QUERY - total_query = select( + total_stmt = select( _count, _duration, _cost, @@ -363,7 +369,7 @@ async def analytics( _timestamp, ).select_from(NodesDBE) - error_query = select( + error_stmt = select( _count, _duration, _cost, @@ -373,29 +379,29 @@ async def analytics( # ---------- # WINDOWING - total_query = total_query.filter( + total_stmt = total_stmt.filter( NodesDBE.created_at >= oldest, NodesDBE.created_at < newest, ) - error_query = error_query.filter( + error_stmt = error_stmt.filter( NodesDBE.created_at >= oldest, NodesDBE.created_at < newest, ) # --------- # SCOPING - total_query = total_query.filter_by( + total_stmt = total_stmt.filter_by( project_id=project_id, ) - error_query = error_query.filter_by( + error_stmt = error_stmt.filter_by( project_id=project_id, ) # ------- # TOTAL vs ERROR - error_query = error_query.filter( + error_stmt = error_stmt.filter( NodesDBE.exception.isnot(None), ) # ---------------- @@ -407,14 +413,14 @@ async def analytics( operator = filtering.operator conditions = filtering.conditions - total_query = total_query.filter( + total_stmt = total_stmt.filter( _combine( operator, _filters(conditions), ) ) - error_query = error_query.filter( + error_stmt = error_stmt.filter( _combine( operator, _filters(conditions), @@ -427,26 +433,26 @@ async def analytics( analytics_dto.grouping and analytics_dto.grouping.focus.value != "node" ): - total_query = total_query.filter_by( + total_stmt = total_stmt.filter_by( parent_id=None, ) - error_query = error_query.filter_by( + error_stmt = error_stmt.filter_by( parent_id=None, ) # -------- # SORTING - total_query = total_query.group_by("timestamp") + total_stmt = total_stmt.group_by("timestamp") - error_query = error_query.group_by("timestamp") + error_stmt = error_stmt.group_by("timestamp") # ------- # DEBUGGING # TODO: HIDE THIS BEFORE RELEASING # print( # str( - # total_query.compile( + # total_stmt.compile( # dialect=postgresql.dialect(), # compile_kwargs={"literal_binds": True}, # ) @@ -455,7 +461,7 @@ async def analytics( # print("...") # print( # str( - # error_query.compile( + # error_stmt.compile( # dialect=postgresql.dialect(), # compile_kwargs={"literal_binds": True}, # ) @@ -464,18 +470,18 @@ async def analytics( # --------- # QUERY EXECUTION - total_bucket_dbes = (await session.execute(total_query)).all() - error_bucket_dbes = (await session.execute(error_query)).all() + total_bucket_dbes = (await session.execute(total_stmt)).all() + error_bucket_dbes = (await session.execute(error_stmt)).all() # --------------- - window = _to_minutes(window_text) + interval = _to_minutes(interval_text) - timestamps = _to_timestamps(oldest, newest, window) + timestamps = _to_timestamps(oldest, newest, interval) bucket_dtos, count = map_bucket_dbes_to_dtos( total_bucket_dbes=total_bucket_dbes, error_bucket_dbes=error_bucket_dbes, - window=window, + interval=interval, timestamps=timestamps, ) @@ -486,8 +492,8 @@ async def analytics( if "QueryCanceledError" in str(e.orig): raise FilteringException( - "Query execution was cancelled due to timeout. " - "Please try again with a smaller time window." + "TracingQuery execution was cancelled due to timeout. " + "Please try again with a smaller time range." ) from e raise e @@ -553,14 +559,14 @@ async def read_one( ) -> Union[Optional[SpanDTO], Optional[NodesDBE]]: span_dbe = None async with engine.tracing_session() as session: - query = select(NodesDBE) + stmt = select(NodesDBE) - query = query.filter_by( + stmt = stmt.filter_by( project_id=project_id, node_id=node_id, ) - span_dbe = (await session.execute(query)).scalars().one_or_none() + span_dbe = (await session.execute(stmt)).scalars().one_or_none() span_dto = None if span_dbe and to_dto: @@ -579,13 +585,13 @@ async def read_many( ) -> Union[List[SpanDTO], List[NodesDBE]]: span_dbes = [] async with engine.tracing_session() as session: - query = select(NodesDBE) + stmt = select(NodesDBE) - query = query.filter_by(project_id=project_id) + stmt = stmt.filter_by(project_id=project_id) - query = query.filter(NodesDBE.node_id.in_(node_ids)) + stmt = stmt.filter(NodesDBE.node_id.in_(node_ids)) - span_dbes = (await session.execute(query)).scalars().all() + span_dbes = (await session.execute(stmt)).scalars().all() span_dtos = [] if span_dbes and to_dto: @@ -604,13 +610,13 @@ async def read_children( ) -> Union[List[SpanDTO], List[NodesDBE]]: span_dbes = [] async with engine.tracing_session() as session: - query = select(NodesDBE) + stmt = select(NodesDBE) - query = query.filter_by(project_id=project_id) + stmt = stmt.filter_by(project_id=project_id) - query = query.filter_by(parent_id=parent_id) + stmt = stmt.filter_by(parent_id=parent_id) - span_dbes = (await session.execute(query)).scalars().all() + span_dbes = (await session.execute(stmt)).scalars().all() span_dtos = [] if span_dbes and to_dto: @@ -685,48 +691,48 @@ async def delete_many( def _chunk( - query: select, + stmt: Select, page: Optional[int] = None, size: Optional[int] = None, next: Optional[datetime] = None, # pylint: disable=W0621:redefined-builtin stop: Optional[datetime] = None, -) -> select: +) -> Select: # 1. LIMIT size OFFSET (page - 1) * size # -> unstable if windowing.newest is not set if page and size: limit = size offset = (page - 1) * size - query = query.limit(limit).offset(offset) + stmt = stmt.limit(limit).offset(offset) # 2. WHERE next > created_at LIMIT size # -> unstable if created_at is not unique elif next and size: - query = query.filter(NodesDBE.created_at < next) - query = query.limit(size) + stmt = stmt.filter(NodesDBE.created_at < next) + stmt = stmt.limit(size) # 3. WHERE next > created_at AND created_at >= stop # -> stable thanks to the = stop) + stmt = stmt.filter(NodesDBE.created_at < next) + stmt = stmt.filter(NodesDBE.created_at >= stop) # 4. WHERE LIMIT size - # -> useful as a starter query + # -> useful as a starter elif size: - query = query.limit(size) + stmt = stmt.limit(size) # 5. WHERE created_at >= stop - # -> useful as a starter query + # -> useful as a starter elif stop: - query = query.filter(NodesDBE.created_at >= stop) + stmt = stmt.filter(NodesDBE.created_at >= stop) # 6. WHERE next > created_at # -> rather useless elif next: - query = query.filter(NodesDBE.created_at < next) + stmt = stmt.filter(NodesDBE.created_at < next) - return query + return stmt def _combine( @@ -889,9 +895,9 @@ def _filters(filtering: FilteringDTO) -> list: def _to_minutes( - window_text: str, + interval_text: str, ) -> int: - quantity, unit = window_text.split() + quantity, unit = interval_text.split() quantity = int(quantity) if unit == "minute" or unit == "minutes": @@ -911,7 +917,7 @@ def _to_minutes( def _to_timestamps( oldest: datetime, newest: datetime, - window: int, + interval: int, ) -> List[datetime]: buckets = [] @@ -932,6 +938,6 @@ def _to_timestamps( while bucket_start < _newest: buckets.append(bucket_start) - bucket_start += timedelta(minutes=window) + bucket_start += timedelta(minutes=interval) return buckets diff --git a/api/oss/src/dbs/postgres/observability/mappings.py b/api/oss/src/dbs/postgres/observability/mappings.py index 93dcbf854e..6fcc8269c9 100644 --- a/api/oss/src/dbs/postgres/observability/mappings.py +++ b/api/oss/src/dbs/postgres/observability/mappings.py @@ -140,7 +140,7 @@ def map_span_dto_to_span_dbe( def map_bucket_dbes_to_dtos( total_bucket_dbes: List[NodesDBE], error_bucket_dbes: List[NodesDBE], - window: int, + interval: int, timestamps: Optional[List[datetime]] = None, ) -> Tuple[List[BucketDTO], int]: total_metrics = { @@ -178,7 +178,7 @@ def map_bucket_dbes_to_dtos( bucket_dtos = [ BucketDTO( timestamp=timestamp, - window=window, + interval=interval, total=total_metrics.get(timestamp, MetricsDTO()), error=error_metrics.get(timestamp, MetricsDTO()), ) diff --git a/api/oss/src/dbs/postgres/queries/__init__.py b/api/oss/src/dbs/postgres/queries/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/oss/src/dbs/postgres/queries/dbes.py b/api/oss/src/dbs/postgres/queries/dbes.py new file mode 100644 index 0000000000..9f7f64dd33 --- /dev/null +++ b/api/oss/src/dbs/postgres/queries/dbes.py @@ -0,0 +1,136 @@ +from sqlalchemy.orm import relationship +from sqlalchemy import ( + ForeignKeyConstraint, + PrimaryKeyConstraint, + Index, + UniqueConstraint, +) + +from oss.src.dbs.postgres.shared.base import Base +from oss.src.dbs.postgres.shared.dbas import ProjectScopeDBA +from oss.src.dbs.postgres.git.dbas import VariantDBA, RevisionDBA, ArtifactDBA + + +CASCADE_ALL_DELETE = "all, delete-orphan" + + +class QueryArtifactDBE(Base, ProjectScopeDBA, ArtifactDBA): + __tablename__ = "query_artifacts" + + __table_args__ = ( + PrimaryKeyConstraint( + "project_id", + "id", + ), + UniqueConstraint( + "project_id", + "slug", + ), + ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + Index( + "ix_query_artifacts_project_id_slug", + "project_id", + "slug", + ), + ) + + +class QueryVariantDBE(Base, ProjectScopeDBA, VariantDBA): + __tablename__ = "query_variants" + + __table_args__ = ( + PrimaryKeyConstraint( + "project_id", + "id", + ), + UniqueConstraint( + "project_id", + "slug", + ), + ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + ForeignKeyConstraint( + ["project_id", "artifact_id"], + ["query_artifacts.project_id", "query_artifacts.id"], + ondelete="CASCADE", + ), + Index( + "ix_query_variants_project_id_slug", + "project_id", + "slug", + ), + Index( + "ix_query_variants_project_id_artifact_id", + "project_id", + "artifact_id", + ), + ) + + artifact = relationship( + "QueryArtifactDBE", + backref="query_variants", + cascade=CASCADE_ALL_DELETE, + passive_deletes=True, + single_parent=True, + ) + + +class QueryRevisionDBE(Base, ProjectScopeDBA, RevisionDBA): + __tablename__ = "query_revisions" + + __table_args__ = ( + PrimaryKeyConstraint( + "project_id", + "id", + ), + UniqueConstraint( + "project_id", + "slug", + ), + ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + ForeignKeyConstraint( + ["project_id", "artifact_id"], + ["query_artifacts.project_id", "query_artifacts.id"], + ondelete="CASCADE", + ), + ForeignKeyConstraint( + ["project_id", "variant_id"], + ["query_variants.project_id", "query_variants.id"], + ondelete="CASCADE", + ), + Index( + "ix_query_revisions_project_id_slug", + "project_id", + "slug", + ), + Index( + "ix_query_revisions_project_id_artifact_id", + "project_id", + "artifact_id", + ), + Index( + "ix_query_revisions_project_id_variant_id", + "project_id", + "variant_id", + ), + ) + + artifact = relationship( + "QueryArtifactDBE", + viewonly=True, + ) + variant = relationship( + "QueryVariantDBE", + viewonly=True, + ) diff --git a/api/oss/src/dbs/postgres/secrets/dao.py b/api/oss/src/dbs/postgres/secrets/dao.py index c04ffc753b..51811500eb 100644 --- a/api/oss/src/dbs/postgres/secrets/dao.py +++ b/api/oss/src/dbs/postgres/secrets/dao.py @@ -43,11 +43,11 @@ async def get( secret_id: UUID, ): async with engine.core_session() as session: - query = select(SecretsDBE).filter_by( + stmt = select(SecretsDBE).filter_by( id=secret_id, project_id=project_id, ) - result = await session.execute(query) # type: ignore + result = await session.execute(stmt) # type: ignore secrets_dbe = result.scalar() if secrets_dbe is None: @@ -58,9 +58,9 @@ async def get( async def list(self, project_id: UUID): async with engine.core_session() as session: - query = select(SecretsDBE).filter_by(project_id=project_id) + stmt = select(SecretsDBE).filter_by(project_id=project_id) - results = await session.execute(query) # type: ignore + results = await session.execute(stmt) # type: ignore secrets_dbes = results.scalars().all() vault_secret_dtos = [ map_secrets_dbe_to_dto(secrets_dbe=secret_dbe) @@ -75,11 +75,11 @@ async def update( update_secret_dto: UpdateSecretDTO, ): async with engine.core_session() as session: - query = select(SecretsDBE).filter_by( + stmt = select(SecretsDBE).filter_by( id=secret_id, project_id=project_id, ) - result = await session.execute(query) + result = await session.execute(stmt) secrets_dbe = result.scalar() if secrets_dbe is None: @@ -101,11 +101,11 @@ async def delete( secret_id: UUID, ): async with engine.core_session() as session: - query = select(SecretsDBE).filter_by( + stmt = select(SecretsDBE).filter_by( id=secret_id, project_id=project_id, ) - result = await session.execute(query) # type: ignore + result = await session.execute(stmt) # type: ignore vault_secret_dbe = result.scalar() if vault_secret_dbe is None: return diff --git a/api/oss/src/dbs/postgres/shared/dbas.py b/api/oss/src/dbs/postgres/shared/dbas.py index 31d149dea3..c6ada32dff 100644 --- a/api/oss/src/dbs/postgres/shared/dbas.py +++ b/api/oss/src/dbs/postgres/shared/dbas.py @@ -1,6 +1,6 @@ import uuid_utils.compat as uuid -from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.dialects.postgresql import JSONB, JSON from sqlalchemy import Column, String, UUID, TIMESTAMP, func, Integer @@ -161,7 +161,7 @@ class MetaDBA: __abstract__ = True meta = Column( - JSONB(none_as_null=True), + JSON(none_as_null=True), nullable=True, ) @@ -170,7 +170,7 @@ class DataDBA: __abstract__ = True data = Column( - JSONB(none_as_null=True), + JSON(none_as_null=True), nullable=True, ) diff --git a/api/oss/src/dbs/postgres/shared/engine.py b/api/oss/src/dbs/postgres/shared/engine.py index 6603a59b32..67253fbed1 100644 --- a/api/oss/src/dbs/postgres/shared/engine.py +++ b/api/oss/src/dbs/postgres/shared/engine.py @@ -17,7 +17,7 @@ ) -DATABASE_MEMORY = 16 * 1024 * 1024 * 1024 # 8 GB +DATABASE_MEMORY = 32 * 1024 * 1024 * 1024 # 32 GB DATABASE_FACTOR = 8 * 1024 * 1024 * 1.15 # 8 MB + 15% overhead DATABASE_MAX_CONNECTIONS = 5000 # 5000 connections MAX_CONNECTIONS = min(DATABASE_MEMORY / DATABASE_FACTOR, DATABASE_MAX_CONNECTIONS) diff --git a/api/oss/src/dbs/postgres/shared/utils.py b/api/oss/src/dbs/postgres/shared/utils.py new file mode 100644 index 0000000000..7674bf114a --- /dev/null +++ b/api/oss/src/dbs/postgres/shared/utils.py @@ -0,0 +1,103 @@ +from typing import Optional + +from sqlalchemy import Select, and_, or_ + +from oss.src.core.shared.dtos import Windowing + + +def apply_windowing( + *, + stmt: Select, + DBE, + attribute: str, + order: str, + windowing: Windowing, +) -> Select: + # ---------------------------------------------------------------- # + entitty_id_attribute = DBE.id if getattr(DBE, "id", None) else None # type: ignore + span_id_attribute = DBE.span_id if getattr(DBE, "span_id", None) else None # type: ignore + id_attribute = span_id_attribute or entitty_id_attribute or None + created_at_attribute = DBE.created_at if getattr(DBE, "created_at", None) else None # type: ignore + start_time_attribute = DBE.start_time if getattr(DBE, "start_time", None) else None # type: ignore + time_attribute = start_time_attribute or created_at_attribute or None + # UUID7 -> id ---------------------------------------------------- # + order_attribute = { + "id": id_attribute, + "span_id": span_id_attribute, + "created_at": created_at_attribute, + "start_time": start_time_attribute, + }.get(attribute.lower(), created_at_attribute) + + if not order_attribute or not time_attribute or not id_attribute: + return stmt + # ---------------------------------------------------------------- # + ascending_order = order_attribute.asc() # type: ignore + descending_order = order_attribute.desc() # type: ignore + # time-style -> descending --------------------------------------- # + if order.lower() == "descending": + windowing_order = descending_order + elif order.lower() == "ascending": + windowing_order = ascending_order + else: + windowing_order = ascending_order + + # ---------------------------------------------------------------- # + + if windowing.order: + if windowing.order.lower() == "descending": + windowing_order = descending_order + elif windowing.order.lower() == "ascending": + windowing_order = ascending_order + + if windowing_order == ascending_order: + if windowing.newest: + stmt = stmt.filter(time_attribute <= windowing.newest) + if windowing.oldest: + if windowing.next: + stmt = stmt.filter(time_attribute >= windowing.oldest) + else: + stmt = stmt.filter(time_attribute > windowing.oldest) + if windowing.next: + if order_attribute is id_attribute: # UUID7 case + stmt = stmt.filter(id_attribute > windowing.next) + elif windowing.oldest: # time-based order: use .oldest + .next + stmt = stmt.filter( + or_( + time_attribute > windowing.oldest, + and_( + time_attribute == windowing.oldest, + id_attribute > windowing.next, + ), + ) + ) + else: + if windowing.newest: + if windowing.next: + stmt = stmt.filter(time_attribute <= windowing.newest) + else: + stmt = stmt.filter(time_attribute < windowing.newest) + if windowing.oldest: + stmt = stmt.filter(time_attribute >= windowing.oldest) + if windowing.next: + if order_attribute is id_attribute: # UUID7 case + stmt = stmt.filter(id_attribute < windowing.next) + elif windowing.newest: # time-based order: use .newest + .next + stmt = stmt.filter( + or_( + time_attribute < windowing.newest, + and_( + time_attribute == windowing.newest, + id_attribute < windowing.next, + ), + ) + ) + + if order_attribute is id_attribute: + stmt = stmt.order_by(windowing_order) + else: + stmt = stmt.order_by(windowing_order, id_attribute) + + if windowing.limit: + stmt = stmt.limit(windowing.limit) + + return stmt diff --git a/api/oss/src/dbs/postgres/tracing/dao.py b/api/oss/src/dbs/postgres/tracing/dao.py index 320eb4292e..d765102113 100644 --- a/api/oss/src/dbs/postgres/tracing/dao.py +++ b/api/oss/src/dbs/postgres/tracing/dao.py @@ -1,30 +1,39 @@ -from typing import Optional, List +from typing import Any, Dict, Optional, List, cast as type_cast from uuid import UUID from traceback import format_exc +from datetime import datetime +from sqlalchemy import cast, func, select, text, distinct +from sqlalchemy.types import Numeric, BigInteger +from sqlalchemy.sql import Select, and_, or_ from sqlalchemy.exc import DBAPIError from sqlalchemy import distinct, text from sqlalchemy import Select, column from sqlalchemy.dialects.postgresql import dialect from sqlalchemy.future import select -from sqlalchemy import func, cast, Numeric -from sqlalchemy.dialects import postgresql +from sqlalchemy.sql.elements import ColumnElement, Label +from sqlalchemy.dialects.postgresql import BIT from oss.src.utils.logging import get_module_logger from oss.src.utils.exceptions import suppress_exceptions +from oss.src.core.shared.dtos import Windowing from oss.src.core.shared.exceptions import EntityCreationConflict from oss.src.core.tracing.interfaces import TracingDAOInterface from oss.src.core.tracing.dtos import ( OTelLink, OTelFlatSpan, - Query, + TracingQuery, Focus, Bucket, + Filtering, + MetricSpec, + MetricsBucket, Condition, ListOperator, ) +from oss.src.dbs.postgres.shared.utils import apply_windowing from oss.src.dbs.postgres.shared.exceptions import check_entity_creation_conflict from oss.src.dbs.postgres.shared.engine import engine from oss.src.dbs.postgres.tracing.dbes import SpanDBE @@ -36,18 +45,33 @@ map_buckets, ) from oss.src.dbs.postgres.tracing.utils import ( + DEBUG_ARGS, + TIMEOUT_STMT, + # combine, filter, + # parse_windowing, + build_specs_values, + build_base_cte, + build_extract_cte, + build_type_flags, + build_statistics_stmt, + # + compute_range, + parse_pcts, + compute_iqrs, + compute_cqvs, + compute_pscs, + normalize_hist, + parse_bin_freq, + normalize_freq, + compute_uniq, ) log = get_module_logger(__name__) -DEBUG_ARGS = {"dialect": dialect(), "compile_kwargs": {"literal_binds": True}} -STATEMENT_TIMEOUT = 60_000 # milliseconds -TIMEOUT_STATEMENT = text(f"SET LOCAL statement_timeout = '{STATEMENT_TIMEOUT}'") - class TracingDAO(TracingDAOInterface): def __init__(self): @@ -136,14 +160,14 @@ async def read_span( span_id: UUID, ) -> Optional[OTelFlatSpan]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.span_id == span_id, ) - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) span_dbe = result.scalars().first() @@ -165,14 +189,14 @@ async def read_spans( span_ids: List[UUID], ) -> List[OTelFlatSpan]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.span_id.in_(span_ids), ) - query = query.limit(len(span_ids)) + stmt = stmt.limit(len(span_ids)) - result = await session.execute(query) + result = await session.execute(stmt) span_dbes = result.scalars().all() @@ -202,14 +226,14 @@ async def update_span( ) async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.span_id == new_span_dbe.span_id, ) - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) existing_span_dbe = result.scalars().first() @@ -254,14 +278,14 @@ async def update_spans( async with engine.tracing_session() as session: link_dtos: List[OTelLink] = [] - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.span_id.in_(span_ids), ) - query = query.limit(len(span_ids)) + stmt = stmt.limit(len(span_ids)) - result = await session.execute(query) + result = await session.execute(stmt) existing_span_dbes = result.scalars().all() @@ -310,14 +334,14 @@ async def delete_span( span_id: UUID, ) -> Optional[OTelLink]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.span_id == span_id, ) - query = query.limit(1) + stmt = stmt.limit(1) - result = await session.execute(query) + result = await session.execute(stmt) span_dbe = result.scalars().first() @@ -344,14 +368,14 @@ async def delete_spans( span_ids: List[UUID], ) -> List[OTelLink]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.span_id.in_(span_ids), ) - query = query.limit(len(span_ids)) + stmt = stmt.limit(len(span_ids)) - result = await session.execute(query) + result = await session.execute(stmt) span_dbes = result.scalars().all() @@ -383,19 +407,19 @@ async def read_trace( trace_id: UUID, ) -> List[OTelFlatSpan]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.trace_id == trace_id, ) - query = query.order_by(SpanDBE.start_time.asc()) + stmt = stmt.order_by(SpanDBE.start_time.asc()) - result = await session.execute(query) + result = await session.execute(stmt) span_dbes = result.scalars().all() if not span_dbes: - return None + return [] span_dtos = [ map_span_dbe_to_span_dto( @@ -415,19 +439,19 @@ async def read_traces( trace_ids: List[UUID], ) -> List[OTelFlatSpan]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.trace_id.in_(trace_ids), ) - query = query.order_by(SpanDBE.start_time.asc()) + stmt = stmt.order_by(SpanDBE.start_time.asc()) - result = await session.execute(query) + result = await session.execute(stmt) span_dbes = result.scalars().all() if not span_dbes: - return None + return [] span_dtos = [ map_span_dbe_to_span_dto( @@ -448,17 +472,17 @@ async def delete_trace( trace_id: UUID, ) -> List[OTelLink]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.trace_id == trace_id, ) - result = await session.execute(query) + result = await session.execute(stmt) span_dbes = result.scalars().all() if not span_dbes: - return None + return [] link_dtos = [ map_span_dbe_to_link_dto( @@ -484,17 +508,17 @@ async def delete_traces( trace_ids: List[UUID], ) -> List[OTelLink]: async with engine.tracing_session() as session: - query = select(SpanDBE).filter( + stmt = select(SpanDBE).filter( SpanDBE.project_id == project_id, SpanDBE.trace_id.in_(trace_ids), ) - result = await session.execute(query) + result = await session.execute(stmt) span_dbes = result.scalars().all() if not span_dbes: - return None + return [] link_dtos = [ map_span_dbe_to_link_dto( @@ -518,23 +542,29 @@ async def query( *, project_id: UUID, # - query: Query, + query: TracingQuery, # type: ignore ) -> List[OTelFlatSpan]: # DE-STRUCTURING focus = query.formatting.focus if query.formatting else None oldest = query.windowing.oldest if query.windowing else None newest = query.windowing.newest if query.windowing else None + next = query.windowing.next if query.windowing else None limit = query.windowing.limit if query.windowing else None + rate = query.windowing.rate if query.windowing else None operator = query.filtering.operator if query.filtering else None conditions = query.filtering.conditions if query.filtering else None # -------------- + # DEBUGGING + # log.trace(query.model_dump(mode="json", exclude_none=True)) + # --------- + try: async with engine.tracing_session() as session: # TIMEOUT - await session.execute(TIMEOUT_STATEMENT) + await session.execute(TIMEOUT_STMT) # ------- # BASE (SUB-)STMT @@ -545,7 +575,7 @@ async def query( if focus == Focus.TRACE: base = select( SpanDBE.trace_id, - SpanDBE.created_at, + SpanDBE.start_time, ).distinct(SpanDBE.trace_id) # -------- @@ -553,31 +583,68 @@ async def query( base = base.filter(SpanDBE.project_id == project_id) # ------- - # WINDOWING - if oldest: - base = base.filter(SpanDBE.start_time >= oldest) - if newest: - base = base.filter(SpanDBE.start_time < newest) - # --------- - - # DEBUGGING - # log.trace(query) - # --------- - # FILTERING if operator and conditions: - base = base.filter(combine(operator, filter(conditions))) + base = base.filter( + type_cast( + ColumnElement[bool], + combine( + operator=operator, + clauses=filter(conditions), + ), + ) + ) + # --------- + + # WINDOWING + if rate is not None: + percent = max(0, min(int(rate * 100.0), 100)) + + if percent == 0: + return [] + + if percent < 100: + base = base.where( + cast( + text("concat('x', left(cast(trace_id as varchar), 8))"), + BIT(32), + ).cast(BigInteger) + % 100 + < percent + ) # --------- # GROUPING if focus == Focus.TRACE: - base = base.order_by(SpanDBE.trace_id, SpanDBE.created_at.desc()) + # WINDOWING + if newest: + if next: + base = base.filter(SpanDBE.start_time <= newest) + else: + base = base.filter(SpanDBE.start_time < newest) + if oldest: + base = base.filter(SpanDBE.start_time >= oldest) + # --------- + + base = base.order_by(SpanDBE.trace_id, SpanDBE.start_time.desc()) inner = base.subquery("latest_per_trace") - # now order by created_at DESC globally and apply LIMIT here uniq = select(inner.c.trace_id) - uniq = uniq.order_by(inner.c.created_at.desc()) + + uniq = uniq.order_by(inner.c.start_time.desc()) + + if next and newest: + uniq = uniq.filter( + or_( + inner.c.start_time < newest, + and_( + inner.c.start_time == newest, + inner.c.trace_id < next, + ), + ) + ) + if limit: uniq = uniq.limit(limit) @@ -587,10 +654,16 @@ async def query( .order_by(SpanDBE.created_at.desc(), SpanDBE.start_time.asc()) ) else: - stmt = base.order_by(SpanDBE.created_at.desc()) - - if limit: - stmt = stmt.limit(limit) + if query.windowing: + stmt = apply_windowing( + stmt=base, + DBE=SpanDBE, + attribute="start_time", + order="descending", + windowing=query.windowing, + ) + else: + stmt = base # -------- # DEBUGGING @@ -614,8 +687,8 @@ async def query( if "QueryCanceledError" in str(e.orig): raise Exception( # pylint: disable=broad-exception-raised - "Query execution was cancelled due to timeout. " - "Please try again with a smaller time window." + "TracingQuery execution was cancelled due to timeout. " + "Please try again with a smaller time interval." ) from e raise e @@ -627,29 +700,29 @@ async def query( ### ANALYTICS - async def analytics( + @suppress_exceptions(default=[]) + async def legacy_analytics( self, *, project_id: UUID, # - query: Query, + query: TracingQuery, ) -> List[Bucket]: + # DEBUGGING + # log.trace(query.model_dump(mode="json", exclude_none=True)) + # --------- + ( oldest, newest, stride, - window, + interval, timestamps, ) = parse_windowing(query.windowing) - # DEBUGGING - # log.trace(query.model_dump(mode="json", exclude_none=True)) - # --------- - try: async with engine.tracing_session() as session: - stmt = text(f"SET LOCAL statement_timeout = '{STATEMENT_TIMEOUT}'") - await session.execute(stmt) + await session.execute(TIMEOUT_STMT) # BASE QUERY HELPERS _count = func.count().label("count") # pylint: disable=not-callable @@ -699,7 +772,7 @@ async def analytics( # -------- # BASE QUERY - total_query = select( + total_stmt = select( _count, _duration, _costs, @@ -707,7 +780,7 @@ async def analytics( _timestamp, ).select_from(SpanDBE) - errors_query = select( + errors_stmt = select( _count, _duration, _costs, @@ -717,23 +790,23 @@ async def analytics( # ---------- # WINDOWING - total_query = total_query.filter( + total_stmt = total_stmt.filter( SpanDBE.created_at >= oldest, SpanDBE.created_at < newest, ) - errors_query = errors_query.filter( + errors_stmt = errors_stmt.filter( SpanDBE.created_at >= oldest, SpanDBE.created_at < newest, ) # --------- # SCOPING - total_query = total_query.filter_by( + total_stmt = total_stmt.filter_by( project_id=project_id, ) - errors_query = errors_query.filter_by( + errors_stmt = errors_stmt.filter_by( project_id=project_id, ) # ------- @@ -747,64 +820,76 @@ async def analytics( operator = query.filtering.operator conditions = query.filtering.conditions - total_query = total_query.filter( - combine( - operator, - filter(conditions), + total_stmt = total_stmt.filter( + type_cast( + ColumnElement[bool], + combine( + operator=operator, + clauses=filter(conditions), + ), ) ) - errors_query = errors_query.filter( - combine( - operator, - filter( - conditions - + [ - Condition( - field="events", - operator=ListOperator.IN, - value=[{"name": "exception"}], - ) - ] + errors_stmt = errors_stmt.filter( + type_cast( + ColumnElement[bool], + combine( + operator=operator, + clauses=filter( + conditions + + [ + Condition( + field="events", + operator=ListOperator.IN, + value=[{"name": "exception"}], + ) + ] + ), ), - ), + ) ) # --------- # GROUPING if query.formatting and query.formatting.focus == Focus.TRACE: - total_query = total_query.filter_by( + total_stmt = total_stmt.filter_by( parent_id=None, ) - errors_query = errors_query.filter_by( + errors_stmt = errors_stmt.filter_by( parent_id=None, ) # -------- # SORTING - total_query = total_query.group_by("timestamp") + total_stmt = total_stmt.group_by("timestamp") - errors_query = errors_query.group_by("timestamp") + errors_stmt = errors_stmt.group_by("timestamp") # ------- # DEBUGGING - # log.trace(str(total_query.compile(**DEBUG_ARGS)).replace("\n", " ")) - # log.trace(str(errors_query.compile(**DEBUG_ARGS)).replace("\n", " ")) + # log.trace(str(total_stmt.compile(**DEBUG_ARGS)).replace("\n", " ")) + # log.trace(str(errors_stmt.compile(**DEBUG_ARGS)).replace("\n", " ")) # --------- # QUERY EXECUTION - total_buckets = (await session.execute(total_query)).all() - errors_buckets = (await session.execute(errors_query)).all() + total_buckets = list((await session.execute(total_stmt)).all()) + errors_buckets = list((await session.execute(errors_stmt)).all()) # --------------- buckets = map_buckets( total_buckets=total_buckets, errors_buckets=errors_buckets, - window=window, + interval=interval, timestamps=timestamps, ) + # DEBUGGING + # log.trace( + # [b.model_dump(mode="json", exclude_none=True) for b in buckets] + # ) + # --------- + return buckets except DBAPIError as e: @@ -814,7 +899,7 @@ async def analytics( if "AnalyticsCanceledError" in str(e.orig): raise Exception( # pylint: disable=broad-exception-raised "Analytics execution was cancelled due to timeout. " - "Please try again with a smaller time window." + "Please try again with a smaller time interval." ) from e raise e @@ -823,3 +908,209 @@ async def analytics( log.error(f"{type(e).__name__}: {e}") log.error(format_exc()) raise e + + @suppress_exceptions(default=[]) + async def analytics( + self, + *, + project_id: UUID, + # + query: TracingQuery, + specs: List[MetricSpec], + ) -> List[MetricsBucket]: + # DEBUGGING + # log.trace(query.model_dump(mode="json", exclude_none=True)) + # log.trace([s.model_dump(mode="json", exclude_none=True) for s in specs]) + # --------- + + if not query.windowing: + query.windowing = Windowing() + + if not query.filtering: + query.filtering = Filtering() + + ( + oldest, + newest, + stride, + interval, + timestamps, + ) = parse_windowing( + windowing=query.windowing, + ) + + if query.windowing.rate is not None: + percent = max(0, min(int(query.windowing.rate * 100.0), 100)) + if percent == 0: + return [] + + metric_specs: Dict[int, MetricSpec] = { + idx: MetricSpec( + **s.model_dump(exclude={"path"}), + path=s.path.removeprefix("attributes."), # path prefix removal + ) + for idx, s in enumerate(specs) + } + + type_flags = build_type_flags( + metric_specs=list(metric_specs.values()), + ) + + if type_flags is None: + log.warning("[TRACING] [analytics] no type flags found") + return [] + + specs_values = build_specs_values( + metric_specs=list(metric_specs.values()), + ) + + if specs_values is None: + log.warning("[TRACING] [analytics] no specs values found") + return [] + + base_cte = build_base_cte( + project_id=project_id, + # + oldest=oldest, + newest=newest, + stride=stride, + rate=query.windowing.rate, + # + filtering=query.filtering, + ) + + if base_cte is None: + log.warning("[TRACING] [analytics] no base CTE found") + return [] + + extract_cte = build_extract_cte( + base_cte=base_cte, + specs_values=specs_values, + ) + + if extract_cte is None: + log.warning("[TRACING] [analytics] no extract CTE found") + return [] + + statistics_stmt = build_statistics_stmt( + extract_cte=extract_cte, + type_flags=type_flags, + ) + + if statistics_stmt is None: + log.warning("[TRACING] [analytics] no statistics CTE found") + return [] + + # DEBUGGING + # log.trace(str(statistics_stmt.compile(**DEBUG_ARGS)).replace("\n", " ")) + # --------- + + async with engine.tracing_session() as session: + await session.execute(TIMEOUT_STMT) + + rows = (await session.execute(select(statistics_stmt))).mappings().all() + + rows = [{**row} for row in rows] + + for r in rows: + kind: str = r["kind"] + value: Dict[str, Any] = dict() + + if kind == "cont_count": + value = r["value"] or {} + elif kind == "cont_basics": + value = r["value"] or {} + value = compute_range(value) + elif kind == "cont_pcts": + value = {} + value = parse_pcts(r["value"] or []) + value = compute_iqrs(value) + value = compute_cqvs(value) + value = compute_pscs(value) + elif kind == "cont_hist": + value = normalize_hist(r["value"] or []) + + elif kind == "disc_count": + value = r["value"] or {} + elif kind == "disc_basics": + value = r["value"] or {} + value = compute_range(value) + elif kind == "disc_pcts": + value = {} + value = parse_pcts(r["value"] or []) + value = compute_iqrs(value) + value = compute_cqvs(value) + value = compute_pscs(value) + elif kind == "disc_freq": + value = normalize_freq(r["value"] or []) + value = compute_uniq(value) + + elif kind == "cls_count": + value = r["value"] or {} + elif kind == "cls_freq": + value = normalize_freq(r["value"] or []) + value = compute_uniq(value) + + elif kind == "lbl_count": + value = r["value"] or {} + elif kind == "lbl_freq": + value = normalize_freq(r["value"] or []) + value = compute_uniq(value) + + elif kind == "bin_count": + value = r["value"] or {} + elif kind == "bin_freq": + value = r["value"] or {} + value = normalize_freq(parse_bin_freq(value)) + value = compute_uniq(value) + + elif kind == "str_count": + value = r["value"] or {} + + elif kind == "json_count": + value = r["value"] or {} + + r["value"] = value + + per_timestamp: Dict[datetime, Dict[str, Dict[str, Any]]] = dict() + + for r in rows: + _timestamp: datetime = r["timestamp"] + _idx: int = r["idx"] + + if _idx > len(metric_specs): + continue + + _spec = metric_specs.get(_idx) + + if not _spec: + continue + + _path = "attributes." + _spec.path # revert path prefix removal + _type = _spec.type + + if _timestamp not in per_timestamp: + per_timestamp[_timestamp] = dict() + + if _path not in per_timestamp[_timestamp]: + per_timestamp[_timestamp][_path] = dict(type=_type.value) + + per_timestamp[_timestamp][_path] = ( + per_timestamp[_timestamp][_path] | r["value"] + ) + + buckets: List[MetricsBucket] = [] + + for timestamp, metrics in per_timestamp.items(): + bucket = MetricsBucket( + timestamp=timestamp, + interval=interval, + metrics=metrics, + ) + buckets.append(bucket) + + # DEBUGGING + # log.trace([b.model_dump(mode="json", exclude_none=True) for b in buckets]) + # --------- + + return buckets diff --git a/api/oss/src/dbs/postgres/tracing/dbes.py b/api/oss/src/dbs/postgres/tracing/dbes.py index 0fec8ff1e9..6bf291d5dc 100644 --- a/api/oss/src/dbs/postgres/tracing/dbes.py +++ b/api/oss/src/dbs/postgres/tracing/dbes.py @@ -1,6 +1,8 @@ from sqlalchemy import ( PrimaryKeyConstraint, Index, + desc, + text, ) from oss.src.dbs.postgres.shared.base import Base @@ -52,6 +54,12 @@ class SpanDBE( "project_id", "span_type", ), # for filtering + Index( + "ix_spans_project_id_trace_id_created_at", + "project_id", + "trace_id", + desc("created_at"), + ), # for sorting and scrolling within a trace Index( "ix_attributes_gin", "attributes", @@ -79,5 +87,16 @@ class SpanDBE( "ix_events_gin", "events", postgresql_using="gin", + postgresql_ops={"events": "jsonb_path_ops"}, ), # for filtering + Index( + "ix_spans_fts_attributes_gin", + text("to_tsvector('simple', attributes)"), + postgresql_using="gin", + ), # for full-text search on attributes + Index( + "ix_spans_fts_events_gin", + text("to_tsvector('simple', events)"), + postgresql_using="gin", + ), # for full-text search on events ) diff --git a/api/oss/src/dbs/postgres/tracing/mappings.py b/api/oss/src/dbs/postgres/tracing/mappings.py index a7c9862bca..f8ce24ad3a 100644 --- a/api/oss/src/dbs/postgres/tracing/mappings.py +++ b/api/oss/src/dbs/postgres/tracing/mappings.py @@ -9,6 +9,7 @@ OTelLink, OTelHash, OTelReference, + OTelFlatSpan, OTelSpan, OTelSpanKind, OTelStatusCode, @@ -129,8 +130,8 @@ def map_span_dbe_to_span_dto( def map_span_dto_to_span_dbe( - project_id: str, - span_dto: OTelSpan, + project_id: UUID, + span_dto: OTelFlatSpan, user_id: Optional[UUID] = None, ) -> SpanDBE: span_dbe = SpanDBE( @@ -195,7 +196,7 @@ def map_span_dto_to_span_dbe( def map_buckets( total_buckets: list, errors_buckets: list, - window: int, + interval: int, timestamps: Optional[List[datetime]] = None, ) -> List[Bucket]: total_metrics = { @@ -238,7 +239,7 @@ def map_buckets( buckets = [ Bucket( timestamp=timestamp, - window=window, + interval=interval, total=total_metrics.get(timestamp, Analytics()), errors=errors_metrics.get(timestamp, Analytics()), ) diff --git a/api/oss/src/dbs/postgres/tracing/utils.py b/api/oss/src/dbs/postgres/tracing/utils.py index 0eea68a7aa..1dead72f74 100644 --- a/api/oss/src/dbs/postgres/tracing/utils.py +++ b/api/oss/src/dbs/postgres/tracing/utils.py @@ -1,12 +1,20 @@ -from typing import Any, Optional, List, Dict, Union, Tuple +from typing import Any, Dict, Tuple, Optional, Union, List, cast as type_cast from json import dumps from datetime import datetime, timedelta, time, timezone +from uuid import UUID +from math import ceil, floor -from sqlalchemy import and_, or_, not_, cast, Column, bindparam, Text, text +from sqlalchemy import case, cast, func, literal, literal_column, select, text +from sqlalchemy import and_, or_, not_, Column, bindparam, Text +from sqlalchemy import values, column, true from sqlalchemy import Integer, String, Float -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.sql import func, ClauseElement, ColumnElement - +from sqlalchemy.sql import func, Select, ClauseElement, ColumnElement +from sqlalchemy.types import Numeric, Boolean, Integer, String, BigInteger +from sqlalchemy.future import select +from sqlalchemy.sql.elements import ColumnElement +from sqlalchemy.sql.selectable import FromClause +from sqlalchemy.dialects.postgresql import dialect, ARRAY, TEXT, JSONB, BIT +from sqlalchemy.dialects.postgresql import aggregate_order_by from oss.src.utils.logging import get_module_logger @@ -36,10 +44,17 @@ ListOperator, ExistenceOperator, Windowing, + # + MetricType, + MetricSpec, ) log = get_module_logger(__name__) +DEBUG_ARGS = {"dialect": dialect(), "compile_kwargs": {"literal_binds": True}} +TIMEOUT_STMT = text(f"SET LOCAL statement_timeout = '{15_000}'") # milliseconds + + # UTILS @@ -644,6 +659,7 @@ def _handle_fts_field( condition: Condition, ) -> List[ClauseElement]: conditions = [] + # ------------------------- # # field = condition.field # key = condition.key @@ -658,19 +674,19 @@ def _handle_fts_field( conditions.append(ts_vector.op("@@")(ts_query)) - # # ------------------------- # - # # field = condition.field - # # key = condition.key - # value = condition.value - # # options = condition.options - # # operator = condition.operator - # attribute: Column = getattr(SpanDBE, "events") - # # ------------------------- # + # ------------------------- # + # field = condition.field + # key = condition.key + value = condition.value + # options = condition.options + # operator = condition.operator + attribute: Column = getattr(SpanDBE, "events") + # ------------------------- # - # ts_vector = func.to_tsvector(text("'simple'"), attribute) - # ts_query = func.websearch_to_tsquery(text("'simple'"), text(f"'{value}'")) + ts_vector = func.to_tsvector(text("'simple'"), attribute) + ts_query = func.websearch_to_tsquery(text("'simple'"), text(f"'{value}'")) - # conditions.append(ts_vector.op("@@")(ts_query)) + conditions.append(ts_vector.op("@@")(ts_query)) return [or_(*conditions)] @@ -704,7 +720,7 @@ def combine( def filter( # pylint:disable=redefined-builtin - filtering: Filtering, + filtering: List[Union[Condition, Filtering]], ) -> List[ClauseElement]: clauses = [] @@ -774,25 +790,65 @@ def filter( # pylint:disable=redefined-builtin # ANALYTICS _DEFAULT_TIME_DELTA = timedelta(days=30) -_DEFAULT_WINDOW_MINUTES = 1440 # 1 day -_DEFAULT_WINDOW_TEXT = "1 day" _MAX_ALLOWED_BUCKETS = 1024 _SUGGESTED_BUCKETS_LIST = [ - (1, "1 minute"), - (5, "5 minutes"), - (15, "15 minutes"), - (30, "30 minutes"), - (60, "1 hour"), - (720, "12 hours"), - (1440, "1 day"), + (1 * 1, "1 minute"), + (1 * 5, "5 minutes"), + (1 * 15, "15 minutes"), + (1 * 30, "30 minutes"), + (1 * 60 * 1, "1 hour"), + (1 * 60 * 3, "3 hours"), + (1 * 60 * 6, "6 hours"), + (1 * 60 * 12, "12 hours"), + (1 * 60 * 24 * 1, "1 day"), + (1 * 60 * 24 * 3, "3 days"), + (1 * 60 * 24 * 7, "7 days"), + (1 * 60 * 24 * 14, "14 days"), + (1 * 60 * 24 * 30, "30 days"), ] -def _stride_to_window( - window_text: str, +def _pluralize_minutes(m: int) -> str: + return f"{m} minute{'s' if m != 1 else ''}" + + +def _get_stride( + oldest: datetime, + newest: datetime, + interval: Optional[int] = None, +) -> str: + # total range in minutes (never negative) + range_minutes_float = max(0.0, (newest - oldest).total_seconds() / 60.0) + + # If no interval is provided, make the stride the whole range -> single bucket + if interval is None: + minutes = max(1, int(ceil(range_minutes_float))) + return _pluralize_minutes(minutes) + + # Interval given: enforce bucket limits + desired_interval = max(1, int(interval)) + # number of buckets with requested interval + desired_buckets = floor(range_minutes_float / desired_interval) + + if desired_buckets <= _MAX_ALLOWED_BUCKETS: + return _pluralize_minutes(desired_interval) + + # Too many buckets -> pick the first suggested interval that satisfies the cap + for suggested_minutes, suggested_text in _SUGGESTED_BUCKETS_LIST: + suggested_minutes = max(1, int(suggested_minutes)) + suggested_buckets = floor(range_minutes_float / suggested_minutes) + if suggested_buckets <= _MAX_ALLOWED_BUCKETS: + return suggested_text + + # If nothing fits, use the last (largest) suggestion + return _SUGGESTED_BUCKETS_LIST[-1][1] + + +def _get_interval( + stride: str, ) -> int: - qty, unit = window_text.split() - qty = int(qty) + qty_text, unit = stride.split() + qty = int(qty_text) return { "minute": qty, "minutes": qty, @@ -805,22 +861,25 @@ def _stride_to_window( }[unit] -def _window_to_timestamps( +def _get_timestamps( oldest: datetime, newest: datetime, - window: int, + interval: int, ) -> List[datetime]: current = oldest buckets = [] while current < newest: buckets.append(current) - current += timedelta(minutes=window) + current += timedelta(minutes=interval) return buckets def parse_windowing( - windowing: Windowing, -) -> int: + windowing: Optional[Windowing] = None, +) -> tuple: + if not windowing: + windowing = Windowing() + now = datetime.now(timezone.utc) start_of_next_day = datetime.combine( now + timedelta(days=1), time.min, tzinfo=timezone.utc @@ -840,28 +899,1142 @@ def parse_windowing( else newest - _DEFAULT_TIME_DELTA ) - desired_window = ( - windowing.window if windowing and windowing.window else _DEFAULT_WINDOW_MINUTES + stride = _get_stride(oldest, newest, windowing.interval) + interval = _get_interval(stride) + timestamps = _get_timestamps(oldest, newest, interval) + + return oldest, newest, stride, interval, timestamps + + +PERCENTILE_LEVELS = { + k: v / 100 + for k, v in { + "p00.05": 0.05, + "p00.1": 0.1, + "p00.5": 0.5, + "p01": 1, + "p02.5": 2.5, + "p05": 5, + "p10": 10, + "p12.5": 12.5, + "p20": 20, + "p25": 25, + "p30": 30, + "p37.5": 37.5, + "p40": 40, + "p50": 50, + "p60": 60, + "p62.5": 62.5, + "p70": 70, + "p75": 75, + "p80": 80, + "p87.5": 87.5, + "p90": 90, + "p95": 95, + "p97.5": 97.5, + "p99": 99, + "p99.5": 99.5, + "p99.9": 99.9, + "p99.95": 99.95, + }.items() +} +PERCENTILES_KEYS: List[str] = list(PERCENTILE_LEVELS.keys()) +PERCENTILES_VALUES: List[float] = list(PERCENTILE_LEVELS.values()) +IQR_ITEMS: List[Tuple[str, Tuple[str, str]]] = [ + ("iqr25", ("p37.5", "p62.5")), + ("iqr50", ("p25", "p75")), + ("iqr75", ("p12.5", "p87.5")), + ("iqr80", ("p10", "p90")), + ("iqr90", ("p05", "p95")), + ("iqr95", ("p02.5", "p97.5")), + ("iqr99", ("p00.5", "p99.5")), + ("iqr99.9", ("p00.05", "p99.95")), +] +CQV_ITEMS: List[Tuple[str, Tuple[str, str]]] = [ + ("cqv25", ("p37.5", "p62.5")), + ("cqv50", ("p25", "p75")), + ("cqv75", ("p12.5", "p87.5")), + ("cqv80", ("p10", "p90")), + ("cqv90", ("p05", "p95")), + ("cqv99", ("p00.5", "p99.5")), + ("cqv95", ("p02.5", "p97.5")), + ("cqv99.9", ("p00.05", "p99.95")), +] +PSC_ITEMS: List[Tuple[str, Tuple[str, str]]] = [ + ("psc25", ("p37.5", "p62.5")), + ("psc50", ("p25", "p75")), + ("psc75", ("p12.5", "p87.5")), + ("psc80", ("p10", "p90")), + ("psc90", ("p05", "p95")), + ("psc99", ("p00.5", "p99.5")), + ("psc95", ("p02.5", "p97.5")), + ("psc99.9", ("p00.05", "p99.95")), +] +TOP_K = 3 + + +def build_type_flags( + *, + metric_specs: List[MetricSpec], +) -> Optional[Dict[str, bool]]: + present = {s.type for s in metric_specs} + + return { + "need_numeric_continuous": (MetricType.NUMERIC_CONTINUOUS in present), + "need_numeric_discrete": (MetricType.NUMERIC_DISCRETE in present), + "need_categorical_single": (MetricType.CATEGORICAL_SINGLE in present), + "need_categorical_multiple": (MetricType.CATEGORICAL_MULTIPLE in present), + "need_binary": (MetricType.BINARY in present), + "need_string": (MetricType.STRING in present), + "need_json": (MetricType.JSON in present), + } + + +def build_specs_values( + *, + metric_specs: List[MetricSpec], +) -> Optional[FromClause]: + if not len(metric_specs): + return None + + data = [ + ( + idx, + s.path.split("."), # -> text[] + s.type.value, + s.bins, + s.vmin, + s.vmax, + (s.edge if s.edge is not None else True), + ) + for idx, s in enumerate(metric_specs) + ] + + vals = values( + column("idx", Integer), + column("path", ARRAY(TEXT())), + column("type", String), + column("bins", Integer), + column("vmin", Numeric), + column("vmax", Numeric), + column("edge", Boolean), + name="specs_values", + ).data(data) + + return vals.alias("specs_values") + + +def build_base_cte( + *, + project_id: UUID, + oldest: datetime, + newest: datetime, + stride: str, + rate: Optional[float] = None, + filtering: Optional[Filtering] = None, +) -> Optional[FromClause]: + timestamp = func.date_bin( + text(f"'{stride}'"), + SpanDBE.created_at, + oldest, + ).label("timestamp") + + base_stmt: Select = ( + select( + timestamp, + SpanDBE.project_id, + SpanDBE.trace_id, + SpanDBE.span_id, + SpanDBE.parent_id, + SpanDBE.created_at, + SpanDBE.attributes.label("attributes"), + ) + .select_from(SpanDBE) + .where( + SpanDBE.project_id == project_id, + SpanDBE.created_at >= oldest, + SpanDBE.created_at < newest, + ) + .where(SpanDBE.parent_id.is_(None)) ) - period_minutes = (newest - oldest).total_seconds() / 60 - desired_buckets = period_minutes // desired_window - if desired_buckets > _MAX_ALLOWED_BUCKETS: - stride = None - for suggested_minutes, suggested_text in _SUGGESTED_BUCKETS_LIST: - suggested_buckets = period_minutes // suggested_minutes - if suggested_buckets <= _MAX_ALLOWED_BUCKETS: - desired_window = suggested_minutes - stride = suggested_text - break - if not stride: - # fallback to last in list - desired_window = _SUGGESTED_BUCKETS_LIST[-1][0] - stride = _SUGGESTED_BUCKETS_LIST[-1][1] - else: - stride = f"{desired_window} minute{'s' if desired_window > 1 else ''}" + # External filters + if filtering is not None: + base_stmt = base_stmt.filter( + type_cast( + ColumnElement[bool], + combine( + operator=filtering.operator, + clauses=filter(filtering.conditions), + ), + ) + ) + + if rate is not None: + percent = max(0, min(int(rate * 100.0), 100)) - window = _stride_to_window(stride) - timestamps = _window_to_timestamps(oldest, newest, window) + if percent == 0: + return None - return oldest, newest, stride, window, timestamps + if percent < 100: + base_stmt = base_stmt.where( + cast( + text("concat('x', left(cast(trace_id as varchar), 8))"), BIT(32) + ).cast(BigInteger) + % 100 + < percent + ) + + return base_stmt.cte("base_cte") + + +def build_extract_cte( + *, + base_cte: FromClause, + specs_values: FromClause, +) -> Optional[FromClause]: + extract_stmt = ( + select( + base_cte.c.timestamp, + specs_values.c.idx, + specs_values.c.type, + specs_values.c.bins, + specs_values.c.vmin, + specs_values.c.vmax, + specs_values.c.edge, + base_cte.c.attributes.op("#>")(specs_values.c.path).label("jv"), + ) + .select_from(base_cte) + .join(specs_values, true()) + ) + + return extract_stmt.cte("extract_cte") + + +def build_statistics_stmt( + extract_cte: FromClause, + type_flags: Dict[str, bool], +) -> Optional[FromClause]: + blocks: List[Select] = [] + + # Use independent IFs so multiple families can be included together + if type_flags.get("need_numeric_continuous"): + blocks += build_numeric_continuous_blocks(extract_cte=extract_cte) + + if type_flags.get("need_numeric_discrete"): + blocks += build_numeric_discrete_blocks(extract_cte=extract_cte) + + if type_flags.get("need_categorical_single"): + blocks += build_categorical_single_blocks(extract_cte=extract_cte) + + if type_flags.get("need_categorical_multiple"): + blocks += build_categorical_multiple_blocks(extract_cte=extract_cte) + + if type_flags.get("need_binary"): + blocks += build_binary_blocks(extract_cte=extract_cte) + + if type_flags.get("need_string"): + blocks += build_string_blocks(extract_cte=extract_cte) + + if type_flags.get("need_json"): + blocks += build_json_blocks(extract_cte=extract_cte) + + if not blocks: + return None + + # If only one block family, skip union_all for clarity/perf + if len(blocks) == 1: + return blocks[0].cte("statistics_stmt") + + return blocks[0].union_all(*blocks[1:]).cte("statistics_stmt") + + +def build_numeric_continuous_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + # ------------------------------------------------- + # 1. Only valid numeric/continuous rows + # ------------------------------------------------- + cont_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + extract_cte.c.bins.label("bins_opt"), + extract_cte.c.vmin.label("vmin_opt"), + extract_cte.c.vmax.label("vmax_opt"), + extract_cte.c.edge.label("edge_opt"), + cast(extract_cte.c.jv.op("#>>")(text("'{}'")), Numeric).label("value"), + ).where( + extract_cte.c.type == literal(MetricType.NUMERIC_CONTINUOUS.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("number"), + ) + ).cte("cont_raw") + + # ------------------------------------------------- + # 2. Per-group stats + # ------------------------------------------------- + cont_minmax = ( + select( + cont_raw.c.timestamp, + cont_raw.c.idx, + func.count(cont_raw.c.value).label("n"), + func.min(cont_raw.c.value).label("vmin"), + func.max(cont_raw.c.value).label("vmax"), + func.max(cont_raw.c.bins_opt).label("bins_opt"), + func.max(cont_raw.c.vmin_opt).label("vmin_opt"), + func.max(cont_raw.c.vmax_opt).label("vmax_opt"), + func.bool_or(cont_raw.c.edge_opt).label("edge_opt"), + ).group_by(cont_raw.c.timestamp, cont_raw.c.idx) + ).cte("cont_minmax") + + # ------------------------------------------------- + # 3. Count metric + # ------------------------------------------------- + cont_count = ( + select( + cont_raw.c.timestamp, + cont_raw.c.idx, + literal("cont_count").label("kind"), + func.jsonb_build_object("count", func.count(cont_raw.c.value)).label( + "value" + ), + ) + .select_from( + cont_raw.join( + cont_minmax, + (cont_raw.c.timestamp == cont_minmax.c.timestamp) + & (cont_raw.c.idx == cont_minmax.c.idx), + ) + ) + .where(cont_minmax.c.n > 0) + .group_by(cont_raw.c.timestamp, cont_raw.c.idx) + ) + results.append(cont_count) + + # ------------------------------------------------- + # 4. Basic stats + # ------------------------------------------------- + cont_basics = ( + select( + cont_raw.c.timestamp, + cont_raw.c.idx, + literal("cont_basics").label("kind"), + func.jsonb_build_object( + "sum", + func.sum(cont_raw.c.value), + "mean", + func.avg(cont_raw.c.value), + "min", + func.min(cont_raw.c.value), + "max", + func.max(cont_raw.c.value), + ).label("value"), + ) + .select_from( + cont_raw.join( + cont_minmax, + (cont_raw.c.timestamp == cont_minmax.c.timestamp) + & (cont_raw.c.idx == cont_minmax.c.idx), + ) + ) + .where(cont_minmax.c.n > 0) + .group_by(cont_raw.c.timestamp, cont_raw.c.idx) + ) + results.append(cont_basics) + + # ------------------------------------------------- + # 5. Percentiles + # ------------------------------------------------- + cont_pcts = ( + select( + cont_raw.c.timestamp, + cont_raw.c.idx, + literal("cont_pcts").label("kind"), + func.to_jsonb( + func.percentile_cont( + literal(PERCENTILES_VALUES, ARRAY(Numeric())) + ).within_group(cont_raw.c.value) + ).label("value"), + ) + .select_from( + cont_raw.join( + cont_minmax, + (cont_raw.c.timestamp == cont_minmax.c.timestamp) + & (cont_raw.c.idx == cont_minmax.c.idx), + ) + ) + .where(cont_minmax.c.n > 0) + .group_by(cont_raw.c.timestamp, cont_raw.c.idx) + ) + results.append(cont_pcts) + + # ------------------------------------------------- + # 6. Chosen min/max/bins + # ------------------------------------------------- + chosen_min = case( + (cont_minmax.c.vmin_opt.isnot(None), cast(cont_minmax.c.vmin_opt, Numeric)), + else_=cont_minmax.c.vmin, + ) + chosen_max = case( + (cont_minmax.c.vmax_opt.isnot(None), cast(cont_minmax.c.vmax_opt, Numeric)), + else_=cont_minmax.c.vmax, + ) + chosen_bins = case( + (cont_minmax.c.bins_opt.isnot(None), cast(cont_minmax.c.bins_opt, Integer)), + else_=cast(func.ceil(func.sqrt(cast(cont_minmax.c.n, Numeric))), Integer), + ) + + cont_bins = ( + select( + cont_minmax.c.timestamp, + cont_minmax.c.idx, + case( + ( + (cont_minmax.c.n <= 1) | (chosen_min == chosen_max), + literal(1, type_=Integer), + ), + else_=chosen_bins, + ).label("bins"), + chosen_min.label("vmin"), + chosen_max.label("vmax"), + cont_minmax.c.n.label("n"), + cont_minmax.c.edge_opt.label("edge"), + ).where(cont_minmax.c.n > 0) + ).cte("cont_bins") + + # ------------------------------------------------- + # 7. Bin series & intervals (precompute is_last_bin) + # ------------------------------------------------- + cont_bin_series = ( + func.generate_series(1, cont_bins.c.bins) + .table_valued("bin") + .render_derived(name="cont_bin_series") + ) + + is_edge_aligned = cont_bins.c.edge.is_(None) | cont_bins.c.edge.is_(True) + + bin_width = case( + (is_edge_aligned, (cont_bins.c.vmax - cont_bins.c.vmin) / cont_bins.c.bins), + else_=(cont_bins.c.vmax - cont_bins.c.vmin) / (cont_bins.c.bins - 1), + ) + + bin_intervals = ( + select( + cont_bins.c.timestamp, + cont_bins.c.idx, + cont_bin_series.c.bin, + (cont_bin_series.c.bin == cont_bins.c.bins).label("is_last_bin"), + case( + ( + is_edge_aligned, + cont_bins.c.vmin + (cont_bin_series.c.bin - 1) * bin_width, + ), + else_=(cont_bins.c.vmin + (cont_bin_series.c.bin - 1) * bin_width) + - (bin_width / 2), + ).label("interval_start"), + case( + (is_edge_aligned, cont_bins.c.vmin + cont_bin_series.c.bin * bin_width), + else_=(cont_bins.c.vmin + (cont_bin_series.c.bin - 1) * bin_width) + + (bin_width / 2), + ).label("interval_end"), + ).select_from(cont_bins.join(cont_bin_series, literal(True))) + ).cte("bin_intervals") + + # ------------------------------------------------- + # 8. Bin counts (use is_last_bin for <= on last bin) + # ------------------------------------------------- + cont_bin_counts = ( + select( + cont_raw.c.timestamp, + cont_raw.c.idx, + bin_intervals.c.bin, + func.count().label("count"), + ) + .select_from( + cont_raw.join( + bin_intervals, + (cont_raw.c.timestamp == bin_intervals.c.timestamp) + & (cont_raw.c.idx == bin_intervals.c.idx) + & (cont_raw.c.value >= bin_intervals.c.interval_start) + & case( + ( + bin_intervals.c.is_last_bin, + cont_raw.c.value <= bin_intervals.c.interval_end, + ), + else_=(cont_raw.c.value < bin_intervals.c.interval_end), + ), + ) + ) + .group_by(cont_raw.c.timestamp, cont_raw.c.idx, bin_intervals.c.bin) + ).cte("cont_bin_counts") + + # ------------------------------------------------- + # 9. Full histogram (includes empty bins) + # ------------------------------------------------- + full_hist = ( + select( + bin_intervals.c.timestamp, + bin_intervals.c.idx, + literal("cont_hist").label("kind"), + func.coalesce( + func.jsonb_agg( + aggregate_order_by( + func.jsonb_build_object( + "bin", + bin_intervals.c.bin, + "count", + func.coalesce(cont_bin_counts.c.count, literal(0)), + "interval", + func.jsonb_build_array( + bin_intervals.c.interval_start, + bin_intervals.c.interval_end, + ), + ), + bin_intervals.c.bin.asc(), + ) + ), + func.jsonb_build_array(), + ).label("value"), + ) + .select_from( + bin_intervals.outerjoin( + cont_bin_counts, + (bin_intervals.c.timestamp == cont_bin_counts.c.timestamp) + & (bin_intervals.c.idx == cont_bin_counts.c.idx) + & (bin_intervals.c.bin == cont_bin_counts.c.bin), + ) + ) + .group_by(bin_intervals.c.timestamp, bin_intervals.c.idx) + ) + results.append(full_hist) + + return results + + +def build_numeric_discrete_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + # Only valid numeric/discrete rows + disc_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + cast(extract_cte.c.jv.op("#>>")(text("'{}'")), Numeric).label("value"), + ).where( + extract_cte.c.type == literal(MetricType.NUMERIC_DISCRETE.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("number"), + ) + ).cte("disc_raw") + + disc_counts = ( + select( + disc_raw.c.timestamp, + disc_raw.c.idx, + func.count(disc_raw.c.value).label("n"), + ).group_by(disc_raw.c.timestamp, disc_raw.c.idx) + ).cte("disc_counts") + + # Count (emit only when n>0) + disc_count = ( + select( + disc_raw.c.timestamp, + disc_raw.c.idx, + literal("disc_count").label("kind"), + func.jsonb_build_object("count", func.count(disc_raw.c.value)).label( + "value" + ), + ) + .select_from( + disc_raw.join( + disc_counts, + (disc_raw.c.timestamp == disc_counts.c.timestamp) + & (disc_raw.c.idx == disc_counts.c.idx), + ) + ) + .where(disc_counts.c.n > 0) + .group_by(disc_raw.c.timestamp, disc_raw.c.idx) + ) + results.append(disc_count) + + # Basics (emit only when n>0) + disc_basics = ( + select( + disc_raw.c.timestamp, + disc_raw.c.idx, + literal("disc_basics").label("kind"), + func.jsonb_build_object( + "sum", + func.sum(disc_raw.c.value), + "mean", + func.avg(disc_raw.c.value), + "min", + func.min(disc_raw.c.value), + "max", + func.max(disc_raw.c.value), + ).label("value"), + ) + .select_from( + disc_raw.join( + disc_counts, + (disc_raw.c.timestamp == disc_counts.c.timestamp) + & (disc_raw.c.idx == disc_counts.c.idx), + ) + ) + .where(disc_counts.c.n > 0) + .group_by(disc_raw.c.timestamp, disc_raw.c.idx) + ) + results.append(disc_basics) + + # Percentiles (emit only when n>0) + disc_pcts = ( + select( + disc_raw.c.timestamp, + disc_raw.c.idx, + literal("disc_pcts").label("kind"), + func.to_jsonb( + func.percentile_cont( + literal(PERCENTILES_VALUES, ARRAY(Numeric())) + ).within_group(disc_raw.c.value) + ).label("value"), + ) + .select_from( + disc_raw.join( + disc_counts, + (disc_raw.c.timestamp == disc_counts.c.timestamp) + & (disc_raw.c.idx == disc_counts.c.idx), + ) + ) + .where(disc_counts.c.n > 0) + .group_by(disc_raw.c.timestamp, disc_raw.c.idx) + ) + results.append(disc_pcts) + + # Exact-value frequency (naturally empty when no rows) + disc_rows = ( + select( + disc_raw.c.timestamp, + disc_raw.c.idx, + disc_raw.c.value.label("value"), + func.count().label("count"), + ).group_by(disc_raw.c.timestamp, disc_raw.c.idx, disc_raw.c.value) + ).cte("disc_rows") + + disc_freq = select( + disc_rows.c.timestamp, + disc_rows.c.idx, + literal("disc_freq").label("kind"), + func.coalesce( + func.jsonb_agg( + aggregate_order_by( + func.jsonb_build_object( + "value", disc_rows.c.value, "count", disc_rows.c.count + ), + disc_rows.c.count.desc(), + disc_rows.c.value.asc(), + ) + ), + func.jsonb_build_array(), + ).label("value"), + ).group_by(disc_rows.c.timestamp, disc_rows.c.idx) + results.append(disc_freq) + + return results + + +def build_categorical_single_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + # Only valid string rows for categorical/single + cls_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + cast(extract_cte.c.jv.op("#>>")(text("'{}'")), String).label("value"), + ).where( + extract_cte.c.type == literal(MetricType.CATEGORICAL_SINGLE.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("string"), + ) + ).cte("cls_raw") + + # Per-group counts for gating + cls_counts = ( + select( + cls_raw.c.timestamp, + cls_raw.c.idx, + func.count().label("n"), + ).group_by(cls_raw.c.timestamp, cls_raw.c.idx) + ).cte("cls_counts") + + # Total count (emit only when n>0) + cls_count = ( + select( + cls_raw.c.timestamp, + cls_raw.c.idx, + literal("cls_count").label("kind"), + func.jsonb_build_object("count", func.count()).label("value"), + ) + .select_from( + cls_raw.join( + cls_counts, + (cls_raw.c.timestamp == cls_counts.c.timestamp) + & (cls_raw.c.idx == cls_counts.c.idx), + ) + ) + .where(cls_counts.c.n > 0) + .group_by(cls_raw.c.timestamp, cls_raw.c.idx) + ) + results.append(cls_count) + + # Frequency table (only groups with rows appear) + cls_rows = ( + select( + cls_raw.c.timestamp, + cls_raw.c.idx, + cls_raw.c.value.label("value"), + func.count().label("count"), + ).group_by(cls_raw.c.timestamp, cls_raw.c.idx, cls_raw.c.value) + ).cte("cls_rows") + + cls_freq = select( + cls_rows.c.timestamp, + cls_rows.c.idx, + literal("cls_freq").label("kind"), + func.coalesce( + func.jsonb_agg( + aggregate_order_by( + func.jsonb_build_object( + "value", + cls_rows.c.value, + "count", + cls_rows.c.count, + ), + cls_rows.c.count.desc(), + cls_rows.c.value.asc(), + ) + ), + func.jsonb_build_array(), + ).label("value"), + ).group_by(cls_rows.c.timestamp, cls_rows.c.idx) + results.append(cls_freq) + + return results + + +def build_categorical_multiple_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + # Unnest array -> "value" (strings only) + elem = func.jsonb_array_elements(extract_cte.c.jv).table_valued("value") + + lbl_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + elem.c.value.op("#>>")(text("'{}'")).label("value"), + ) + .select_from(extract_cte.join(elem, literal(True))) + .where( + extract_cte.c.type == literal(MetricType.CATEGORICAL_MULTIPLE.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("array"), + func.jsonb_typeof(elem.c.value) == literal("string"), + ) + ).cte("lbl_raw") + + # Per-group counts for gating + lbl_counts = ( + select( + lbl_raw.c.timestamp, + lbl_raw.c.idx, + func.count().label("n"), + ).group_by(lbl_raw.c.timestamp, lbl_raw.c.idx) + ).cte("lbl_counts") + + # Total count (emit only when n>0) + lbl_count = ( + select( + lbl_raw.c.timestamp, + lbl_raw.c.idx, + literal("lbl_count").label("kind"), + func.jsonb_build_object("count", func.count()).label("value"), + ) + .select_from( + lbl_raw.join( + lbl_counts, + (lbl_raw.c.timestamp == lbl_counts.c.timestamp) + & (lbl_raw.c.idx == lbl_counts.c.idx), + ) + ) + .where(lbl_counts.c.n > 0) + .group_by(lbl_raw.c.timestamp, lbl_raw.c.idx) + ) + results.append(lbl_count) + + # Frequency table (only groups with rows appear) + lbl_rows = ( + select( + lbl_raw.c.timestamp, + lbl_raw.c.idx, + lbl_raw.c.value.label("value"), + func.count().label("count"), + ).group_by(lbl_raw.c.timestamp, lbl_raw.c.idx, lbl_raw.c.value) + ).cte("lbl_rows") + + lbl_freq = select( + lbl_rows.c.timestamp, + lbl_rows.c.idx, + literal("lbl_freq").label("kind"), + func.coalesce( + func.jsonb_agg( + aggregate_order_by( + func.jsonb_build_object( + "value", + lbl_rows.c.value, + "count", + lbl_rows.c.count, + ), + lbl_rows.c.count.desc(), + lbl_rows.c.value.asc(), + ) + ), + func.jsonb_build_array(), + ).label("value"), + ).group_by(lbl_rows.c.timestamp, lbl_rows.c.idx) + results.append(lbl_freq) + + return results + + +def build_binary_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + bin_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + extract_cte.c.jv.label("value"), + ).where( + extract_cte.c.type == literal(MetricType.BINARY.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("boolean"), + ) + ).cte("bin_raw") + + # Count (gate empty groups) + bin_counts = ( + select( + bin_raw.c.timestamp, + bin_raw.c.idx, + func.count().label("n"), + ).group_by(bin_raw.c.timestamp, bin_raw.c.idx) + ).cte("bin_counts") + + bin_count = ( + select( + bin_raw.c.timestamp, + bin_raw.c.idx, + literal("bin_count").label("kind"), + func.jsonb_build_object("count", func.count()).label("value"), + ) + .select_from( + bin_raw.join( + bin_counts, + (bin_raw.c.timestamp == bin_counts.c.timestamp) + & (bin_raw.c.idx == bin_counts.c.idx), + ) + ) + .where(bin_counts.c.n > 0) + .group_by(bin_raw.c.timestamp, bin_raw.c.idx) + ) + results.append(bin_count) + + # Frequency via FILTER (fewer CASEs, no NULLs) + t_cond = cast(bin_raw.c.value, Boolean).is_(True) + f_cond = cast(bin_raw.c.value, Boolean).is_(False) + + bin_freq = select( + bin_raw.c.timestamp, + bin_raw.c.idx, + literal("bin_freq").label("kind"), + func.jsonb_build_object( + True, + func.count().filter(t_cond), + False, + func.count().filter(f_cond), + ).label("value"), + ).group_by(bin_raw.c.timestamp, bin_raw.c.idx) + results.append(bin_freq) + + return results + + +def build_string_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + # Only valid strings + str_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + extract_cte.c.jv.label("value"), + ).where( + extract_cte.c.type == literal(MetricType.STRING.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("string"), + ) + ).cte("str_raw") + + str_counts = ( + select( + str_raw.c.timestamp, + str_raw.c.idx, + func.count().label("n"), + ).group_by(str_raw.c.timestamp, str_raw.c.idx) + ).cte("str_counts") + + # Count (emit only when n>0) + str_count = ( + select( + str_raw.c.timestamp, + str_raw.c.idx, + literal("str_count").label("kind"), + func.jsonb_build_object("count", func.count()).label("value"), + ) + .select_from( + str_raw.join( + str_counts, + (str_raw.c.timestamp == str_counts.c.timestamp) + & (str_raw.c.idx == str_counts.c.idx), + ) + ) + .where(str_counts.c.n > 0) + .group_by(str_raw.c.timestamp, str_raw.c.idx) + ) + results.append(str_count) + + return results + + +def build_json_blocks( + extract_cte: FromClause, +) -> List[Select]: + results: List[Select] = [] + + # Only valid JSON objects + json_raw = ( + select( + extract_cte.c.timestamp, + extract_cte.c.idx, + extract_cte.c.jv.label("value"), + ).where( + extract_cte.c.type == literal(MetricType.JSON.value), + extract_cte.c.jv.isnot(None), + func.jsonb_typeof(extract_cte.c.jv) == literal("object"), + ) + ).cte("json_raw") + + json_counts = ( + select( + json_raw.c.timestamp, + json_raw.c.idx, + func.count().label("n"), + ).group_by(json_raw.c.timestamp, json_raw.c.idx) + ).cte("json_counts") + + # Count (emit only when n>0) + json_count = ( + select( + json_raw.c.timestamp, + json_raw.c.idx, + literal("json_count").label("kind"), + func.jsonb_build_object("count", func.count()).label("value"), + ) + .select_from( + json_raw.join( + json_counts, + (json_raw.c.timestamp == json_counts.c.timestamp) + & (json_raw.c.idx == json_counts.c.idx), + ) + ) + .where(json_counts.c.n > 0) + .group_by(json_raw.c.timestamp, json_raw.c.idx) + ) + results.append(json_count) + + return results + + +def compute_range( + value: Dict[str, Any], +) -> Dict[str, Any]: + if "min" in value and "max" in value: + value["range"] = value["max"] - value["min"] + + return value + + +def parse_pcts( + value: List[Optional[float]], +) -> Dict[str, Any]: + if value is None or len(value) != len(PERCENTILES_KEYS): + return {} + + return {"pcts": dict(zip(PERCENTILES_KEYS, value))} + + +def compute_iqrs( + value: Dict[str, Any], +) -> Dict[str, Any]: + pcts = value.get("pcts") + + if not pcts: + return value + + iqrs: Dict[str, float] = {} + + for k, v in IQR_ITEMS: + if v[0] in pcts and v[1] in pcts: + iqrs[k] = pcts[v[1]] - pcts[v[0]] + + value["iqrs"] = iqrs + + return value + + +def compute_cqvs( + value: Dict[str, Any], +) -> Dict[str, Any]: + pcts = value.get("pcts") + + if not pcts: + return value + + if ( + pcts["p01"] * pcts["p99"] < 0 + or abs(pcts["p50"]) < abs(pcts["p99"]) / 100.0 + or abs(pcts["p50"]) < abs(pcts["p01"]) / 100.0 + ): + return value + + pscs = {} + + for k, v in PSC_ITEMS: + if v[0] in pcts and v[1] in pcts: + pscs[k] = (pcts[v[1]] - pcts[v[0]]) / (pcts[v[1]] + pcts[v[0]]) + + value["pscs"] = pscs + + return value + + +def compute_pscs( + value: Dict[str, Any], +) -> Dict[str, Any]: + pcts = value.get("pcts") + + if not pcts: + return value + + if ( + pcts["p01"] * pcts["p99"] < 0 + or abs(pcts["p50"]) < abs(pcts["p99"]) / 100.0 + or abs(pcts["p50"]) < abs(pcts["p01"]) / 100.0 + ): + return value + + pscs = {} + + for k, v in PSC_ITEMS: + if v[0] in pcts and v[1] in pcts: + pscs[k] = (pcts[v[1]] - pcts[v[0]]) / pcts["p50"] + + value["pscs"] = pscs + + return value + + +def normalize_hist( + value: List[Dict[str, Any]], +) -> Dict[str, Any]: + hist = value + + if not hist: + return {} + + count = 0.0 + + for h in hist: + count += float(h.get("count", 0.0)) + + for h in hist: + h["count"] = float(h.get("count", 0.0)) / count if count > 0.0 else 0.0 + + return {"hist": hist} + + +def parse_bin_freq( + value: Dict[str, Any], +) -> List[Dict[str, Any]]: + return [ + { + "value": True, + "count": value.get("true", 0), + }, + { + "value": False, + "count": value.get("false", 0), + }, + ] + + +def normalize_freq( + value: List[Dict[str, Any]], +) -> Dict[str, Any]: + freq = value + + if not freq: + return {} + + count = 0.0 + + for f in freq: + count += float(f.get("count", 0.0)) + + for f in freq: + f["count"] = float(f.get("count", 0.0)) / count if count > 0.0 else 0.0 + + return {"freq": freq} + + +def compute_uniq( + value: Dict[str, Any], +) -> Dict[str, Any]: + freq = value.get("freq") + + if not freq: + return value + + uniq = [] + + for f in freq: + if f.get("value") is not None: + uniq.append(f.get("value")) + + value["uniq"] = uniq + + return value diff --git a/api/oss/src/models/api/evaluation_model.py b/api/oss/src/models/api/evaluation_model.py index 222186292c..38bcfa4f11 100644 --- a/api/oss/src/models/api/evaluation_model.py +++ b/api/oss/src/models/api/evaluation_model.py @@ -7,6 +7,8 @@ from oss.src.utils import traces from oss.src.models.api.api_models import Result +from oss.src.core.shared.dtos import Tags, Meta + class LegacyEvaluator(BaseModel): name: str diff --git a/api/oss/src/models/deprecated_models.py b/api/oss/src/models/deprecated_models.py index 8de1c3b1b5..95d15a66cf 100644 --- a/api/oss/src/models/deprecated_models.py +++ b/api/oss/src/models/deprecated_models.py @@ -28,6 +28,32 @@ class ProjectScopedAppDB(DeprecatedBase): ) +class DeprecatedOrganizationDB(DeprecatedBase): + __tablename__ = "organizations" + __table_args__ = {"extend_existing": True} + + id = Column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid7, + unique=True, + nullable=False, + ) + name = Column(String, default="agenta") + description = Column( + String, + default="The open-source LLM developer platform for cross-functional teams.", + ) + type = Column(String, nullable=True) + owner = Column(String, nullable=True) # TODO: deprecate and remove + created_at = Column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + updated_at = Column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + + class DeprecatedAppDB(DeprecatedBase): __tablename__ = "app_db" __table_args__ = {"extend_existing": True} @@ -50,6 +76,30 @@ class DeprecatedAppDB(DeprecatedBase): ) +class DeprecatedTestSetDB(DeprecatedBase): + __tablename__ = "testsets" + __table_args__ = {"extend_existing": True} + + id = Column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid7, + unique=True, + nullable=False, + ) + name = Column(String) + project_id = Column( + UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE") + ) + csvdata = Column(mutable_json_type(dbtype=JSONB, nested=True)) + created_at = Column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + updated_at = Column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + + class DeprecatedAppVariantDB(DeprecatedBase): __tablename__ = "app_variants" __table_args__ = {"extend_existing": True} @@ -199,6 +249,33 @@ class DeprecatedEvaluatorConfigDBwProject(DeprecatedBase): ) +class DeprecatedAutoEvaluatorConfigDBwProject(DeprecatedBase): + __tablename__ = "auto_evaluator_configs" + __table_args__ = {"extend_existing": True} + + id = Column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid7, + unique=True, + nullable=False, + ) + + name = Column(String) + evaluator_key = Column(String) + settings_values = Column(mutable_json_type(dbtype=JSONB, nested=True), default=dict) # type: ignore + created_at = Column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + updated_at = Column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + + project_id = Column( + UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE") + ) + + class DeprecatedProjectDB(DeprecatedBase): __tablename__ = "projects" __table_args__ = {"extend_existing": True} diff --git a/api/oss/src/routers/app_router.py b/api/oss/src/routers/app_router.py index a4c86051e9..10fff40c93 100644 --- a/api/oss/src/routers/app_router.py +++ b/api/oss/src/routers/app_router.py @@ -26,7 +26,7 @@ from ee.src.utils.permissions import ( check_action_access, check_rbac_permission, - check_apikey_action_access, + # check_apikey_action_access, ) from ee.src.models.shared_models import Permission from ee.src.models.api.api_models import ( @@ -97,7 +97,7 @@ async def list_app_variants( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -155,7 +155,7 @@ async def get_variant_by_env( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app.project_id), - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -199,16 +199,6 @@ async def create_app( """ if is_ee(): - auth_header = request.headers.get("Authorization", None) - # if auth_header is not None and auth_header.startswith("ApiKey "): - # # Only check API key access if it's actually an API key, not a JWT - # api_key = auth_header.split(" ")[-1] # ["ApiKey", "xxxxx.xxxxxx"] - # await check_apikey_action_access( - # api_key, - # request.state.user_id, - # Permission.CREATE_APPLICATION, - # ) - try: user_org_workspace_data = await get_user_org_and_workspace_id( request.state.user_id @@ -222,7 +212,7 @@ async def create_app( has_permission = await check_rbac_permission( user_org_workspace_data=user_org_workspace_data, project_id=request.state.project_id, - permission=Permission.CREATE_APPLICATION, + permission=Permission.EDIT_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -301,7 +291,7 @@ async def update_app( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app.project_id), - permission=Permission.EDIT_APPLICATION, + permission=Permission.EDIT_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -341,7 +331,7 @@ async def list_apps( has_permission = await check_rbac_permission( # type: ignore user_org_workspace_data=user_org_workspace_data, project_id=request.state.project_id, - permission=Permission.VIEW_APPLICATION, # type: ignore + permission=Permission.VIEW_APPLICATIONS, # type: ignore ) if not has_permission: raise HTTPException( @@ -387,7 +377,7 @@ async def add_variant_from_url( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app.project_id), - permission=Permission.CREATE_APPLICATION, + permission=Permission.EDIT_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -497,7 +487,7 @@ async def remove_app( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app.project_id), - permission=Permission.DELETE_APPLICATION, + permission=Permission.EDIT_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -557,7 +547,7 @@ async def list_environments( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." @@ -602,7 +592,7 @@ async def list_app_environment_revisions( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = "You do not have access to perform this action. Please contact your organization admin." diff --git a/api/oss/src/routers/bases_router.py b/api/oss/src/routers/bases_router.py index 2d6cbdeb9b..078e66a637 100644 --- a/api/oss/src/routers/bases_router.py +++ b/api/oss/src/routers/bases_router.py @@ -46,7 +46,7 @@ async def list_bases( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app.project_i), - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." diff --git a/api/oss/src/routers/evaluators_router.py b/api/oss/src/routers/evaluators_router.py index 5ddc13f2ae..6df148805e 100644 --- a/api/oss/src/routers/evaluators_router.py +++ b/api/oss/src/routers/evaluators_router.py @@ -83,7 +83,7 @@ async def evaluator_run( """ providers_keys_from_vault = await get_llm_providers_secrets( - provider_id=request.state.project_id + project_id=request.state.project_id ) payload.credentials = providers_keys_from_vault diff --git a/api/oss/src/routers/variants_router.py b/api/oss/src/routers/variants_router.py index d6fbbb7cd7..8edeef5d3c 100644 --- a/api/oss/src/routers/variants_router.py +++ b/api/oss/src/routers/variants_router.py @@ -64,7 +64,7 @@ async def add_variant_from_base_and_config( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(base_db.project_id), - permission=Permission.CREATE_APPLICATION, + permission=Permission.EDIT_APPLICATIONS, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." @@ -129,7 +129,7 @@ async def remove_variant( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(variant.project_id), - permission=Permission.DELETE_APPLICATION_VARIANT, + permission=Permission.EDIT_APPLICATIONS_VARIANT, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." @@ -256,7 +256,7 @@ async def update_variant_url(request: Request, payload: UpdateVariantURLPayload) has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(db_app_variant.project_id), - permission=Permission.CREATE_APPLICATION, + permission=Permission.EDIT_APPLICATIONS, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." @@ -314,7 +314,7 @@ async def get_variant( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app_variant.project_id), - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." @@ -355,7 +355,7 @@ async def get_variant_revisions( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." @@ -402,7 +402,7 @@ async def get_variant_revision( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(app_variant.project_id), - permission=Permission.VIEW_APPLICATION, + permission=Permission.VIEW_APPLICATIONS, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." @@ -449,7 +449,7 @@ async def remove_variant_revision( has_permission = await check_action_access( user_uid=request.state.user_id, project_id=str(variant.project_id), - permission=Permission.DELETE_APPLICATION_VARIANT, + permission=Permission.EDIT_APPLICATIONS_VARIANT, ) if not has_permission: error_msg = f"You do not have permission to perform this action. Please contact your organization admin." diff --git a/api/oss/src/services/analytics_service.py b/api/oss/src/services/analytics_service.py index 6ff4fb555a..f6fc4dcdbb 100644 --- a/api/oss/src/services/analytics_service.py +++ b/api/oss/src/services/analytics_service.py @@ -139,18 +139,20 @@ async def analytics_middleware(request: Request, call_next: Callable): ) # -------------------------------------------------------------- - # log.debug( - # distinct_id=request.state.user_email, - # event=event_name, - # properties=properties, - # ) + distinct_id = None - if env.POSTHOG_API_KEY: + try: + distinct_id = request.state.user_email + except: # pylint: disable=bare-except + pass + + if distinct_id and env.POSTHOG_API_KEY: posthog.capture( - distinct_id=request.state.user_email, + distinct_id=distinct_id, event=event_name, properties=properties or {}, ) + except Exception as e: log.error(f"❌ Error capturing event in PostHog: {e}") diff --git a/api/oss/src/services/auth_helper.py b/api/oss/src/services/auth_helper.py index bca7984acd..9f49188cb3 100644 --- a/api/oss/src/services/auth_helper.py +++ b/api/oss/src/services/auth_helper.py @@ -55,11 +55,13 @@ "/api/openapi.json", # SUPERTOKENS "/auth", + "/api/auth", # STRIPE "/billing/stripe/events/", + "/api/billing/stripe/events/", ) -_ADMIN_ENDPOINT_PREFIX = "/admin/" +_ADMIN_ENDPOINT_IDENTIFIER = "/admin/" _SECRET_KEY = env.AGENTA_AUTH_KEY _SECRET_EXP = 15 * 60 # 15 minutes @@ -137,7 +139,7 @@ async def _authenticate(request: Request): if request.url.path.startswith(_PUBLIC_ENDPOINTS): return - if request.url.path.startswith(_ADMIN_ENDPOINT_PREFIX): + if _ADMIN_ENDPOINT_IDENTIFIER in request.url.path: auth_header = ( request.headers.get("Authorization") or request.headers.get("authorization") diff --git a/api/oss/src/services/db_manager.py b/api/oss/src/services/db_manager.py index d215414f95..f1557f051b 100644 --- a/api/oss/src/services/db_manager.py +++ b/api/oss/src/services/db_manager.py @@ -132,6 +132,27 @@ async def fetch_app_by_id(app_id: str) -> AppDB: return app +async def fetch_latest_app_variant(app_id: str) -> Optional[AppVariantDB]: + """Fetches the latest app variant for a given app ID. + + Args: + app_id (str): The ID of the app to fetch the latest variant for. + + Returns: + AppVariantDB: The latest app variant, or None if no app variant was found. + """ + + async with engine.core_session() as session: + base_query = ( + select(AppVariantDB) + .filter_by(app_id=uuid.UUID(app_id)) + .order_by(AppVariantDB.created_at.desc()) + ) + result = await session.execute(base_query) + app_variant = result.scalars().first() + return app_variant + + async def fetch_app_variant_by_id(app_variant_id: str) -> Optional[AppVariantDB]: """ Fetches an app variant by its ID. @@ -600,6 +621,7 @@ async def create_app_and_envs( app_name: str, template_key: Optional[str] = None, project_id: Optional[str] = None, + user_id: Optional[str] = None, ) -> AppDB: """ Create a new app with the given name and organization ID. @@ -616,7 +638,8 @@ async def create_app_and_envs( """ app = await fetch_app_by_name_and_parameters( - app_name=app_name, project_id=project_id + app_name=app_name, + project_id=project_id, ) if app is not None: raise ValueError("App with the same name already exists") @@ -627,7 +650,10 @@ async def create_app_and_envs( async with engine.core_session() as session: app = AppDB( - app_name=app_name, project_id=uuid.UUID(project_id), app_type=app_type + project_id=uuid.UUID(project_id), + app_name=app_name, + app_type=app_type, + modified_by_id=uuid.UUID(user_id) if user_id else None, ) session.add(app) @@ -638,7 +664,7 @@ async def create_app_and_envs( return app -async def update_app(app_id: str, values_to_update: dict) -> None: +async def update_app(app_id: str, values_to_update: dict): """Update the app in the database. Arguments: @@ -676,6 +702,7 @@ async def update_app(app_id: str, values_to_update: dict) -> None: setattr(app, key, value) await session.commit() + return app async def get_deployment_by_id(deployment_id: str) -> DeploymentDB: @@ -2475,14 +2502,17 @@ async def list_app_variant_revisions_by_variant( return app_variant_revisions -async def fetch_app_variant_revision(app_variant: str, revision_number: int): - """Returns list of app variant revision for the given app variant +async def fetch_app_variant_revision( + app_variant: str, revision_number: int +) -> Optional[AppVariantRevisionsDB]: + """Returns a specific app variant revision for the given app variant and revision number. Args: - app_variant (AppVariantDB): The app variant to retrieve environments for. + app_variant (str): The ID of the app variant to retrieve the revision for. + revision_number (int): The revision number to retrieve. Returns: - List[AppVariantRevisionsDB]: A list of AppVariantRevisionsDB objects. + AppVariantRevisionsDB: The app variant revision object, or None if not found. """ async with engine.core_session() as session: @@ -2504,8 +2534,8 @@ async def fetch_app_variant_revision(app_variant: str, revision_number: int): ) # type: ignore ) result = await session.execute(query) - app_variant_revisions = result.scalars().first() - return app_variant_revisions + app_variant_revision = result.scalars().first() + return app_variant_revision async def remove_environment(environment_db: AppEnvironmentDB): @@ -3095,7 +3125,8 @@ async def get_object_uuid(object_id: str, table_name: str) -> str: """ from bson import ObjectId - from bson.errors import InvalidId + + # from bson.errors import InvalidId try: # Ensure the object_id is a valid MongoDB ObjectId @@ -3103,7 +3134,7 @@ async def get_object_uuid(object_id: str, table_name: str) -> str: object_uuid_as_str = await fetch_corresponding_object_uuid( table_name=table_name, object_id=object_id ) - except InvalidId: + except: # Use the object_id directly if it is not a valid MongoDB ObjectId object_uuid_as_str = object_id diff --git a/api/oss/src/services/variants_manager.py b/api/oss/src/services/variants_manager.py index dff4c2885d..2c9db0fb64 100644 --- a/api/oss/src/services/variants_manager.py +++ b/api/oss/src/services/variants_manager.py @@ -934,7 +934,7 @@ async def fork_config_by_variant_ref( slug=( variant_ref.slug if variant_ref.slug - else app_variant.config_name + "_" + uuid4().hex[20:] + else app_variant.config_name + "_" + uuid4().hex[-12:] ), params=app_variant_revision.config_parameters, base_id=app_variant.base_id, diff --git a/api/oss/src/utils/helpers.py b/api/oss/src/utils/helpers.py index 6fa926e5cf..d02ceff9fe 100644 --- a/api/oss/src/utils/helpers.py +++ b/api/oss/src/utils/helpers.py @@ -1,3 +1,4 @@ +from typing import List, Dict from uuid import UUID import os import sys @@ -9,6 +10,45 @@ from oss.src.utils.env import env +def get_metrics_keys_from_schema(schema=None, path=()) -> List[Dict[str, str]]: + metrics = [] + + if not isinstance(schema, dict) or "type" not in schema: + return metrics + + metric_type = None + + t = schema["type"] + + if t == "object": + if "properties" in schema: + for key, prop in schema["properties"].items(): + metrics.extend(get_metrics_keys_from_schema(prop, path + (key,))) + else: + metric_type = "json" + + elif t == "array" and "items" in schema: + if schema["items"].get("type") == "string" and "enum" in schema["items"]: + metric_type = "categorical/multiple" + + elif t == "boolean": + metric_type = "binary" + + elif t == "string": + metric_type = "categorical/single" if "enum" in schema else "string" + + elif t == "number": + metric_type = "numeric/continuous" + + elif t == "integer": + metric_type = "numeric/discrete" + + if metric_type: + metrics.append({"path": ".".join(path), "type": metric_type}) + + return metrics + + def get_slug_from_name_and_id( name: str, id: UUID, # pylint: disable=redefined-builtin diff --git a/api/oss/tests/manual/evaluations/crud.http b/api/oss/tests/manual/evaluations/crud.http index 790c251b21..aa0ef43d36 100644 --- a/api/oss/tests/manual/evaluations/crud.http +++ b/api/oss/tests/manual/evaluations/crud.http @@ -30,10 +30,11 @@ Authorization: {{authorization}} "my-meta": "aloha" }, "data": { - "steps": [ + "results": [ { - "key": "input", - "is_testcase": true, + "key": "some-testset", + "type": "input", + "origin": "custom", "references": { "testset": { "id": "4448964e-dc0d-41ec-a855-46773decad01" @@ -47,7 +48,9 @@ Authorization: {{authorization}} } }, { - "key": "invocation", + "key": "some-application", + "type": "invocation", + "origin": "custom", "references": { "application": { "id": "c9d5e07c-2324-4b63-85a5-b83fa1a1f2ba" @@ -60,11 +63,13 @@ Authorization: {{authorization}} } }, "inputs": [ - { "key": "input" } + { "key": "some-testset" } ] }, { - "key": "annotation", + "key": "some-evaluator", + "type": "annotation", + "origin": "custom", "references": { "evaluator": { "id": "2037d384-f201-455f-b890-b6be8fdb017c" @@ -77,57 +82,69 @@ Authorization: {{authorization}} } }, "inputs": [ - { "key": "input" }, - { "key": "invocation" } + { "key": "some-testset" }, + { "key": "some-application" } ] } ], "mappings": [ { - "kind": "input", - "name": "Country", + "column":{ + "kind": "input", + "name": "Country" + }, "step": { - "key": "input", + "key": "some-testset", "path": "country" } }, { - "kind": "ground_truth", - "name": "Capital (expected)", + "column": { + "kind": "input", + "name": "Capital (expected)" + }, "step": { - "key": "input", + "key": "some-testset", "path": "correct_answer" } }, { - "kind": "application", - "name": "Capital (actual)", + "column": { + "kind": "application", + "name": "Capital (actual)" + }, "step": { - "key": "invocation", + "key": "some-application", "path": "data.outputs.answer" } }, { - "kind": "evaluator", - "name": "Score", + "column": { + "kind": "evaluator", + "name": "Score" + }, "step": { - "key": "annotation", + "key": "some-application", "path": "data.outputs.score" } }, { - "kind": "evaluator", - "name": "Confidence", + "column": { + "kind": "evaluator", + "name": "Confidence" + }, "step": { - "key": "annotation", + "key": "some-application", "path": "data.outputs.confidence" } }, { - "kind": "evaluator", - "name": "Explanation", + "column": { + "kind": "evaluator", + "name": "Explanation" + }, "step": { - "key": "annotation", + "key": "some-application", "path": "data.outputs.explanation" } } @@ -145,7 +162,12 @@ POST {{base_url}}/runs/{{run_id}}/close Content-Type: application/json Authorization: {{authorization}} -### 1.3. REQUEST: Get run +### 1.3. REQUEST: Open run +POST {{base_url}}/runs/{{run_id}}/open +Content-Type: application/json +Authorization: {{authorization}} + +### 1.4. REQUEST: Get run GET {{base_url}}/runs/{{run_id}} Content-Type: application/json Authorization: {{authorization}} @@ -175,51 +197,70 @@ Authorization: {{authorization}} ### 2.1.1. REQUEST: Get all scenarios by run_id # @name get_scenarios -GET {{base_url}}/scenarios/?run_id={{run_id}} +POST {{base_url}}/scenarios/query Content-Type: application/json Authorization: {{authorization}} +{ + "scenario": { + "run_id": "{{run_id}}" + } +} + ### 2.1.2. REQUEST: Get scenarios by run_id with windowing # @name get_scenarios -GET {{base_url}}/scenarios/?run_id={{run_id}}&limit=2 +POST {{base_url}}/scenarios/query Content-Type: application/json Authorization: {{authorization}} +{ + "scenario": { + "run_id": "{{run_id}}" + }, + "windowing": { + "limit": 2 + } +} + ### @next_scenario = {{get_scenarios.response.body.scenarios[1].id}} ### 2.1.3. REQUEST: Get scenarios by run_id with windowing # @name get_scenarios -GET {{base_url}}/scenarios/?run_id={{run_id}}&limit=2&next={{next_scenario}} +POST {{base_url}}/scenarios/query Content-Type: application/json Authorization: {{authorization}} -### - STEPS -------------------------------------------------------------------- +{ + "scenario": { + "run_id": "{{run_id}}" + }, + "windowing": { + "limit": 2, + "next": "{{next_scenario}}" + } +} + +### - RESULTS ------------------------------------------------------------------ ### 3.1.1 REQUEST: Create input steps for scenarios 1 and 2 -# @name create_steps -POST {{base_url}}/steps/ +# @name create_results +POST {{base_url}}/results/ Content-Type: application/json Authorization: {{authorization}} { - "steps": [ + "results": [ { "status": "success", - "key": "input", - "repeat_id": "e0f2b1a4-3c8d-4b5c-9f7d-6a0e1f3a2b5c", - "retry_id": "ed775bb9-a493-4465-8fe7-54d249d94009", - "hash_id": "f5d1c10f-e0b6-4f29-ac3c-33d86b4eb3d2", + "step_key": "some-testset", "testcase_id": "5619b65c-8639-40c8-be68-c8b661f22dbe", "scenario_id": "{{scenario_id_1}}", "run_id": "{{run_id}}" }, { "status": "success", - "key": "input", - "repeat_id": "482af080-388a-4340-99b5-aef3d2815f08", - "retry_id": "a04274cd-3624-4a0f-8c52-c430c2742da9", - "hash_id": "91034143-fb8e-424e-98cf-a808da7479ef", + "step_key": "some-testest", "testcase_id": "1b3c9a82-c97b-44ec-a77d-73c754069c70", "scenario_id": "{{scenario_id_2}}", "run_id": "{{run_id}}" @@ -228,18 +269,16 @@ Authorization: {{authorization}} } ### 3.1.2 REQUEST: Create invocation step for scenario 1 -POST {{base_url}}/steps/ +POST {{base_url}}/results/ Content-Type: application/json Authorization: {{authorization}} { - "steps": [ + "results": [ { "status": "success", - "key": "invocation", + "step_key": "some-application", "trace_id": "b84f3862c7b74e759e19eeee88b5a1ba", - "repeat_id": "e0f2b1a4-3c8d-4b5c-9f7d-6a0e1f3a2b5c", - "retry_id": "8ed80b5f-4a62-42cd-893b-df695224007c", "scenario_id": "{{scenario_id_1}}", "run_id": "{{run_id}}" } @@ -247,18 +286,16 @@ Authorization: {{authorization}} } ### 3.1.3 REQUEST: Create annotation step for scenario 1 -POST {{base_url}}/steps/ +POST {{base_url}}/results/ Content-Type: application/json Authorization: {{authorization}} { - "steps": [ + "results": [ { "status": "success", - "key": "annotation", + "step_key": "some-evaluator", "trace_id": "306eb5ccd299493bb651985dff3ad9a7", - "repeat_id": "e0f2b1a4-3c8d-4b5c-9f7d-6a0e1f3a2b5c", - "retry_id": "bd64c09a-3504-4e7d-8cd5-de3d20d75eac", "scenario_id": "{{scenario_id_1}}", "run_id": "{{run_id}}" } @@ -266,18 +303,16 @@ Authorization: {{authorization}} } ### 3.1.4 REQUEST: Create invocation step for scenario 2 -POST {{base_url}}/steps/ +POST {{base_url}}/results/ Content-Type: application/json Authorization: {{authorization}} { - "steps": [ + "results": [ { "status": "success", - "key": "invocation", - "trace_id": "0773c47f216349ed8e5617830ae436ce", - "repeat_id": "482af080-388a-4340-99b5-aef3d2815f08", - "retry_id":"cfcd1fd1-1794-47bc-893a-b5d88183ca56", + "step_key": "some-application", + "trace_id": "0773c47f216349ed8e5617830ae436ce", "scenario_id": "{{scenario_id_2}}", "run_id": "{{run_id}}" } @@ -285,18 +320,16 @@ Authorization: {{authorization}} } ### 3.1.5 REQUEST: Create annotation step for scenario 2 -POST {{base_url}}/steps/ +POST {{base_url}}/results/ Content-Type: application/json Authorization: {{authorization}} { - "steps": [ + "results": [ { "status": "success", - "key": "annotation", + "step_key": "some-evaluator", "trace_id": "09989c5181784174ad44dd19da5b4f67", - "repeat_id": "482af080-388a-4340-99b5-aef3d2815f08", - "retry_id": "1907c5f3-a3df-4b64-af5f-fedb6c933d7e", "scenario_id": "{{scenario_id_2}}", "run_id": "{{run_id}}" } @@ -305,42 +338,89 @@ Authorization: {{authorization}} ### 3.2.1 REQUEST: Get all steps by run_id # @name get_all_steps -GET {{base_url}}/steps/?run_id={{run_id}} +POST {{base_url}}/results/query Content-Type: application/json Authorization: {{authorization}} -### +{ + "result": { + "run_id": "{{run_id}}" + } +} ### -@next_step = {{get_steps.response.body.steps[1].id}} ### 3.2.2 REQUEST: Get all steps by run_id with windowing # @name get_steps -GET {{base_url}}/steps/?run_id={{run_id}}&limit=2 +POST {{base_url}}/results/query Content-Type: application/json Authorization: {{authorization}} +{ + "result": { + "run_id": "{{run_id}}" + }, + "windowing": { + "limit": 2 + } +} + +### +@next_result = {{get_steps.response.body.results[1].id}} + + ### 3.2.3 REQUEST: Get all steps by run_id with windowing # @name get_steps -GET {{base_url}}/steps/?run_id={{run_id}}&limit=2&next={{next_step}} +POST {{base_url}}/results/query Content-Type: application/json Authorization: {{authorization}} +{ + "result": { + "run_id": "{{run_id}}" + }, + "windowing": { + "limit": 2, + "next": "{{next_result}}" + } +} + ### 3.3.1 REQUEST: Get all steps by run_id and key -GET {{base_url}}/steps/?run_id={{run_id}}&key=invocation&key=annotation +POST {{base_url}}/results/query Content-Type: application/json Authorization: {{authorization}} +{ + "result": { + "run_id": "{{run_id}}", + "step_keys": ["some-application", "some-evaluator"] + } +} + ### 3.3.2 REQUEST: Get all steps by run_id and status -GET {{base_url}}/steps/?run_id={{run_id}}&status=pending&status=success +POST {{base_url}}/results/query Content-Type: application/json Authorization: {{authorization}} +{ + "result": { + "run_id": "{{run_id}}", + "statuses": ["pending", "success"] + } +} + ### 3.3.3 REQUEST: Get all steps by run_id and scenario_id -GET {{base_url}}/steps/?run_id={{run_id}}&scenario_id={{scenario_id_1}}&scenario_id={{scenario_id_2}} +POST {{base_url}}/results/query Content-Type: application/json Authorization: {{authorization}} +{ + "result": { + "run_id": "{{run_id}}", + "scenario_ids": ["{{scenario_id_1}}"] + } +} + ### - METRICS ------------------------------------------------------------------ ### 6.1 REQUEST: Create run metrics @@ -397,11 +477,24 @@ Authorization: {{authorization}} ### 9. REQUEST: Get all metrics by run_id -GET {{base_url}}/metrics/?run_id={{run_id}} +POST {{base_url}}/metrics/query Content-Type: application/json Authorization: {{authorization}} +{ + "metrics": { + "run_id": "{{run_id}}" + } +} + ### 9. REQUEST: Get all metrics by run_id and scenario_id -GET {{base_url}}/metrics/?run_id={{run_id}}&scenario_id={{scenario_id_1}} +POST {{base_url}}/metrics/query Content-Type: application/json -Authorization: {{authorization}} \ No newline at end of file +Authorization: {{authorization}} + +{ + "metrics": { + "run_id": "{{run_id}}", + "scenario_ids": ["{{scenario_id_1}}"] + } +} \ No newline at end of file diff --git a/api/oss/tests/manual/tracing/ingestion/openinference_openai_streaming.py b/api/oss/tests/manual/tracing/ingestion/openinference_openai_streaming.py index 8d62d02c55..f059c2e952 100644 --- a/api/oss/tests/manual/tracing/ingestion/openinference_openai_streaming.py +++ b/api/oss/tests/manual/tracing/ingestion/openinference_openai_streaming.py @@ -15,7 +15,7 @@ openai = OpenAI() -# # + OpenAIInstrumentor().instrument() diff --git a/api/oss/tests/manual/workflows/interface.py b/api/oss/tests/manual/workflows/interface.py index fe00cafc01..6a2bd3f88b 100644 --- a/api/oss/tests/manual/workflows/interface.py +++ b/api/oss/tests/manual/workflows/interface.py @@ -28,7 +28,7 @@ ) -from oss.src.core.workflows.utils import exact_match_v1 +from oss.src.core.services.v0 import auto_exact_match_v0 AGENTA_API_URL = "http://localhost/api" AGENTA_API_KEY = getenv("AGENTA_API_KEY") @@ -167,7 +167,7 @@ def ag_instrument( @ag_workflow @ag_instrument -async def exact_match_v1( +async def auto_exact_match_v0( *, inputs: Data, outputs: Data | str, @@ -194,7 +194,7 @@ async def exact_match_v1( __ag_workflow_registry__ = { "/": { - "invoke": exact_match_v1, + "invoke": auto_exact_match_v0, } } @@ -331,14 +331,14 @@ async def workflow_decorator_wrapper( "agenta": { "function": { "exact_match": { - "latest": workflow_decorator(exact_match_v1), - "v1": workflow_decorator(exact_match_v1), + "latest": workflow_decorator(auto_exact_match_v0), + "v0": workflow_decorator(auto_exact_match_v0), }, }, "code": { "local": { "latest": run_script_locally, - "v1": run_script_locally, + "v0": run_script_locally, } }, }, @@ -349,7 +349,7 @@ async def workflow_decorator_wrapper( app.add_api_route( "/agenta-function-exact_match-latest", - workflow_decorator(exact_match_v1), + workflow_decorator(auto_exact_match_v0), methods=["POST"], ) diff --git a/api/oss/tests/pytest/evaluations/test_evaluation_metrics_basics.py b/api/oss/tests/pytest/evaluations/test_evaluation_metrics_basics.py index 1edc23d1e5..900608f0fa 100644 --- a/api/oss/tests/pytest/evaluations/test_evaluation_metrics_basics.py +++ b/api/oss/tests/pytest/evaluations/test_evaluation_metrics_basics.py @@ -148,7 +148,7 @@ def test_delete_evaluation_metrics(self, authed_api): response = authed_api( "DELETE", "/preview/evaluations/metrics/", - json={"metric_ids": [metrics[0]["id"]]}, + json={"metrics_ids": [metrics[0]["id"]]}, ) # ---------------------------------------------------------------------- @@ -156,14 +156,14 @@ def test_delete_evaluation_metrics(self, authed_api): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert response["metric_ids"][0] == metrics[0]["id"] + assert response["metrics_ids"][0] == metrics[0]["id"] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "DELETE", "/preview/evaluations/metrics/", - json={"metric_ids": [metrics[0]["id"]]}, + json={"metrics_ids": [metrics[0]["id"]]}, ) # ---------------------------------------------------------------------- @@ -339,7 +339,7 @@ def test_delete_evaluation_metric(self, authed_api): response = authed_api( "DELETE", "/preview/evaluations/metrics/", - json={"metric_ids": [metric["id"]]}, + json={"metrics_ids": [metric["id"]]}, ) # ---------------------------------------------------------------------- @@ -347,14 +347,14 @@ def test_delete_evaluation_metric(self, authed_api): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert response["metric_ids"][0] == metric["id"] + assert response["metrics_ids"][0] == metric["id"] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "DELETE", "/preview/evaluations/metrics/", - json={"metric_ids": [metric["id"]]}, + json={"metrics_ids": [metric["id"]]}, ) # ---------------------------------------------------------------------- diff --git a/api/oss/tests/pytest/evaluations/test_evaluation_metrics_queries.py b/api/oss/tests/pytest/evaluations/test_evaluation_metrics_queries.py index 32bd1c391e..8c604f564d 100644 --- a/api/oss/tests/pytest/evaluations/test_evaluation_metrics_queries.py +++ b/api/oss/tests/pytest/evaluations/test_evaluation_metrics_queries.py @@ -79,7 +79,7 @@ class TestEvaluationMetricsQueries: def test_query_metrics_by_ids(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] - metric_ids = [metric["id"] for metric in metrics] + metrics_ids = [metric["id"] for metric in metrics] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -88,7 +88,7 @@ def test_query_metrics_by_ids(self, authed_api, mock_data): "/preview/evaluations/metrics/query", json={ "metric": { - "ids": metric_ids, + "ids": metrics_ids, } }, ) @@ -98,13 +98,13 @@ def test_query_metrics_by_ids(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 2 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- def test_query_metrics_by_tags(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] - metric_ids = [metric["id"] for metric in metrics] + metrics_ids = [metric["id"] for metric in metrics] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -126,13 +126,13 @@ def test_query_metrics_by_tags(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- def test_query_metrics_by_meta(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] - metric_ids = [metric["id"] for metric in metrics] + metrics_ids = [metric["id"] for metric in metrics] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -154,13 +154,13 @@ def test_query_metrics_by_meta(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- def test_query_metrics_by_status(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] - metric_ids = [ + metrics_ids = [ metric["id"] for metric in metrics if metric["status"] == "success" ] # ---------------------------------------------------------------------- @@ -181,13 +181,13 @@ def test_query_metrics_by_status(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- def test_query_metrics_by_statuses(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] - metric_ids = [ + metrics_ids = [ metric["id"] for metric in metrics if metric["status"] in ["success", "failure"] @@ -210,14 +210,14 @@ def test_query_metrics_by_statuses(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 2 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- def test_query_metrics_by_run_id(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] run_id = metrics[0]["run_id"] - metric_ids = [metric["id"] for metric in metrics if metric["run_id"] == run_id] + metrics_ids = [metric["id"] for metric in metrics if metric["run_id"] == run_id] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -236,14 +236,16 @@ def test_query_metrics_by_run_id(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- def test_query_metrics_by_run_ids(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- metrics = mock_data["metrics"] run_ids = [metrics[0]["run_id"], metrics[1]["run_id"]] - metric_ids = [metric["id"] for metric in metrics if metric["run_id"] in run_ids] + metrics_ids = [ + metric["id"] for metric in metrics if metric["run_id"] in run_ids + ] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -262,5 +264,5 @@ def test_query_metrics_by_run_ids(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 2 - assert all(metric["id"] in metric_ids for metric in response["metrics"]) + assert all(metric["id"] in metrics_ids for metric in response["metrics"]) # ---------------------------------------------------------------------- diff --git a/api/oss/tests/pytest/evaluations/test_evaluation_steps_basics.py b/api/oss/tests/pytest/evaluations/test_evaluation_steps_basics.py index 5ad87c47a9..c571409dc8 100644 --- a/api/oss/tests/pytest/evaluations/test_evaluation_steps_basics.py +++ b/api/oss/tests/pytest/evaluations/test_evaluation_steps_basics.py @@ -45,7 +45,7 @@ def mock_data(authed_api): return _mock_data -class TestEvaluationStepsBasics: +class TestEvaluationResultsBasics: def test_create_evaluation_steps(self, authed_api, mock_data): # ARRANGE -------------------------------------------------------------- run_id = mock_data["runs"][0]["id"] @@ -69,7 +69,7 @@ def test_create_evaluation_steps(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) # ---------------------------------------------------------------------- @@ -122,7 +122,7 @@ def test_fetch_evaluation_steps(self, authed_api, mock_data): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -134,7 +134,7 @@ def test_fetch_evaluation_steps(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "GET", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", params={"scenario_id": scenario_id}, ) # ---------------------------------------------------------------------- @@ -185,7 +185,7 @@ def test_edit_evaluation_steps(self, authed_api, mock_data): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -197,7 +197,7 @@ def test_edit_evaluation_steps(self, authed_api, mock_data): assert response["steps"][2]["key"] == key_3 steps = response["steps"] - step_ids = [step["id"] for step in steps] + result_ids = [step["id"] for step in steps] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -207,18 +207,18 @@ def test_edit_evaluation_steps(self, authed_api, mock_data): response = authed_api( "PATCH", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) assert response.status_code == 200 response = response.json() assert response["count"] == 3 - assert response["steps"][0]["id"] == step_ids[0] + assert response["steps"][0]["id"] == result_ids[0] assert response["steps"][0]["status"] == "success" - assert response["steps"][1]["id"] == step_ids[1] + assert response["steps"][1]["id"] == result_ids[1] assert response["steps"][1]["status"] == "failure" - assert response["steps"][2]["id"] == step_ids[2] + assert response["steps"][2]["id"] == result_ids[2] assert response["steps"][2]["status"] == "cancelled" # ---------------------------------------------------------------------- @@ -249,7 +249,7 @@ def test_delete_evaluation_steps(self, authed_api, mock_data): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -257,14 +257,14 @@ def test_delete_evaluation_steps(self, authed_api, mock_data): response = response.json() assert response["count"] == 2 - step_ids = [step["id"] for step in response["steps"]] + result_ids = [step["id"] for step in response["steps"]] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "DELETE", - "/preview/evaluations/steps/", - json={"step_ids": step_ids}, + "/preview/evaluations/results/", + json={"result_ids": result_ids}, ) # ---------------------------------------------------------------------- @@ -272,14 +272,14 @@ def test_delete_evaluation_steps(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 2 - assert response["step_ids"] == step_ids + assert response["result_ids"] == result_ids # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "DELETE", - "/preview/evaluations/steps/", - json={"step_ids": step_ids}, + "/preview/evaluations/results/", + json={"result_ids": result_ids}, ) # ---------------------------------------------------------------------- @@ -306,7 +306,7 @@ def test_fetch_evaluation_step(self, authed_api, mock_data): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -314,13 +314,13 @@ def test_fetch_evaluation_step(self, authed_api, mock_data): response = response.json() assert response["count"] == 1 - step_id = response["steps"][0]["id"] + result_id = response["steps"][0]["id"] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "GET", - f"/preview/evaluations/steps/{step_id}", + f"/preview/evaluations/results/{result_id}", ) # ---------------------------------------------------------------------- @@ -328,7 +328,7 @@ def test_fetch_evaluation_step(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert response["step"]["id"] == step_id + assert response["step"]["id"] == result_id # ---------------------------------------------------------------------- def test_edit_evaluation_step(self, authed_api, mock_data): @@ -348,7 +348,7 @@ def test_edit_evaluation_step(self, authed_api, mock_data): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -359,7 +359,7 @@ def test_edit_evaluation_step(self, authed_api, mock_data): assert response["steps"][0]["status"] == "pending" step = response["steps"][0] - step_id = step["id"] + result_id = step["id"] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ @@ -367,7 +367,7 @@ def test_edit_evaluation_step(self, authed_api, mock_data): response = authed_api( "PATCH", - f"/preview/evaluations/steps/{step_id}", + f"/preview/evaluations/results/{result_id}", json={"step": step}, ) # ---------------------------------------------------------------------- @@ -377,7 +377,7 @@ def test_edit_evaluation_step(self, authed_api, mock_data): response = response.json() print(response) assert response["count"] == 1 - assert response["step"]["id"] == step_id + assert response["step"]["id"] == result_id assert response["step"]["status"] == "success" # ---------------------------------------------------------------------- @@ -398,7 +398,7 @@ def test_delete_evaluation_step(self, authed_api, mock_data): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -406,13 +406,13 @@ def test_delete_evaluation_step(self, authed_api, mock_data): response = response.json() assert response["count"] == 1 - step_id = response["steps"][0]["id"] + result_id = response["steps"][0]["id"] # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "DELETE", - f"/preview/evaluations/steps/{step_id}", + f"/preview/evaluations/results/{result_id}", ) # ---------------------------------------------------------------------- @@ -420,13 +420,13 @@ def test_delete_evaluation_step(self, authed_api, mock_data): assert response.status_code == 200 response = response.json() assert response["count"] == 1 - assert response["step_id"] == step_id + assert response["result_id"] == result_id # ---------------------------------------------------------------------- # ACT ------------------------------------------------------------------ response = authed_api( "DELETE", - f"/preview/evaluations/steps/{step_id}", + f"/preview/evaluations/results/{result_id}", ) # ---------------------------------------------------------------------- diff --git a/api/oss/tests/pytest/evaluations/test_evaluation_steps_queries.py b/api/oss/tests/pytest/evaluations/test_evaluation_steps_queries.py index 36a88f2209..6cc2ce4405 100644 --- a/api/oss/tests/pytest/evaluations/test_evaluation_steps_queries.py +++ b/api/oss/tests/pytest/evaluations/test_evaluation_steps_queries.py @@ -133,7 +133,7 @@ def mock_data(authed_api): response = authed_api( "POST", - "/preview/evaluations/steps/", + "/preview/evaluations/results/", json={"steps": steps}, ) @@ -153,12 +153,12 @@ def mock_data(authed_api): return _mock_data -class TestEvaluationStepsQueries: - def test_query_steps_all(self, authed_api, mock_data): +class TestEvaluationResultsQueries: + def test_query_results_all(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": {}, }, @@ -171,11 +171,11 @@ def test_query_steps_all(self, authed_api, mock_data): assert response["count"] == 9 # ---------------------------------------------------------------------- - def test_query_steps_by_tags(self, authed_api, mock_data): + def test_query_results_by_tags(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "tags": { @@ -193,11 +193,11 @@ def test_query_steps_by_tags(self, authed_api, mock_data): assert response["count"] == 3 # ---------------------------------------------------------------------- - def test_query_steps_by_meta(self, authed_api, mock_data): + def test_query_results_by_meta(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "meta": { @@ -215,11 +215,11 @@ def test_query_steps_by_meta(self, authed_api, mock_data): assert response["count"] == 3 # ---------------------------------------------------------------------- - def test_query_steps_by_run_id(self, authed_api, mock_data): + def test_query_results_by_run_id(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "run_id": mock_data["runs"][0]["id"], @@ -234,11 +234,11 @@ def test_query_steps_by_run_id(self, authed_api, mock_data): assert response["count"] == 9 # ---------------------------------------------------------------------- - def test_query_steps_by_run_ids(self, authed_api, mock_data): + def test_query_results_by_run_ids(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "run_ids": [mock_data["runs"][0]["id"]], @@ -253,11 +253,11 @@ def test_query_steps_by_run_ids(self, authed_api, mock_data): assert response["count"] == 9 # ---------------------------------------------------------------------- - def test_query_steps_by_scenario_id(self, authed_api, mock_data): + def test_query_results_by_scenario_id(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "scenario_id": mock_data["scenarios"][0]["id"], @@ -272,11 +272,11 @@ def test_query_steps_by_scenario_id(self, authed_api, mock_data): assert response["count"] == 6 # ---------------------------------------------------------------------- - def test_query_steps_by_scenario_ids(self, authed_api, mock_data): + def test_query_results_by_scenario_ids(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "scenario_ids": [s["id"] for s in mock_data["scenarios"]], @@ -291,11 +291,11 @@ def test_query_steps_by_scenario_ids(self, authed_api, mock_data): assert response["count"] == 9 # ---------------------------------------------------------------------- - def test_query_steps_by_ids(self, authed_api, mock_data): + def test_query_results_by_ids(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "ids": [s["id"] for s in mock_data["steps"][:-1]], @@ -310,11 +310,11 @@ def test_query_steps_by_ids(self, authed_api, mock_data): assert response["count"] == 9 - 1 # ---------------------------------------------------------------------- - def test_query_steps_by_key(self, authed_api, mock_data): + def test_query_results_by_key(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "key": "input", @@ -329,11 +329,11 @@ def test_query_steps_by_key(self, authed_api, mock_data): assert response["count"] == 3 # ---------------------------------------------------------------------- - def test_query_steps_by_keys(self, authed_api, mock_data): + def test_query_results_by_keys(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "keys": ["input", "invocation"], @@ -348,11 +348,11 @@ def test_query_steps_by_keys(self, authed_api, mock_data): assert response["count"] == 6 # ---------------------------------------------------------------------- - def test_query_steps_by_repeat_id(self, authed_api, mock_data): + def test_query_results_by_repeat_id(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "repeat_id": mock_data["steps"][0]["repeat_id"], @@ -367,11 +367,11 @@ def test_query_steps_by_repeat_id(self, authed_api, mock_data): assert response["count"] == 6 # ---------------------------------------------------------------------- - def test_query_steps_by_repeat_ids(self, authed_api, mock_data): + def test_query_results_by_repeat_ids(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "repeat_ids": [ @@ -389,11 +389,11 @@ def test_query_steps_by_repeat_ids(self, authed_api, mock_data): assert response["count"] == 9 # ---------------------------------------------------------------------- - def test_query_steps_by_retry_id(self, authed_api, mock_data): + def test_query_results_by_retry_id(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "retry_id": mock_data["steps"][0]["retry_id"], @@ -408,11 +408,11 @@ def test_query_steps_by_retry_id(self, authed_api, mock_data): assert response["count"] == 6 # ---------------------------------------------------------------------- - def test_query_steps_by_retry_ids(self, authed_api, mock_data): + def test_query_results_by_retry_ids(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "retry_ids": [ @@ -430,11 +430,11 @@ def test_query_steps_by_retry_ids(self, authed_api, mock_data): assert response["count"] == 9 # ---------------------------------------------------------------------- - def test_query_steps_by_status(self, authed_api, mock_data): + def test_query_results_by_status(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "status": "success", @@ -449,11 +449,11 @@ def test_query_steps_by_status(self, authed_api, mock_data): assert response["count"] == 3 # ---------------------------------------------------------------------- - def test_query_steps_by_statuses(self, authed_api, mock_data): + def test_query_results_by_statuses(self, authed_api, mock_data): # ACT ------------------------------------------------------------------ response = authed_api( "POST", - "/preview/evaluations/steps/query", + "/preview/evaluations/results/query", json={ "step": { "statuses": ["success", "failure"], diff --git a/api/poetry.lock b/api/poetry.lock index 8b61f9027a..422a54b61f 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1,48 +1,38 @@ # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] -name = "aioboto3" -version = "12.4.0" -description = "Async boto3 wrapper" +name = "agenta" +version = "0.55.1" +description = "The SDK for agenta is an open-source LLMOps platform." optional = false -python-versions = "<4.0,>=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "aioboto3-12.4.0-py3-none-any.whl", hash = "sha256:a8d5a60852482cc7a472f3544e5ad7d2f5a911054ffa066357140dc6690da94b"}, - {file = "aioboto3-12.4.0.tar.gz", hash = "sha256:0fa03ac7a8c2c187358dd27cdf84da05e91bc1a3bd85519cad13521343a3d767"}, -] - -[package.dependencies] -aiobotocore = {version = "2.12.3", extras = ["boto3"]} - -[package.extras] -chalice = ["chalice (>=1.24.0)"] -s3cse = ["cryptography (>=2.3.1)"] - -[[package]] -name = "aiobotocore" -version = "2.12.3" -description = "Async client for aws services using botocore and aiohttp" -optional = false -python-versions = ">=3.8" +python-versions = "<4.0,>=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "aiobotocore-2.12.3-py3-none-any.whl", hash = "sha256:86737685f4625e8f05c4e7a608a07cc97607263279f66cf6b02b640c4eafd324"}, - {file = "aiobotocore-2.12.3.tar.gz", hash = "sha256:e2a2929207bc5d62eb556106c2224c1fd106d5c65be2eb69f15cc8c34c44c236"}, + {file = "agenta-0.55.1-py3-none-any.whl", hash = "sha256:b0e02cf4d095198f40f2eb167d37c59e30269ec8b061815e035e4062b3f9fc98"}, + {file = "agenta-0.55.1.tar.gz", hash = "sha256:6291ff290f02168b271475829a564146ba922a20399cd15a2ebce4f7ea31f5f0"}, ] [package.dependencies] -aiohttp = ">=3.7.4.post0,<4.0.0" -aioitertools = ">=0.5.1,<1.0.0" -boto3 = {version = ">=1.34.41,<1.34.70", optional = true, markers = "extra == \"boto3\""} -botocore = ">=1.34.41,<1.34.70" -wrapt = ">=1.10.10,<2.0.0" - -[package.extras] -awscli = ["awscli (>=1.32.41,<1.32.70)"] -boto3 = ["boto3 (>=1.34.41,<1.34.70)"] +decorator = ">=5.2.1,<6.0.0" +fastapi = ">=0.116.0,<0.117.0" +h11 = ">=0.16.0" +httpx = ">=0.28.0" +huggingface-hub = "<0.31.0" +importlib-metadata = ">=8.0.0,<9.0" +jinja2 = ">=3.1.6,<4.0.0" +litellm = "1.76.0" +openai = ">=1.100.0" +opentelemetry-api = ">=1.27.0,<2.0.0" +opentelemetry-exporter-otlp-proto-http = ">=1.27.0,<2.0.0" +opentelemetry-instrumentation = ">=0.56b0" +opentelemetry-sdk = ">=1.27.0,<2.0.0" +pydantic = ">=2" +python-dotenv = ">=1.0.0,<2.0.0" +pyyaml = ">=6.0.2,<7.0.0" +starlette = ">=0.47.0,<0.48.0" +structlog = ">=25.2.0,<26.0.0" +toml = ">=0.10.2,<0.11.0" [[package]] name = "aiohappyeyeballs" @@ -59,99 +49,133 @@ files = [ [[package]] name = "aiohttp" -version = "3.12.15" +version = "3.13.0" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, - {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, - {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, - {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, - {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, - {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, - {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, - {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, - {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, - {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, - {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, - {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, - {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, - {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, + {file = "aiohttp-3.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ca69ec38adf5cadcc21d0b25e2144f6a25b7db7bea7e730bac25075bc305eff0"}, + {file = "aiohttp-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:240f99f88a9a6beb53ebadac79a2e3417247aa756202ed234b1dbae13d248092"}, + {file = "aiohttp-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a4676b978a9711531e7cea499d4cdc0794c617a1c0579310ab46c9fdf5877702"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48fcdd5bc771cbbab8ccc9588b8b6447f6a30f9fe00898b1a5107098e00d6793"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eeea0cdd2f687e210c8f605f322d7b0300ba55145014a5dbe98bd4be6fff1f6c"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b3f01d5aeb632adaaf39c5e93f040a550464a768d54c514050c635adcbb9d0"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4dc0b83e25267f42ef065ea57653de4365b56d7bc4e4cfc94fabe56998f8ee6"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:72714919ed9b90f030f761c20670e529c4af96c31bd000917dd0c9afd1afb731"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:564be41e85318403fdb176e9e5b3e852d528392f42f2c1d1efcbeeed481126d7"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:84912962071087286333f70569362e10793f73f45c48854e6859df11001eb2d3"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90b570f1a146181c3d6ae8f755de66227ded49d30d050479b5ae07710f7894c5"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d71ca30257ce756e37a6078b1dff2d9475fee13609ad831eac9a6531bea903b"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:cd45eb70eca63f41bb156b7dffbe1a7760153b69892d923bdb79a74099e2ed90"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5ae3a19949a27982c7425a7a5a963c1268fdbabf0be15ab59448cbcf0f992519"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ea6df292013c9f050cbf3f93eee9953d6e5acd9e64a0bf4ca16404bfd7aa9bcc"}, + {file = "aiohttp-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3b64f22fbb6dcd5663de5ef2d847a5638646ef99112503e6f7704bdecb0d1c4d"}, + {file = "aiohttp-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8d877aa60d80715b2afc565f0f1aea66565824c229a2d065b31670e09fed6d7"}, + {file = "aiohttp-3.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99eb94e97a42367fef5fc11e28cb2362809d3e70837f6e60557816c7106e2e20"}, + {file = "aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca"}, + {file = "aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aab715b1a0c37f7f11f9f1f579c6fbaa51ef569e47e3c0a4644fba46077a9409"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7972c82bed87d7bd8e374b60a6b6e816d75ba4f7c2627c2d14eed216e62738e1"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca8313cb852af788c78d5afdea24c40172cbfff8b35e58b407467732fde20390"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c333a2385d2a6298265f4b3e960590f787311b87f6b5e6e21bb8375914ef504"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ddedba3d0043349edc79df3dc2da49c72b06d59a45a42c1c8d987e6b8d175b8"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23ca762140159417a6bbc959ca1927f6949711851e56f2181ddfe8d63512b5ad"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfe824d6707a5dc3c5676685f624bc0c63c40d79dc0239a7fd6c034b98c25ebe"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3c11fa5dd2ef773a8a5a6daa40243d83b450915992eab021789498dc87acc114"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00fdfe370cffede3163ba9d3f190b32c0cfc8c774f6f67395683d7b0e48cdb8a"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6475e42ef92717a678bfbf50885a682bb360a6f9c8819fb1a388d98198fdcb80"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:77da5305a410910218b99f2a963092f4277d8a9c1f429c1ff1b026d1826bd0b6"}, + {file = "aiohttp-3.13.0-cp311-cp311-win32.whl", hash = "sha256:2f9d9ea547618d907f2ee6670c9a951f059c5994e4b6de8dcf7d9747b420c820"}, + {file = "aiohttp-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469"}, + {file = "aiohttp-3.13.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c272a9a18a5ecc48a7101882230046b83023bb2a662050ecb9bfcb28d9ab53a"}, + {file = "aiohttp-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:97891a23d7fd4e1afe9c2f4473e04595e4acb18e4733b910b6577b74e7e21985"}, + {file = "aiohttp-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:475bd56492ce5f4cffe32b5533c6533ee0c406d1d0e6924879f83adcf51da0ae"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c32ada0abb4bc94c30be2b681c42f058ab104d048da6f0148280a51ce98add8c"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4af1f8877ca46ecdd0bc0d4a6b66d4b2bddc84a79e2e8366bc0d5308e76bceb8"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e04ab827ec4f775817736b20cdc8350f40327f9b598dec4e18c9ffdcbea88a93"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a6d9487b9471ec36b0faedf52228cd732e89be0a2bbd649af890b5e2ce422353"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469167d5372f5bb3aedff4fc53035d593884fff2617a75317740e885acd48b04"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a9f3546b503975a69b547c9fd1582cad10ede1ce6f3e313a2f547c73a3d7814f"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6b4174fcec98601f0cfdf308ee29a6ae53c55f14359e848dab4e94009112ee7d"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a533873a7a4ec2270fb362ee5a0d3b98752e4e1dc9042b257cd54545a96bd8ed"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ce887c5e54411d607ee0959cac15bb31d506d86a9bcaddf0b7e9d63325a7a802"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d871f6a30d43e32fc9252dc7b9febe1a042b3ff3908aa83868d7cf7c9579a59b"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:222c828243b4789d79a706a876910f656fad4381661691220ba57b2ab4547865"}, + {file = "aiohttp-3.13.0-cp312-cp312-win32.whl", hash = "sha256:682d2e434ff2f1108314ff7f056ce44e457f12dbed0249b24e106e385cf154b9"}, + {file = "aiohttp-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a2be20eb23888df130214b91c262a90e2de1553d6fb7de9e9010cec994c0ff2"}, + {file = "aiohttp-3.13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00243e51f16f6ec0fb021659d4af92f675f3cf9f9b39efd142aa3ad641d8d1e6"}, + {file = "aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54"}, + {file = "aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4aa995b9156ae499393d949a456a7ab0b994a8241a96db73a3b73c7a090eff6a"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55ca0e95a3905f62f00900255ed807c580775174252999286f283e646d675a49"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:49ce7525853a981fc35d380aa2353536a01a9ec1b30979ea4e35966316cace7e"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2117be9883501eaf95503bd313eb4c7a23d567edd44014ba15835a1e9ec6d852"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:703ad3f742fc81e543638a7bebddd35acadaa0004a5e00535e795f4b6f2c25ca"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bf635c3476f4119b940cc8d94ad454cbe0c377e61b4527f0192aabeac1e9370"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cfe6285ef99e7ee51cef20609be2bc1dd0e8446462b71c9db8bb296ba632810a"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8af6391c5f2e69749d7f037b614b8c5c42093c251f336bdbfa4b03c57d6c4"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:12f5d820fadc5848d4559ea838aef733cf37ed2a1103bba148ac2f5547c14c29"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f1338b61ea66f4757a0544ed8a02ccbf60e38d9cfb3225888888dd4475ebb96"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:582770f82513419512da096e8df21ca44f86a2e56e25dc93c5ab4df0fe065bf0"}, + {file = "aiohttp-3.13.0-cp313-cp313-win32.whl", hash = "sha256:3194b8cab8dbc882f37c13ef1262e0a3d62064fa97533d3aa124771f7bf1ecee"}, + {file = "aiohttp-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21"}, + {file = "aiohttp-3.13.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c417f8c2e1137775569297c584a8a7144e5d1237789eae56af4faf1894a0b861"}, + {file = "aiohttp-3.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f84b53326abf8e56ebc28a35cebf4a0f396a13a76300f500ab11fe0573bf0b52"}, + {file = "aiohttp-3.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:990a53b9d6a30b2878789e490758e568b12b4a7fb2527d0c89deb9650b0e5813"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c811612711e01b901e18964b3e5dec0d35525150f5f3f85d0aee2935f059910a"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee433e594d7948e760b5c2a78cc06ac219df33b0848793cf9513d486a9f90a52"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:19bb08e56f57c215e9572cd65cb6f8097804412c54081d933997ddde3e5ac579"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f27b7488144eb5dd9151cf839b195edd1569629d90ace4c5b6b18e4e75d1e63a"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d812838c109757a11354a161c95708ae4199c4fd4d82b90959b20914c1d097f6"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c20db99da682f9180fa5195c90b80b159632fb611e8dbccdd99ba0be0970620"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cf8b0870047900eb1f17f453b4b3953b8ffbf203ef56c2f346780ff930a4d430"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b8a5557d5af3f4e3add52a58c4cf2b8e6e59fc56b261768866f5337872d596d"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:052bcdd80c1c54b8a18a9ea0cd5e36f473dc8e38d51b804cea34841f677a9971"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:76484ba17b2832776581b7ab466d094e48eba74cb65a60aea20154dae485e8bd"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:62d8a0adcdaf62ee56bfb37737153251ac8e4b27845b3ca065862fb01d99e247"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5004d727499ecb95f7c9147dd0bfc5b5670f71d355f0bd26d7af2d3af8e07d2f"}, + {file = "aiohttp-3.13.0-cp314-cp314-win32.whl", hash = "sha256:a1c20c26af48aea984f63f96e5d7af7567c32cb527e33b60a0ef0a6313cf8b03"}, + {file = "aiohttp-3.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:56f7d230ec66e799fbfd8350e9544f8a45a4353f1cf40c1fea74c1780f555b8f"}, + {file = "aiohttp-3.13.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:2fd35177dc483ae702f07b86c782f4f4b100a8ce4e7c5778cea016979023d9fd"}, + {file = "aiohttp-3.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4df1984c8804ed336089e88ac81a9417b1fd0db7c6f867c50a9264488797e778"}, + {file = "aiohttp-3.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e68c0076052dd911a81d3acc4ef2911cc4ef65bf7cadbfbc8ae762da24da858f"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc95c49853cd29613e4fe4ff96d73068ff89b89d61e53988442e127e8da8e7ba"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bdc89413117b40cc39baae08fd09cbdeb839d421c4e7dce6a34f6b54b3ac1"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e77a729df23be2116acc4e9de2767d8e92445fbca68886dd991dc912f473755"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e88ab34826d6eeb6c67e6e92400b9ec653faf5092a35f07465f44c9f1c429f82"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:019dbef24fe28ce2301419dd63a2b97250d9760ca63ee2976c2da2e3f182f82e"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2c4aeaedd20771b7b4bcdf0ae791904445df6d856c02fc51d809d12d17cffdc7"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b3a8e6a2058a0240cfde542b641d0e78b594311bc1a710cbcb2e1841417d5cb3"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8e38d55ca36c15f36d814ea414ecb2401d860de177c49f84a327a25b3ee752b"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a921edbe971aade1bf45bcbb3494e30ba6863a5c78f28be992c42de980fd9108"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:474cade59a447cb4019c0dce9f0434bf835fb558ea932f62c686fe07fe6db6a1"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:99a303ad960747c33b65b1cb65d01a62ac73fa39b72f08a2e1efa832529b01ed"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bb34001fc1f05f6b323e02c278090c07a47645caae3aa77ed7ed8a3ce6abcce9"}, + {file = "aiohttp-3.13.0-cp314-cp314t-win32.whl", hash = "sha256:dea698b64235d053def7d2f08af9302a69fcd760d1c7bd9988fd5d3b6157e657"}, + {file = "aiohttp-3.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1f164699a060c0b3616459d13c1464a981fddf36f892f0a5027cbd45121fb14b"}, + {file = "aiohttp-3.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fcc425fb6fd2a00c6d91c85d084c6b75a61bc8bc12159d08e17c5711df6c5ba4"}, + {file = "aiohttp-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c2c4c9ce834801651f81d6760d0a51035b8b239f58f298de25162fcf6f8bb64"}, + {file = "aiohttp-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f91e8f9053a07177868e813656ec57599cd2a63238844393cd01bd69c2e40147"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df46d9a3d78ec19b495b1107bf26e4fcf97c900279901f4f4819ac5bb2a02a4c"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b1eb9871cbe43b6ca6fac3544682971539d8a1d229e6babe43446279679609d"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:62a3cddf8d9a2eae1f79585fa81d32e13d0c509bb9e7ad47d33c83b45a944df7"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0f735e680c323ee7e9ef8e2ea26425c7dbc2ede0086fa83ce9d7ccab8a089f26"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a51839f778b0e283b43cd82bb17f1835ee2cc1bf1101765e90ae886e53e751c"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac90cfab65bc281d6752f22db5fa90419e33220af4b4fa53b51f5948f414c0e7"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:62fd54f3e6f17976962ba67f911d62723c760a69d54f5d7b74c3ceb1a4e9ef8d"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cf2b60b65df05b6b2fa0d887f2189991a0dbf44a0dd18359001dc8fcdb7f1163"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1ccedfe280e804d9a9d7fe8b8c4309d28e364b77f40309c86596baa754af50b1"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ea01ffbe23df53ece0c8732d1585b3d6079bb8c9ee14f3745daf000051415a31"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:19ba8625fa69523627b67f7e9901b587a4952470f68814d79cdc5bc460e9b885"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b14bfae90598d331b5061fd15a7c290ea0c15b34aeb1cf620464bb5ec02a602"}, + {file = "aiohttp-3.13.0-cp39-cp39-win32.whl", hash = "sha256:cf7a4b976da219e726d0043fc94ae8169c0dba1d3a059b3c1e2c964bafc5a77d"}, + {file = "aiohttp-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b9697d15231aeaed4786f090c9c8bc3ab5f0e0a6da1e76c135a310def271020"}, + {file = "aiohttp-3.13.0.tar.gz", hash = "sha256:378dbc57dd8cf341ce243f13fa1fa5394d68e2e02c15cd5f28eae35a70ec7f67"}, ] [package.dependencies] @@ -164,7 +188,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi", "zstandard"] [[package]] name = "aiohttp-retry" @@ -182,23 +206,6 @@ files = [ [package.dependencies] aiohttp = "*" -[[package]] -name = "aioitertools" -version = "0.12.0" -description = "itertools and builtins for AsyncIO and mixed iterables" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796"}, - {file = "aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b"}, -] - -[package.extras] -dev = ["attribution (==1.8.0)", "black (==24.8.0)", "build (>=1.2)", "coverage (==7.6.1)", "flake8 (==7.1.1)", "flit (==3.9.0)", "mypy (==1.11.2)", "ufmt (==2.7.1)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==8.0.2)", "sphinx-mdinclude (==0.6.2)"] - [[package]] name = "aiosignal" version = "1.4.0" @@ -285,37 +292,36 @@ files = [ [[package]] name = "anyio" -version = "3.7.1" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +version = "4.11.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, + {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, ] [package.dependencies] idna = ">=2.8" sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +trio = ["trio (>=0.31.0)"] [[package]] name = "asgiref" -version = "3.9.1" +version = "3.10.0" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c"}, - {file = "asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142"}, + {file = "asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734"}, + {file = "asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e"}, ] [package.extras] @@ -334,22 +340,6 @@ files = [ {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] -[[package]] -name = "asyncer" -version = "0.0.2" -description = "Asyncer, async and await, focused on developer experience." -optional = false -python-versions = ">=3.6.2,<4.0.0" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "asyncer-0.0.2-py3-none-any.whl", hash = "sha256:46e0e1423ce21588350ad425875e81795280b9e1f517e8a389de940b86c348bd"}, - {file = "asyncer-0.0.2.tar.gz", hash = "sha256:d546c85f3626ebbaf06bb4395db49761c902a61a6ac802b1a74133cab4f7f433"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<4.0.0" - [[package]] name = "asyncpg" version = "0.30.0" @@ -417,25 +407,17 @@ test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0 [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] - [[package]] name = "autoevals" version = "0.0.83" @@ -475,93 +457,19 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] -[[package]] -name = "beanie" -version = "1.30.0" -description = "Asynchronous Python ODM for MongoDB" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "beanie-1.30.0-py3-none-any.whl", hash = "sha256:385f1b850b36a19dd221aeb83e838c83ec6b47bbf6aeac4e5bf8b8d40bfcfe51"}, - {file = "beanie-1.30.0.tar.gz", hash = "sha256:33ead17ff2742144c510b4b24e188f6b316dd1b614d86b57a3cfe20bc7b768c9"}, -] - -[package.dependencies] -click = ">=7" -lazy-model = "0.2.0" -motor = ">=2.5.0,<4.0.0" -pydantic = ">=1.10.18,<3.0" -typing-extensions = ">=4.7" - -[package.extras] -aws = ["motor[aws] (>=2.5.0,<4.0.0)"] -ci = ["requests", "tomli (>=2.2.1,<3.0.0)", "tomli-w (>=1.0.0,<2.0.0)", "types-requests"] -doc = ["Markdown (>=3.3)", "Pygments (>=2.8.0)", "jinja2 (>=3.0.3)", "mkdocs (>=1.4)", "mkdocs-material (>=9.0)", "pydoc-markdown (>=4.8)"] -encryption = ["motor[encryption] (>=2.5.0,<4.0.0)"] -gssapi = ["motor[gssapi] (>=2.5.0,<4.0.0)"] -ocsp = ["motor[ocsp] (>=2.5.0,<4.0.0)"] -queue = ["beanie-batteries-queue (>=0.2)"] -snappy = ["motor[snappy] (>=2.5.0,<4.0.0)"] -test = ["asgi-lifespan (>=1.0.1)", "dnspython (>=2.1.0)", "fastapi (>=0.100)", "httpx (>=0.23.0)", "pre-commit (>=3.5.0)", "pydantic-extra-types (>=2)", "pydantic-settings (>=2)", "pydantic[email]", "pyright (>=0)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24.0)", "pytest-cov (>=5.0.0)"] -zstd = ["motor[zstd] (>=2.5.0,<4.0.0)"] - [[package]] name = "billiard" -version = "4.2.1" +version = "4.2.2" description = "Python multiprocessing fork with improvements and bugfixes" optional = false python-versions = ">=3.7" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb"}, - {file = "billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f"}, -] - -[[package]] -name = "boto3" -version = "1.34.69" -description = "The AWS SDK for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "boto3-1.34.69-py3-none-any.whl", hash = "sha256:2e25ef6bd325217c2da329829478be063155897d8d3b29f31f7f23ab548519b1"}, - {file = "boto3-1.34.69.tar.gz", hash = "sha256:898a5fed26b1351352703421d1a8b886ef2a74be6c97d5ecc92432ae01fda203"}, -] - -[package.dependencies] -botocore = ">=1.34.69,<1.35.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.10.0,<0.11.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.34.69" -description = "Low-level, data-driven core of boto 3." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "botocore-1.34.69-py3-none-any.whl", hash = "sha256:d3802d076d4d507bf506f9845a6970ce43adc3d819dd57c2791f5c19ed6e5950"}, - {file = "botocore-1.34.69.tar.gz", hash = "sha256:d1ab2bff3c2fd51719c2021d9fa2f30fbb9ed0a308f69e9a774ac92c8091380a"}, + {file = "billiard-4.2.2-py3-none-any.whl", hash = "sha256:4bc05dcf0d1cc6addef470723aac2a6232f3c7ed7475b0b580473a9145829457"}, + {file = "billiard-4.2.2.tar.gz", hash = "sha256:e815017a062b714958463e07ba15981d802dc53d41c5b69d28c5a7c238f8ecf3"}, ] -[package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} - -[package.extras] -crt = ["awscrt (==0.19.19)"] - [[package]] name = "braintrust-core" version = "0.0.49" @@ -576,18 +484,21 @@ files = [ ] [[package]] -name = "cachetools" -version = "5.5.2" -description = "Extensible memoizing collections and decorators" +name = "bson" +version = "0.5.10" +description = "BSON codec for Python" optional = false -python-versions = ">=3.7" +python-versions = "*" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, - {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, + {file = "bson-0.5.10.tar.gz", hash = "sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590"}, ] +[package.dependencies] +python-dateutil = ">=2.4.0" +six = ">=1.9.0" + [[package]] name = "celery" version = "5.5.3" @@ -648,97 +559,114 @@ zstd = ["zstandard (==0.23.0)"] [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] markers = "(python_version == \"3.11\" or python_version >= \"3.12\") and platform_python_implementation != \"PyPy\"" files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] -pycparser = "*" +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" @@ -845,15 +773,15 @@ files = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "dev"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, ] [package.dependencies] @@ -925,69 +853,99 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "(platform_system == \"Windows\" or sys_platform == \"win32\") and (python_version == \"3.11\" or python_version >= \"3.12\")", dev = "(python_version == \"3.11\" or python_version >= \"3.12\") and sys_platform == \"win32\""} +markers = {main = "(python_version == \"3.11\" or python_version >= \"3.12\") and platform_system == \"Windows\"", dev = "(platform_system == \"Windows\" or sys_platform == \"win32\") and (python_version == \"3.11\" or python_version >= \"3.12\")"} [[package]] name = "cryptography" -version = "45.0.7" +version = "46.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3"}, - {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3"}, - {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6"}, - {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd"}, - {file = "cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8"}, - {file = "cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443"}, - {file = "cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27"}, - {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17"}, - {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b"}, - {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c"}, - {file = "cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5"}, - {file = "cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141"}, - {file = "cryptography-45.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b"}, - {file = "cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63"}, - {file = "cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc"}, + {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b"}, + {file = "cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1"}, + {file = "cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b"}, + {file = "cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee"}, + {file = "cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb"}, + {file = "cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470"}, + {file = "cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e"}, + {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36"}, + {file = "cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a"}, + {file = "cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c"}, + {file = "cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1"}, + {file = "cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9"}, + {file = "cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0"}, + {file = "cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023"}, + {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e"}, + {file = "cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90"}, + {file = "cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be"}, + {file = "cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c"}, + {file = "cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62"}, + {file = "cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1"}, + {file = "cryptography-46.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f25a41f5b34b371a06dad3f01799706631331adc7d6c05253f5bca22068c7a34"}, + {file = "cryptography-46.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e12b61e0b86611e3f4c1756686d9086c1d36e6fd15326f5658112ad1f1cc8807"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1d3b3edd145953832e09607986f2bd86f85d1dc9c48ced41808b18009d9f30e5"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fe245cf4a73c20592f0f48da39748b3513db114465be78f0a36da847221bd1b4"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2b9cad9cf71d0c45566624ff76654e9bae5f8a25970c250a26ccfc73f8553e2d"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9bd26f2f75a925fdf5e0a446c0de2714f17819bf560b44b7480e4dd632ad6c46"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7282d8f092b5be7172d6472f29b0631f39f18512a3642aefe52c3c0e0ccfad5a"}, + {file = "cryptography-46.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c4b93af7920cdf80f71650769464ccf1fb49a4b56ae0024173c24c48eb6b1612"}, + {file = "cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe"}, ] [package.dependencies] -cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9\" and platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==45.0.7)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] + [[package]] name = "deprecated" version = "1.2.18" @@ -1020,28 +978,6 @@ files = [ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -[[package]] -name = "dnspython" -version = "2.7.0" -description = "DNS toolkit" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, - {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=43)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=1.0.0)"] -idna = ["idna (>=3.7)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - [[package]] name = "ecdsa" version = "0.19.1" @@ -1096,20 +1032,20 @@ python-dateutil = ">=2.4" [[package]] name = "fastapi" -version = "0.116.1" +version = "0.116.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"}, - {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"}, + {file = "fastapi-0.116.2-py3-none-any.whl", hash = "sha256:c3a7a8fb830b05f7e087d920e0d786ca1fc9892eb4e9a84b227be4c1bc7569db"}, + {file = "fastapi-0.116.2.tar.gz", hash = "sha256:231a6af2fe21cfa2c32730170ad8514985fc250bec16c9b242d3b94c835ef529"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.48.0" +starlette = ">=0.40.0,<0.49.0" typing-extensions = ">=4.8.0" [package.extras] @@ -1132,130 +1068,156 @@ files = [ [[package]] name = "frozenlist" -version = "1.7.0" +version = "1.8.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, - {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, - {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, - {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, - {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, - {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, - {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, - {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, - {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, - {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, - {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, - {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, - {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, - {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, - {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, - {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, - {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, + {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, + {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, + {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, + {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, + {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, ] [[package]] name = "fsspec" -version = "2025.7.0" +version = "2025.9.0" description = "File-system specification" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21"}, - {file = "fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58"}, + {file = "fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7"}, + {file = "fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19"}, ] [package.extras] @@ -1299,34 +1261,6 @@ files = [ {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, ] -[[package]] -name = "google-auth" -version = "2.40.3" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca"}, - {file = "google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] -enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] -urllib3 = ["packaging", "urllib3"] - [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -1451,28 +1385,6 @@ files = [ {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] -[[package]] -name = "hf-xet" -version = "1.1.9" -description = "Fast transfer of large files with the Hugging Face Hub." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "(python_version == \"3.11\" or python_version >= \"3.12\") and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\")" -files = [ - {file = "hf_xet-1.1.9-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a3b6215f88638dd7a6ff82cb4e738dcbf3d863bf667997c093a3c990337d1160"}, - {file = "hf_xet-1.1.9-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9b486de7a64a66f9a172f4b3e0dfe79c9f0a93257c501296a2521a13495a698a"}, - {file = "hf_xet-1.1.9-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c5a840c2c4e6ec875ed13703a60e3523bc7f48031dfd750923b2a4d1a5fc3c"}, - {file = "hf_xet-1.1.9-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:96a6139c9e44dad1c52c52520db0fffe948f6bce487cfb9d69c125f254bb3790"}, - {file = "hf_xet-1.1.9-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ad1022e9a998e784c97b2173965d07fe33ee26e4594770b7785a8cc8f922cd95"}, - {file = "hf_xet-1.1.9-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86754c2d6d5afb11b0a435e6e18911a4199262fe77553f8c50d75e21242193ea"}, - {file = "hf_xet-1.1.9-cp37-abi3-win_amd64.whl", hash = "sha256:5aad3933de6b725d61d51034e04174ed1dce7a57c63d530df0014dea15a40127"}, - {file = "hf_xet-1.1.9.tar.gz", hash = "sha256:c99073ce404462e909f1d5839b2d14a3827b8fe75ed8aed551ba6609c026c803"}, -] - -[package.extras] -tests = ["pytest"] - [[package]] name = "httpcore" version = "1.0.9" @@ -1524,21 +1436,20 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "huggingface-hub" -version = "0.34.4" +version = "0.30.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a"}, - {file = "huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c"}, + {file = "huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28"}, + {file = "huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466"}, ] [package.dependencies] filelock = "*" fsspec = ">=2023.5.0" -hf-xet = {version = ">=1.1.3,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} packaging = ">=20.9" pyyaml = ">=5.1" requests = "*" @@ -1546,19 +1457,17 @@ tqdm = ">=4.42.1" typing-extensions = ">=3.7.4.3" [package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] hf-transfer = ["hf-transfer (>=0.1.4)"] -hf-xet = ["hf-xet (>=1.1.2,<2.0.0)"] +hf-xet = ["hf-xet (>=0.1.4)"] inference = ["aiohttp"] -mcp = ["aiohttp", "mcp (>=1.8.0)", "typer"] -oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] -quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "ruff (>=0.9.0)"] +quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] tensorflow = ["graphviz", "pydot", "tensorflow"] tensorflow-testing = ["keras (<3.0)", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] @@ -1609,7 +1518,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["dev"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, @@ -1637,103 +1546,91 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.10.0" +version = "0.11.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, - {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, - {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, - {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, - {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, - {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, - {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, - {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, - {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, - {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, - {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, - {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, - {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, - {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, - {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, - {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, - {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, - {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, - {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, -] - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, + {file = "jiter-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3893ce831e1c0094a83eeaf56c635a167d6fa8cc14393cc14298fd6fdc2a2449"}, + {file = "jiter-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25c625b9b61b5a8725267fdf867ef2e51b429687f6a4eef211f4612e95607179"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4ca85fb6a62cf72e1c7f5e34ddef1b660ce4ed0886ec94a1ef9777d35eaa1f"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:572208127034725e79c28437b82414028c3562335f2b4f451d98136d0fc5f9cd"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:494ba627c7f550ad3dabb21862864b8f2216098dc18ff62f37b37796f2f7c325"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8da18a99f58bca3ecc2d2bba99cac000a924e115b6c4f0a2b98f752b6fbf39a"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ffd3b0fff3fabbb02cc09910c08144db6bb5697a98d227a074401e01ee63dd"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fe6530aa738a4f7d4e4702aa8f9581425d04036a5f9e25af65ebe1f708f23be"}, + {file = "jiter-0.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e35d66681c133a03d7e974e7eedae89720fe8ca3bd09f01a4909b86a8adf31f5"}, + {file = "jiter-0.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59459beca2fbc9718b6f1acb7bfb59ebc3eb4294fa4d40e9cb679dafdcc6c60"}, + {file = "jiter-0.11.0-cp310-cp310-win32.whl", hash = "sha256:b7b0178417b0dcfc5f259edbc6db2b1f5896093ed9035ee7bab0f2be8854726d"}, + {file = "jiter-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:11df2bf99fb4754abddd7f5d940a48e51f9d11624d6313ca4314145fcad347f0"}, + {file = "jiter-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cb5d9db02979c3f49071fce51a48f4b4e4cf574175fb2b11c7a535fa4867b222"}, + {file = "jiter-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1dc6a123f3471c4730db7ca8ba75f1bb3dcb6faeb8d46dd781083e7dee88b32d"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09858f8d230f031c7b8e557429102bf050eea29c77ad9c34c8fe253c5329acb7"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbe2196c4a0ce760925a74ab4456bf644748ab0979762139626ad138f6dac72d"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5beb56d22b63647bafd0b74979216fdee80c580c0c63410be8c11053860ffd09"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97025d09ef549795d8dc720a824312cee3253c890ac73c621721ddfc75066789"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50880a6da65d8c23a2cf53c412847d9757e74cc9a3b95c5704a1d1a24667347"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:452d80a1c86c095a242007bd9fc5d21b8a8442307193378f891cb8727e469648"}, + {file = "jiter-0.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e84e58198d4894668eec2da660ffff60e0f3e60afa790ecc50cb12b0e02ca1d4"}, + {file = "jiter-0.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df64edcfc5dd5279a791eea52aa113d432c933119a025b0b5739f90d2e4e75f1"}, + {file = "jiter-0.11.0-cp311-cp311-win32.whl", hash = "sha256:144fc21337d21b1d048f7f44bf70881e1586401d405ed3a98c95a114a9994982"}, + {file = "jiter-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:b0f32e644d241293b892b1a6dd8f0b9cc029bfd94c97376b2681c36548aabab7"}, + {file = "jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada"}, + {file = "jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09"}, + {file = "jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5"}, + {file = "jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206"}, + {file = "jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b"}, + {file = "jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c"}, + {file = "jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb"}, + {file = "jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1"}, + {file = "jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758"}, + {file = "jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166"}, + {file = "jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80"}, + {file = "jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6"}, + {file = "jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33"}, + {file = "jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03"}, + {file = "jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba"}, + {file = "jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72"}, + {file = "jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2"}, + {file = "jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0"}, + {file = "jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73"}, + {file = "jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2"}, + {file = "jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40"}, + {file = "jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406"}, + {file = "jiter-0.11.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:719891c2fb7628a41adff4f2f54c19380a27e6fdfdb743c24680ef1a54c67bd0"}, + {file = "jiter-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df7f1927cbdf34cb91262a5418ca06920fd42f1cf733936d863aeb29b45a14ef"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e71ae6d969d0c9bab336c5e9e2fabad31e74d823f19e3604eaf96d9a97f463df"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5661469a7b2be25ade3a4bb6c21ffd1e142e13351a0759f264dfdd3ad99af1ab"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76c15ef0d3d02f8b389066fa4c410a0b89e9cc6468a1f0674c5925d2f3c3e890"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63782a1350917a27817030716566ed3d5b3c731500fd42d483cbd7094e2c5b25"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a7092b699646a1ddc03a7b112622d9c066172627c7382659befb0d2996f1659"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f637b8e818f6d75540f350a6011ce21252573c0998ea1b4365ee54b7672c23c5"}, + {file = "jiter-0.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a624d87719e1b5d09c15286eaee7e1532a40c692a096ea7ca791121365f548c1"}, + {file = "jiter-0.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9d0146d8d9b3995821bb586fc8256636258947c2f39da5bab709f3a28fb1a0b"}, + {file = "jiter-0.11.0-cp39-cp39-win32.whl", hash = "sha256:d067655a7cf0831eb8ec3e39cbd752995e9b69a2206df3535b3a067fac23b032"}, + {file = "jiter-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:f05d03775a11aaf132c447436983169958439f1219069abf24662a672851f94e"}, + {file = "jiter-0.11.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:902b43386c04739229076bd1c4c69de5d115553d982ab442a8ae82947c72ede7"}, + {file = "jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4"}, ] [[package]] @@ -1761,15 +1658,15 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2025.4.1" +version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, - {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, + {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, + {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, ] [package.dependencies] @@ -1812,50 +1709,6 @@ sqs = ["boto3 (>=1.26.143)", "urllib3 (>=1.26.16)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=2.8.0)"] -[[package]] -name = "kubernetes" -version = "28.1.0" -description = "Kubernetes python client" -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "kubernetes-28.1.0-py2.py3-none-any.whl", hash = "sha256:10f56f8160dcb73647f15fafda268e7f60cf7dbc9f8e46d52fcd46d3beb0c18d"}, - {file = "kubernetes-28.1.0.tar.gz", hash = "sha256:1468069a573430fb1cb5ad22876868f57977930f80a6749405da31cd6086a7e9"}, -] - -[package.dependencies] -certifi = ">=14.05.14" -google-auth = ">=1.0.1" -oauthlib = ">=3.2.2" -python-dateutil = ">=2.5.3" -pyyaml = ">=5.4.1" -requests = "*" -requests-oauthlib = "*" -six = ">=1.9.0" -urllib3 = ">=1.24.2,<2.0" -websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" - -[package.extras] -adal = ["adal (>=1.0.2)"] - -[[package]] -name = "lazy-model" -version = "0.2.0" -description = "" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "lazy-model-0.2.0.tar.gz", hash = "sha256:57c0e91e171530c4fca7aebc3ac05a163a85cddd941bf7527cc46c0ddafca47c"}, - {file = "lazy_model-0.2.0-py3-none-any.whl", hash = "sha256:5a3241775c253e36d9069d236be8378288a93d4fc53805211fd152e04cc9c342"}, -] - -[package.dependencies] -pydantic = ">=1.9.0" - [[package]] name = "levenshtein" version = "0.27.1" @@ -2021,74 +1874,102 @@ testing = ["pytest"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] [[package]] @@ -2104,191 +1985,201 @@ files = [ {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, ] -[[package]] -name = "motor" -version = "3.7.1" -description = "Non-blocking MongoDB driver for Tornado or asyncio" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "motor-3.7.1-py3-none-any.whl", hash = "sha256:8a63b9049e38eeeb56b4fdd57c3312a6d1f25d01db717fe7d82222393c410298"}, - {file = "motor-3.7.1.tar.gz", hash = "sha256:27b4d46625c87928f331a6ca9d7c51c2f518ba0e270939d395bc1ddc89d64526"}, -] - -[package.dependencies] -pymongo = ">=4.9,<5.0" - -[package.extras] -aws = ["pymongo[aws] (>=4.5,<5)"] -docs = ["aiohttp", "furo (==2024.8.6)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<8)", "sphinx-rtd-theme (>=2,<3)", "tornado"] -encryption = ["pymongo[encryption] (>=4.5,<5)"] -gssapi = ["pymongo[gssapi] (>=4.5,<5)"] -ocsp = ["pymongo[ocsp] (>=4.5,<5)"] -snappy = ["pymongo[snappy] (>=4.5,<5)"] -test = ["aiohttp (>=3.8.7)", "cffi (>=1.17.0rc1)", "mockupdb", "pymongo[encryption] (>=4.5,<5)", "pytest (>=7)", "pytest-asyncio", "tornado (>=5)"] -zstd = ["pymongo[zstd] (>=4.5,<5)"] - [[package]] name = "multidict" -version = "6.6.4" +version = "6.7.0" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, - {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, - {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, - {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, - {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, - {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, - {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, - {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, - {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, - {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, - {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, - {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, - {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, - {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, - {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, - {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, - {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, - {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, - {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, - {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, - {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, + {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, + {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, + {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, + {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, + {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, + {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, + {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, + {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, + {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, + {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, + {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, + {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, + {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, + {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, + {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, + {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, + {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, + {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, + {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, + {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, + {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, + {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, + {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, + {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, + {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, + {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, ] [[package]] name = "newrelic" -version = "10.16.0" +version = "10.17.0" description = "New Relic Python Agent" optional = false python-versions = ">=3.7" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "newrelic-10.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:833992875fda7aa3f1db9e9b8782a29d71094b7e6c0e7bcd7113534a652ba74d"}, - {file = "newrelic-10.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:287e8fbab1c10e5fe2e20c81aec3fece01bf613ebf4985b6e36360fce2f5cd80"}, - {file = "newrelic-10.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4b761b2a45bb5a76f22fa8d780e089a0dc6c61315441456d487f752b84da7d3"}, - {file = "newrelic-10.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5176be5123f987d6f9c12ebd189dabd739bba809225feac8c4ee5f9176580a0a"}, - {file = "newrelic-10.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ddbf08714dc5d102dd5b4db87da08981c6390cb838f972387839f6031aad11c"}, - {file = "newrelic-10.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:952cc8f94bbb43a07f0f2982a251e6c973a2bb5db0febc5534586171a09a5619"}, - {file = "newrelic-10.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4beec39a1e290f808e35041b7728d61105737cddec9525d4e7dc2cf107c8904e"}, - {file = "newrelic-10.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:020e384fbb8543e5c54e0cc900b170876faa03fe27d00ba49b7a6ba6a2b0d5b4"}, - {file = "newrelic-10.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f74301ed32ce625fb84c8bd0c079498dca603ff111d5baa2f59b6480ef7355b"}, - {file = "newrelic-10.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3efef3b10f02a086d9009a80b020db8f0b1db5072075779c9facd5d2fe3bb916"}, - {file = "newrelic-10.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8328f621f3683b28156cfa8e1648a56b894480bf4c4953cde7e659b1480ae1e9"}, - {file = "newrelic-10.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07f65faed0a9aa40695e6e6ce4c943ca9c9643fc848c2344bb26665a65893a80"}, - {file = "newrelic-10.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:053e73518cf0b2af412490723d3fa8338dab6ed48b748c0b7a0288160d1c33e2"}, - {file = "newrelic-10.16.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8263ae16eb4745def12bace2e3e38e013e76d4e765333a77342b270702e3eaa9"}, - {file = "newrelic-10.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e83e1341a72ac92118d1af40c2c3ba56069c589acceb1e89d236beab8d7891a"}, - {file = "newrelic-10.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71950c5e1f26bf1ed563d3641da97f6bb209f80697675888ee12808d8ac69452"}, - {file = "newrelic-10.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6361b21416b529415208b551f9903a33c172489142df35c93fdad337c0309050"}, - {file = "newrelic-10.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7555b252e06848b98c0a0616d5c3487ad58903a1f21114f947ab91c4fac772a"}, - {file = "newrelic-10.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:072e54f72d57ab9b7891209ab85b3839a494fa6f89b1674da5ca7848a4b5b1ad"}, - {file = "newrelic-10.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a6dbd306b72b0b3c6477b4ccfdc6fc7d7e62b793631f7856684733aad405ef50"}, - {file = "newrelic-10.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ee00809ca2b85ae049f2f7d93248f6e30bc005a3a41931a26bdfdf5d4e752c"}, - {file = "newrelic-10.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe5c2f72f56f9670fabe2889b70d3d239aae715bb97c2460929605ffa8742fb"}, - {file = "newrelic-10.16.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a71c1034b47494179567341178f436804c6d33538fa25430580955725c847ef2"}, - {file = "newrelic-10.16.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:caa7b9d026ec221bcfed35a6c3be65b7e9e0b50e68a1278ad5e38dab6d12d45f"}, - {file = "newrelic-10.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49a5f734847269ccba31962132fa570b49bcf04378d554aa6a871eda1a4abcf1"}, - {file = "newrelic-10.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901212e07779fd437229b99fdc1f3d82509bf2d27afe77f80224409eeda51355"}, - {file = "newrelic-10.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:90478ee70f1e6520cf70271dbb41e7c0d88e3a968b8361e0d2d82872c70de6b7"}, - {file = "newrelic-10.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bfc9621afef69b089ee1ec6ab729220c2f5540b81e5666a3414797f48f5df3e6"}, - {file = "newrelic-10.16.0.tar.gz", hash = "sha256:d20eb934380a88d787f93e037d2ccfd5a7c80e657db5bb2e645216eaafe32e26"}, + {file = "newrelic-10.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91ebeaae460beaaeb3113474873bc5fc9c6c13ab6b0fd8936f584c508fec373d"}, + {file = "newrelic-10.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30b8b6a9f447a7276fd19a41ccc6dba264f9ef569e4bed92200a58b76e7f7754"}, + {file = "newrelic-10.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:214bd42ef8fb1c2046727bfe15c8f374ebf4ff2d5091ca1ac870abd2a7574ffe"}, + {file = "newrelic-10.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36a14fd92b44324d9bb33e1c1698c2cd15ffa0e291d222365e929ee17cd9d6da"}, + {file = "newrelic-10.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f62f70f1c01aa527de7e6ce2eb4c0285a7c40a89b42107be92bcc5dfd0fa8bb4"}, + {file = "newrelic-10.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54898db2b9eb81db9f3421302d2d03451630c033f2001fbe9f21d0e068f0418f"}, + {file = "newrelic-10.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65f7d08f90caf6dd3708068a0eda8328c49ec18e51d514a43afaa5a68180e9c"}, + {file = "newrelic-10.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dd61153b82b1e265754820f3e91c96c16b1b75508b6c169e27b5c196f1b7b95"}, + {file = "newrelic-10.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ab9cbce3f8da5bfcd085b8a59591cfb653d75834b362e3eccb86bdf21eea917"}, + {file = "newrelic-10.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:889969407457a9344927b1a9e9d5afde77345857db4d2ea75b119d02c02d9174"}, + {file = "newrelic-10.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d74c06863356b19c1fcf19900f85057ab33ef779aa3e0de3cb7e3d3f37ca8e20"}, + {file = "newrelic-10.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:083777458f93d16ae9e605fd66a6298d856e32deea11cf3270ed237858dcbfe6"}, + {file = "newrelic-10.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:165c7045da474342d4ceab67776e8aeb66e77674fab7484b4e2e4ea68d02ed4d"}, + {file = "newrelic-10.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3c3c82c95b57fe30fc1f684bf920bd8b0ecae70a16cc11f55d0867ffb7520d"}, + {file = "newrelic-10.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5789f739e6ca1c4a49649e406b538d67c063ec33ab4658d01049303dfad9398b"}, + {file = "newrelic-10.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4144dd96fa55772456326b3a6032a53d54aa9245ffc5041b002ce9eb5dbd0992"}, + {file = "newrelic-10.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c444e96918508decee6f532f04f0fa79027db0728770669ace4a7008c5dd52ca"}, + {file = "newrelic-10.17.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5171f1020bc834b01faae8466b5375e8c2162fa219c98430e42af1c6e5077b14"}, + {file = "newrelic-10.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0b43fb53f8a63c1e792a9ba4439680265b481c44efd64f7d92069e974c38b199"}, + {file = "newrelic-10.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:78a673440037ad61e0073da69f2549fac92271dadd7a2087c22a20ddc7f91260"}, + {file = "newrelic-10.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e81b726f3fab5fed155407f157ed67931d2d83403cc0ae473327e0b7b42bc5"}, + {file = "newrelic-10.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed6f549b1ab42ad31d5ee34c8041f1484ab769e045ad3b9f321c9588339079f3"}, + {file = "newrelic-10.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3a34df04ff6315619a8d098c59751f766de57f14e5cd5c09427dde47bfbc5012"}, + {file = "newrelic-10.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d92b43c07cf137d8a6d8dc73c87fbef99ca449564c25385e66727f156c2c34d8"}, + {file = "newrelic-10.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7669597193d8f6a3512fd5e8728a7b2b244db9694b3a03b3b99930ba9efc9e3b"}, + {file = "newrelic-10.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1027039eae1b4d3ffee3077135900a41ffdc41bd2bee3162d6cf36bd990cf3b"}, + {file = "newrelic-10.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:af28eb5e7f368f6f4fcde9593e7dd189b68e39a2e7409874a0cdea3a4bb4e904"}, + {file = "newrelic-10.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:328162a61e30f7b3e76241830989110d8f86d8392805a78034f8109e4f5434b4"}, + {file = "newrelic-10.17.0.tar.gz", hash = "sha256:f092109ac024f9524fafdd06126924c0fbd2af54684571167c0ee1d1cc1bcb7d"}, ] [package.extras] @@ -2296,80 +2187,100 @@ infinite-tracing = ["grpcio", "protobuf"] [[package]] name = "numpy" -version = "1.26.3" +version = "2.3.3" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, - {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, - {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, - {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, - {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, - {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, - {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, - {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, - {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, - {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, - {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, -] - -[[package]] -name = "oauthlib" -version = "3.3.1" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, - {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125"}, + {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48"}, + {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6"}, + {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa"}, + {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30"}, + {file = "numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57"}, + {file = "numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa"}, + {file = "numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b"}, + {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8"}, + {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20"}, + {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea"}, + {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7"}, + {file = "numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf"}, + {file = "numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb"}, + {file = "numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7"}, + {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c"}, + {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93"}, + {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae"}, + {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86"}, + {file = "numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8"}, + {file = "numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf"}, + {file = "numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19"}, + {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30"}, + {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e"}, + {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3"}, + {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea"}, + {file = "numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd"}, + {file = "numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d"}, + {file = "numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a"}, + {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe"}, + {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421"}, + {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021"}, + {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf"}, + {file = "numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0"}, + {file = "numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8"}, + {file = "numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a"}, + {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54"}, + {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e"}, + {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097"}, + {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970"}, + {file = "numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5"}, + {file = "numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f"}, + {file = "numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc"}, + {file = "numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029"}, ] -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - [[package]] name = "openai" -version = "1.102.0" +version = "1.109.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, - {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, + {file = "openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315"}, + {file = "openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869"}, ] [package.dependencies] @@ -2388,22 +2299,131 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] +[[package]] +name = "opentelemetry-api" +version = "1.37.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47"}, + {file = "opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7"}, +] + +[package.dependencies] +importlib-metadata = ">=6.0,<8.8.0" +typing-extensions = ">=4.5.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.37.0" +description = "OpenTelemetry Protobuf encoding" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.37.0-py3-none-any.whl", hash = "sha256:53038428449c559b0c564b8d718df3314da387109c4d36bd1b94c9a641b0292e"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.37.0.tar.gz", hash = "sha256:c87a1bdd9f41fdc408d9cc9367bb53f8d2602829659f2b90be9f9d79d0bfe62c"}, +] + +[package.dependencies] +opentelemetry-proto = "1.37.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.37.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.37.0.tar.gz", hash = "sha256:e52e8600f1720d6de298419a802108a8f5afa63c96809ff83becb03f874e44ac"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.37.0" +opentelemetry-proto = "1.37.0" +opentelemetry-sdk = ">=1.37.0,<1.38.0" +requests = ">=2.7,<3.0" +typing-extensions = ">=4.5.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.58b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "opentelemetry_instrumentation-0.58b0-py3-none-any.whl", hash = "sha256:50f97ac03100676c9f7fc28197f8240c7290ca1baa12da8bfbb9a1de4f34cc45"}, + {file = "opentelemetry_instrumentation-0.58b0.tar.gz", hash = "sha256:df640f3ac715a3e05af145c18f527f4422c6ab6c467e40bd24d2ad75a00cb705"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +opentelemetry-semantic-conventions = "0.58b0" +packaging = ">=18.0" +wrapt = ">=1.0.0,<2.0.0" + [[package]] name = "opentelemetry-proto" -version = "1.36.0" +version = "1.37.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, - {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, + {file = "opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2"}, + {file = "opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538"}, ] [package.dependencies] protobuf = ">=5.0,<7.0" +[[package]] +name = "opentelemetry-sdk" +version = "1.37.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c"}, + {file = "opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5"}, +] + +[package.dependencies] +opentelemetry-api = "1.37.0" +opentelemetry-semantic-conventions = "0.58b0" +typing-extensions = ">=4.5.0" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.58b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28"}, + {file = "opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25"}, +] + +[package.dependencies] +opentelemetry-api = "1.37.0" +typing-extensions = ">=4.5.0" + [[package]] name = "orjson" version = "3.11.3" @@ -2513,55 +2533,68 @@ files = [ [[package]] name = "pandas" -version = "2.3.2" +version = "2.3.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35"}, - {file = "pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b"}, - {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424"}, - {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf"}, - {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba"}, - {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6"}, - {file = "pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a"}, - {file = "pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743"}, - {file = "pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4"}, - {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2"}, - {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e"}, - {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea"}, - {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372"}, - {file = "pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f"}, - {file = "pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9"}, - {file = "pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b"}, - {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175"}, - {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9"}, - {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4"}, - {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811"}, - {file = "pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae"}, - {file = "pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e"}, - {file = "pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9"}, - {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a"}, - {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b"}, - {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6"}, - {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a"}, - {file = "pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b"}, - {file = "pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57"}, - {file = "pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2"}, - {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9"}, - {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2"}, - {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012"}, - {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370"}, - {file = "pandas-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88080a0ff8a55eac9c84e3ff3c7665b3b5476c6fbc484775ca1910ce1c3e0b87"}, - {file = "pandas-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4a558c7620340a0931828d8065688b3cc5b4c8eb674bcaf33d18ff4a6870b4a"}, - {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45178cf09d1858a1509dc73ec261bf5b25a625a389b65be2e47b559905f0ab6a"}, - {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77cefe00e1b210f9c76c697fedd8fdb8d3dd86563e9c8adc9fa72b90f5e9e4c2"}, - {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13bd629c653856f00c53dc495191baa59bcafbbf54860a46ecc50d3a88421a96"}, - {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:36d627906fd44b5fd63c943264e11e96e923f8de77d6016dc2f667b9ad193438"}, - {file = "pandas-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a9d7ec92d71a420185dec44909c32e9a362248c4ae2238234b76d5be37f208cc"}, - {file = "pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, ] [package.dependencies] @@ -2646,7 +2679,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["dev"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, @@ -2702,209 +2735,155 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.3.2" +version = "0.4.0" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, - {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, - {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, - {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, - {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, - {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, - {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, - {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, - {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, - {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, - {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, - {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, - {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, - {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, - {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993"}, + {file = "propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57"}, + {file = "propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71"}, + {file = "propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0"}, + {file = "propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577"}, + {file = "propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0"}, + {file = "propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194"}, + {file = "propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c"}, + {file = "propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907"}, + {file = "propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833"}, + {file = "propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc"}, + {file = "propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281"}, + {file = "propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da"}, + {file = "propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db"}, + {file = "propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d"}, + {file = "propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca"}, + {file = "propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93"}, + {file = "propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b"}, + {file = "propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec"}, + {file = "propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373"}, + {file = "propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28"}, + {file = "propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0cd30341142c68377cf3c4e2d9f0581e6e528694b2d57c62c786be441053d2fc"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c46d37955820dd883cf9156ceb7825b8903e910bdd869902e20a5ac4ecd2c8b"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0b12df77eb19266efd146627a65b8ad414f9d15672d253699a50c8205661a820"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cdabd60e109506462e6a7b37008e57979e737dc6e7dfbe1437adcfe354d1a0a"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65ff56a31f25925ef030b494fe63289bf07ef0febe6da181b8219146c590e185"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:96153e037ae065bb71cae889f23c933190d81ae183f3696a030b47352fd8655d"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bf95be277fbb51513895c2cecc81ab12a421cdbd8837f159828a919a0167f96"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8d18d796ffecdc8253742fd53a94ceee2e77ad149eb9ed5960c2856b5f692f71"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4a52c25a51d5894ba60c567b0dbcf73de2f3cd642cf5343679e07ca3a768b085"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e0ce7f3d1faf7ad58652ed758cc9753049af5308b38f89948aa71793282419c5"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:545987971b2aded25ba4698135ea0ae128836e7deb6e18c29a581076aaef44aa"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7da5c4c72ae40fd3ce87213ab057db66df53e55600d0b9e72e2b7f5a470a2cc4"}, + {file = "propcache-0.4.0-cp39-cp39-win32.whl", hash = "sha256:2015218812ee8f13bbaebc9f52b1e424cc130b68d4857bef018e65e3834e1c4d"}, + {file = "propcache-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:39f0f6a3b56e82dc91d84c763b783c5c33720a33c70ee48a1c13ba800ac1fa69"}, + {file = "propcache-0.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:236c8da353ea7c22a8e963ab78cddb1126f700ae9538e2c4c6ef471e5545494b"}, + {file = "propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557"}, + {file = "propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529"}, ] [[package]] name = "protobuf" -version = "6.32.0" +version = "6.32.1" description = "" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, - {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, - {file = "protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c"}, - {file = "protobuf-6.32.0-cp39-cp39-win32.whl", hash = "sha256:7db8ed09024f115ac877a1427557b838705359f047b2ff2f2b2364892d19dacb"}, - {file = "protobuf-6.32.0-cp39-cp39-win_amd64.whl", hash = "sha256:15eba1b86f193a407607112ceb9ea0ba9569aed24f93333fe9a497cf2fda37d3"}, - {file = "protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783"}, - {file = "protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2"}, -] - -[[package]] -name = "psycopg2-binary" -version = "2.9.10" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, - {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, - {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, - {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, - {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, - {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, + {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, + {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, + {file = "protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281"}, + {file = "protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4"}, + {file = "protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710"}, + {file = "protobuf-6.32.1-cp39-cp39-win32.whl", hash = "sha256:68ff170bac18c8178f130d1ccb94700cf72852298e016a2443bdb9502279e5f1"}, + {file = "protobuf-6.32.1-cp39-cp39-win_amd64.whl", hash = "sha256:d0975d0b2f3e6957111aa3935d08a0eb7e006b1505d825f862a1fffc8348e122"}, + {file = "protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346"}, + {file = "protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d"}, ] [[package]] @@ -2920,46 +2899,17 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] -[[package]] -name = "pyasn1" -version = "0.6.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, - {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, -] - -[package.dependencies] -pyasn1 = ">=0.6.1,<0.7.0" - [[package]] name = "pycparser" -version = "2.22" +version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "(python_version == \"3.11\" or python_version >= \"3.12\") and platform_python_implementation != \"PyPy\"" +markers = "(python_version == \"3.11\" or python_version >= \"3.12\") and platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] @@ -3007,15 +2957,15 @@ files = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.10" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, - {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, + {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, + {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, ] [package.dependencies] @@ -3141,6 +3091,22 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyjwt" version = "2.10.1" @@ -3163,87 +3129,6 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] -[[package]] -name = "pymongo" -version = "4.14.1" -description = "PyMongo - the Official MongoDB Python driver" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pymongo-4.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97f0da391fb32f989f0afcd1838faff5595456d24c56d196174eddbb7c3a494c"}, - {file = "pymongo-4.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec160c4e1184da11d375a4315917f5a04180ea0ff522f0a97cf78acbb65810d8"}, - {file = "pymongo-4.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95ce2e0dcd9a556e1f51a4132db88c40e8e0a49c0b16d1dddba624f640895b"}, - {file = "pymongo-4.14.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7b965614c16ac7d2cf297fbfb16a9ec81c0493bd5916f455a8e8020e432300b"}, - {file = "pymongo-4.14.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f81e8156a862ad8b44a065bd89978361a3054571e61b5e802ebdef91bb13ccad"}, - {file = "pymongo-4.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0fe8e7bbb59cb0652df0efd285e80e6a92207f5ced4a0f7de56275fd9c21b77"}, - {file = "pymongo-4.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6d426e70a35d1dd5003a535ac8c0683998bea783949daa980d70272baa5cb05"}, - {file = "pymongo-4.14.1-cp310-cp310-win32.whl", hash = "sha256:8a4fe1b1603865e44c3dbce2b91ac2f18b1672208ff49203e8a480ab68a2d8f5"}, - {file = "pymongo-4.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:27cb44c71e6f220b163e1d3c0dd18559e534d5d7cb7e16afa0cf1b7761403492"}, - {file = "pymongo-4.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af4e667902314bcc05c90ea4ac0351bb759410ae0c5496ae47aef80659a12a44"}, - {file = "pymongo-4.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98c36403c97ec3a439a9ea5cdea730e34f0bf3c39eacfcab3fb07b34f5ef42a7"}, - {file = "pymongo-4.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95bfb5fe10a8aa11029868c403939945092fb8d160ca3a10d386778ed9623533"}, - {file = "pymongo-4.14.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44beff3470a6b1736f9e9cf7fb6477fdb2342b6f19a722cab3bbc989c5f3f693"}, - {file = "pymongo-4.14.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3176250b89ecc0db8120caf9945ded340eacebec7183f2093e58370041c2d5a8"}, - {file = "pymongo-4.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a37312c841be2c2edd090b49861dab2e6117ff15cabf801f5910931105740e"}, - {file = "pymongo-4.14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573b1ed740dbb51be0819ede005012f4fa37df2c27c94d7d2e18288e16e1ef10"}, - {file = "pymongo-4.14.1-cp311-cp311-win32.whl", hash = "sha256:4812d168f9cd5f257805807a44637afcd0bb7fd22ac4738321bc6aa50ebd9d4f"}, - {file = "pymongo-4.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:9485278fed0a8933c8ce8f97ab518158b82e884d4a7bc34e1d784b751c7b69f3"}, - {file = "pymongo-4.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2cafb545a77738f0506cd538be1b14e9f40ad0b62634d89e1845dee3c726ad5"}, - {file = "pymongo-4.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a76afb1375f6914fecfdc3bfe6fb7c8c36b682c4707b7fb8ded5c2e17a1c2d77"}, - {file = "pymongo-4.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f5a4223c6acecb0ab25202a5b4ed6f2b6a41c30204ef44d3d46525e8ea455a9"}, - {file = "pymongo-4.14.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89c1f6804ae16101d5dd6cf0bd06b10e70e5e870aa98a198824c772ce3cb8ba3"}, - {file = "pymongo-4.14.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaef22550ba1034e9b0ed309395ec72944348c277e27cc973cd5b07322b1d088"}, - {file = "pymongo-4.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71500e97dbbda5d3e5dc9354dca865246c7502eea9d041c1ce0ae2c3fa018fd2"}, - {file = "pymongo-4.14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6eeea7c92fd8ccd24ad156e2f9c2a117220f1ba0a41968b26d953dc6b8082b1d"}, - {file = "pymongo-4.14.1-cp312-cp312-win32.whl", hash = "sha256:78e9ec6345a14e2144a514f501e3bfe69ec8c8fefd0759757e4f47bf0b243522"}, - {file = "pymongo-4.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:714589ce1df891e91f808b1e6e678990040997972d2c70454efebfefd1c8e299"}, - {file = "pymongo-4.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb147d0d77863ae89fa73cf8c0cc1a68d7dd7c5689cf0381501505307136b2bd"}, - {file = "pymongo-4.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e386721b57a50a5acd6e19c3c14cb975cbc0bf1a0364227d6cc15b486bb094cc"}, - {file = "pymongo-4.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49a2bf594ce1693f8a3cc4123ec3fa3a86215b395333b22be83c9eb765b24ecb"}, - {file = "pymongo-4.14.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb6679929e5bab898e9c5b46ee6fd025f6eb14380e9d4a210e122d79b223548"}, - {file = "pymongo-4.14.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcbea95a877b2c7c4e4a18527c4eecbe91bdcb0b202f93d5713d50386138ffa3"}, - {file = "pymongo-4.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04e780ff2854278d24f7a2011aed45b3df89520c89ca29a7c1ccf9a9f0d513d0"}, - {file = "pymongo-4.14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:147711a3b95d45dd11377a078e77fa302142b67656a8f57076693aa7fba124c1"}, - {file = "pymongo-4.14.1-cp313-cp313-win32.whl", hash = "sha256:6b945dda0359ba13171201fa2f1e32d4b5e73f57606b8c6dd560eeebf4a69d84"}, - {file = "pymongo-4.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fba1dcad4260a9c96aa5bd576bf96edeea5682cd6da6b5777c644ef103f16f6"}, - {file = "pymongo-4.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:184b0b6c3663bec2c13d7e2f0a99233c24b1bc7d8163b8b9a019a3ab159b1ade"}, - {file = "pymongo-4.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0a9bdb95e6fab64c8453dae84834dfd7a8b91cfbc7a3e288d9cdd161621a867"}, - {file = "pymongo-4.14.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df5cc411dbe2b064945114598fdb3e36c3eeb38ed2559e459d5a7b2d91074a54"}, - {file = "pymongo-4.14.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33a8b2c47db66f3bb33d62e3884fb531b77a58efd412b67b0539c685950c2382"}, - {file = "pymongo-4.14.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f08880ad8bd6bdd4bdb5c93c4a6946c5c4e429b648c3b665c435af02005e7db"}, - {file = "pymongo-4.14.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92f8c2a3d0f17c432d68304d3abcab36a8a7ba78db93a143ac77eef6b70bc126"}, - {file = "pymongo-4.14.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:019f8f9b8a61a5780450c5908c38f63e4248f286d804163d3728bc544f0b07b2"}, - {file = "pymongo-4.14.1-cp313-cp313t-win32.whl", hash = "sha256:414a999a5b9212635f51c8b23481626406b731abaea16659a39df00f538d06d8"}, - {file = "pymongo-4.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9375cf27c04d2be7d02986262e0593ece1e78fa1934744bdd74c0c0b0cd2c2f2"}, - {file = "pymongo-4.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d8945b11c4e39c13b47ec79dd0ee05126a6cf4753cf5fdceabf8cc51c02e21e6"}, - {file = "pymongo-4.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7d6114f4a60b04205b4fce120567955402816ac75329b9282fc8a603ac615ef"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6649018ae12a28b8d8399ddda5cb662ac364e338faf0a621e6b9e5ec643134df"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0bd1a446b39216453f53d55143a82e8617730723f100de940f1611ee35e78d6"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e09e59bb15edf0d948de6fa2b6f1cbb25ee63e7beba6d45ef6e94609e759efaa"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1604d9f669b044d30ca1775ebe37ddbd1972eaa7ffd041dde9e026b0334c69bd"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91f9a3d771ab86229244098125b1c22111aa3e3679534d626db8d05cd9c59ea4"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c93d1f5db2bf63b4958aef2a914520c7103187d68359b512a8d6d62f5d7a752"}, - {file = "pymongo-4.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ed9c0e22f874419f07022a9133e8d62aa8b665ceb2d89218ee88450c2824185e"}, - {file = "pymongo-4.14.1-cp39-cp39-win32.whl", hash = "sha256:06e2e8996324823e19bccea4dfd7ed543513410bbc7be9860502b62822d62bd4"}, - {file = "pymongo-4.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:0e679c8f62ec0e6ba64799ce55b22d76c80cd042f7d99fa2cfbb4d935ac61bea"}, - {file = "pymongo-4.14.1.tar.gz", hash = "sha256:d78f5b0b569f4320e2485599d89b088aa6d750aad17cc98fd81a323b544ed3d0"}, -] - -[package.dependencies] -dnspython = ">=1.16.0,<3.0.0" - -[package.extras] -aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] -docs = ["furo (==2025.7.19)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] -encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.13.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] -snappy = ["python-snappy"] -test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] -zstd = ["zstandard"] - [[package]] name = "pyotp" version = "2.9.0" @@ -3262,25 +3147,26 @@ test = ["coverage", "mypy", "ruff", "wheel"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] +python-versions = ">=3.9" +groups = ["dev"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" @@ -3304,15 +3190,15 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy [[package]] name = "pytest-mock" -version = "3.14.1" +version = "3.15.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["dev"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, - {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, ] [package.dependencies] @@ -3390,15 +3276,15 @@ files = [ [[package]] name = "python-multipart" -version = "0.0.18" +version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, - {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] [[package]] @@ -3416,169 +3302,182 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] name = "rapidfuzz" -version = "3.14.0" +version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "rapidfuzz-3.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:91d8c7d9d38835d5fcf9bc87593add864eaea41eb33654d93ded3006b198a326"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a1e574230262956d28e40191dd44ad3d81d2d29b5e716c6c7c0ba17c4d1524e"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1eda6546831f15e6d8d27593873129ae5e4d2f05cf13bacc2d5222e117f3038"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d29686b524b35f93fc14961026a8cfb37283af76ab6f4ed49aebf4df01b44a4a"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0fb99bc445014e893c152e36e98b3e9418cc2c0fa7b83d01f3d1b89e73618ed2"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d9cd4212ca2ea18d026b3f3dfc1ec25919e75ddfd2c7dd20bf7797f262e2460"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:e6a41c6be1394b17b03bc3af3051f54ba0b4018324a0d4cb34c7d2344ec82e79"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:19bee793c4a84b0f5153fcff2e7cfeaeeb976497a5892baaadb6eadef7e6f398"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:977144b50b2f1864c825796ad2d41f47a3fd5b7632a2e9905c4d2c8883a8234d"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ca7c7274bec8085f7a2b68b0490d270a260385d45280d8a2a8ae5884cfb217ba"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:efa7eca15825c78dc2b9e9e5824fa095cef8954de98e5a6d2f4ad2416a3d5ddf"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a780c08c41e7ec4336d7a8fcdcd7920df74de6c57be87b72adad4e1b40a31632"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-win32.whl", hash = "sha256:cf540e48175c0620639aa4f4e2b56d61291935c0f684469e8e125e7fa4daef65"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:e7769fbc78aba051f514d8a08374e3989124b2d1eee6888c72706a174d0e8a6d"}, - {file = "rapidfuzz-3.14.0-cp310-cp310-win_arm64.whl", hash = "sha256:71442f5e9fad60a4942df3be340acd5315e59aefc5a83534b6a9aa62db67809d"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6501e49395ad5cecf1623cb4801639faa1c833dbacc07c26fa7b8f7fa19fd1c0"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c3cd9b8d5e159c67d242f80cae1b9d9b1502779fc69fcd268a1eb7053f58048"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a578cadbe61f738685ffa20e56e8346847e40ecb033bdc885373a070cfe4a351"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b46340872a1736544b23f3c355f292935311623a0e63a271f284ffdbab05e4"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:238422749da213c3dfe36397b746aeda8579682e93b723a1e77655182198e693"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83f3ad0e7ad3cf1138e36be26f4cacb7580ac0132b26528a89e8168a0875afd8"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:7c34e34fb7e01aeea1e84192cf01daf1d56ccc8a0b34c0833f9799b341c6d539"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a58bbbbdd2a150c76c6b3af5ac2bbe9afcff26e6b17e1f60b6bd766cc7094fcf"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d0e50b4bea57bfcda4afee993eef390fd8f0a64981c971ac4decd9452143892d"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:357eb9d394bfc742d3528e8bb13afa9baebc7fbe863071975426b47fc21db220"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb960ec526030077658764a309b60e907d86d898f8efbe959845ec2873e514eb"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6bedb19db81d8d723cc4d914cb079d89ff359364184cc3c3db7cef1fc7819444"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-win32.whl", hash = "sha256:8dba3d6e10a34aa255a6f6922cf249f8d0b9829e6b00854e371d803040044f7f"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:ce79e37b23c1cbf1dc557159c8f20f6d71e9d28aef63afcf87bcb58c8add096a"}, - {file = "rapidfuzz-3.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:e140ff4b5d0ea386b998137ddd1335a7bd4201ef987d4cb5a48c3e8c174f8aec"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:93c8739f7bf7931d690aeb527c27e2a61fd578f076d542ddd37e29fa535546b6"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7596e95ab03da6cff70f4ec9a5298b2802e8bdd443159d18180b186c80df1416"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cdd49e097ced3746eadb5fb87379f377c0b093f9aba1133ae4f311b574e2ed8"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4cd4898f21686bb141e151ba920bcd1744cab339277f484c0f97fe7de2c45c8"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:83427518ad72050add47e2cf581080bde81df7f69882e508da3e08faad166b1f"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05435b4f2472cbf7aac8b837e2e84a165e595c60d79da851da7cfa85ed15895d"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:2dae744c1cdb8b1411ed511a719b505a0348da1970a652bfc735598e68779287"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ca05daaca07232037014fc6ce2c2ef0a05c69712f6a5e77da6da5209fb04d7c"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:2227f4b3742295f380adefef7b6338c30434f8a8e18a11895a1a7c9308b6635d"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:847ea42b5a6077bc796e1b99cd357a641207b20e3573917b0469b28b5a22238a"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:539506f13cf0dd6ef2f846571f8e116dba32a468e52d05a91161785ab7de2ed1"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03c4b4d4f45f846e4eae052ee18d39d6afe659d74f6d99df5a0d2c5d53930505"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-win32.whl", hash = "sha256:aff0baa3980a8aeb2ce5e15930140146b5fe3fb2d63c8dc4cb08dfbd2051ceb2"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1eef7f0694fe4cf991f61adaa040955da1e0072c8c41d7db5eb60e83da9e61b"}, - {file = "rapidfuzz-3.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:269d8d1fe5830eef46a165a5c6dd240a05ad44c281a77957461b79cede1ece0f"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5cf3828b8cbac02686e1d5c499c58e43c5f613ad936fe19a2d092e53f3308ccd"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68c3931c19c51c11654cf75f663f34c0c7ea04c456c84ccebfd52b2047121dba"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b4232168959af46f2c0770769e7986ff6084d97bc4b6b2b16b2bfa34164421b"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:174c784cecfafe22d783b5124ebffa2e02cc01e49ffe60a28ad86d217977f478"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b2dedf216f43a50f227eee841ef0480e29e26b2ce2d7ee680b28354ede18627"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5698239eecf5b759630450ef59521ad3637e5bd4afc2b124ae8af2ff73309c41"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:0acc9553fc26f1c291c381a6aa8d3c5625be23b5721f139528af40cc4119ae1d"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00141dfd3b8c9ae15fbb5fbd191a08bde63cdfb1f63095d8f5faf1698e30da93"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:67f725c3f5713da6e0750dc23f65f0f822c6937c25e3fc9ee797aa6783bef8c1"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ba351cf2678d40a23fb4cbfe82cc45ea338a57518dca62a823c5b6381aa20c68"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:558323dcd5fb38737226be84c78cafbe427706e47379f02c57c3e35ac3745061"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb4e4ea174add5183c707d890a816a85e9330f93e5ded139dab182adc727930c"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-win32.whl", hash = "sha256:ec379e1b407935d729c08da9641cfc5dfb2a7796f74cdd82158ce5986bb8ff88"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:4b59ba48a909bdf7ec5dad6e3a5a0004aeec141ae5ddb205d0c5bd4389894cf9"}, - {file = "rapidfuzz-3.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:e688b0a98edea42da450fa6ba41736203ead652a78b558839916c10df855f545"}, - {file = "rapidfuzz-3.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cb6c5a46444a2787e466acd77e162049f061304025ab24da02b59caedea66064"}, - {file = "rapidfuzz-3.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99ed7a9e9ff798157caf3c3d96ca7da6560878902d8f70fa7731acc94e0d293c"}, - {file = "rapidfuzz-3.14.0-cp313-cp313t-win32.whl", hash = "sha256:c8e954dd59291ff0cd51b9c0f425e5dc84731bb006dbd5b7846746fe873a0452"}, - {file = "rapidfuzz-3.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5754e3ca259667c46a2b58ca7d7568251d6e23d2f0e354ac1cc5564557f4a32d"}, - {file = "rapidfuzz-3.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:558865f6825d27006e6ae2e1635cfe236d736c8f2c5c82db6db4b1b6df4478bc"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3cc4bd8de6643258c5899f21414f9d45d7589d158eee8d438ea069ead624823b"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:081aac1acb4ab449f8ea7d4e5ea268227295503e1287f56f0b56c7fc3452da1e"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e0209c6ef7f2c732e10ce4fccafcf7d9e79eb8660a81179aa307c7bd09fafcd"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e4610997e9de08395e8632b605488a9efc859fe0516b6993b3925f3057f9da7"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd0095cde6d0179c92c997ede4b85158bf3c7386043e2fadbee291018b29300"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a141c07f9e97c45e67aeed677bac92c08f228c556a80750ea3e191e82d54034"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:5a9de40fa6be7809fd2579c8020b9edaf6f50ffc43082b14e95ad3928a254f22"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20f510dae17bad8f4909ab32b40617f964af55131e630de7ebc0ffa7f00fe634"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:79c3fd17a432c3f74de94782d7139f9a22e948cec31659a1a05d67b5c0f4290e"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8cde9ffb86ea33d67cce9b26b513a177038be48ee2eb4d856cc60a75cb698db7"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:cafb657c8f2959761bca40c0da66f29d111e2c40d91f8ed4a75cc486c99b33ae"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4d80a9f673c534800d73f164ed59620e2ba820ed3840abb67c56022ad043564b"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-win32.whl", hash = "sha256:da9878a01357c7906fb16359b3622ce256933a3286058ee503358859e1442f68"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:09af941076ef18f6c2b35acfd5004c60d03414414058e98ece6ca9096f454870"}, - {file = "rapidfuzz-3.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:1a878eb065ce6061038dd1c0b9e8eb7477f7d05d5c5161a1d2a5fa630818f938"}, - {file = "rapidfuzz-3.14.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33ce0326e6feb0d2207a7ca866a5aa6a2ac2361f1ca43ca32aca505268c18ec9"}, - {file = "rapidfuzz-3.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e8056d10e99dedf110e929fdff4de6272057115b28eeef4fb6f0d99fd73c026f"}, - {file = "rapidfuzz-3.14.0-cp314-cp314t-win32.whl", hash = "sha256:ddde238b7076e49c2c21a477ee4b67143e1beaf7a3185388fe0b852e64c6ef52"}, - {file = "rapidfuzz-3.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:ef24464be04a7da1adea741376ddd2b092e0de53c9b500fd3c2e38e071295c9e"}, - {file = "rapidfuzz-3.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:fd4a27654f51bed3518bc5bbf166627caf3ddd858b12485380685777421f8933"}, - {file = "rapidfuzz-3.14.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4c9a00ef2f684b1132aeb3c0737483dc8f85a725dbe792aee1d1c3cbcf329b34"}, - {file = "rapidfuzz-3.14.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2e203d76b3dcd1b466ee196f7adb71009860906303db274ae20c7c5af62bc1a8"}, - {file = "rapidfuzz-3.14.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2b317a71fd938348d8dbbe2f559cda58a67fdcafdd3107afca7ab0fb654efa86"}, - {file = "rapidfuzz-3.14.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5d610a2c5efdb2a3f9eaecac4ecd6d849efb2522efa36000e006179062056dc"}, - {file = "rapidfuzz-3.14.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:c053cad08ab872df4e201daacb66d7fd04b5b4c395baebb193b9910c63ed22ec"}, - {file = "rapidfuzz-3.14.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7e52ac8a458b2f09291fa968b23192d6664c7568a43607de2a51a088d016152d"}, - {file = "rapidfuzz-3.14.0.tar.gz", hash = "sha256:672b6ba06150e53d7baf4e3d5f12ffe8c213d5088239a15b5ae586ab245ac8b2"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0307f018b16feaa36074bcec2496f6f120af151a098910296e72e233232a62f"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bc133652da143aca1ab72de235446432888b2b7f44ee332d006f8207967ecb8a"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e9e71b3fe7e4a1590843389a90fe2a8684649fc74b9b7446e17ee504ddddb7de"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c51519eb2f20b52eba6fc7d857ae94acc6c2a1f5d0f2d794b9d4977cdc29dd7"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:fe87d94602624f8f25fff9a0a7b47f33756c4d9fc32b6d3308bb142aa483b8a4"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d665380503a575dda52eb712ea521f789e8f8fd629c7a8e6c0f8ff480febc78"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0f0dd022b8a7cbf3c891f6de96a80ab6a426f1069a085327816cea749e096c2"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf1ba22d36858b265c95cd774ba7fe8991e80a99cd86fe4f388605b01aee81a3"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ca1c1494ac9f9386d37f0e50cbaf4d07d184903aed7691549df1b37e9616edc9"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9e4b12e921b0fa90d7c2248742a536f21eae5562174090b83edd0b4ab8b557d7"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-win32.whl", hash = "sha256:5e1c1f2292baa4049535b07e9e81feb29e3650d2ba35ee491e64aca7ae4cb15e"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:59a8694beb9a13c4090ab3d1712cabbd896c6949706d1364e2a2e1713c413760"}, + {file = "rapidfuzz-3.14.1-cp310-cp310-win_arm64.whl", hash = "sha256:e94cee93faa792572c574a615abe12912124b4ffcf55876b72312914ab663345"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d976701060886a791c8a9260b1d4139d14c1f1e9a6ab6116b45a1acf3baff67"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e6ba7e6eb2ab03870dcab441d707513db0b4264c12fba7b703e90e8b4296df2"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e532bf46de5fd3a1efde73a16a4d231d011bce401c72abe3c6ecf9de681003f"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9b6a6fb8ed9b951e5f3b82c1ce6b1665308ec1a0da87f799b16e24fc59e4662"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b6ac3f9810949caef0e63380b11a3c32a92f26bacb9ced5e32c33560fcdf8d1"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52e4c34fd567f77513e886b66029c1ae02f094380d10eba18ba1c68a46d8b90"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2ef72e41b1a110149f25b14637f1cedea6df192462120bea3433980fe9d8ac05"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fb654a35b373d712a6b0aa2a496b2b5cdd9d32410cfbaecc402d7424a90ba72a"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2b2c12e5b9eb8fe9a51b92fe69e9ca362c0970e960268188a6d295e1dec91e6d"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4f069dec5c450bd987481e752f0a9979e8fdf8e21e5307f5058f5c4bb162fa56"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4d0d9163725b7ad37a8c46988cae9ebab255984db95ad01bf1987ceb9e3058dd"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db656884b20b213d846f6bc990c053d1f4a60e6d4357f7211775b02092784ca1"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-win32.whl", hash = "sha256:4b42f7b9c58cbcfbfaddc5a6278b4ca3b6cd8983e7fd6af70ca791dff7105fb9"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:e5847f30d7d4edefe0cb37294d956d3495dd127c1c56e9128af3c2258a520bb4"}, + {file = "rapidfuzz-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:5087d8ad453092d80c042a08919b1cb20c8ad6047d772dc9312acd834da00f75"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:809515194f628004aac1b1b280c3734c5ea0ccbd45938c9c9656a23ae8b8f553"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0afcf2d6cb633d0d4260d8df6a40de2d9c93e9546e2c6b317ab03f89aa120ad7"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1c3d07d53dcafee10599da8988d2b1f39df236aee501ecbd617bd883454fcd"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e9ee3e1eb0a027717ee72fe34dc9ac5b3e58119f1bd8dd15bc19ed54ae3e62b"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:70c845b64a033a20c44ed26bc890eeb851215148cc3e696499f5f65529afb6cb"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26db0e815213d04234298dea0d884d92b9cb8d4ba954cab7cf67a35853128a33"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:6ad3395a416f8b126ff11c788531f157c7debeb626f9d897c153ff8980da10fb"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61c5b9ab6f730e6478aa2def566223712d121c6f69a94c7cc002044799442afd"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13e0ea3d0c533969158727d1bb7a08c2cc9a816ab83f8f0dcfde7e38938ce3e6"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6325ca435b99f4001aac919ab8922ac464999b100173317defb83eae34e82139"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:07a9fad3247e68798424bdc116c1094e88ecfabc17b29edf42a777520347648e"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8ff5dbe78db0a10c1f916368e21d328935896240f71f721e073cf6c4c8cdedd"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-win32.whl", hash = "sha256:9c83270e44a6ae7a39fc1d7e72a27486bccc1fa5f34e01572b1b90b019e6b566"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:e06664c7fdb51c708e082df08a6888fce4c5c416d7e3cc2fa66dd80eb76a149d"}, + {file = "rapidfuzz-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:6c7c26025f7934a169a23dafea6807cfc3fb556f1dd49229faf2171e5d8101cc"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8d69f470d63ee824132ecd80b1974e1d15dd9df5193916901d7860cef081a260"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6f571d20152fc4833b7b5e781b36d5e4f31f3b5a596a3d53cf66a1bd4436b4f4"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61d77e09b2b6bc38228f53b9ea7972a00722a14a6048be9a3672fb5cb08bad3a"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b41d95ef86a6295d353dc3bb6c80550665ba2c3bef3a9feab46074d12a9af8f"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0591df2e856ad583644b40a2b99fb522f93543c65e64b771241dda6d1cfdc96b"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f277801f55b2f3923ef2de51ab94689a0671a4524bf7b611de979f308a54cd6f"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:893fdfd4f66ebb67f33da89eb1bd1674b7b30442fdee84db87f6cb9074bf0ce9"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fe2651258c1f1afa9b66f44bf82f639d5f83034f9804877a1bbbae2120539ad1"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ace21f7a78519d8e889b1240489cd021c5355c496cb151b479b741a4c27f0a25"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cb5acf24590bc5e57027283b015950d713f9e4d155fda5cfa71adef3b3a84502"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:67ea46fa8cc78174bad09d66b9a4b98d3068e85de677e3c71ed931a1de28171f"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:44e741d785de57d1a7bae03599c1cbc7335d0b060a35e60c44c382566e22782e"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-win32.whl", hash = "sha256:b1fe6001baa9fa36bcb565e24e88830718f6c90896b91ceffcb48881e3adddbc"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:83b8cc6336709fa5db0579189bfd125df280a554af544b2dc1c7da9cdad7e44d"}, + {file = "rapidfuzz-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:cf75769662eadf5f9bd24e865c19e5ca7718e879273dce4e7b3b5824c4da0eb4"}, + {file = "rapidfuzz-3.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d937dbeda71c921ef6537c6d41a84f1b8112f107589c9977059de57a1d726dd6"}, + {file = "rapidfuzz-3.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a2d80cc1a4fcc7e259ed4f505e70b36433a63fa251f1bb69ff279fe376c5efd"}, + {file = "rapidfuzz-3.14.1-cp313-cp313t-win32.whl", hash = "sha256:40875e0c06f1a388f1cab3885744f847b557e0b1642dfc31ff02039f9f0823ef"}, + {file = "rapidfuzz-3.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:876dc0c15552f3d704d7fb8d61bdffc872ff63bedf683568d6faad32e51bbce8"}, + {file = "rapidfuzz-3.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:61458e83b0b3e2abc3391d0953c47d6325e506ba44d6a25c869c4401b3bc222c"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e84d9a844dc2e4d5c4cabd14c096374ead006583304333c14a6fbde51f612a44"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:40301b93b99350edcd02dbb22e37ca5f2a75d0db822e9b3c522da451a93d6f27"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fedd5097a44808dddf341466866e5c57a18a19a336565b4ff50aa8f09eb528f6"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e3e61c9e80d8c26709d8aa5c51fdd25139c81a4ab463895f8a567f8347b0548"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da011a373722fac6e64687297a1d17dc8461b82cb12c437845d5a5b161bc24b9"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5967d571243cfb9ad3710e6e628ab68c421a237b76e24a67ac22ee0ff12784d6"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:474f416cbb9099676de54aa41944c154ba8d25033ee460f87bb23e54af6d01c9"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ae2d57464b59297f727c4e201ea99ec7b13935f1f056c753e8103da3f2fc2404"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:57047493a1f62f11354c7143c380b02f1b355c52733e6b03adb1cb0fe8fb8816"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4acc20776f225ee37d69517a237c090b9fa7e0836a0b8bc58868e9168ba6ef6f"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4373f914ff524ee0146919dea96a40a8200ab157e5a15e777a74a769f73d8a4a"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:37017b84953927807847016620d61251fe236bd4bcb25e27b6133d955bb9cafb"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-win32.whl", hash = "sha256:c8d1dd1146539e093b84d0805e8951475644af794ace81d957ca612e3eb31598"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:f51c7571295ea97387bac4f048d73cecce51222be78ed808263b45c79c40a440"}, + {file = "rapidfuzz-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:01eab10ec90912d7d28b3f08f6c91adbaf93458a53f849ff70776ecd70dd7a7a"}, + {file = "rapidfuzz-3.14.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:60879fcae2f7618403c4c746a9a3eec89327d73148fb6e89a933b78442ff0669"}, + {file = "rapidfuzz-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f94d61e44db3fc95a74006a394257af90fa6e826c900a501d749979ff495d702"}, + {file = "rapidfuzz-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:93b6294a3ffab32a9b5f9b5ca048fa0474998e7e8bb0f2d2b5e819c64cb71ec7"}, + {file = "rapidfuzz-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6cb56b695421538fdbe2c0c85888b991d833b8637d2f2b41faa79cea7234c000"}, + {file = "rapidfuzz-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7cd312c380d3ce9d35c3ec9726b75eee9da50e8a38e89e229a03db2262d3d96b"}, + {file = "rapidfuzz-3.14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:673ce55a9be5b772dade911909e42382c0828b8a50ed7f9168763fa6b9f7054d"}, + {file = "rapidfuzz-3.14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:45c62ada1980ebf4c64c4253993cc8daa018c63163f91db63bb3af69cb74c2e3"}, + {file = "rapidfuzz-3.14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4d51efb29c0df0d4f7f64f672a7624c2146527f0745e3572098d753676538800"}, + {file = "rapidfuzz-3.14.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4a21ccdf1bd7d57a1009030527ba8fae1c74bf832d0a08f6b67de8f5c506c96f"}, + {file = "rapidfuzz-3.14.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:589fb0af91d3aff318750539c832ea1100dbac2c842fde24e42261df443845f6"}, + {file = "rapidfuzz-3.14.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a4f18092db4825f2517d135445015b40033ed809a41754918a03ef062abe88a0"}, + {file = "rapidfuzz-3.14.1.tar.gz", hash = "sha256:b02850e7f7152bd1edff27e9d584505b84968cacedee7a734ec4050c655a803c"}, ] [package.extras] @@ -3625,100 +3524,128 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2025.8.29" +version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "regex-2025.8.29-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a367dbb66842a08744f49c64ba1aab23e4cbcc924bae8ef40870f2c51d6cb240"}, - {file = "regex-2025.8.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:090d20a6f308c1cd3c33824e892666089d9719ff88e139d4b63623e881d3945c"}, - {file = "regex-2025.8.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e7ee69fdc9daf6aa98693b0db27a76e3d960c80d87c695af262c2608ccfc6a"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50628bc413193041838001b3926570629369d675b92badd6962c402aa09ed4c4"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fadf22d84901f1b6cc6b27439d98688a33cefb83e70c885791c2c27524907ed4"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3948db57ebe3c4bfb7e05765411ce6186820cafa27e5c737d72dbc5249010b3"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c42fbffe25ac6291f8dd00176d1916165550aa649d14e9c4668d6a3d6a5c900"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f3498dcc96266b8db76512ffb2432bab2587df5e8ebfdceba5e737378e2bd1"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2dadb4ecaad42562771697685a381e3f723bd4d522e357c07ae4a541ebf5753c"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bc94bccb0482a1eceb34961e3c46e25a3746633fa19f93c93a42ff4b231ee6c3"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:96adc63fd63c05e2feb9c6b8a7212e2b9f52ccb1fa1f18eaed4f9e0ac2cbd186"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:145fb4ca5a85e26c330b464fc71bbe0e92523ec5d295c6de9a1e31b06ebccf25"}, - {file = "regex-2025.8.29-cp310-cp310-win32.whl", hash = "sha256:119a0e930916bb26fe028ef5098c6cad66d7a298560cacbc6942e834580dfba5"}, - {file = "regex-2025.8.29-cp310-cp310-win_amd64.whl", hash = "sha256:e8f709146e0f3dafdb4315884de1490ab59f1b93ecf7f9c6c8b0f655f437e593"}, - {file = "regex-2025.8.29-cp310-cp310-win_arm64.whl", hash = "sha256:dc12259599d953bc25bc01f19b056b9115a96cd3cfe05f154d4570c9649800b0"}, - {file = "regex-2025.8.29-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:156f711019968ffb3512723a38b06d94d379675c296bdb6104d1abb6e57374c6"}, - {file = "regex-2025.8.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9082c0db8d43c696fac70b5b0592934f21533940f0118239b5c32fa23e51ed1a"}, - {file = "regex-2025.8.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b3535b9a69a818735ebac392876dae4b215fe28c13b145353a2dac468ebae16"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c460628f6098cf8916b2d62fb39a37a39e49cca0279ac301ff9d94f7e75033e"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dad3ce46390fe3d81ae1c131e29179f010925fa164e15b918fb037effdb7ad9"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f89e5beb3012d3c36c526fd4af163ada24011a0b417378f726b17c2fb382a35d"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40eeff06bbcfa69201b60488f3f3aa38ad3c92c7c0ab2cfc7c9599abfdf24262"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7a9bc68610d22735b6ac01a3c3ef5b03d9303a18bd3e2249340213389f273dc"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e785e40f7edfc19ff0b81b27f25eefdb0251cfd2ac4a9fa1eea03f5129e93758"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ba1deae2ceaa0b181ac9fd4cb8f04d6ba1494f3c8d053c8999f7c0dadb93497b"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15869e4f36de7091342e1dae90216aafa3746e3a069f30b34503a36931036f95"}, - {file = "regex-2025.8.29-cp311-cp311-win32.whl", hash = "sha256:aef62e0b08b0e3c2616783a9f75a02f001254695a0a1d28b829dc9fb6a3603e4"}, - {file = "regex-2025.8.29-cp311-cp311-win_amd64.whl", hash = "sha256:fd347592a4811ba1d246f99fb53db82a1898a5aebb511281ac0c2d81632e1789"}, - {file = "regex-2025.8.29-cp311-cp311-win_arm64.whl", hash = "sha256:d93801012bb23901df403ae0adf528abfd50041c9e1136a303937d45c14466e0"}, - {file = "regex-2025.8.29-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd61f18dc4446bc3a2904559a61f32e98091cef7fb796e06fa35b9bfefe4c0c5"}, - {file = "regex-2025.8.29-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f21b416be10a8348a7313ba8c610569a1ab4bf8ec70731750540842a4551cd3d"}, - {file = "regex-2025.8.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:008947a7fa92f4cb3b28201c9aa7becc0a44c31a7c2fcb934356e1877baccc09"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78ab1b3e68b890d7ebd69218cfbfe4a09dc00b8a47be8648510b81b932d55ff"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a848368797515bc141d3fad5fd2d81bf9e8a6a22d9ac1a4be4690dd22e997854"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8eaf3ea6631f804efcf0f5bd0e4ab62ba984fd9b70e3aef44b05cc6b951cc728"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4561aeb36b0bf3bb44826e4b61a80c6ace0d8839bf4914d78f061f9ba61444b4"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:93e077d1fbd24033fa427eab43d80ad47e449d25700cda78e8cac821a30090bf"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d92379e53d782bdb773988687300e3bccb91ad38157b754b04b1857aaeea16a3"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d41726de2040c2a487bbac70fdd6e3ff2f1aa47dc91f0a29f6955a6dfa0f06b6"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1915dfda52bd4d466f3a66b66988db1f647ee1d9c605858640ceeb779cffd908"}, - {file = "regex-2025.8.29-cp312-cp312-win32.whl", hash = "sha256:e2ef0087ad6949918836f215480a9331f6c59ad54912a9a412f08ab1c9ccbc98"}, - {file = "regex-2025.8.29-cp312-cp312-win_amd64.whl", hash = "sha256:c15d361fe9800bf38ef69c2e0c4b8b961ae4ce2f076fcf4f28e1fc9ea127f55a"}, - {file = "regex-2025.8.29-cp312-cp312-win_arm64.whl", hash = "sha256:305577fab545e64fb84d9a24269aa3132dbe05e1d7fa74b3614e93ec598fe6e6"}, - {file = "regex-2025.8.29-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:eed02e5c39f91268ea4ddf68ee19eed189d57c605530b7d32960f54325c52e7a"}, - {file = "regex-2025.8.29-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:630d5c7e0a490db2fee3c7b282c8db973abcbb036a6e4e6dc06c4270965852be"}, - {file = "regex-2025.8.29-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2206d3a30469e8fc8848139884168127f456efbaca8ae14809c26b98d2be15c6"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:394c492c398a9f9e17545e19f770c58b97e65963eedaa25bb879e80a03e2b327"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:db8b0e05af08ff38d78544950e844b5f159032b66dedda19b3f9b17297248be7"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd7c1821eff911917c476d41030b422791ce282c23ee9e1b8f7681fd0993f1e4"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d8a7f75da748a2d0c045600259f1899c9dd8dd9d3da1daa50bf534c3fa5ba"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5cd74545c32e0da0d489c2293101a82f4a1b88050c235e45509e4123017673b2"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:97b98ea38fc3c1034f3d7bd30288d2c5b3be8cdcd69e2061d1c86cb14644a27b"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8decb26f271b989d612c5d99db5f8f741dcd63ece51c59029840070f5f9778bf"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62141843d1ec079cd66604424af566e542e7e072b2d9e37165d414d2e6e271dd"}, - {file = "regex-2025.8.29-cp313-cp313-win32.whl", hash = "sha256:dd23006c90d9ff0c2e4e5f3eaf8233dcefe45684f2acb330869ec5c2aa02b1fb"}, - {file = "regex-2025.8.29-cp313-cp313-win_amd64.whl", hash = "sha256:d41a71342819bdfe87c701f073a14ea4bd3f847333d696c7344e9ff3412b7f70"}, - {file = "regex-2025.8.29-cp313-cp313-win_arm64.whl", hash = "sha256:54018e66344d60b214f4aa151c046e0fa528221656f4f7eba5a787ccc7057312"}, - {file = "regex-2025.8.29-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c03308757831a8d89e7c007abb75d1d4c9fbca003b5fb32755d4475914535f08"}, - {file = "regex-2025.8.29-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0d4b71791975fc203e0e6c50db974abb23a8df30729c1ac4fd68c9f2bb8c9358"}, - {file = "regex-2025.8.29-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:284fcd2dcb613e8b89b22a30cf42998c9a73ee360b8a24db8457d24f5c42282e"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b394b5157701b22cf63699c792bfeed65fbfeacbd94fea717a9e2036a51148ab"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ea197ac22396faf5e70c87836bb89f94ed5b500e1b407646a4e5f393239611f1"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:decd84f195c08b3d9d0297a7e310379aae13ca7e166473534508c81b95c74bba"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebaf81f7344dbf1a2b383e35923648de8f78fb262cf04154c82853887ac3e684"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d82fb8a97e5ed8f1d3ed7f8e0e7fe1760faa95846c0d38b314284dfdbe86b229"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1dcec2448ed0062f63e82ca02d1d05f74d4127cb6a9d76a73df60e81298d380b"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d0ffe4a3257a235f9d39b99c6f1bc53c7a4b11f28565726b1aa00a5787950d60"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5421a2d2026e8189500f12375cfd80a9a1914466d446edd28b37eb33c1953b39"}, - {file = "regex-2025.8.29-cp314-cp314-win32.whl", hash = "sha256:ceeeaab602978c8eac3b25b8707f21a69c0bcd179d9af72519da93ef3966158f"}, - {file = "regex-2025.8.29-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f8b0d5b88c33fe4060e6def58001fd8334b03c7ce2126964fa8851ab5d1b"}, - {file = "regex-2025.8.29-cp314-cp314-win_arm64.whl", hash = "sha256:7b4a3dc155984f09a55c64b90923cb136cd0dad21ca0168aba2382d90ea4c546"}, - {file = "regex-2025.8.29-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4d6dbdfdb4de3a77d1b2f9ec6bded2e056081407923d69236e13457924cf5fd7"}, - {file = "regex-2025.8.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f747541fd1ad1dcf859ce221749a5d26d7dbe6d928efdd407c97a2d27c8f434"}, - {file = "regex-2025.8.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90c37a24d9a809ff1898e74f3318a4e21f8bb3db9975a560fa3722e42c370285"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:470138c8882d66493969f45fad2f8e20f35e381b9f96a37f59a5ac786e653cf6"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc8c7fc96c9eb18b6690c96ec9c8fb63ea2fa78c6df4258fd76b59d4fbf46645"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33a26d4b2dc639868d73b9ec4ff8a89eb295797170125e4d4810ad23228f93c8"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b839268539b44a965f3ed680fda6270337a05bd425cc80542e0c6808efdc9a7e"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16b5ca6570c71b1ee61dd30f24a1944eb82a372364e37f58f9b9731636cc6ba9"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:421b6ccd037ad551e1ef1bc31debc3a914b579c27c0807f35c85f13b0eccbff3"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d8cb77df92d1a204a0c218d93c5fb14945e2a7b40da2d9f15b05c9ddae393b43"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:dd7df4ae4ea0efe0d378535e9825bd20e3be8d57eb3d55291d8094d61c9ccd9e"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:348cbcdf2d9dd0d09f05a78218776a33779e95aa57d553065a00429a96c553d3"}, - {file = "regex-2025.8.29-cp39-cp39-win32.whl", hash = "sha256:590de47e6c390a42e6bfb1bdbe2148456827a6b28464c6e387f51b4bbe1f83e2"}, - {file = "regex-2025.8.29-cp39-cp39-win_amd64.whl", hash = "sha256:df8deeb34e06c8ba196beabbcf2810d5ecd8cf71cfe69899e93806244610f7ae"}, - {file = "regex-2025.8.29-cp39-cp39-win_arm64.whl", hash = "sha256:fbabdb18fdd1fc4b0740f4e6b3070d7f41f98a88b8c38cf1962b6dcb3e745e56"}, - {file = "regex-2025.8.29.tar.gz", hash = "sha256:731ddb27a0900fa227dfba976b4efccec8c1c6fba147829bb52e71d49e91a5d7"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"}, + {file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"}, + {file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"}, + {file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"}, + {file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"}, + {file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"}, + {file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"}, + {file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"}, + {file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"}, + {file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"}, + {file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"}, + {file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"}, + {file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"}, + {file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"}, + {file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"}, + {file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"}, + {file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"}, + {file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"}, + {file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"}, + {file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"}, + {file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"}, + {file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"}, + {file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"}, + {file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"}, + {file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"}, + {file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"}, ] [[package]] @@ -3760,26 +3687,6 @@ files = [ [package.dependencies] requests = ">=1.0.0" -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -description = "OAuthlib authentication support for Requests." -optional = false -python-versions = ">=3.4" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, - {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, -] - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - [[package]] name = "restrictedpython" version = "8.0" @@ -3963,56 +3870,21 @@ files = [ {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"}, ] -[[package]] -name = "rsa" -version = "4.9.1" -description = "Pure-Python RSA implementation" -optional = false -python-versions = "<4,>=3.6" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, - {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "s3transfer" -version = "0.10.4" -description = "An Amazon S3 Transfer Manager" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, - {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, -] - -[package.dependencies] -botocore = ">=1.33.2,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] - [[package]] name = "sendgrid" -version = "6.12.4" +version = "6.12.5" description = "Twilio SendGrid library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "sendgrid-6.12.4-py3-none-any.whl", hash = "sha256:9a211b96241e63bd5b9ed9afcc8608f4bcac426e4a319b3920ab877c8426e92c"}, - {file = "sendgrid-6.12.4.tar.gz", hash = "sha256:9e88b849daf0fa4bdf256c3b5da9f5a3272402c0c2fd6b1928c9de440db0a03d"}, + {file = "sendgrid-6.12.5-py3-none-any.whl", hash = "sha256:96f92cc91634bf552fdb766b904bbb53968018da7ae41fdac4d1090dc0311ca8"}, + {file = "sendgrid-6.12.5.tar.gz", hash = "sha256:ea9aae30cd55c332e266bccd11185159482edfc07c149b6cd15cf08869fabdb7"}, ] [package.dependencies] -ecdsa = ">=0.19.1,<1" +cryptography = ">=45.0.6" python-http-client = ">=3.2.1" werkzeug = [ {version = ">=2.2.0", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, @@ -4320,32 +4192,32 @@ testing = ["mypy", "pytest", "pytest-gitignore", "pytest-mock", "responses", "ru [[package]] name = "tokenizers" -version = "0.22.0" +version = "0.22.1" description = "" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "tokenizers-0.22.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:eaa9620122a3fb99b943f864af95ed14c8dfc0f47afa3b404ac8c16b3f2bb484"}, - {file = "tokenizers-0.22.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:71784b9ab5bf0ff3075bceeb198149d2c5e068549c0d18fe32d06ba0deb63f79"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5b71f668a8076802b0241a42387d48289f25435b86b769ae1837cad4172a17"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea8562fa7498850d02a16178105b58803ea825b50dc9094d60549a7ed63654bb"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4136e1558a9ef2e2f1de1555dcd573e1cbc4a320c1a06c4107a3d46dc8ac6e4b"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf5954de3962a5fd9781dc12048d24a1a6f1f5df038c6e95db328cd22964206"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8337ca75d0731fc4860e6204cc24bb36a67d9736142aa06ed320943b50b1e7ed"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a89264e26f63c449d8cded9061adea7b5de53ba2346fc7e87311f7e4117c1cc8"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:790bad50a1b59d4c21592f9c3cf5e5cf9c3c7ce7e1a23a739f13e01fb1be377a"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:76cf6757c73a10ef10bf06fa937c0ec7393d90432f543f49adc8cab3fb6f26cb"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1626cb186e143720c62c6c6b5371e62bbc10af60481388c0da89bc903f37ea0c"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da589a61cbfea18ae267723d6b029b84598dc8ca78db9951d8f5beff72d8507c"}, - {file = "tokenizers-0.22.0-cp39-abi3-win32.whl", hash = "sha256:dbf9d6851bddae3e046fedfb166f47743c1c7bd11c640f0691dd35ef0bcad3be"}, - {file = "tokenizers-0.22.0-cp39-abi3-win_amd64.whl", hash = "sha256:c78174859eeaee96021f248a56c801e36bfb6bd5b067f2e95aa82445ca324f00"}, - {file = "tokenizers-0.22.0.tar.gz", hash = "sha256:2e33b98525be8453f355927f3cab312c36cd3e44f4d7e9e97da2fa94d0a49dcb"}, + {file = "tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73"}, + {file = "tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390"}, + {file = "tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82"}, + {file = "tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138"}, + {file = "tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9"}, ] [package.dependencies] -huggingface-hub = ">=0.16.4,<1.0" +huggingface-hub = ">=0.16.4,<2.0" [package.extras] dev = ["tokenizers[testing]"] @@ -4390,15 +4262,15 @@ telegram = ["requests"] [[package]] name = "twilio" -version = "9.7.2" +version = "9.8.3" description = "Twilio API client and TwiML generator" optional = false python-versions = ">=3.7.0" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "twilio-9.7.2-py2.py3-none-any.whl", hash = "sha256:bb751ebd914ea42591641daf093f018d11c032e789b066bd489459c530170985"}, - {file = "twilio-9.7.2.tar.gz", hash = "sha256:a491e3ceeb51b89e07e52d62581fcf661d170c8e35ecf4ee24bdd1ffa952b4f0"}, + {file = "twilio-9.8.3-py2.py3-none-any.whl", hash = "sha256:9182797ce26e0ed0ab95e7631aa3a91587f35da6402f6a2b33d87b8badc8d588"}, + {file = "twilio-9.8.3.tar.gz", hash = "sha256:4409af2681f53c6d4a052232615b6e63b2b3f7fbd83b14bed54d90891cb12d0d"}, ] [package.dependencies] @@ -4422,15 +4294,15 @@ files = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] [package.dependencies] @@ -4451,21 +4323,22 @@ files = [ [[package]] name = "urllib3" -version = "1.26.20" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, - {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uuid-utils" @@ -4507,15 +4380,15 @@ files = [ [[package]] name = "uvicorn" -version = "0.22.0" +version = "0.34.3" description = "The lightning-fast ASGI server." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, - {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, + {file = "uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885"}, + {file = "uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a"}, ] [package.dependencies] @@ -4523,7 +4396,7 @@ click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "vine" @@ -4584,35 +4457,17 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" description = "Measures the displayed width of unicode strings in a terminal" optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[[package]] -name = "websocket-client" -version = "1.8.0" -description = "WebSocket client for Python with low level API options" -optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, - {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, + {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, + {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, ] -[package.extras] -docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - [[package]] name = "werkzeug" version = "3.1.3" @@ -4726,117 +4581,143 @@ files = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.22.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version == \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, - {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, - {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, - {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, - {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, - {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, - {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, - {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, - {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, - {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, - {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, - {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, - {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, - {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, - {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, + {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, + {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, + {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, + {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, + {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, + {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, + {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, + {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, + {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, + {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, + {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, + {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, + {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, + {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, + {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, + {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, + {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, + {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, + {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, + {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, + {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, + {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, + {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, + {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, + {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, + {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, ] [package.dependencies] @@ -4867,5 +4748,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" -python-versions = ">=3.11,<4.0" -content-hash = "56dadab7968f77bc502a2d93899fb99614f77c3b1c149625c1820be9e79b7154" +python-versions = "^3.11" +content-hash = "b2d062b090b396223536f43b188f80c2c3db6484193c0bd8ceba09e38de616c9" diff --git a/api/pyproject.toml b/api/pyproject.toml index 86fa7d0687..1dd926f24e 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,8 +1,11 @@ [project] name = "api" -version = "0.55.2" +version = "0.56.0" description = "Agenta API" -authors = [{ name = "Mahmoud Mabrouk", email = "mahmoud@agenta.ai" }] +authors = [ + { name = "Mahmoud Mabrouk", email = "mahmoud@agenta.ai" }, + { name = "Juan Vega", email = "jp@agenta.ai" } +] [tool.poetry] package-mode = false # use pyproject.toml as dependency management and not for packaging @@ -12,68 +15,57 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry.dependencies] -python = ">=3.11,<4.0" -starlette = "^0.47.0" -fastapi = "^0.116.0" -pydantic = "^2.7.4" -toml = "^0.10.2" -uvicorn = "^0.22.0" -motor = "^3.1.2" -python-multipart = "^0.0.18" -backoff = "^2.2.1" -redis = "^6.1.0" -sendgrid = "^6.10.0" -restrictedpython = { version = "^8.0", python = ">=3.11,<3.12" } -pytest-mock = "^3.11.1" -boto3 = "^1.28.63" -asyncer = "^0.0.2" -anyio = "==3.7.1" -kubernetes = "^28.1.0" -celery = "^5.3.6" -watchdog = { extras = ["watchmedo"], version = "^3.0.0" } -beanie = "^1.25.0" -newrelic = "^10.13.0" -aioboto3 = "^12.4.0" -openai = ">=1.100.0" -sqlalchemy = "^2.0.30" +python = "^3.11" +fastapi = "^0.116.1" +pydantic = "^2.11.7" +uvicorn = "^0.34.3" +redis = "^6.4.0" +sendgrid = "^6.12.4" +restrictedpython = { version = "^8.0", python = ">=3.11,<3.12" } +celery = "^5.5.3" +newrelic = "^10.17.0" +openai = "^1.106.0" +sqlalchemy = "^2.0.43" asyncpg = "^0.30.0" -psycopg2-binary = "^2.9.9" uuid-utils = "^0.10.0" -sqlalchemy-json = "^0.7.0" -tqdm = "^4.66.6" -alembic = "^1.13.2" -numpy = "1.26.3" +alembic = "^1.16.5" +numpy = "^2.3.2" autoevals = "^0.0.83" -supertokens-python = "^0.29.0" -opentelemetry-proto = ">=1.27.0,<2.0.0" +supertokens-python = "^0.29.2" +opentelemetry-proto = "^1.36.0" litellm = "==1.76.0" -jinja2 = "^3.1.6" -click = "^8.1.8" -posthog = "^3.23.0" +posthog = "^3.25.0" stripe = "^11.6.0" -structlog= "^25.2.0" -httpx = ">=0.28.0" -genson = ">=1.3.0" -jsonschema = ">=4.23.0" -orjson = ">=3.10.18" -pandas = ">=2.2.3" -googleapis-common-protos = ">=1.60.0" +structlog= "^25.4.0" +httpx = "^0.28.1" +genson = "^1.3.0" +jsonschema = "^4.23.0" +orjson = "^3.11.3" +pandas = "^2.3.2" +googleapis-common-protos = "^1.70.0" +watchdog = { extras = ["watchmedo"], version = "^3.0.0" } +sqlalchemy-json = "^0.7.0" +python-multipart = "^0.0.20" +gunicorn = "^23.0.0" -# opentelemetry-api = ">=1.27.0,<2.0.0" -# opentelemetry-sdk = ">=1.27.0,<2.0.0" -# opentelemetry-exporter-otlp-proto-http =">=1.27.0,<2.0.0" +# opentelemetry-api = "^1.36.0" +# opentelemetry-sdk = "^1.36.0" +# opentelemetry-exporter-otlp-proto-http = "^1.36.0" # protobuf = "<5.0.0" # audit fixes -h11 = ">=0.16.0" +h11 = "^0.16.0" ecdsa = "^0.19.1" -gunicorn = "^23.0.0" +bson = "^0.5.10" +agenta = "^0.55.0" tiktoken = "0.11.0" [tool.poetry.group.dev.dependencies] -pytest = "^7.3.1" +pytest = "^8.4.2" pytest-asyncio = "^0.21.1" faker = "^23.2.0" pexpect = "^4.9.0" pytest-xdist = "^3.6.1" pytz = "^2024.2" +pytest-mock = "^3.11.1" +click = "^8.1.8" diff --git a/examples/jupyter/capture_user_feedback.ipynb b/examples/jupyter/capture_user_feedback.ipynb index 9693d437ef..a8dcb2e146 100644 --- a/examples/jupyter/capture_user_feedback.ipynb +++ b/examples/jupyter/capture_user_feedback.ipynb @@ -75,7 +75,9 @@ "# Note: Replace these with your actual API keys\n", "\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key_here\"\n", - "os.environ[\"AGENTA_HOST\"] = \"https://cloud.agenta.ai\" # Use your self-hosted URL if applicable\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Use your self-hosted URL if applicable\n", "os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key_here\"" ] }, @@ -120,13 +122,13 @@ "source": [ "def annotate(trace_id, span_id, annotation, evaluator_slug):\n", " \"\"\"Create an annotation for a specific trace/span with evaluation data.\n", - " \n", + "\n", " Args:\n", " trace_id: The ID of the trace to annotate\n", " span_id: The ID of the span to annotate\n", " annotation: Dictionary containing evaluation data (scores, comments, etc.)\n", " evaluator_slug: Identifier for the evaluator (creates one if it doesn't exist)\n", - " \n", + "\n", " Returns:\n", " The annotation response data if successful, None otherwise\n", " \"\"\"\n", @@ -140,8 +142,12 @@ " annotation_data = {\n", " \"annotation\": {\n", " \"data\": {\"outputs\": annotation}, # Your feedback data goes here\n", - " \"references\": {\"evaluator\": {\"slug\": evaluator_slug}}, # Evaluator reference\n", - " \"links\": {\"invocation\": {\"trace_id\": trace_id, \"span_id\": span_id}}, # Link to the trace\n", + " \"references\": {\n", + " \"evaluator\": {\"slug\": evaluator_slug}\n", + " }, # Evaluator reference\n", + " \"links\": {\n", + " \"invocation\": {\"trace_id\": trace_id, \"span_id\": span_id}\n", + " }, # Link to the trace\n", " }\n", " }\n", "\n", @@ -207,10 +213,10 @@ "@ag.instrument() # This decorator creates a root span for tracking the entire function\n", "def generate(topic: str):\n", " \"\"\"Generate a story about the given topic and add feedback as an annotation.\n", - " \n", + "\n", " Args:\n", " topic: The subject of the story to generate\n", - " \n", + "\n", " Returns:\n", " The generated story text\n", " \"\"\"\n", @@ -228,34 +234,35 @@ " },\n", " ],\n", " )\n", - " \n", + "\n", " # In a real application, you would obtain trace_id and span_id dynamically\n", " # using one of these methods:\n", - " \n", + "\n", " # Method 1: Get the current span context\n", " # span_ctx = ag.tracing.get_span_context()\n", " # trace_id = f\"{span_ctx.trace_id:032x}\" # Format as hexadecimal\n", " # span_id = f\"{span_ctx.span_id:016x}\" # Format as hexadecimal\n", - " \n", + "\n", " # Method 2: Use the helper function\n", " link = ag.tracing.build_invocation_link()\n", " trace_id = link.trace_id\n", " span_id = link.span_id\n", - " \n", + "\n", " # Add feedback annotation (simulating user feedback)\n", " annotate(\n", " trace_id=trace_id,\n", " span_id=span_id,\n", " annotation={\n", - " \"score\": 5, # Numerical score (1-5)\n", - " \"comment\": \"This is a comment\" # Text feedback\n", + " \"score\": 5, # Numerical score (1-5)\n", + " \"comment\": \"This is a comment\", # Text feedback\n", " },\n", - " evaluator_slug=\"score-evaluator\" # Creates this evaluator if it doesn't exist\n", - " ) \n", - " \n", + " evaluator_slug=\"score-evaluator\", # Creates this evaluator if it doesn't exist\n", + " )\n", + "\n", " # Return the generated story\n", " return response.choices[0].message.content\n", "\n", + "\n", "# Test our function by generating a story about AI\n", "generate(topic=\"AI\")" ] diff --git a/examples/jupyter/integrations/dspy-integration.ipynb b/examples/jupyter/integrations/dspy-integration.ipynb index ee91151c3e..6909808cf5 100644 --- a/examples/jupyter/integrations/dspy-integration.ipynb +++ b/examples/jupyter/integrations/dspy-integration.ipynb @@ -73,9 +73,9 @@ "\n", "# Load configuration from environment\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key\"\n", - "os.environ[\"AGENTA_HOST\"] = (\n", - " \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", - ")\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", "os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # Required for OpenAI models\n", "\n", "\n", @@ -197,7 +197,7 @@ "source": [ "class Outline(dspy.Signature):\n", " \"\"\"Outline a thorough overview of a topic.\"\"\"\n", - " \n", + "\n", " topic: str = dspy.InputField()\n", " title: str = dspy.OutputField()\n", " sections: list[str] = dspy.OutputField()\n", @@ -208,7 +208,7 @@ "\n", "class DraftSection(dspy.Signature):\n", " \"\"\"Draft a top-level section of an article.\"\"\"\n", - " \n", + "\n", " topic: str = dspy.InputField()\n", " section_heading: str = dspy.InputField()\n", " section_subheadings: list[str] = dspy.InputField()\n", @@ -219,7 +219,7 @@ " def __init__(self):\n", " self.build_outline = dspy.ChainOfThought(Outline)\n", " self.draft_section = dspy.ChainOfThought(DraftSection)\n", - " \n", + "\n", " def forward(self, topic):\n", " outline = self.build_outline(topic=topic)\n", " sections = []\n", diff --git a/examples/jupyter/integrations/langgraph-integration.ipynb b/examples/jupyter/integrations/langgraph-integration.ipynb index 4737aaf71e..30797cc017 100644 --- a/examples/jupyter/integrations/langgraph-integration.ipynb +++ b/examples/jupyter/integrations/langgraph-integration.ipynb @@ -81,9 +81,9 @@ "\n", "# Load configuration from environment\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key\"\n", - "os.environ[\"AGENTA_HOST\"] = (\n", - " \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", - ")\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", "os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # Required for OpenAI Agents SDK\n", "\n", "\n", @@ -256,12 +256,12 @@ " builder.add_node(\"segment\", RunnableLambda(segment_by_speaker))\n", " builder.add_node(\"summarize\", RunnableLambda(summarize_per_speaker))\n", " builder.add_node(\"extract_actions\", RunnableLambda(extract_actions))\n", - " \n", + "\n", " builder.set_entry_point(\"segment\")\n", " builder.add_edge(\"segment\", \"summarize\")\n", " builder.add_edge(\"summarize\", \"extract_actions\")\n", " builder.add_edge(\"extract_actions\", END)\n", - " \n", + "\n", " graph = builder.compile()\n", " result = graph.invoke({\"input\": transcript})\n", " return result" @@ -428,14 +428,18 @@ "def determine_priority(state):\n", " sentiment = state[\"sentiment\"]\n", " categories = state[\"categories\"]\n", - " result = llm.invoke(f\"Determine priority (high/medium/low) based on sentiment: {sentiment} and categories: {categories}\")\n", + " result = llm.invoke(\n", + " f\"Determine priority (high/medium/low) based on sentiment: {sentiment} and categories: {categories}\"\n", + " )\n", " return {**state, \"priority\": result.content}\n", "\n", "\n", "def draft_response(state):\n", " feedback = state[\"input\"]\n", " sentiment = state[\"sentiment\"]\n", - " result = llm.invoke(f\"Draft a professional response to this {sentiment} feedback: {feedback}\")\n", + " result = llm.invoke(\n", + " f\"Draft a professional response to this {sentiment} feedback: {feedback}\"\n", + " )\n", " return {**state, \"response_draft\": result.content}\n", "\n", "\n", @@ -446,13 +450,13 @@ " builder.add_node(\"categorize\", RunnableLambda(categorize_feedback))\n", " builder.add_node(\"priority\", RunnableLambda(determine_priority))\n", " builder.add_node(\"response\", RunnableLambda(draft_response))\n", - " \n", + "\n", " builder.set_entry_point(\"sentiment\")\n", " builder.add_edge(\"sentiment\", \"categorize\")\n", " builder.add_edge(\"categorize\", \"priority\")\n", " builder.add_edge(\"priority\", \"response\")\n", " builder.add_edge(\"response\", END)\n", - " \n", + "\n", " graph = builder.compile()\n", " return graph.invoke({\"input\": feedback_text})" ] @@ -506,47 +510,51 @@ "def research_analyzer(paper_text: str):\n", " def extract_abstract(state):\n", " paper = state[\"input\"]\n", - " result = llm.invoke(f\"Extract and summarize the abstract from this research paper: {paper}\")\n", + " result = llm.invoke(\n", + " f\"Extract and summarize the abstract from this research paper: {paper}\"\n", + " )\n", " return {**state, \"abstract_summary\": result.content}\n", - " \n", + "\n", " def identify_findings(state):\n", " paper = state[\"input\"]\n", " result = llm.invoke(f\"List the key findings from this research paper: {paper}\")\n", " return {**state, \"key_findings\": result.content.split(\"\\n\")}\n", - " \n", + "\n", " def analyze_methodology(state):\n", " paper = state[\"input\"]\n", " result = llm.invoke(f\"Describe the methodology used in this research: {paper}\")\n", " return {**state, \"methodology\": result.content}\n", - " \n", + "\n", " def assess_limitations(state):\n", " paper = state[\"input\"]\n", " result = llm.invoke(f\"Identify limitations mentioned in this research: {paper}\")\n", " return {**state, \"limitations\": result.content}\n", - " \n", + "\n", " def score_relevance(state):\n", " abstract = state[\"abstract_summary\"]\n", - " result = llm.invoke(f\"Rate the relevance of this research on a scale of 0-10: {abstract}\")\n", + " result = llm.invoke(\n", + " f\"Rate the relevance of this research on a scale of 0-10: {abstract}\"\n", + " )\n", " try:\n", " score = float(result.content.strip())\n", " except:\n", " score = 5.0\n", " return {**state, \"relevance_score\": score}\n", - " \n", + "\n", " builder = StateGraph(ResearchState)\n", " builder.add_node(\"abstract\", RunnableLambda(extract_abstract))\n", " builder.add_node(\"findings\", RunnableLambda(identify_findings))\n", " builder.add_node(\"methodology\", RunnableLambda(analyze_methodology))\n", " builder.add_node(\"limitations\", RunnableLambda(assess_limitations))\n", " builder.add_node(\"relevance\", RunnableLambda(score_relevance))\n", - " \n", + "\n", " builder.set_entry_point(\"abstract\")\n", " builder.add_edge(\"abstract\", \"findings\")\n", " builder.add_edge(\"findings\", \"methodology\")\n", " builder.add_edge(\"methodology\", \"limitations\")\n", " builder.add_edge(\"limitations\", \"relevance\")\n", " builder.add_edge(\"relevance\", END)\n", - " \n", + "\n", " graph = builder.compile()\n", " return graph.invoke({\"input\": paper_text})" ] @@ -582,12 +590,14 @@ " except:\n", " score = 0.0\n", " return {**state, \"toxicity_score\": score}\n", - " \n", + "\n", " def categorize_content(state):\n", " content = state[\"input\"]\n", - " result = llm.invoke(f\"Categorize this content (spam, harassment, hate speech, etc.): {content}\")\n", + " result = llm.invoke(\n", + " f\"Categorize this content (spam, harassment, hate speech, etc.): {content}\"\n", + " )\n", " return {**state, \"content_categories\": result.content.split(\", \")}\n", - " \n", + "\n", " def determine_action(state):\n", " toxicity = state[\"toxicity_score\"]\n", " categories = state[\"content_categories\"]\n", @@ -597,19 +607,21 @@ " action = \"flag_for_review\"\n", " else:\n", " action = \"approve\"\n", - " explanation = f\"Decision based on toxicity score: {toxicity} and categories: {categories}\"\n", + " explanation = (\n", + " f\"Decision based on toxicity score: {toxicity} and categories: {categories}\"\n", + " )\n", " return {**state, \"action_required\": action, \"explanation\": explanation}\n", - " \n", + "\n", " builder = StateGraph(ModerationState)\n", " builder.add_node(\"toxicity\", RunnableLambda(assess_toxicity))\n", " builder.add_node(\"categorize\", RunnableLambda(categorize_content))\n", " builder.add_node(\"action\", RunnableLambda(determine_action))\n", - " \n", + "\n", " builder.set_entry_point(\"toxicity\")\n", " builder.add_edge(\"toxicity\", \"categorize\")\n", " builder.add_edge(\"categorize\", \"action\")\n", " builder.add_edge(\"action\", END)\n", - " \n", + "\n", " graph = builder.compile()\n", " return graph.invoke({\"input\": user_content})" ] diff --git a/examples/jupyter/integrations/observability-openinference-llamaindex.ipynb b/examples/jupyter/integrations/observability-openinference-llamaindex.ipynb index 4d033df3f0..9b4dfe2aa1 100644 --- a/examples/jupyter/integrations/observability-openinference-llamaindex.ipynb +++ b/examples/jupyter/integrations/observability-openinference-llamaindex.ipynb @@ -71,9 +71,9 @@ "\n", "# Load configuration from environment\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key\"\n", - "os.environ[\"AGENTA_HOST\"] = (\n", - " \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", - ")\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", "\n", "\n", "# Start Agenta SDK\n", @@ -132,9 +132,9 @@ "\n", "# Configuration setup\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key\"\n", - "os.environ[\"AGENTA_HOST\"] = (\n", - " \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", - ")\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", "\n", "# Initialize observability\n", "ag.init()\n", @@ -151,16 +151,16 @@ " \"\"\"\n", " # Load documents from local directory\n", " docs = SimpleDirectoryReader(\"data\").load_data()\n", - " \n", + "\n", " # Build vector search index\n", " search_index = VectorStoreIndex.from_documents(docs)\n", - " \n", + "\n", " # Initialize query processor\n", " query_processor = search_index.as_query_engine()\n", - " \n", + "\n", " # Process user query\n", " answer = query_processor.query(user_query)\n", - " \n", + "\n", " return answer\n", "\n", "\n", diff --git a/examples/jupyter/integrations/openai-agents-integration.ipynb b/examples/jupyter/integrations/openai-agents-integration.ipynb index 605e482325..976b0d3160 100644 --- a/examples/jupyter/integrations/openai-agents-integration.ipynb +++ b/examples/jupyter/integrations/openai-agents-integration.ipynb @@ -74,9 +74,9 @@ "\n", "# Load configuration from environment\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key\"\n", - "os.environ[\"AGENTA_HOST\"] = (\n", - " \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", - ")\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", "os.environ[\"OPENAI_API_KEY\"] = \"your_openai_api_key\" # Required for OpenAI Agents SDK\n", "\n", "\n", @@ -203,6 +203,7 @@ " response = await duolingo(\"What is 'What's your name' in French?\")\n", " print(\"Response:\", response)\n", "\n", + "\n", "# Run the example\n", "await main()" ] @@ -330,7 +331,7 @@ ")\n", "\n", "billing_agent = Agent(\n", - " name=\"Billing Agent\", \n", + " name=\"Billing Agent\",\n", " instructions=\"You handle billing-related questions and payment issues\",\n", ")\n", "\n", @@ -339,18 +340,25 @@ " instructions=\"You provide technical support and troubleshooting assistance\",\n", ")\n", "\n", + "\n", "@ag.instrument(spankind=\"workflow\")\n", "async def customer_support_system(customer_query: str):\n", " orchestrator = Agent(\n", " name=\"Support Orchestrator\",\n", " instructions=\"Route customer queries to the appropriate specialist agent\",\n", " tools=[\n", - " support_agent.as_tool(\"general_support\", \"Handle general customer inquiries\"),\n", - " billing_agent.as_tool(\"billing_support\", \"Handle billing and payment issues\"),\n", - " technical_agent.as_tool(\"technical_support\", \"Provide technical assistance\"),\n", - " ]\n", + " support_agent.as_tool(\n", + " \"general_support\", \"Handle general customer inquiries\"\n", + " ),\n", + " billing_agent.as_tool(\n", + " \"billing_support\", \"Handle billing and payment issues\"\n", + " ),\n", + " technical_agent.as_tool(\n", + " \"technical_support\", \"Provide technical assistance\"\n", + " ),\n", + " ],\n", " )\n", - " \n", + "\n", " result = await Runner.run(orchestrator, input=customer_query)\n", " return result.final_output" ] @@ -374,21 +382,21 @@ " name=\"Data Collector\",\n", " instructions=\"Gather relevant information and data on the given topic\",\n", " )\n", - " \n", + "\n", " analyst = Agent(\n", - " name=\"Research Analyst\", \n", + " name=\"Research Analyst\",\n", " instructions=\"Analyze collected data and provide insights\",\n", " )\n", - " \n", + "\n", " reporter = Agent(\n", " name=\"Report Writer\",\n", " instructions=\"Create comprehensive reports based on analysis\",\n", " tools=[\n", " data_collector.as_tool(\"collect_data\", \"Gather relevant research data\"),\n", " analyst.as_tool(\"analyze_data\", \"Perform data analysis and insights\"),\n", - " ]\n", + " ],\n", " )\n", - " \n", + "\n", " result = await Runner.run(reporter, input=research_topic)\n", " return result.final_output" ] @@ -412,17 +420,17 @@ " name=\"Content Writer\",\n", " instructions=\"Create engaging content based on the brief\",\n", " )\n", - " \n", + "\n", " editor = Agent(\n", " name=\"Content Editor\",\n", " instructions=\"Review and improve content quality and style\",\n", " )\n", - " \n", + "\n", " seo_specialist = Agent(\n", " name=\"SEO Specialist\",\n", " instructions=\"Optimize content for search engines\",\n", " )\n", - " \n", + "\n", " content_manager = Agent(\n", " name=\"Content Manager\",\n", " instructions=\"Coordinate the content creation process and ensure quality\",\n", @@ -430,9 +438,9 @@ " writer.as_tool(\"write_content\", \"Generate initial content draft\"),\n", " editor.as_tool(\"edit_content\", \"Review and edit content\"),\n", " seo_specialist.as_tool(\"optimize_seo\", \"Optimize content for SEO\"),\n", - " ]\n", + " ],\n", " )\n", - " \n", + "\n", " result = await Runner.run(content_manager, input=content_brief)\n", " return result.final_output" ] @@ -451,7 +459,9 @@ "outputs": [], "source": [ "# Test customer support system\n", - "support_response = await customer_support_system(\"I have a billing question about my subscription\")\n", + "support_response = await customer_support_system(\n", + " \"I have a billing question about my subscription\"\n", + ")\n", "print(\"Support Response:\", support_response)" ] }, @@ -473,7 +483,9 @@ "outputs": [], "source": [ "# Test content creation system\n", - "content_response = await content_creation_system(\"Write a blog post about sustainable energy solutions\")\n", + "content_response = await content_creation_system(\n", + " \"Write a blog post about sustainable energy solutions\"\n", + ")\n", "print(\"Content Response:\", content_response)" ] }, diff --git a/examples/jupyter/integrations/pydanticai-integration.ipynb b/examples/jupyter/integrations/pydanticai-integration.ipynb index 6da49ce54c..630bcddcee 100644 --- a/examples/jupyter/integrations/pydanticai-integration.ipynb +++ b/examples/jupyter/integrations/pydanticai-integration.ipynb @@ -76,16 +76,16 @@ "\n", "# Load configuration from environment\n", "os.environ[\"AGENTA_API_KEY\"] = \"your_agenta_api_key\"\n", - "os.environ[\"AGENTA_HOST\"] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", + "os.environ[\n", + " \"AGENTA_HOST\"\n", + "] = \"https://cloud.agenta.ai\" # Optional, defaults to the Agenta cloud API\n", "\n", "# Initialize Agenta SDK\n", "ag.init()\n", "\n", "# Configure Logfire for local development\n", "logfire.configure(\n", - " service_name=\"my_logfire_service\", \n", - " send_to_logfire=False, \n", - " scrubbing=False\n", + " service_name=\"my_logfire_service\", send_to_logfire=False, scrubbing=False\n", ")" ] }, @@ -139,13 +139,13 @@ "# Mock database for demonstration\n", "class DatabaseConn:\n", " \"\"\"Fake database for example purposes.\"\"\"\n", - " \n", + "\n", " @classmethod\n", " async def customer_name(cls, *, id: int) -> str | None:\n", " if id == 123:\n", " return \"John\"\n", " return None\n", - " \n", + "\n", " @classmethod\n", " async def customer_balance(cls, *, id: int, include_pending: bool) -> float:\n", " if id == 123 and include_pending:\n", @@ -464,12 +464,12 @@ "def route_customer_query(customer_id: int, query: str, query_type: str = \"general\"):\n", " \"\"\"Route customer queries to appropriate specialized agents.\"\"\"\n", " deps = CustomerServiceDeps(customer_id=customer_id, db=DatabaseConn())\n", - " \n", + "\n", " if query_type == \"technical\":\n", " result = technical_agent.run_sync(query, deps=deps)\n", " else:\n", " result = general_agent.run_sync(query, deps=deps)\n", - " \n", + "\n", " return result" ] }, @@ -561,11 +561,13 @@ "\n", "\n", "@ag.instrument(spankind=\"agent\")\n", - "def analyze_transaction(transaction_id: str, customer_id: int, amount: float, location: str):\n", + "def analyze_transaction(\n", + " transaction_id: str, customer_id: int, amount: float, location: str\n", + "):\n", " \"\"\"Analyze a transaction for potential fraud.\"\"\"\n", " deps = FraudDeps(transaction_id=transaction_id, customer_id=customer_id)\n", " query = f\"Analyze transaction {transaction_id}: ${amount} in {location}\"\n", - " \n", + "\n", " result = fraud_agent.run_sync(query, deps=deps)\n", " return result" ] @@ -664,7 +666,9 @@ "outputs": [], "source": [ "# Test investment advice\n", - "investment_result = get_investment_advice(123, 50000.0, \"I want to diversify my portfolio for retirement\")\n", + "investment_result = get_investment_advice(\n", + " 123, 50000.0, \"I want to diversify my portfolio for retirement\"\n", + ")\n", "print(\"Investment Advisory Result:\")\n", "print(f\"Recommendation: {investment_result.output.recommendation}\")\n", "print(f\"Risk Level: {investment_result.output.risk_level}\")\n", diff --git a/examples/jupyter/observability_langchain.ipynb b/examples/jupyter/observability_langchain.ipynb index f2b6321392..c41c77c996 100644 --- a/examples/jupyter/observability_langchain.ipynb +++ b/examples/jupyter/observability_langchain.ipynb @@ -74,10 +74,10 @@ "import os\n", "import getpass\n", "\n", - "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(prompt=\"OpenAI API key:\") \n", + "os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(prompt=\"OpenAI API key:\")\n", "# Create an Agenta API key under https://cloud.agenta.ai/settings?tab=apiKeys\n", - "os.environ[\"AGENTA_API_KEY\"] = getpass.getpass(prompt=\"Agenta API key:\") \n", - "os.environ[\"AGENTA_HOST\"] = \"https://cloud.agenta.ai\" # Change for self-hosted\n" + "os.environ[\"AGENTA_API_KEY\"] = getpass.getpass(prompt=\"Agenta API key:\")\n", + "os.environ[\"AGENTA_HOST\"] = \"https://cloud.agenta.ai\" # Change for self-hosted" ] }, { @@ -115,7 +115,7 @@ "\n", "ag.init()\n", "\n", - "LangchainInstrumentor().instrument()\n" + "LangchainInstrumentor().instrument()" ] }, { @@ -174,17 +174,19 @@ "Answer:\n", "\"\"\"\n", "\n", - "prompt_template = ChatPromptTemplate([\n", - " (\"human\", prompt),\n", - "])\n", + "prompt_template = ChatPromptTemplate(\n", + " [\n", + " (\"human\", prompt),\n", + " ]\n", + ")\n", "\n", "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n", "\n", "loader = WebBaseLoader(\n", - " web_paths=(\"https://docs.agenta.ai/prompt-engineering/prompt-management/prompt-management-sdk\",),\n", - " bs_kwargs=dict(\n", - " parse_only=bs4.SoupStrainer('article') # Only parse the core\n", + " web_paths=(\n", + " \"https://docs.agenta.ai/prompt-engineering/prompt-management/prompt-management-sdk\",\n", " ),\n", + " bs_kwargs=dict(parse_only=bs4.SoupStrainer(\"article\")), # Only parse the core\n", ")\n", "docs = loader.load()\n", "\n", @@ -203,8 +205,7 @@ " | StrOutputParser()\n", ")\n", "\n", - "rag_chain.invoke(\"How can I save a new version of a prompt in Agenta?\")\n", - "\n" + "rag_chain.invoke(\"How can I save a new version of a prompt in Agenta?\")" ] } ], diff --git a/hosting/docker-compose/oss/docker-compose.dev.yml b/hosting/docker-compose/oss/docker-compose.dev.yml index 8489bc0f01..db992a8db4 100644 --- a/hosting/docker-compose/oss/docker-compose.dev.yml +++ b/hosting/docker-compose/oss/docker-compose.dev.yml @@ -37,6 +37,7 @@ services: volumes: - ../../../api/entrypoint.py:/app/entrypoint.py - ../../../api/oss:/app/oss + # - ../../../sdk:/sdk env_file: - ${ENV_FILE:-./.env.oss.dev} @@ -83,6 +84,7 @@ services: volumes: - ../../../api/entrypoint.py:/app/entrypoint.py - ../../../api/oss:/app/oss + # - ../../../sdk:/sdk env_file: - ${ENV_FILE:-./.env.oss.dev} @@ -109,6 +111,7 @@ services: volumes: - ../../../api/entrypoint.py:/app/entrypoint.py - ../../../api/oss:/app/oss + # - ../../../sdk:/sdk env_file: - ${ENV_FILE:-./.env.oss.dev} @@ -119,7 +122,7 @@ services: networks: - agenta-network - command: sh -c "python -c 'from oss.databases.postgres.migrations.utils import split_core_and_tracing as split; split(); from oss.databases.postgres.migrations.core.utils import run_alembic_migration as run; run(); from oss.databases.postgres.migrations.tracing.utils import run_alembic_migration as run; run(); from oss.databases.postgres.migrations.utils import copy_nodes_from_core_to_tracing as copy; copy();'" + command: sh -c "python -m oss.databases.postgres.migrations.runner" completion: build: diff --git a/hosting/docker-compose/oss/docker-compose.gh.ssl.yml b/hosting/docker-compose/oss/docker-compose.gh.ssl.yml index 65101d4c0b..ab0590b990 100644 --- a/hosting/docker-compose/oss/docker-compose.gh.ssl.yml +++ b/hosting/docker-compose/oss/docker-compose.gh.ssl.yml @@ -117,7 +117,8 @@ services: env_file: - ${ENV_FILE:-./.env.oss.gh} - command: sh -c "python -c 'from oss.databases.postgres.migrations.utils import split_core_and_tracing as split; split(); from oss.databases.postgres.migrations.core.utils import run_alembic_migration as run; run(); from oss.databases.postgres.migrations.tracing.utils import run_alembic_migration as run; run(); from oss.databases.postgres.migrations.utils import copy_nodes_from_core_to_tracing as copy; copy();'" + command: sh -c "python -m oss.databases.postgres.migrations.runner" + depends_on: postgres: condition: service_healthy diff --git a/hosting/docker-compose/oss/docker-compose.gh.yml b/hosting/docker-compose/oss/docker-compose.gh.yml index 867296bb2f..cc2e6612a3 100644 --- a/hosting/docker-compose/oss/docker-compose.gh.yml +++ b/hosting/docker-compose/oss/docker-compose.gh.yml @@ -93,7 +93,7 @@ services: env_file: - ${ENV_FILE:-./.env.oss.gh} - command: sh -c "python -c 'from oss.databases.postgres.migrations.utils import split_core_and_tracing as split; split(); from oss.databases.postgres.migrations.core.utils import run_alembic_migration as run; run(); from oss.databases.postgres.migrations.tracing.utils import run_alembic_migration as run; run(); from oss.databases.postgres.migrations.utils import copy_nodes_from_core_to_tracing as copy; copy();'" + command: sh -c "python -m oss.databases.postgres.migrations.runner" depends_on: postgres: diff --git a/sdk/agenta/sdk/tracing/tracing.py b/sdk/agenta/sdk/tracing/tracing.py index ff24383e6c..762037a4e8 100644 --- a/sdk/agenta/sdk/tracing/tracing.py +++ b/sdk/agenta/sdk/tracing/tracing.py @@ -52,8 +52,6 @@ class Link(BaseModel): class Tracing(metaclass=Singleton): - VERSION = "0.1.0" - Status = Status StatusCode = StatusCode diff --git a/sdk/poetry.lock b/sdk/poetry.lock index 050e0b7288..d467156f9d 100644 --- a/sdk/poetry.lock +++ b/sdk/poetry.lock @@ -14,98 +14,132 @@ files = [ [[package]] name = "aiohttp" -version = "3.12.15" +version = "3.13.0" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, - {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, - {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, - {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, - {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, - {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, - {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, - {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, - {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, - {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, - {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, - {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, - {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, - {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, + {file = "aiohttp-3.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ca69ec38adf5cadcc21d0b25e2144f6a25b7db7bea7e730bac25075bc305eff0"}, + {file = "aiohttp-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:240f99f88a9a6beb53ebadac79a2e3417247aa756202ed234b1dbae13d248092"}, + {file = "aiohttp-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a4676b978a9711531e7cea499d4cdc0794c617a1c0579310ab46c9fdf5877702"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48fcdd5bc771cbbab8ccc9588b8b6447f6a30f9fe00898b1a5107098e00d6793"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eeea0cdd2f687e210c8f605f322d7b0300ba55145014a5dbe98bd4be6fff1f6c"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b3f01d5aeb632adaaf39c5e93f040a550464a768d54c514050c635adcbb9d0"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4dc0b83e25267f42ef065ea57653de4365b56d7bc4e4cfc94fabe56998f8ee6"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:72714919ed9b90f030f761c20670e529c4af96c31bd000917dd0c9afd1afb731"}, + {file = "aiohttp-3.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:564be41e85318403fdb176e9e5b3e852d528392f42f2c1d1efcbeeed481126d7"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:84912962071087286333f70569362e10793f73f45c48854e6859df11001eb2d3"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90b570f1a146181c3d6ae8f755de66227ded49d30d050479b5ae07710f7894c5"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d71ca30257ce756e37a6078b1dff2d9475fee13609ad831eac9a6531bea903b"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:cd45eb70eca63f41bb156b7dffbe1a7760153b69892d923bdb79a74099e2ed90"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5ae3a19949a27982c7425a7a5a963c1268fdbabf0be15ab59448cbcf0f992519"}, + {file = "aiohttp-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ea6df292013c9f050cbf3f93eee9953d6e5acd9e64a0bf4ca16404bfd7aa9bcc"}, + {file = "aiohttp-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3b64f22fbb6dcd5663de5ef2d847a5638646ef99112503e6f7704bdecb0d1c4d"}, + {file = "aiohttp-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8d877aa60d80715b2afc565f0f1aea66565824c229a2d065b31670e09fed6d7"}, + {file = "aiohttp-3.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99eb94e97a42367fef5fc11e28cb2362809d3e70837f6e60557816c7106e2e20"}, + {file = "aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca"}, + {file = "aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aab715b1a0c37f7f11f9f1f579c6fbaa51ef569e47e3c0a4644fba46077a9409"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7972c82bed87d7bd8e374b60a6b6e816d75ba4f7c2627c2d14eed216e62738e1"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca8313cb852af788c78d5afdea24c40172cbfff8b35e58b407467732fde20390"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c333a2385d2a6298265f4b3e960590f787311b87f6b5e6e21bb8375914ef504"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3"}, + {file = "aiohttp-3.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ddedba3d0043349edc79df3dc2da49c72b06d59a45a42c1c8d987e6b8d175b8"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23ca762140159417a6bbc959ca1927f6949711851e56f2181ddfe8d63512b5ad"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfe824d6707a5dc3c5676685f624bc0c63c40d79dc0239a7fd6c034b98c25ebe"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3c11fa5dd2ef773a8a5a6daa40243d83b450915992eab021789498dc87acc114"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00fdfe370cffede3163ba9d3f190b32c0cfc8c774f6f67395683d7b0e48cdb8a"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6475e42ef92717a678bfbf50885a682bb360a6f9c8819fb1a388d98198fdcb80"}, + {file = "aiohttp-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:77da5305a410910218b99f2a963092f4277d8a9c1f429c1ff1b026d1826bd0b6"}, + {file = "aiohttp-3.13.0-cp311-cp311-win32.whl", hash = "sha256:2f9d9ea547618d907f2ee6670c9a951f059c5994e4b6de8dcf7d9747b420c820"}, + {file = "aiohttp-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469"}, + {file = "aiohttp-3.13.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c272a9a18a5ecc48a7101882230046b83023bb2a662050ecb9bfcb28d9ab53a"}, + {file = "aiohttp-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:97891a23d7fd4e1afe9c2f4473e04595e4acb18e4733b910b6577b74e7e21985"}, + {file = "aiohttp-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:475bd56492ce5f4cffe32b5533c6533ee0c406d1d0e6924879f83adcf51da0ae"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c32ada0abb4bc94c30be2b681c42f058ab104d048da6f0148280a51ce98add8c"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4af1f8877ca46ecdd0bc0d4a6b66d4b2bddc84a79e2e8366bc0d5308e76bceb8"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e04ab827ec4f775817736b20cdc8350f40327f9b598dec4e18c9ffdcbea88a93"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a6d9487b9471ec36b0faedf52228cd732e89be0a2bbd649af890b5e2ce422353"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8"}, + {file = "aiohttp-3.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469167d5372f5bb3aedff4fc53035d593884fff2617a75317740e885acd48b04"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a9f3546b503975a69b547c9fd1582cad10ede1ce6f3e313a2f547c73a3d7814f"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6b4174fcec98601f0cfdf308ee29a6ae53c55f14359e848dab4e94009112ee7d"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a533873a7a4ec2270fb362ee5a0d3b98752e4e1dc9042b257cd54545a96bd8ed"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ce887c5e54411d607ee0959cac15bb31d506d86a9bcaddf0b7e9d63325a7a802"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d871f6a30d43e32fc9252dc7b9febe1a042b3ff3908aa83868d7cf7c9579a59b"}, + {file = "aiohttp-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:222c828243b4789d79a706a876910f656fad4381661691220ba57b2ab4547865"}, + {file = "aiohttp-3.13.0-cp312-cp312-win32.whl", hash = "sha256:682d2e434ff2f1108314ff7f056ce44e457f12dbed0249b24e106e385cf154b9"}, + {file = "aiohttp-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a2be20eb23888df130214b91c262a90e2de1553d6fb7de9e9010cec994c0ff2"}, + {file = "aiohttp-3.13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00243e51f16f6ec0fb021659d4af92f675f3cf9f9b39efd142aa3ad641d8d1e6"}, + {file = "aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54"}, + {file = "aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4aa995b9156ae499393d949a456a7ab0b994a8241a96db73a3b73c7a090eff6a"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55ca0e95a3905f62f00900255ed807c580775174252999286f283e646d675a49"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:49ce7525853a981fc35d380aa2353536a01a9ec1b30979ea4e35966316cace7e"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2117be9883501eaf95503bd313eb4c7a23d567edd44014ba15835a1e9ec6d852"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b"}, + {file = "aiohttp-3.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:703ad3f742fc81e543638a7bebddd35acadaa0004a5e00535e795f4b6f2c25ca"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bf635c3476f4119b940cc8d94ad454cbe0c377e61b4527f0192aabeac1e9370"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cfe6285ef99e7ee51cef20609be2bc1dd0e8446462b71c9db8bb296ba632810a"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8af6391c5f2e69749d7f037b614b8c5c42093c251f336bdbfa4b03c57d6c4"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:12f5d820fadc5848d4559ea838aef733cf37ed2a1103bba148ac2f5547c14c29"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f1338b61ea66f4757a0544ed8a02ccbf60e38d9cfb3225888888dd4475ebb96"}, + {file = "aiohttp-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:582770f82513419512da096e8df21ca44f86a2e56e25dc93c5ab4df0fe065bf0"}, + {file = "aiohttp-3.13.0-cp313-cp313-win32.whl", hash = "sha256:3194b8cab8dbc882f37c13ef1262e0a3d62064fa97533d3aa124771f7bf1ecee"}, + {file = "aiohttp-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21"}, + {file = "aiohttp-3.13.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c417f8c2e1137775569297c584a8a7144e5d1237789eae56af4faf1894a0b861"}, + {file = "aiohttp-3.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f84b53326abf8e56ebc28a35cebf4a0f396a13a76300f500ab11fe0573bf0b52"}, + {file = "aiohttp-3.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:990a53b9d6a30b2878789e490758e568b12b4a7fb2527d0c89deb9650b0e5813"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c811612711e01b901e18964b3e5dec0d35525150f5f3f85d0aee2935f059910a"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee433e594d7948e760b5c2a78cc06ac219df33b0848793cf9513d486a9f90a52"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:19bb08e56f57c215e9572cd65cb6f8097804412c54081d933997ddde3e5ac579"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f27b7488144eb5dd9151cf839b195edd1569629d90ace4c5b6b18e4e75d1e63a"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d812838c109757a11354a161c95708ae4199c4fd4d82b90959b20914c1d097f6"}, + {file = "aiohttp-3.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c20db99da682f9180fa5195c90b80b159632fb611e8dbccdd99ba0be0970620"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cf8b0870047900eb1f17f453b4b3953b8ffbf203ef56c2f346780ff930a4d430"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b8a5557d5af3f4e3add52a58c4cf2b8e6e59fc56b261768866f5337872d596d"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:052bcdd80c1c54b8a18a9ea0cd5e36f473dc8e38d51b804cea34841f677a9971"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:76484ba17b2832776581b7ab466d094e48eba74cb65a60aea20154dae485e8bd"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:62d8a0adcdaf62ee56bfb37737153251ac8e4b27845b3ca065862fb01d99e247"}, + {file = "aiohttp-3.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5004d727499ecb95f7c9147dd0bfc5b5670f71d355f0bd26d7af2d3af8e07d2f"}, + {file = "aiohttp-3.13.0-cp314-cp314-win32.whl", hash = "sha256:a1c20c26af48aea984f63f96e5d7af7567c32cb527e33b60a0ef0a6313cf8b03"}, + {file = "aiohttp-3.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:56f7d230ec66e799fbfd8350e9544f8a45a4353f1cf40c1fea74c1780f555b8f"}, + {file = "aiohttp-3.13.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:2fd35177dc483ae702f07b86c782f4f4b100a8ce4e7c5778cea016979023d9fd"}, + {file = "aiohttp-3.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4df1984c8804ed336089e88ac81a9417b1fd0db7c6f867c50a9264488797e778"}, + {file = "aiohttp-3.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e68c0076052dd911a81d3acc4ef2911cc4ef65bf7cadbfbc8ae762da24da858f"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc95c49853cd29613e4fe4ff96d73068ff89b89d61e53988442e127e8da8e7ba"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bdc89413117b40cc39baae08fd09cbdeb839d421c4e7dce6a34f6b54b3ac1"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e77a729df23be2116acc4e9de2767d8e92445fbca68886dd991dc912f473755"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e88ab34826d6eeb6c67e6e92400b9ec653faf5092a35f07465f44c9f1c429f82"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:019dbef24fe28ce2301419dd63a2b97250d9760ca63ee2976c2da2e3f182f82e"}, + {file = "aiohttp-3.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2c4aeaedd20771b7b4bcdf0ae791904445df6d856c02fc51d809d12d17cffdc7"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b3a8e6a2058a0240cfde542b641d0e78b594311bc1a710cbcb2e1841417d5cb3"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8e38d55ca36c15f36d814ea414ecb2401d860de177c49f84a327a25b3ee752b"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a921edbe971aade1bf45bcbb3494e30ba6863a5c78f28be992c42de980fd9108"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:474cade59a447cb4019c0dce9f0434bf835fb558ea932f62c686fe07fe6db6a1"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:99a303ad960747c33b65b1cb65d01a62ac73fa39b72f08a2e1efa832529b01ed"}, + {file = "aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bb34001fc1f05f6b323e02c278090c07a47645caae3aa77ed7ed8a3ce6abcce9"}, + {file = "aiohttp-3.13.0-cp314-cp314t-win32.whl", hash = "sha256:dea698b64235d053def7d2f08af9302a69fcd760d1c7bd9988fd5d3b6157e657"}, + {file = "aiohttp-3.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1f164699a060c0b3616459d13c1464a981fddf36f892f0a5027cbd45121fb14b"}, + {file = "aiohttp-3.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fcc425fb6fd2a00c6d91c85d084c6b75a61bc8bc12159d08e17c5711df6c5ba4"}, + {file = "aiohttp-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c2c4c9ce834801651f81d6760d0a51035b8b239f58f298de25162fcf6f8bb64"}, + {file = "aiohttp-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f91e8f9053a07177868e813656ec57599cd2a63238844393cd01bd69c2e40147"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df46d9a3d78ec19b495b1107bf26e4fcf97c900279901f4f4819ac5bb2a02a4c"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b1eb9871cbe43b6ca6fac3544682971539d8a1d229e6babe43446279679609d"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:62a3cddf8d9a2eae1f79585fa81d32e13d0c509bb9e7ad47d33c83b45a944df7"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0f735e680c323ee7e9ef8e2ea26425c7dbc2ede0086fa83ce9d7ccab8a089f26"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a51839f778b0e283b43cd82bb17f1835ee2cc1bf1101765e90ae886e53e751c"}, + {file = "aiohttp-3.13.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac90cfab65bc281d6752f22db5fa90419e33220af4b4fa53b51f5948f414c0e7"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:62fd54f3e6f17976962ba67f911d62723c760a69d54f5d7b74c3ceb1a4e9ef8d"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cf2b60b65df05b6b2fa0d887f2189991a0dbf44a0dd18359001dc8fcdb7f1163"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1ccedfe280e804d9a9d7fe8b8c4309d28e364b77f40309c86596baa754af50b1"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ea01ffbe23df53ece0c8732d1585b3d6079bb8c9ee14f3745daf000051415a31"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:19ba8625fa69523627b67f7e9901b587a4952470f68814d79cdc5bc460e9b885"}, + {file = "aiohttp-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b14bfae90598d331b5061fd15a7c290ea0c15b34aeb1cf620464bb5ec02a602"}, + {file = "aiohttp-3.13.0-cp39-cp39-win32.whl", hash = "sha256:cf7a4b976da219e726d0043fc94ae8169c0dba1d3a059b3c1e2c964bafc5a77d"}, + {file = "aiohttp-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b9697d15231aeaed4786f090c9c8bc3ab5f0e0a6da1e76c135a310def271020"}, + {file = "aiohttp-3.13.0.tar.gz", hash = "sha256:378dbc57dd8cf341ce243f13fa1fa5394d68e2e02c15cd5f28eae35a70ec7f67"}, ] [package.dependencies] @@ -119,7 +153,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi", "zstandard"] [[package]] name = "aiosignal" @@ -151,14 +185,14 @@ files = [ [[package]] name = "anyio" -version = "4.10.0" +version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, - {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, + {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, + {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, ] [package.dependencies] @@ -168,7 +202,7 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -trio = ["trio (>=0.26.1)"] +trio = ["trio (>=0.31.0)"] [[package]] name = "async-timeout" @@ -185,24 +219,16 @@ files = [ [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] - [[package]] name = "backoff" version = "2.2.1" @@ -217,34 +243,34 @@ files = [ [[package]] name = "boto3" -version = "1.40.21" +version = "1.40.46" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "boto3-1.40.21-py3-none-any.whl", hash = "sha256:3772fb828864d3b7046c8bdf2f4860aaca4a79f25b7b060206c6a5f4944ea7f9"}, - {file = "boto3-1.40.21.tar.gz", hash = "sha256:876ccc0b25517b992bd27976282510773a11ebc771aa5b836a238ea426c82187"}, + {file = "boto3-1.40.46-py3-none-any.whl", hash = "sha256:0dfdc13992ceac1ef36a3ab0ac281cd4a45210a53181dc9a71afabfc1db889fe"}, + {file = "boto3-1.40.46.tar.gz", hash = "sha256:3676767a03d84544b01b3390a2bbdc3b98479223661e90f0ba0b22f4d3f0cb9f"}, ] [package.dependencies] -botocore = ">=1.40.21,<1.41.0" +botocore = ">=1.40.46,<1.41.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.13.0,<0.14.0" +s3transfer = ">=0.14.0,<0.15.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.21" +version = "1.40.46" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "botocore-1.40.21-py3-none-any.whl", hash = "sha256:574ecf9b68c1721650024a27e00e0080b6f141c281ebfce49e0d302969270ef4"}, - {file = "botocore-1.40.21.tar.gz", hash = "sha256:f77e9c199df0252b14ea739a9ac99723940f6bde90f4c2e7802701553a62827b"}, + {file = "botocore-1.40.46-py3-none-any.whl", hash = "sha256:d2c8e0d9ba804d6fd9b942db0aa3e6cfbdd9aab86581b472ee97809b6e5103e0"}, + {file = "botocore-1.40.46.tar.gz", hash = "sha256:4b0c0efdba788117ef365bf930c0be7300fa052e5e195ea3ed53ab278fc6d7b1"}, ] [package.dependencies] @@ -260,14 +286,14 @@ crt = ["awscrt (==0.27.6)"] [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, ] [[package]] @@ -447,19 +473,19 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "fastapi" -version = "0.116.1" +version = "0.116.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"}, - {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"}, + {file = "fastapi-0.116.2-py3-none-any.whl", hash = "sha256:c3a7a8fb830b05f7e087d920e0d786ca1fc9892eb4e9a84b227be4c1bc7569db"}, + {file = "fastapi-0.116.2.tar.gz", hash = "sha256:231a6af2fe21cfa2c32730170ad8514985fc250bec16c9b242d3b94c835ef529"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.48.0" +starlette = ">=0.40.0,<0.49.0" typing-extensions = ">=4.8.0" [package.extras] @@ -481,128 +507,154 @@ files = [ [[package]] name = "frozenlist" -version = "1.7.0" +version = "1.8.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, - {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, - {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, - {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, - {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, - {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, - {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, - {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, - {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, - {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, - {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, - {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, - {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, - {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, - {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, - {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, - {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, + {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, + {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, + {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, + {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, + {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, + {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, + {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, + {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, + {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, + {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, + {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, + {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, + {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, + {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, + {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, + {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, + {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, + {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, + {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, + {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, + {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, + {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, + {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, + {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, + {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, + {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, + {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, + {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, + {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, ] [[package]] name = "fsspec" -version = "2025.7.0" +version = "2025.9.0" description = "File-system specification" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21"}, - {file = "fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58"}, + {file = "fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7"}, + {file = "fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19"}, ] [package.extras] @@ -817,89 +869,90 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.10.0" +version = "0.11.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, - {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, - {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, - {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, - {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, - {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, - {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, - {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, - {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, - {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, - {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, - {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, - {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, - {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, - {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, - {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, - {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, - {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, - {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, + {file = "jiter-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3893ce831e1c0094a83eeaf56c635a167d6fa8cc14393cc14298fd6fdc2a2449"}, + {file = "jiter-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25c625b9b61b5a8725267fdf867ef2e51b429687f6a4eef211f4612e95607179"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4ca85fb6a62cf72e1c7f5e34ddef1b660ce4ed0886ec94a1ef9777d35eaa1f"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:572208127034725e79c28437b82414028c3562335f2b4f451d98136d0fc5f9cd"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:494ba627c7f550ad3dabb21862864b8f2216098dc18ff62f37b37796f2f7c325"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8da18a99f58bca3ecc2d2bba99cac000a924e115b6c4f0a2b98f752b6fbf39a"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ffd3b0fff3fabbb02cc09910c08144db6bb5697a98d227a074401e01ee63dd"}, + {file = "jiter-0.11.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fe6530aa738a4f7d4e4702aa8f9581425d04036a5f9e25af65ebe1f708f23be"}, + {file = "jiter-0.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e35d66681c133a03d7e974e7eedae89720fe8ca3bd09f01a4909b86a8adf31f5"}, + {file = "jiter-0.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59459beca2fbc9718b6f1acb7bfb59ebc3eb4294fa4d40e9cb679dafdcc6c60"}, + {file = "jiter-0.11.0-cp310-cp310-win32.whl", hash = "sha256:b7b0178417b0dcfc5f259edbc6db2b1f5896093ed9035ee7bab0f2be8854726d"}, + {file = "jiter-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:11df2bf99fb4754abddd7f5d940a48e51f9d11624d6313ca4314145fcad347f0"}, + {file = "jiter-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cb5d9db02979c3f49071fce51a48f4b4e4cf574175fb2b11c7a535fa4867b222"}, + {file = "jiter-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1dc6a123f3471c4730db7ca8ba75f1bb3dcb6faeb8d46dd781083e7dee88b32d"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09858f8d230f031c7b8e557429102bf050eea29c77ad9c34c8fe253c5329acb7"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbe2196c4a0ce760925a74ab4456bf644748ab0979762139626ad138f6dac72d"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5beb56d22b63647bafd0b74979216fdee80c580c0c63410be8c11053860ffd09"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97025d09ef549795d8dc720a824312cee3253c890ac73c621721ddfc75066789"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50880a6da65d8c23a2cf53c412847d9757e74cc9a3b95c5704a1d1a24667347"}, + {file = "jiter-0.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:452d80a1c86c095a242007bd9fc5d21b8a8442307193378f891cb8727e469648"}, + {file = "jiter-0.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e84e58198d4894668eec2da660ffff60e0f3e60afa790ecc50cb12b0e02ca1d4"}, + {file = "jiter-0.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df64edcfc5dd5279a791eea52aa113d432c933119a025b0b5739f90d2e4e75f1"}, + {file = "jiter-0.11.0-cp311-cp311-win32.whl", hash = "sha256:144fc21337d21b1d048f7f44bf70881e1586401d405ed3a98c95a114a9994982"}, + {file = "jiter-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:b0f32e644d241293b892b1a6dd8f0b9cc029bfd94c97376b2681c36548aabab7"}, + {file = "jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada"}, + {file = "jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591"}, + {file = "jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09"}, + {file = "jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5"}, + {file = "jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206"}, + {file = "jiter-0.11.0-cp312-cp312-win32.whl", hash = "sha256:4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b"}, + {file = "jiter-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c"}, + {file = "jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb"}, + {file = "jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64"}, + {file = "jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1"}, + {file = "jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758"}, + {file = "jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166"}, + {file = "jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80"}, + {file = "jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6"}, + {file = "jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33"}, + {file = "jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03"}, + {file = "jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba"}, + {file = "jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72"}, + {file = "jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2"}, + {file = "jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2"}, + {file = "jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0"}, + {file = "jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73"}, + {file = "jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2"}, + {file = "jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40"}, + {file = "jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406"}, + {file = "jiter-0.11.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:719891c2fb7628a41adff4f2f54c19380a27e6fdfdb743c24680ef1a54c67bd0"}, + {file = "jiter-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df7f1927cbdf34cb91262a5418ca06920fd42f1cf733936d863aeb29b45a14ef"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e71ae6d969d0c9bab336c5e9e2fabad31e74d823f19e3604eaf96d9a97f463df"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5661469a7b2be25ade3a4bb6c21ffd1e142e13351a0759f264dfdd3ad99af1ab"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76c15ef0d3d02f8b389066fa4c410a0b89e9cc6468a1f0674c5925d2f3c3e890"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63782a1350917a27817030716566ed3d5b3c731500fd42d483cbd7094e2c5b25"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a7092b699646a1ddc03a7b112622d9c066172627c7382659befb0d2996f1659"}, + {file = "jiter-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f637b8e818f6d75540f350a6011ce21252573c0998ea1b4365ee54b7672c23c5"}, + {file = "jiter-0.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a624d87719e1b5d09c15286eaee7e1532a40c692a096ea7ca791121365f548c1"}, + {file = "jiter-0.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9d0146d8d9b3995821bb586fc8256636258947c2f39da5bab709f3a28fb1a0b"}, + {file = "jiter-0.11.0-cp39-cp39-win32.whl", hash = "sha256:d067655a7cf0831eb8ec3e39cbd752995e9b69a2206df3535b3a067fac23b032"}, + {file = "jiter-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:f05d03775a11aaf132c447436983169958439f1219069abf24662a672851f94e"}, + {file = "jiter-0.11.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:902b43386c04739229076bd1c4c69de5d115553d982ab442a8ae82947c72ede7"}, + {file = "jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4"}, ] [[package]] @@ -938,14 +991,14 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2025.4.1" +version = "2025.9.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, - {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, + {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, + {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, ] [package.dependencies] @@ -986,73 +1039,101 @@ utils = ["numpydoc"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] [[package]] @@ -1069,122 +1150,158 @@ files = [ [[package]] name = "multidict" -version = "6.6.4" +version = "6.7.0" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, - {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, - {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, - {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, - {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, - {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, - {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, - {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, - {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, - {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, - {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, - {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, - {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, - {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, - {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, - {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, - {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, - {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, - {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, - {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, - {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, + {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, + {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, + {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, + {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, + {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, + {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, + {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, + {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, + {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, + {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, + {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, + {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, + {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, + {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, + {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, + {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, + {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, + {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, + {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, + {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, + {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, + {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, + {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, + {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, + {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, + {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, ] [package.dependencies] @@ -1192,50 +1309,50 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, - {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, - {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, - {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, - {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, - {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, - {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, - {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, - {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, - {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, - {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, - {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, - {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, - {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, - {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, - {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] @@ -1265,14 +1382,14 @@ files = [ [[package]] name = "openai" -version = "1.102.0" +version = "1.109.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345"}, - {file = "openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9"}, + {file = "openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315"}, + {file = "openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869"}, ] [package.dependencies] @@ -1293,14 +1410,14 @@ voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] [[package]] name = "opentelemetry-api" -version = "1.36.0" +version = "1.37.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, - {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, + {file = "opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47"}, + {file = "opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7"}, ] [package.dependencies] @@ -1309,68 +1426,68 @@ typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.36.0" +version = "1.37.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.37.0-py3-none-any.whl", hash = "sha256:53038428449c559b0c564b8d718df3314da387109c4d36bd1b94c9a641b0292e"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.37.0.tar.gz", hash = "sha256:c87a1bdd9f41fdc408d9cc9367bb53f8d2602829659f2b90be9f9d79d0bfe62c"}, ] [package.dependencies] -opentelemetry-proto = "1.36.0" +opentelemetry-proto = "1.37.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.36.0" +version = "1.37.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.37.0.tar.gz", hash = "sha256:e52e8600f1720d6de298419a802108a8f5afa63c96809ff83becb03f874e44ac"}, ] [package.dependencies] googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.36.0" -opentelemetry-proto = "1.36.0" -opentelemetry-sdk = ">=1.36.0,<1.37.0" +opentelemetry-exporter-otlp-proto-common = "1.37.0" +opentelemetry-proto = "1.37.0" +opentelemetry-sdk = ">=1.37.0,<1.38.0" requests = ">=2.7,<3.0" typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-instrumentation" -version = "0.57b0" +version = "0.58b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation-0.57b0-py3-none-any.whl", hash = "sha256:9109280f44882e07cec2850db28210b90600ae9110b42824d196de357cbddf7e"}, - {file = "opentelemetry_instrumentation-0.57b0.tar.gz", hash = "sha256:f2a30135ba77cdea2b0e1df272f4163c154e978f57214795d72f40befd4fcf05"}, + {file = "opentelemetry_instrumentation-0.58b0-py3-none-any.whl", hash = "sha256:50f97ac03100676c9f7fc28197f8240c7290ca1baa12da8bfbb9a1de4f34cc45"}, + {file = "opentelemetry_instrumentation-0.58b0.tar.gz", hash = "sha256:df640f3ac715a3e05af145c18f527f4422c6ab6c467e40bd24d2ad75a00cb705"}, ] [package.dependencies] opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.57b0" +opentelemetry-semantic-conventions = "0.58b0" packaging = ">=18.0" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-proto" -version = "1.36.0" +version = "1.37.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, - {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, + {file = "opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2"}, + {file = "opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538"}, ] [package.dependencies] @@ -1378,35 +1495,35 @@ protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.36.0" +version = "1.37.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, - {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, + {file = "opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c"}, + {file = "opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5"}, ] [package.dependencies] -opentelemetry-api = "1.36.0" -opentelemetry-semantic-conventions = "0.57b0" +opentelemetry-api = "1.37.0" +opentelemetry-semantic-conventions = "0.58b0" typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.57b0" +version = "0.58b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, - {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, + {file = "opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28"}, + {file = "opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25"}, ] [package.dependencies] -opentelemetry-api = "1.36.0" +opentelemetry-api = "1.37.0" typing-extensions = ">=4.5.0" [[package]] @@ -1507,129 +1624,153 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.3.2" +version = "0.4.0" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, - {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, - {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, - {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, - {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, - {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, - {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, - {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, - {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, - {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, - {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, - {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, - {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, - {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, - {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993"}, + {file = "propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57"}, + {file = "propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71"}, + {file = "propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0"}, + {file = "propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577"}, + {file = "propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0"}, + {file = "propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194"}, + {file = "propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c"}, + {file = "propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907"}, + {file = "propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833"}, + {file = "propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc"}, + {file = "propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281"}, + {file = "propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da"}, + {file = "propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db"}, + {file = "propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d"}, + {file = "propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca"}, + {file = "propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93"}, + {file = "propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b"}, + {file = "propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec"}, + {file = "propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373"}, + {file = "propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28"}, + {file = "propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0cd30341142c68377cf3c4e2d9f0581e6e528694b2d57c62c786be441053d2fc"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c46d37955820dd883cf9156ceb7825b8903e910bdd869902e20a5ac4ecd2c8b"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0b12df77eb19266efd146627a65b8ad414f9d15672d253699a50c8205661a820"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cdabd60e109506462e6a7b37008e57979e737dc6e7dfbe1437adcfe354d1a0a"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65ff56a31f25925ef030b494fe63289bf07ef0febe6da181b8219146c590e185"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:96153e037ae065bb71cae889f23c933190d81ae183f3696a030b47352fd8655d"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bf95be277fbb51513895c2cecc81ab12a421cdbd8837f159828a919a0167f96"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8d18d796ffecdc8253742fd53a94ceee2e77ad149eb9ed5960c2856b5f692f71"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4a52c25a51d5894ba60c567b0dbcf73de2f3cd642cf5343679e07ca3a768b085"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e0ce7f3d1faf7ad58652ed758cc9753049af5308b38f89948aa71793282419c5"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:545987971b2aded25ba4698135ea0ae128836e7deb6e18c29a581076aaef44aa"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7da5c4c72ae40fd3ce87213ab057db66df53e55600d0b9e72e2b7f5a470a2cc4"}, + {file = "propcache-0.4.0-cp39-cp39-win32.whl", hash = "sha256:2015218812ee8f13bbaebc9f52b1e424cc130b68d4857bef018e65e3834e1c4d"}, + {file = "propcache-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:39f0f6a3b56e82dc91d84c763b783c5c33720a33c70ee48a1c13ba800ac1fa69"}, + {file = "propcache-0.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:236c8da353ea7c22a8e963ab78cddb1126f700ae9538e2c4c6ef471e5545494b"}, + {file = "propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557"}, + {file = "propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529"}, ] [[package]] name = "protobuf" -version = "6.32.0" +version = "6.32.1" description = "" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, - {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, - {file = "protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c"}, - {file = "protobuf-6.32.0-cp39-cp39-win32.whl", hash = "sha256:7db8ed09024f115ac877a1427557b838705359f047b2ff2f2b2364892d19dacb"}, - {file = "protobuf-6.32.0-cp39-cp39-win_amd64.whl", hash = "sha256:15eba1b86f193a407607112ceb9ea0ba9569aed24f93333fe9a497cf2fda37d3"}, - {file = "protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783"}, - {file = "protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2"}, + {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, + {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, + {file = "protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281"}, + {file = "protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4"}, + {file = "protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710"}, + {file = "protobuf-6.32.1-cp39-cp39-win32.whl", hash = "sha256:68ff170bac18c8178f130d1ccb94700cf72852298e016a2443bdb9502279e5f1"}, + {file = "protobuf-6.32.1-cp39-cp39-win_amd64.whl", hash = "sha256:d0975d0b2f3e6957111aa3935d08a0eb7e006b1505d825f862a1fffc8348e122"}, + {file = "protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346"}, + {file = "protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d"}, ] [[package]] @@ -1646,14 +1787,14 @@ files = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.10" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, - {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, + {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, + {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, ] [package.dependencies] @@ -1795,14 +1936,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] @@ -1889,65 +2030,78 @@ cli = ["click (>=5.0)"] [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] @@ -1984,99 +2138,127 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2025.8.29" +version = "2025.9.18" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "regex-2025.8.29-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a367dbb66842a08744f49c64ba1aab23e4cbcc924bae8ef40870f2c51d6cb240"}, - {file = "regex-2025.8.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:090d20a6f308c1cd3c33824e892666089d9719ff88e139d4b63623e881d3945c"}, - {file = "regex-2025.8.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e7ee69fdc9daf6aa98693b0db27a76e3d960c80d87c695af262c2608ccfc6a"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50628bc413193041838001b3926570629369d675b92badd6962c402aa09ed4c4"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fadf22d84901f1b6cc6b27439d98688a33cefb83e70c885791c2c27524907ed4"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3948db57ebe3c4bfb7e05765411ce6186820cafa27e5c737d72dbc5249010b3"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c42fbffe25ac6291f8dd00176d1916165550aa649d14e9c4668d6a3d6a5c900"}, - {file = "regex-2025.8.29-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f3498dcc96266b8db76512ffb2432bab2587df5e8ebfdceba5e737378e2bd1"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2dadb4ecaad42562771697685a381e3f723bd4d522e357c07ae4a541ebf5753c"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bc94bccb0482a1eceb34961e3c46e25a3746633fa19f93c93a42ff4b231ee6c3"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:96adc63fd63c05e2feb9c6b8a7212e2b9f52ccb1fa1f18eaed4f9e0ac2cbd186"}, - {file = "regex-2025.8.29-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:145fb4ca5a85e26c330b464fc71bbe0e92523ec5d295c6de9a1e31b06ebccf25"}, - {file = "regex-2025.8.29-cp310-cp310-win32.whl", hash = "sha256:119a0e930916bb26fe028ef5098c6cad66d7a298560cacbc6942e834580dfba5"}, - {file = "regex-2025.8.29-cp310-cp310-win_amd64.whl", hash = "sha256:e8f709146e0f3dafdb4315884de1490ab59f1b93ecf7f9c6c8b0f655f437e593"}, - {file = "regex-2025.8.29-cp310-cp310-win_arm64.whl", hash = "sha256:dc12259599d953bc25bc01f19b056b9115a96cd3cfe05f154d4570c9649800b0"}, - {file = "regex-2025.8.29-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:156f711019968ffb3512723a38b06d94d379675c296bdb6104d1abb6e57374c6"}, - {file = "regex-2025.8.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9082c0db8d43c696fac70b5b0592934f21533940f0118239b5c32fa23e51ed1a"}, - {file = "regex-2025.8.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b3535b9a69a818735ebac392876dae4b215fe28c13b145353a2dac468ebae16"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c460628f6098cf8916b2d62fb39a37a39e49cca0279ac301ff9d94f7e75033e"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dad3ce46390fe3d81ae1c131e29179f010925fa164e15b918fb037effdb7ad9"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f89e5beb3012d3c36c526fd4af163ada24011a0b417378f726b17c2fb382a35d"}, - {file = "regex-2025.8.29-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40eeff06bbcfa69201b60488f3f3aa38ad3c92c7c0ab2cfc7c9599abfdf24262"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7a9bc68610d22735b6ac01a3c3ef5b03d9303a18bd3e2249340213389f273dc"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e785e40f7edfc19ff0b81b27f25eefdb0251cfd2ac4a9fa1eea03f5129e93758"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ba1deae2ceaa0b181ac9fd4cb8f04d6ba1494f3c8d053c8999f7c0dadb93497b"}, - {file = "regex-2025.8.29-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15869e4f36de7091342e1dae90216aafa3746e3a069f30b34503a36931036f95"}, - {file = "regex-2025.8.29-cp311-cp311-win32.whl", hash = "sha256:aef62e0b08b0e3c2616783a9f75a02f001254695a0a1d28b829dc9fb6a3603e4"}, - {file = "regex-2025.8.29-cp311-cp311-win_amd64.whl", hash = "sha256:fd347592a4811ba1d246f99fb53db82a1898a5aebb511281ac0c2d81632e1789"}, - {file = "regex-2025.8.29-cp311-cp311-win_arm64.whl", hash = "sha256:d93801012bb23901df403ae0adf528abfd50041c9e1136a303937d45c14466e0"}, - {file = "regex-2025.8.29-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd61f18dc4446bc3a2904559a61f32e98091cef7fb796e06fa35b9bfefe4c0c5"}, - {file = "regex-2025.8.29-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f21b416be10a8348a7313ba8c610569a1ab4bf8ec70731750540842a4551cd3d"}, - {file = "regex-2025.8.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:008947a7fa92f4cb3b28201c9aa7becc0a44c31a7c2fcb934356e1877baccc09"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78ab1b3e68b890d7ebd69218cfbfe4a09dc00b8a47be8648510b81b932d55ff"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a848368797515bc141d3fad5fd2d81bf9e8a6a22d9ac1a4be4690dd22e997854"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8eaf3ea6631f804efcf0f5bd0e4ab62ba984fd9b70e3aef44b05cc6b951cc728"}, - {file = "regex-2025.8.29-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4561aeb36b0bf3bb44826e4b61a80c6ace0d8839bf4914d78f061f9ba61444b4"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:93e077d1fbd24033fa427eab43d80ad47e449d25700cda78e8cac821a30090bf"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d92379e53d782bdb773988687300e3bccb91ad38157b754b04b1857aaeea16a3"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d41726de2040c2a487bbac70fdd6e3ff2f1aa47dc91f0a29f6955a6dfa0f06b6"}, - {file = "regex-2025.8.29-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1915dfda52bd4d466f3a66b66988db1f647ee1d9c605858640ceeb779cffd908"}, - {file = "regex-2025.8.29-cp312-cp312-win32.whl", hash = "sha256:e2ef0087ad6949918836f215480a9331f6c59ad54912a9a412f08ab1c9ccbc98"}, - {file = "regex-2025.8.29-cp312-cp312-win_amd64.whl", hash = "sha256:c15d361fe9800bf38ef69c2e0c4b8b961ae4ce2f076fcf4f28e1fc9ea127f55a"}, - {file = "regex-2025.8.29-cp312-cp312-win_arm64.whl", hash = "sha256:305577fab545e64fb84d9a24269aa3132dbe05e1d7fa74b3614e93ec598fe6e6"}, - {file = "regex-2025.8.29-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:eed02e5c39f91268ea4ddf68ee19eed189d57c605530b7d32960f54325c52e7a"}, - {file = "regex-2025.8.29-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:630d5c7e0a490db2fee3c7b282c8db973abcbb036a6e4e6dc06c4270965852be"}, - {file = "regex-2025.8.29-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2206d3a30469e8fc8848139884168127f456efbaca8ae14809c26b98d2be15c6"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:394c492c398a9f9e17545e19f770c58b97e65963eedaa25bb879e80a03e2b327"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:db8b0e05af08ff38d78544950e844b5f159032b66dedda19b3f9b17297248be7"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd7c1821eff911917c476d41030b422791ce282c23ee9e1b8f7681fd0993f1e4"}, - {file = "regex-2025.8.29-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d8a7f75da748a2d0c045600259f1899c9dd8dd9d3da1daa50bf534c3fa5ba"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5cd74545c32e0da0d489c2293101a82f4a1b88050c235e45509e4123017673b2"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:97b98ea38fc3c1034f3d7bd30288d2c5b3be8cdcd69e2061d1c86cb14644a27b"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8decb26f271b989d612c5d99db5f8f741dcd63ece51c59029840070f5f9778bf"}, - {file = "regex-2025.8.29-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62141843d1ec079cd66604424af566e542e7e072b2d9e37165d414d2e6e271dd"}, - {file = "regex-2025.8.29-cp313-cp313-win32.whl", hash = "sha256:dd23006c90d9ff0c2e4e5f3eaf8233dcefe45684f2acb330869ec5c2aa02b1fb"}, - {file = "regex-2025.8.29-cp313-cp313-win_amd64.whl", hash = "sha256:d41a71342819bdfe87c701f073a14ea4bd3f847333d696c7344e9ff3412b7f70"}, - {file = "regex-2025.8.29-cp313-cp313-win_arm64.whl", hash = "sha256:54018e66344d60b214f4aa151c046e0fa528221656f4f7eba5a787ccc7057312"}, - {file = "regex-2025.8.29-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c03308757831a8d89e7c007abb75d1d4c9fbca003b5fb32755d4475914535f08"}, - {file = "regex-2025.8.29-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0d4b71791975fc203e0e6c50db974abb23a8df30729c1ac4fd68c9f2bb8c9358"}, - {file = "regex-2025.8.29-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:284fcd2dcb613e8b89b22a30cf42998c9a73ee360b8a24db8457d24f5c42282e"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b394b5157701b22cf63699c792bfeed65fbfeacbd94fea717a9e2036a51148ab"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ea197ac22396faf5e70c87836bb89f94ed5b500e1b407646a4e5f393239611f1"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:decd84f195c08b3d9d0297a7e310379aae13ca7e166473534508c81b95c74bba"}, - {file = "regex-2025.8.29-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebaf81f7344dbf1a2b383e35923648de8f78fb262cf04154c82853887ac3e684"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d82fb8a97e5ed8f1d3ed7f8e0e7fe1760faa95846c0d38b314284dfdbe86b229"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1dcec2448ed0062f63e82ca02d1d05f74d4127cb6a9d76a73df60e81298d380b"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d0ffe4a3257a235f9d39b99c6f1bc53c7a4b11f28565726b1aa00a5787950d60"}, - {file = "regex-2025.8.29-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5421a2d2026e8189500f12375cfd80a9a1914466d446edd28b37eb33c1953b39"}, - {file = "regex-2025.8.29-cp314-cp314-win32.whl", hash = "sha256:ceeeaab602978c8eac3b25b8707f21a69c0bcd179d9af72519da93ef3966158f"}, - {file = "regex-2025.8.29-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f8b0d5b88c33fe4060e6def58001fd8334b03c7ce2126964fa8851ab5d1b"}, - {file = "regex-2025.8.29-cp314-cp314-win_arm64.whl", hash = "sha256:7b4a3dc155984f09a55c64b90923cb136cd0dad21ca0168aba2382d90ea4c546"}, - {file = "regex-2025.8.29-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4d6dbdfdb4de3a77d1b2f9ec6bded2e056081407923d69236e13457924cf5fd7"}, - {file = "regex-2025.8.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f747541fd1ad1dcf859ce221749a5d26d7dbe6d928efdd407c97a2d27c8f434"}, - {file = "regex-2025.8.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90c37a24d9a809ff1898e74f3318a4e21f8bb3db9975a560fa3722e42c370285"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:470138c8882d66493969f45fad2f8e20f35e381b9f96a37f59a5ac786e653cf6"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc8c7fc96c9eb18b6690c96ec9c8fb63ea2fa78c6df4258fd76b59d4fbf46645"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33a26d4b2dc639868d73b9ec4ff8a89eb295797170125e4d4810ad23228f93c8"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b839268539b44a965f3ed680fda6270337a05bd425cc80542e0c6808efdc9a7e"}, - {file = "regex-2025.8.29-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16b5ca6570c71b1ee61dd30f24a1944eb82a372364e37f58f9b9731636cc6ba9"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:421b6ccd037ad551e1ef1bc31debc3a914b579c27c0807f35c85f13b0eccbff3"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d8cb77df92d1a204a0c218d93c5fb14945e2a7b40da2d9f15b05c9ddae393b43"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:dd7df4ae4ea0efe0d378535e9825bd20e3be8d57eb3d55291d8094d61c9ccd9e"}, - {file = "regex-2025.8.29-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:348cbcdf2d9dd0d09f05a78218776a33779e95aa57d553065a00429a96c553d3"}, - {file = "regex-2025.8.29-cp39-cp39-win32.whl", hash = "sha256:590de47e6c390a42e6bfb1bdbe2148456827a6b28464c6e387f51b4bbe1f83e2"}, - {file = "regex-2025.8.29-cp39-cp39-win_amd64.whl", hash = "sha256:df8deeb34e06c8ba196beabbcf2810d5ecd8cf71cfe69899e93806244610f7ae"}, - {file = "regex-2025.8.29-cp39-cp39-win_arm64.whl", hash = "sha256:fbabdb18fdd1fc4b0740f4e6b3070d7f41f98a88b8c38cf1962b6dcb3e745e56"}, - {file = "regex-2025.8.29.tar.gz", hash = "sha256:731ddb27a0900fa227dfba976b4efccec8c1c6fba147829bb52e71d49e91a5d7"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"}, + {file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"}, + {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"}, + {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"}, + {file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"}, + {file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"}, + {file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"}, + {file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"}, + {file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"}, + {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"}, + {file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"}, + {file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"}, + {file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"}, + {file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"}, + {file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"}, + {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"}, + {file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"}, + {file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"}, + {file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"}, + {file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"}, + {file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"}, + {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"}, + {file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"}, + {file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"}, + {file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"}, + {file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"}, + {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"}, + {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"}, + {file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"}, + {file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"}, + {file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"}, + {file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"}, + {file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"}, + {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"}, + {file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"}, + {file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"}, + {file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"}, + {file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"}, + {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"}, + {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"}, + {file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"}, + {file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"}, + {file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"}, + {file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"}, + {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"}, + {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"}, + {file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"}, + {file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"}, + {file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"}, + {file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"}, ] [[package]] @@ -2268,14 +2450,14 @@ files = [ [[package]] name = "s3transfer" -version = "0.13.1" +version = "0.14.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724"}, - {file = "s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf"}, + {file = "s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456"}, + {file = "s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125"}, ] [package.dependencies] @@ -2413,31 +2595,31 @@ blobfile = ["blobfile (>=2)"] [[package]] name = "tokenizers" -version = "0.22.0" +version = "0.22.1" description = "" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tokenizers-0.22.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:eaa9620122a3fb99b943f864af95ed14c8dfc0f47afa3b404ac8c16b3f2bb484"}, - {file = "tokenizers-0.22.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:71784b9ab5bf0ff3075bceeb198149d2c5e068549c0d18fe32d06ba0deb63f79"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5b71f668a8076802b0241a42387d48289f25435b86b769ae1837cad4172a17"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea8562fa7498850d02a16178105b58803ea825b50dc9094d60549a7ed63654bb"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4136e1558a9ef2e2f1de1555dcd573e1cbc4a320c1a06c4107a3d46dc8ac6e4b"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf5954de3962a5fd9781dc12048d24a1a6f1f5df038c6e95db328cd22964206"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8337ca75d0731fc4860e6204cc24bb36a67d9736142aa06ed320943b50b1e7ed"}, - {file = "tokenizers-0.22.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a89264e26f63c449d8cded9061adea7b5de53ba2346fc7e87311f7e4117c1cc8"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:790bad50a1b59d4c21592f9c3cf5e5cf9c3c7ce7e1a23a739f13e01fb1be377a"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:76cf6757c73a10ef10bf06fa937c0ec7393d90432f543f49adc8cab3fb6f26cb"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1626cb186e143720c62c6c6b5371e62bbc10af60481388c0da89bc903f37ea0c"}, - {file = "tokenizers-0.22.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da589a61cbfea18ae267723d6b029b84598dc8ca78db9951d8f5beff72d8507c"}, - {file = "tokenizers-0.22.0-cp39-abi3-win32.whl", hash = "sha256:dbf9d6851bddae3e046fedfb166f47743c1c7bd11c640f0691dd35ef0bcad3be"}, - {file = "tokenizers-0.22.0-cp39-abi3-win_amd64.whl", hash = "sha256:c78174859eeaee96021f248a56c801e36bfb6bd5b067f2e95aa82445ca324f00"}, - {file = "tokenizers-0.22.0.tar.gz", hash = "sha256:2e33b98525be8453f355927f3cab312c36cd3e44f4d7e9e97da2fa94d0a49dcb"}, + {file = "tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73"}, + {file = "tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f"}, + {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a"}, + {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390"}, + {file = "tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82"}, + {file = "tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138"}, + {file = "tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9"}, ] [package.dependencies] -huggingface-hub = ">=0.16.4,<1.0" +huggingface-hub = ">=0.16.4,<2.0" [package.extras] dev = ["tokenizers[testing]"] @@ -2535,14 +2717,14 @@ files = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] [package.dependencies] @@ -2607,14 +2789,14 @@ standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" description = "Measures the displayed width of unicode strings in a terminal" optional = false -python-versions = "*" +python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, + {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, + {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, ] [[package]] @@ -2710,116 +2892,142 @@ files = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.22.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, - {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, - {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, - {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, - {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, - {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, - {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, - {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, - {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, - {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, - {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, - {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, - {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, - {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, - {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, + {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, + {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, + {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, + {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, + {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, + {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, + {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, + {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, + {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, + {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, + {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, + {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, + {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, + {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, + {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, + {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, + {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, + {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, + {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, + {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, + {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, + {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, + {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, + {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, + {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, + {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, ] [package.dependencies] @@ -2850,4 +3058,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "f661d841d5a501c9370ab7eed22c4713413acfaa6a6b77d12dbcc91155fab1c2" +content-hash = "7ffcadeaaf02192474b0e6a6a9e4492603d7a97391e3bcbd166f7b7b684c22d3" diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 808a12e4da..c57d2b8b23 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,9 +1,12 @@ [tool.poetry] name = "agenta" -version = "0.55.2" +version = "0.56.0" description = "The SDK for agenta is an open-source LLMOps platform." readme = "README.md" -authors = ["Mahmoud Mabrouk "] +authors = [ + "Mahmoud Mabrouk ", + "Juan Vega " +] classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", @@ -23,32 +26,31 @@ exclude = [ python = "^3.9" starlette = "^0.47.0" fastapi = "^0.116.0" -pydantic = ">=2" +pydantic = "^2" python-dotenv = "^1.0.0" importlib-metadata = ">=8.0.0,<9.0" -httpx = ">=0.28.0" +httpx = "^0.28.0" pyyaml = "^6.0.2" toml = "^0.10.2" litellm = "==1.76.0" jinja2 = "^3.1.6" -opentelemetry-api = ">=1.27.0,<2.0.0" -opentelemetry-sdk = ">=1.27.0,<2.0.0" +opentelemetry-api = "^1.27.0" +opentelemetry-sdk = "^1.27.0" opentelemetry-instrumentation = ">=0.56b0" -opentelemetry-exporter-otlp-proto-http =">=1.27.0,<2.0.0" +opentelemetry-exporter-otlp-proto-http ="^1.27.0" structlog = "^25.2.0" huggingface-hub = "<0.31.0" # audit fixes -h11 = ">=0.16.0" +h11 = "^0.16.0" decorator = "^5.2.1" -openai = ">=1.100.0" +openai = "^1.106.0" tiktoken = "0.11.0" - [tool.poetry.group.dev.dependencies] posthog = "^3.1.0" questionary = ">=1.10,<3.0" -pytest = "^8.3" +pytest = "^8.4.2" setuptools = ">=71.1,<79.0" pytest-asyncio = "^0.24.0" mypy = "^1.13.0" @@ -56,7 +58,7 @@ pytest-xdist = "^3.6.1" uvicorn = "^0.34.0" requests = "^2.32.3" pexpect = "^4.9.0" -boto3 = "^1.35.87" +boto3 = "^1.40.25" [build-system] requires = ["poetry-core"] diff --git a/web/oss/package.json b/web/oss/package.json index 01f8e1e47d..68c098b5c8 100644 --- a/web/oss/package.json +++ b/web/oss/package.json @@ -1,6 +1,6 @@ { "name": "@agenta/oss", - "version": "0.55.2", + "version": "0.56.0", "private": true, "engines": { "node": ">=18" diff --git a/web/oss/src/components/Filters/EditColumns/index.tsx b/web/oss/src/components/Filters/EditColumns/index.tsx index 37f63e9e57..857814582b 100644 --- a/web/oss/src/components/Filters/EditColumns/index.tsx +++ b/web/oss/src/components/Filters/EditColumns/index.tsx @@ -40,6 +40,9 @@ const collectKeys = (cols: ColumnsType): string[] => { const getSafeName = (col: ColumnGroupType | ColumnType) => { try { + if ((col as any).__editLabel && typeof (col as any).__editLabel === "string") { + return (col as any).__editLabel + } if ("title" in col && typeof col.title === "string") return col.title if ("dataIndex" in col && typeof col.dataIndex === "string") return col.dataIndex if ("key" in col && typeof col.key === "string") return col.key diff --git a/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseContent.tsx b/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseContent.tsx index ad403457e8..e64c3b7a0d 100644 --- a/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseContent.tsx +++ b/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseContent.tsx @@ -6,7 +6,7 @@ import deepEqual from "fast-deep-equal" import {useAtomValue, useSetAtom} from "jotai" import {selectAtom} from "jotai/utils" -import {currentAppContextAtom} from "@/oss/state/newApps" +import {currentAppContextAtom} from "@/oss/state/app/selectors/app" import { promptsAtomFamily, promptVariablesByPromptAtomFamily, diff --git a/web/oss/src/components/Playground/services/api.ts b/web/oss/src/components/Playground/services/api.ts index 69b6f54496..b2d5b365f1 100644 --- a/web/oss/src/components/Playground/services/api.ts +++ b/web/oss/src/components/Playground/services/api.ts @@ -65,26 +65,6 @@ export class VariantAPI { return response.data } - /** - * Update variant configuration - */ - static async updateVariant(params: VariantUpdateParams): Promise { - const response = await fetch(`/api/variants/${params.variantId}`, { - method: "PUT", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - config: params.config, - ...params.optionalParams, - }), - }) - - if (!response.ok) { - throw new Error(`Failed to update variant: ${response.statusText}`) - } - - return response.json() - } - /** * Delete a variant */ @@ -180,156 +160,3 @@ export class VariantAPI { } } } - -/** - * Test execution API operations - */ -export class TestExecutionAPI { - /** - * Execute tests for a variant - */ - static async runTests(params: TestExecutionParams): Promise<{ - executionId: string - status: "running" | "completed" | "failed" - }> { - const response = await fetch(`/api/variants/${params.variantId}/test`, { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - testset_id: params.testsetId, - input_rows: params.inputRows, - config: params.config, - }), - }) - - if (!response.ok) { - throw new Error(`Failed to run tests: ${response.statusText}`) - } - - return response.json() - } - - /** - * Cancel running tests - */ - static async cancelTests(executionId: string): Promise { - const response = await fetch(`/api/test-executions/${executionId}/cancel`, { - method: "POST", - }) - - if (!response.ok) { - throw new Error(`Failed to cancel tests: ${response.statusText}`) - } - } - - /** - * Get test results - */ - static async getTestResults(executionId: string): Promise { - const response = await fetch(`/api/test-executions/${executionId}/results`) - - if (!response.ok) { - throw new Error(`Failed to get test results: ${response.statusText}`) - } - - return response.json() - } - - /** - * Rerun specific chat output - */ - static async rerunChatOutput(params: { - variantId: string - inputRowId: string - config: Record - }): Promise<{outputId: string; result: any}> { - const response = await fetch(`/api/variants/${params.variantId}/rerun`, { - method: "POST", - headers: {"Content-Type": "application/json"}, - body: JSON.stringify({ - input_row_id: params.inputRowId, - config: params.config, - }), - }) - - if (!response.ok) { - throw new Error(`Failed to rerun chat output: ${response.statusText}`) - } - - return response.json() - } -} - -/** - * WebSocket connection for real-time updates - */ -export class PlaygroundWebSocket { - private ws: WebSocket | null = null - private reconnectAttempts = 0 - private maxReconnectAttempts = 5 - private reconnectDelay = 1000 - - constructor( - private url: string, - private onMessage: (data: any) => void, - private onError?: (error: Event) => void, - ) {} - - connect(): void { - try { - this.ws = new WebSocket(this.url) - - this.ws.onopen = () => { - console.log("Playground WebSocket connected") - this.reconnectAttempts = 0 - } - - this.ws.onmessage = (event) => { - try { - const data = JSON.parse(event.data) - this.onMessage(data) - } catch (error) { - console.error("Failed to parse WebSocket message:", error) - } - } - - this.ws.onclose = () => { - console.log("Playground WebSocket disconnected") - this.attemptReconnect() - } - - this.ws.onerror = (error) => { - console.error("Playground WebSocket error:", error) - this.onError?.(error) - } - } catch (error) { - console.error("Failed to create WebSocket connection:", error) - this.attemptReconnect() - } - } - - private attemptReconnect(): void { - if (this.reconnectAttempts < this.maxReconnectAttempts) { - this.reconnectAttempts++ - setTimeout(() => { - console.log( - `Attempting to reconnect WebSocket (${this.reconnectAttempts}/${this.maxReconnectAttempts})`, - ) - this.connect() - }, this.reconnectDelay * this.reconnectAttempts) - } - } - - disconnect(): void { - if (this.ws) { - this.ws.close() - this.ws = null - } - } - - send(data: any): void { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { - this.ws.send(JSON.stringify(data)) - } - } -} diff --git a/web/oss/src/components/Playground/state/atoms/appCreationMutations.ts b/web/oss/src/components/Playground/state/atoms/appCreationMutations.ts index 080a0fa045..620a10c5c0 100644 --- a/web/oss/src/components/Playground/state/atoms/appCreationMutations.ts +++ b/web/oss/src/components/Playground/state/atoms/appCreationMutations.ts @@ -387,7 +387,7 @@ export const createAppMutationAtom = atom( ) } - await axios.put(`/api/variants/${targetVariantId}/parameters?project_id=${projectId}`, { + await axios.put(`/variants/${targetVariantId}/parameters?project_id=${projectId}`, { parameters: parameters.ag_config, }) diff --git a/web/oss/src/components/Playground/state/atoms/variantCrud.ts b/web/oss/src/components/Playground/state/atoms/variantCrud.ts index bce721c99d..654a9762b2 100644 --- a/web/oss/src/components/Playground/state/atoms/variantCrud.ts +++ b/web/oss/src/components/Playground/state/atoms/variantCrud.ts @@ -9,7 +9,7 @@ import {getAllMetadata} from "@/oss/lib/hooks/useStatelessVariants/state" import {transformToRequestBody} from "@/oss/lib/shared/variant/transformer/transformToRequestBody" import {deleteSingleVariantRevision} from "@/oss/services/playground/api" import {duplicateChatHistoryForRevision} from "@/oss/state/generation/utils" -import {currentAppContextAtom} from "@/oss/state/newApps/selectors/apps" +import {currentAppContextAtom} from "@/oss/state/app/selectors/app" import {clearLocalCustomPropsForRevisionAtomFamily} from "@/oss/state/newPlayground/core/customProperties" import { promptsAtomFamily, diff --git a/web/oss/src/components/Playground/state/atoms/variants.ts b/web/oss/src/components/Playground/state/atoms/variants.ts index 53269c200a..f546c275ae 100644 --- a/web/oss/src/components/Playground/state/atoms/variants.ts +++ b/web/oss/src/components/Playground/state/atoms/variants.ts @@ -10,7 +10,7 @@ import { import {getRequestSchema} from "@/oss/lib/shared/variant/openapiUtils" import type {EnhancedVariant} from "@/oss/lib/shared/variant/transformer/types" import {currentAppAtom} from "@/oss/state/app" -import {currentAppContextAtom} from "@/oss/state/newApps/selectors/apps" +import {currentAppContextAtom} from "@/oss/state/app/selectors/app" import {customPropertiesByRevisionAtomFamily} from "@/oss/state/newPlayground/core/customProperties" import {promptsAtomFamily} from "@/oss/state/newPlayground/core/prompts" import { diff --git a/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/components/CustomWorkflowModalFooter.tsx b/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/components/CustomWorkflowModalFooter.tsx index f3d667f982..1934856297 100644 --- a/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/components/CustomWorkflowModalFooter.tsx +++ b/web/oss/src/components/pages/app-management/modals/CustomWorkflowModal/components/CustomWorkflowModalFooter.tsx @@ -59,13 +59,13 @@ const CustomWorkflowModalFooter = ({ {testConnectionStatus.success && ( <> - Successful + Success )} {testConnectionStatus.error && ( <> - Failed + Failure )} diff --git a/web/oss/src/lib/api/assets/axiosConfig.ts b/web/oss/src/lib/api/assets/axiosConfig.ts index 47e412c797..f9df511521 100644 --- a/web/oss/src/lib/api/assets/axiosConfig.ts +++ b/web/oss/src/lib/api/assets/axiosConfig.ts @@ -16,7 +16,7 @@ import {isDemo} from "../../helpers/utils" export const PERMISSION_ERR_MSG = "You don't have permission to perform this action. Please contact your organization admin." -const ENDPOINTS_PROJECT_ID_WHITELIST = ["/api/projects", "/api/profile", "/api/organizations"] +const ENDPOINTS_PROJECT_ID_WHITELIST = ["/projects", "/profile", "/organizations"] const axios = axiosApi.create({ baseURL: getAgentaApiUrl(), headers: { @@ -56,6 +56,12 @@ axios.interceptors.request.use(async (config) => { const controller = new AbortController() const configuredUri = axios.getUri(config) if (!ENDPOINTS_PROJECT_ID_WHITELIST.some((endpoint) => configuredUri.includes(endpoint))) { + console.log("ABORTING REQUEST", { + configuredUri, + projectId, + jwt, + user, + }) controller.abort() } diff --git a/web/oss/src/lib/hooks/useEvaluatorConfigs/index.ts b/web/oss/src/lib/hooks/useEvaluatorConfigs/index.ts index 9d40b78758..436cd23fa8 100644 --- a/web/oss/src/lib/hooks/useEvaluatorConfigs/index.ts +++ b/web/oss/src/lib/hooks/useEvaluatorConfigs/index.ts @@ -28,7 +28,7 @@ const useEvaluatorConfigs = ({ return useSWR, any>( !preview && appId && !!projectId - ? `/api/preview/evaluator_configs/?project_id=${projectId}&app_id=${appId}` + ? `/preview/evaluator_configs/?project_id=${projectId}&app_id=${appId}` : null, fetcher, { diff --git a/web/oss/src/lib/hooks/useEvaluators/index.ts b/web/oss/src/lib/hooks/useEvaluators/index.ts index bc28745e44..0ad2af8b5d 100644 --- a/web/oss/src/lib/hooks/useEvaluators/index.ts +++ b/web/oss/src/lib/hooks/useEvaluators/index.ts @@ -1,10 +1,12 @@ import {useCallback} from "react" +import {useAtomValue} from "jotai" import useSWR, {SWRResponse} from "swr" import {getMetricsFromEvaluator} from "@/oss/components/pages/observability/drawer/AnnotateDrawer/assets/transforms" import {fetchAllEvaluators} from "@/oss/services/evaluators" import {useOrgData} from "@/oss/state/org" +import {userAtom} from "@/oss/state/profile" import {getProjectValues} from "@/oss/state/project" import axios from "../../api/assets/axiosConfig" @@ -32,6 +34,7 @@ const useEvaluators = ({ queries?: {is_human: boolean} }): UseEvaluatorsReturn => { const {selectedOrg} = useOrgData() + const user = useAtomValue(userAtom) const projectId = options?.projectId || getProjectValues()?.projectId || "" const workspace = selectedOrg?.default_workspace const members = workspace?.members || [] @@ -66,8 +69,8 @@ const useEvaluators = ({ } }, [projectId, preview, queries]) - return useSWR( - projectId + const data = useSWR( + user?.id && projectId ? `/api${preview ? "/preview" : ""}/evaluators/?project_id=${projectId}&queries=${JSON.stringify(queries)}` : null, fetcher, @@ -77,6 +80,8 @@ const useEvaluators = ({ ...options, }, ) + + return data } export default useEvaluators diff --git a/web/oss/src/services/evaluations/api/index.ts b/web/oss/src/services/evaluations/api/index.ts deleted file mode 100644 index 059ed99f07..0000000000 --- a/web/oss/src/services/evaluations/api/index.ts +++ /dev/null @@ -1,182 +0,0 @@ -import uniqBy from "lodash/uniqBy" -import {v4 as uuidv4} from "uuid" - -import axios from "@/oss/lib/api/assets/axiosConfig" -import { - ComparisonResultRow, - EvaluationStatus, - KeyValuePair, - LLMRunRateLimit, - TestSet, - _Evaluation, - _EvaluationScenario, -} from "@/oss/lib/Types" -import {fetchTestset} from "@/oss/services/testsets/api" -import {getProjectValues} from "@/oss/state/project" - -export const fetchEvaluation = async (evaluationId: string) => { - const {projectId} = getProjectValues() - - const response = await axios.get(`/evaluations/${evaluationId}?project_id=${projectId}`) - return evaluationTransformer(response.data) as _Evaluation -} - -export const fetchEvaluationStatus = async (evaluationId: string) => { - const {projectId} = getProjectValues() - - const response = await axios.get(`/evaluations/${evaluationId}/status?project_id=${projectId}`) - return response.data as {status: _Evaluation["status"]} -} - -export type CreateEvaluationData = - | { - testset_id: string - variant_ids?: string[] - evaluators_configs: string[] - rate_limit: LLMRunRateLimit - lm_providers_keys?: KeyValuePair - correct_answer_column: string - } - | { - testset_id: string - revisions_ids?: string[] - evaluators_configs: string[] - rate_limit: LLMRunRateLimit - lm_providers_keys?: KeyValuePair - correct_answer_column: string - } -export const createEvaluation = async (appId: string, evaluation: CreateEvaluationData) => { - const {projectId} = getProjectValues() - - // TODO: new AUTO-EVAL trigger - return axios.post(`/api/evaluations/preview/start?project_id=${projectId}`, { - ...evaluation, - app_id: appId, - }) -} - -export const deleteEvaluations = async (evaluationsIds: string[]) => { - const {projectId} = getProjectValues() - - return axios.delete(`/evaluations?project_id=${projectId}`, { - data: {evaluations_ids: evaluationsIds}, - }) -} - -// Evaluation Scenarios -export const fetchAllEvaluationScenarios = async (evaluationId: string) => { - const {projectId} = getProjectValues() - - const [{data: evaluationScenarios}, evaluation] = await Promise.all([ - axios.get(`/evaluations/${evaluationId}/evaluation_scenarios?project_id=${projectId}`), - fetchEvaluation(evaluationId), - ]) - - evaluationScenarios.forEach((scenario: _EvaluationScenario) => { - scenario.evaluation = evaluation - scenario.evaluators_configs = evaluation.aggregated_results.map( - (item) => item.evaluator_config, - ) - }) - return evaluationScenarios as _EvaluationScenario[] -} - -export const updateScenarioStatus = async ( - scenario: _EvaluationScenario, - status: EvaluationStatus, -) => { - const {projectId} = getProjectValues() - return axios.patch(`/preview/evaluations/scenarios/?project_id=${projectId}`, { - scenarios: [{...scenario, status}], - }) -} - -// Comparison -export const fetchAllComparisonResults = async (evaluationIds: string[]) => { - const scenarioGroups = await Promise.all(evaluationIds.map(fetchAllEvaluationScenarios)) - const testset: TestSet = await fetchTestset(scenarioGroups[0][0].evaluation?.testset?.id) - - const inputsNameSet = new Set() - scenarioGroups.forEach((group) => { - group.forEach((scenario) => { - scenario.inputs.forEach((input) => inputsNameSet.add(input.name)) - }) - }) - - const rows: ComparisonResultRow[] = [] - const inputNames = Array.from(inputsNameSet) - const inputValuesSet = new Set() - const variants = scenarioGroups.map((group) => group[0].evaluation.variants[0]) - const correctAnswers = uniqBy( - scenarioGroups.map((group) => group[0].correct_answers).flat(), - "key", - ) - - for (const data of testset.csvdata) { - const inputValues = inputNames - .filter((name) => data[name] !== undefined) - .map((name) => ({name, value: data[name]})) - const inputValuesStr = inputValues.map((ip) => ip.value).join("") - if (inputValuesSet.has(inputValuesStr)) continue - else inputValuesSet.add(inputValuesStr) - - rows.push({ - id: inputValuesStr, - rowId: uuidv4(), - inputs: inputNames - .map((name) => ({name, value: data[name]})) - .filter((ip) => ip.value !== undefined), - ...correctAnswers.reduce((acc, curr) => { - return {...acc, [`correctAnswer_${curr?.key}`]: data[curr?.key!]} - }, {}), - variants: variants.map((variant, ix) => { - const group = scenarioGroups[ix] - const scenario = group.find((scenario) => - scenario.inputs.every((input) => - inputValues.some( - (ip) => ip.name === input.name && ip.value === input.value, - ), - ), - ) - return { - variantId: variant.variantId, - variantName: variant.variantName, - output: scenario?.outputs[0] || { - result: {type: "string", value: "", error: null}, - }, - evaluationId: scenario?.evaluation.id || "", - evaluatorConfigs: (scenario?.evaluators_configs || []).map((config) => ({ - evaluatorConfig: config, - result: scenario?.results.find( - (result) => result.evaluator_config === config.id, - )?.result || {type: "string", value: "", error: null}, // Adjust this line - })), - } - }), - }) - } - - return { - rows, - testset, - evaluations: scenarioGroups.map((group) => group[0].evaluation), - } -} - -// Evaluation IDs by resource -export const fetchEvaluatonIdsByResource = async ({ - resourceIds, - resourceType, -}: { - resourceIds: string[] - resourceType: "testset" | "evaluator_config" | "variant" -}) => { - const {projectId} = getProjectValues() - - return axios.get(`/evaluations/by_resource?project_id=${projectId}`, { - params: {resource_ids: resourceIds, resource_type: resourceType}, - paramsSerializer: { - indexes: null, //no brackets in query params - }, - }) -} diff --git a/web/oss/src/services/testsets/api/index.ts b/web/oss/src/services/testsets/api/index.ts index 05987276a4..13a46d5e14 100644 --- a/web/oss/src/services/testsets/api/index.ts +++ b/web/oss/src/services/testsets/api/index.ts @@ -2,7 +2,8 @@ import axios from "@/oss/lib/api/assets/axiosConfig" import {getAgentaApiUrl} from "@/oss/lib/helpers/api" import {TestSet, PreviewTestSet} from "@/oss/lib/Types" import {getProjectValues} from "@/oss/state/project" -import {useTestset as useTestsetAtom} from "@/oss/state/testset" + +import {PreviewTestsetsQueryPayload} from "./types" //Prefix convention: // - fetch: GET single entity from server @@ -11,28 +12,6 @@ import {useTestset as useTestsetAtom} from "@/oss/state/testset" // - update: PUT data to server // - delete: DELETE data from server -/** - * Hook for fetching a single testset using Jotai atoms - * Migrated from SWR to maintain backward compatibility - * - * @param testsetId - ID of the testset to fetch - * @param preview - Whether to fetch preview version (optional) - * @returns Query result with same interface as SWR hook - * @deprecated Use the atom-based useTestset from @/oss/state/testset instead - */ -export function useTestset( - testsetId?: string, - preview?: T, -): { - data: T extends true ? PreviewTestSet : TestSet - error: any - isLoading: boolean - mutate: () => void -} { - // Use the new atom-based hook internally - return useTestsetAtom(testsetId, preview) -} - export const fetchTestsets = async () => { const {projectId} = getProjectValues() @@ -41,11 +20,12 @@ export const fetchTestsets = async () => { return response.data } -export const fetchPreviewTestsets = async () => { +export const fetchPreviewTestsets = async (payload: PreviewTestsetsQueryPayload = {}) => { const {projectId} = getProjectValues() - const response = await axios.get( - `${getAgentaApiUrl()}/preview/simple/testsets/?project_id=${projectId}`, + const response = await axios.post( + `${getAgentaApiUrl()}/preview/simple/testsets/query?project_id=${projectId}`, + payload, ) return response.data diff --git a/web/oss/src/services/testsets/api/types.ts b/web/oss/src/services/testsets/api/types.ts new file mode 100644 index 0000000000..2e1ec20e92 --- /dev/null +++ b/web/oss/src/services/testsets/api/types.ts @@ -0,0 +1,27 @@ +// Types for testsets API + +export interface PreviewTestsetsQueryPayload { + testset?: { + flags?: { + has_testcases?: boolean + has_traces?: boolean + } + meta?: Record + tags?: Record + } + testset_refs?: { + version?: string + slug?: string + id?: string + }[] + include_archived?: boolean + windowing?: { + newest?: string + oldest?: string + next?: string + limit?: number + order?: "ascending" | "descending" + interval?: number + rate?: number + } +} diff --git a/web/oss/src/services/tracing/api/index.ts b/web/oss/src/services/tracing/api/index.ts index 8a1dfa40c8..fa91ec861b 100644 --- a/web/oss/src/services/tracing/api/index.ts +++ b/web/oss/src/services/tracing/api/index.ts @@ -5,21 +5,32 @@ export const fetchAllPreviewTraces = async (params: Record = {}, ap const projectId = ensureProjectId() const applicationId = ensureAppId(appId) - const url = new URL(`${base}/preview/tracing/spans/`) + // New query endpoint expects POST with JSON body + const url = new URL(`${base}/preview/tracing/spans/query`) if (projectId) url.searchParams.set("project_id", projectId) if (applicationId) url.searchParams.set("application_id", applicationId) + const payload: Record = {} Object.entries(params).forEach(([key, value]) => { if (value === undefined || value === null) return if (key === "size") { - url.searchParams.set("limit", String(value)) + payload.limit = Number(value) + } else if (key === "filter" && typeof value === "string") { + try { + payload.filter = JSON.parse(value) + } catch { + payload.filter = value + } } else { - const encoded = typeof value === "object" ? JSON.stringify(value) : String(value) - url.searchParams.set(key, encoded) + payload[key] = value } }) - return fetchJson(url) + return fetchJson(url, { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(payload), + }) } export const fetchPreviewTrace = async (traceId: string) => { diff --git a/web/oss/src/services/vault/api/index.ts b/web/oss/src/services/vault/api/index.ts index 86cb078a9c..09acc739f6 100644 --- a/web/oss/src/services/vault/api/index.ts +++ b/web/oss/src/services/vault/api/index.ts @@ -1,6 +1,6 @@ import axios from "@/oss/lib/api/assets/axiosConfig" -import {transformSecret} from "@/oss/lib/helpers/llmProviders" import {getAgentaApiUrl} from "@/oss/lib/helpers/api" +import {transformSecret} from "@/oss/lib/helpers/llmProviders" import {CustomSecretDTO, StandardSecretDTO} from "@/oss/lib/Types" import {getProjectValues} from "@/oss/state/project" @@ -14,7 +14,7 @@ import {getProjectValues} from "@/oss/state/project" export const fetchVaultSecret = async () => { const {projectId} = getProjectValues() const response = await axios.get( - `${getAgentaApiUrl()}/vault/v1/secrets?project_id=${projectId}`, + `${getAgentaApiUrl()}/vault/v1/secrets/?project_id=${projectId}`, ) return transformSecret(response.data as StandardSecretDTO[] | CustomSecretDTO[]) } @@ -22,7 +22,7 @@ export const fetchVaultSecret = async () => { export const createVaultSecret = async ({payload}: {payload: T}) => { const {projectId} = getProjectValues() const response = await axios.post( - `${getAgentaApiUrl()}/vault/v1/secrets?project_id=${projectId}`, + `${getAgentaApiUrl()}/vault/v1/secrets/?project_id=${projectId}`, payload, ) return response.data as T diff --git a/web/oss/src/state/app/selectors/app.ts b/web/oss/src/state/app/selectors/app.ts index 8a0eef38bb..577878e99f 100644 --- a/web/oss/src/state/app/selectors/app.ts +++ b/web/oss/src/state/app/selectors/app.ts @@ -55,3 +55,22 @@ export const shouldRenderPlaygroundAtom = eagerAtom((get) => { if (isLoading) return true return Boolean(isUp) }) + +/** + * Current app context - provides full context for current app + * Used by: Components that need current app info + */ +export const currentAppContextAtom = eagerAtom((get) => { + const currentApp = get(currentAppAtom) + const selectedId = get(selectedAppIdAtom) + const {isLoading} = get(appsQueryAtom) + + return { + app: currentApp, + appId: selectedId, + appName: currentApp?.app_name || null, + appType: currentApp?.app_type || null, + hasApp: !!currentApp, + loading: isLoading, + } +}) diff --git a/web/oss/src/state/newApps/api/apps.ts b/web/oss/src/state/newApps/api/apps.ts deleted file mode 100644 index 36c4d399e4..0000000000 --- a/web/oss/src/state/newApps/api/apps.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * New Apps API Layer - * - * Optimized API functions for app management with: - * - Consistent error handling - * - Type safety with proper interfaces - * - Project-scoped operations - * - API endpoints: /apps and /apps/{appId} - */ - -import axios from "@/oss/lib/api/assets/axiosConfig" -import {fetchJson, getBaseUrl} from "@/oss/lib/api/assets/fetchClient" -import {getAgentaApiUrl} from "@/oss/lib/helpers/api" -import {ListAppsItem} from "@/oss/lib/Types" - -import {getProjectId} from "../../utils/projectUtils" - -/** - * Fetch all apps for the current project - * Used by: App table, app selector - */ -export const fetchProjectApps = async (projectId?: string): Promise => { - const project = projectId || getProjectId() - if (!project) { - throw new Error("Project ID is required to fetch apps") - } - - // Support test mode - const isTestMode = typeof process !== "undefined" && process.env.VITEST_TEST_API_URL - const testApiUrl = process.env.VITEST_TEST_API_URL - const testProjectId = process.env.VITEST_TEST_PROJECT_ID - - console.log("🧪 Apps fetcher debug:", { - base: isTestMode ? testApiUrl : getBaseUrl(), - project: isTestMode ? testProjectId : project, - isTestMode, - }) - - const base = isTestMode ? testApiUrl : getBaseUrl() - const finalProjectId = isTestMode ? testProjectId : project - const urlString = `${base}/apps?project_id=${finalProjectId}` - const url = new URL(urlString) - - console.log("🚀 Calling fetchJson with URL:", url.toString()) - - const data = await fetchJson(url) - console.log("✅ Apps fetcher success:", {count: data?.length || 0}) - return data || [] -} - -/** - * Fetch a single app by ID - * Used by: App selector, app details - */ -export const fetchAppById = async ( - appId: string, - projectId?: string, -): Promise => { - const project = projectId || getProjectId() - if (!project) { - throw new Error("Project ID is required to fetch app") - } - - const response = await fetch(`${getAgentaApiUrl()}/apps/${appId}?project_id=${project}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - - if (!response.ok) { - if (response.status === 404) { - return null // App not found - } - throw new Error(`Failed to fetch app: ${response.status} ${response.statusText}`) - } - - return response.json() -} - -/** - * Create a new app - * Used by: Create app modal, template creation - */ -export interface CreateAppRequest { - app_name: string - app_type?: string - project_id?: string -} - -export const createApp = async (request: CreateAppRequest): Promise => { - const project = request.project_id || getProjectId() - if (!project) { - throw new Error("Project ID is required to create app") - } - - const payload = { - ...request, - project_id: project, - } - - const response = await axios.post(`${getAgentaApiUrl()}/apps`, payload) - - return response.data -} - -/** - * Delete an app - * Used by: Delete app modal - */ -export const deleteApp = async (appId: string, projectId?: string): Promise => { - const project = projectId || getProjectId() - if (!project) { - throw new Error("Project ID is required to delete app") - } - - const response = await fetch(`${getAgentaApiUrl()}/apps/${appId}?project_id=${project}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }) - - if (!response.ok) { - throw new Error(`Failed to delete app: ${response.status} ${response.statusText}`) - } -} - -/** - * Update an app - * Used by: Edit app modal - */ -export interface UpdateAppRequest { - app_name?: string - app_type?: string -} - -export const updateApp = async ( - appId: string, - request: UpdateAppRequest, - projectId?: string, -): Promise => { - const project = projectId || getProjectId() - if (!project) { - throw new Error("Project ID is required to update app") - } - - const response = await fetch(`${getAgentaApiUrl()}/apps/${appId}?project_id=${project}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(request), - }) - - if (!response.ok) { - throw new Error(`Failed to update app: ${response.status} ${response.statusText}`) - } - - return response.json() -} diff --git a/web/oss/src/state/newApps/atoms/mutations.ts b/web/oss/src/state/newApps/atoms/mutations.ts deleted file mode 100644 index fdb4461e08..0000000000 --- a/web/oss/src/state/newApps/atoms/mutations.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * New Apps Mutation Atoms - * - * Optimized Jotai atoms for app mutations with: - * - React Query mutations via jotai-tanstack-query - * - Automatic cache invalidation - * - Optimistic updates where appropriate - * - Error handling and loading states - */ - -import {atom} from "jotai" -import {selectAtom} from "jotai/utils" -import {eagerAtom} from "jotai-eager" -import {atomWithMutation} from "jotai-tanstack-query" - -import {createApp, deleteApp, updateApp, CreateAppRequest, UpdateAppRequest} from "../api/apps" - -import {selectedAppIdAtom} from "./queries" - -/** - * Create app mutation - * Used by: Create app modal, template creation - */ -export const createAppMutationAtom = atomWithMutation(() => ({ - mutationFn: async (request: CreateAppRequest) => { - return createApp(request) - }, - onSuccess: (data, variables, context) => { - // Invalidate apps list to refetch with new app - const queryClient = context?.queryClient - if (queryClient) { - queryClient.invalidateQueries({queryKey: ["apps"]}) - } - - console.log("✅ App created successfully:", data.app_name) - }, - onError: (error) => { - console.error("❌ Failed to create app:", error) - }, -})) - -/** - * Delete app mutation - * Used by: Delete app modal - */ -export const deleteAppMutationAtom = atomWithMutation((get) => ({ - mutationFn: async (appId: string) => { - await deleteApp(appId) - return appId - }, - onSuccess: (deletedAppId, variables, context) => { - // Invalidate apps list to refetch without deleted app - const queryClient = context?.queryClient - if (queryClient) { - queryClient.invalidateQueries({queryKey: ["apps"]}) - } - - // Clear selection if deleted app was selected - const selectedId = get(selectedAppIdAtom) - if (selectedId === deletedAppId) { - // Note: This will be handled by the component using the mutation - } - - console.log("✅ App deleted successfully:", deletedAppId) - }, - onError: (error) => { - console.error("❌ Failed to delete app:", error) - }, -})) - -/** - * Update app mutation - * Used by: Edit app modal - */ -export const updateAppMutationAtom = atomWithMutation(() => ({ - mutationFn: async ({appId, request}: {appId: string; request: UpdateAppRequest}) => { - return updateApp(appId, request) - }, - onSuccess: (data, variables, context) => { - // Invalidate apps list to refetch with updated app - const queryClient = context?.queryClient - if (queryClient) { - queryClient.invalidateQueries({queryKey: ["apps"]}) - } - - console.log("✅ App updated successfully:", data.app_name) - }, - onError: (error) => { - console.error("❌ Failed to update app:", error) - }, -})) - -/** - * Switch app mutation (for app selection) - * Used by: App selector, navigation - */ -export const switchAppMutationAtom = atom(null, (get, set, appId: string | null) => { - // Update selected app ID - set(selectedAppIdAtom, appId) - - // Log the switch for debugging - if (appId) { - console.log("🔄 Switched to app:", appId) - } else { - console.log("🔄 Cleared app selection") - } - - // Return success indicator - return Promise.resolve({success: true, appId}) -}) - -/** - * Mutation loading states for UI feedback - using selectAtom for performance - */ -export const createAppLoadingAtom = selectAtom( - createAppMutationAtom, - (mutation) => mutation.isPending, -) -export const deleteAppLoadingAtom = selectAtom( - deleteAppMutationAtom, - (mutation) => mutation.isPending, -) -export const updateAppLoadingAtom = selectAtom( - updateAppMutationAtom, - (mutation) => mutation.isPending, -) - -/** - * Combined loading state for any app operation - eager evaluation - */ -export const anyAppMutationLoadingAtom = eagerAtom((get) => { - return get(createAppLoadingAtom) || get(deleteAppLoadingAtom) || get(updateAppLoadingAtom) -}) - -/** - * Mutation error states - using selectAtom for performance - */ -export const createAppErrorAtom = selectAtom(createAppMutationAtom, (mutation) => mutation.error) -export const deleteAppErrorAtom = selectAtom(deleteAppMutationAtom, (mutation) => mutation.error) -export const updateAppErrorAtom = selectAtom(updateAppMutationAtom, (mutation) => mutation.error) - -/** - * Mutation success states - using selectAtom for performance - */ -export const createAppSuccessAtom = selectAtom( - createAppMutationAtom, - (mutation) => mutation.isSuccess, -) -export const deleteAppSuccessAtom = selectAtom( - deleteAppMutationAtom, - (mutation) => mutation.isSuccess, -) -export const updateAppSuccessAtom = selectAtom( - updateAppMutationAtom, - (mutation) => mutation.isSuccess, -) diff --git a/web/oss/src/state/newApps/atoms/queries.ts b/web/oss/src/state/newApps/atoms/queries.ts deleted file mode 100644 index e159798613..0000000000 --- a/web/oss/src/state/newApps/atoms/queries.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * New Apps Query Atoms - * - * Optimized Jotai atoms for app data fetching with: - * - React Query integration via jotai-tanstack-query - * - Smart caching strategies - * - Project-scoped queries - * - Derived atoms for common use cases - */ - -import deepEqual from "fast-deep-equal" -import {atom} from "jotai" -import {loadable, selectAtom, atomWithStorage} from "jotai/utils" -import {eagerAtom} from "jotai-eager" -import {atomWithQuery} from "jotai-tanstack-query" - -import {LS_APP_KEY} from "../../app/assets/constants" -import {projectIdAtom} from "../../project/selectors/project" -import {jwtReadyAtom} from "../../session/jwt" -import {stringStorage} from "../../utils/stringStorage" -import {fetchProjectApps} from "../api/apps" -import {sessionExistsAtom} from "@/oss/state/session" - -// Get project ID for queries -const currentProjectIdAtom = atom((get) => { - const projectId = get(projectIdAtom) - - // In test environment, fall back to environment variable - const isTestMode = typeof process !== "undefined" && process.env.VITEST_TEST_API_URL - if (isTestMode) { - return process.env.VITEST_TEST_PROJECT_ID || null - } - - return projectId -}) - -/** - * Main apps query atom - fetches all apps for the current project - * Optimized with caching for app management pages - */ -export const appsQueryAtom = atomWithQuery((get) => { - const projectId = get(currentProjectIdAtom) - const jwtReady = get(jwtReadyAtom) - const sessionExists = get(sessionExistsAtom) - - // Support test mode - const isTestMode = typeof process !== "undefined" && process.env.VITEST_TEST_API_URL - const testApiUrl = process.env.VITEST_TEST_API_URL - - console.log("🔍 Apps query test mode:", { - testApiUrl, - projectId, - enabled: !!(sessionExists && projectId && (jwtReady.data || isTestMode)), - }) - - return { - queryKey: ["apps", projectId], - queryFn: async () => { - try { - console.log("🌐 Apps query executing...") - const apps = await fetchProjectApps(projectId || undefined) - console.log("📱 Fetched apps:", apps.length) - return apps - } catch (error) { - console.error("❌ Apps query error:", error) - throw error - } - }, - enabled: !!(sessionExists && projectId && (jwtReady.data || isTestMode)), - staleTime: 60000, // Cache for 1 minute - apps don't change frequently - gcTime: 300000, // Keep in cache for 5 minutes - refetchOnWindowFocus: false, - refetchOnReconnect: true, - experimental_prefetchInRender: true, // Enable prefetch for better performance - } -}) - -/** - * Loadable wrapper for apps query - exposes {state, data, error} - * Used for non-blocking UI updates and loading states - */ -export const appsLoadableAtom = loadable(appsQueryAtom) - -/** - * Eager apps atom - provides immediate access to apps data - * Uses selectAtom with deepEqual for optimized re-renders - */ -const EmptyApps: any[] = [] -export const appsAtom = selectAtom( - appsQueryAtom, - (queryResult) => queryResult.data ?? EmptyApps, - deepEqual, -) - -/** - * Derived atom for app table data - eager evaluation for better performance - * Transforms raw app data into table-optimized format - */ -export const appTableDataAtom = eagerAtom((get) => { - const apps = get(appsAtom) - - if (!apps || apps.length === 0) { - return [] - } - - // Transform for table display with consistent sorting - return apps - .map((app) => ({ - key: app.app_id, - app_id: app.app_id, - app_name: app.app_name, - app_type: app.app_type || "custom", - updated_at: app.updated_at, - // Add computed fields for table - displayName: app.app_name, - typeTag: app.app_type || "custom", - })) - .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) -}) - -/** - * Derived atom for app selector options - eager evaluation - * Provides apps in selector-friendly format - */ -export const appSelectorOptionsAtom = eagerAtom((get) => { - const apps = get(appsAtom) - - if (!apps || apps.length === 0) { - return [] - } - - return apps.map((app) => ({ - value: app.app_id, - label: app.app_name, - app_type: app.app_type, - updated_at: app.updated_at, - })) -}) - -/** - * Current selected app ID atom - * Manages app selection state with localStorage persistence - */ -// Storage-backed app selection with test fallback -const selectedAppIdStorageAtom = atomWithStorage(LS_APP_KEY, null, stringStorage) - -export const selectedAppIdAtom = atom( - (get) => { - const stored = get(selectedAppIdStorageAtom) - if (!stored && typeof process !== "undefined" && process.env.NODE_ENV === "test") { - return process.env.VITEST_TEST_APP_ID || null - } - return stored - }, - (get, set, update: string | null) => { - set(selectedAppIdStorageAtom, update) - }, -) - -/** - * Current app atom - eager evaluation for immediate access - * Returns the full app object for the currently selected app - */ -export const currentAppAtom = eagerAtom((get) => { - const apps = get(appsAtom) - const selectedId = get(selectedAppIdAtom) - - if (!selectedId || !apps || apps.length === 0) { - return null - } - - return apps.find((app) => app.app_id === selectedId) || null -}) - -/** - * App loading states for UI feedback - using selectAtom for performance - */ -export const appsLoadingAtom = selectAtom(appsQueryAtom, (query) => query.isPending) -export const appsErrorAtom = selectAtom(appsQueryAtom, (query) => query.error) - -/** - * Apps count atom for dashboard/stats - eager evaluation - */ -export const appsCountAtom = eagerAtom((get) => { - const apps = get(appsAtom) - return apps?.length || 0 -}) - -/** - * Prefetch atom - mount once to eagerly fetch apps when dependencies are ready - * This enables non-blocking UI updates - */ -export const appsPrefetchAtom = atom((get) => { - const loadable = get(appsLoadableAtom) - return loadable -}) diff --git a/web/oss/src/state/newApps/atoms/skeleton-queries.ts b/web/oss/src/state/newApps/atoms/skeleton-queries.ts deleted file mode 100644 index 2b7284a72c..0000000000 --- a/web/oss/src/state/newApps/atoms/skeleton-queries.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Skeleton-Enhanced NewApps Query Atoms - * - * Enhanced versions of newApps atoms that provide skeleton data - * during loading states for better UX and immediate UI rendering - */ - -import deepEqual from "fast-deep-equal" -import {atom} from "jotai" -import {selectAtom} from "jotai/utils" -import {eagerAtom} from "jotai-eager" -import {atomWithQuery} from "jotai-tanstack-query" - -import {ListAppsItem} from "@/oss/lib/Types" -import {selectedProjectIdAtom} from "@/oss/state/app/atoms/app" -import {selectedAppIdAtom as persistedSelectedAppIdAtom} from "@/oss/state/app/atoms/app" - -// Skeleton system imports -import { - createSkeletonApps, - createSkeletonTableData, - createSkeletonSelectorOptions, - createSkeletonAppStats, - extractData, - hasSkeletonItems, -} from "../../skeleton/generators" -import { - atomWithSkeletonQuery, - skeletonLoadableAtom, - incrementalLoadingAtom, - skeletonLoadingStateAtom, -} from "../../skeleton/loadable" -import {SkeletonData} from "../../skeleton/types" -import {listApps} from "../api/apps" - -/** - * Enhanced apps query atom with skeleton data support - */ -export const appsSkeletonQueryAtom = atomWithSkeletonQuery( - (get) => ({ - queryKey: ["apps", get(selectedProjectIdAtom)], - queryFn: async () => { - const projectId = get(selectedProjectIdAtom) - if (!projectId) return [] - return await listApps() - }, - enabled: (get) => !!get(selectedProjectIdAtom), - staleTime: 5 * 60 * 1000, // 5 minutes - experimental_prefetchInRender: true, - }), - createSkeletonApps, - { - count: 8, // Show 8 skeleton items initially - realisticValues: true, - includeNested: true, - priority: "high", - }, -) - -/** - * Eager apps atom that provides immediate access to apps data or skeleton - */ -export const appsSkeletonAtom = eagerAtom((get) => { - const skeletonData = get(appsSkeletonQueryAtom) - return extractData(skeletonData) -}) - -/** - * Enhanced table data atom with skeleton support - */ -export const appTableSkeletonDataAtom = eagerAtom((get) => { - const skeletonData = get(appsSkeletonQueryAtom) - const apps = extractData(skeletonData) - - // If we have skeleton data, return skeleton table data - if (skeletonData.meta.isSkeleton) { - return createSkeletonTableData({ - count: 8, - realisticValues: true, - priority: "high", - }) - } - - // Transform real data to table format - return apps.map((app: ListAppsItem, index: number) => ({ - key: app.app_id, - app_id: app.app_id, - app_name: app.app_name, - app_type: app.app_type, - updated_at: app.updated_at, - displayName: app.app_name, - typeTag: app.app_type, - })) -}) - -/** - * Enhanced selector options atom with skeleton support - */ -export const appSelectorSkeletonOptionsAtom = eagerAtom((get) => { - const skeletonData = get(appsSkeletonQueryAtom) - const apps = extractData(skeletonData) - - // If we have skeleton data, return skeleton options - if (skeletonData.meta.isSkeleton) { - return createSkeletonSelectorOptions({ - count: 5, - realisticValues: true, - priority: "high", - }) - } - - // Transform real data to selector options - return apps.map((app: ListAppsItem) => ({ - value: app.app_id, - label: app.app_name, - app_type: app.app_type, - updated_at: app.updated_at, - })) -}) - -/** - * Enhanced current app atom with skeleton support - */ -export const currentAppSkeletonAtom = eagerAtom((get) => { - const selectedId = get(persistedSelectedAppIdAtom) - const skeletonData = get(appsSkeletonQueryAtom) - const apps = extractData(skeletonData) - - if (!selectedId) return null - - // If we have skeleton data, return skeleton current app - if (skeletonData.meta.isSkeleton) { - const skeletonApps = createSkeletonApps({count: 1, realisticValues: true}) - return skeletonApps[0] || null - } - - // Find real current app - return apps.find((app: ListAppsItem) => app.app_id === selectedId) || null -}) - -/** - * Enhanced loading state atom - */ -export const appsSkeletonLoadingAtom = skeletonLoadingStateAtom(appsSkeletonQueryAtom) - -/** - * Enhanced error state atom - */ -export const appsSkeletonErrorAtom = selectAtom( - appsSkeletonQueryAtom, - (skeletonData) => { - // Don't show errors during skeleton loading - if (skeletonData.meta.isSkeleton) return null - return null // Real error handling would go here - }, - deepEqual, -) - -/** - * Enhanced count atom - */ -export const appsSkeletonCountAtom = selectAtom(appsSkeletonQueryAtom, (skeletonData) => { - const apps = extractData(skeletonData) - return Array.isArray(apps) ? apps.length : 0 -}) - -/** - * Enhanced app stats atom with skeleton support - */ -export const appStatsSkeletonAtom = eagerAtom((get) => { - const skeletonData = get(appsSkeletonQueryAtom) - const apps = extractData(skeletonData) - const count = get(appsSkeletonCountAtom) - - // If we have skeleton data, return skeleton stats - if (skeletonData.meta.isSkeleton) { - return createSkeletonAppStats() - } - - // Calculate real stats - const byType = apps.reduce( - (acc: Record, app: any) => { - const type = app.app_type || "custom" - acc[type] = (acc[type] || 0) + 1 - return acc - }, - {} as Record, - ) - - // Get recently updated apps (last 7 days) - const sevenDaysAgo = new Date() - sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7) - - const recentlyUpdated = apps - .filter((app: any) => new Date(app.updated_at) > sevenDaysAgo) - .sort( - (a: any, b: any) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(), - ) - .slice(0, 5) - - return { - total: count, - byType, - recentlyUpdated, - loading: false, - error: null, - } -}) - -/** - * Incremental loading atom that combines multiple data sources - */ -export const appsIncrementalAtom = incrementalLoadingAtom( - appsSkeletonQueryAtom, - [], // Additional atoms can be added here for nested data - createSkeletonApps, - { - preserveSkeletonStructure: true, - mergeStrategy: "merge", - }, -) - -/** - * Utility atom to check if any data is still in skeleton state - */ -export const hasSkeletonDataAtom = eagerAtom((get) => { - const tableData = get(appTableSkeletonDataAtom) - const selectorOptions = get(appSelectorSkeletonOptionsAtom) - - return hasSkeletonItems(tableData) || hasSkeletonItems(selectorOptions) -}) - -/** - * Progress atom for loading indicators - */ -export const appsLoadingProgressAtom = selectAtom( - appsSkeletonLoadingAtom, - (loadingState) => loadingState.progress || 0, -) diff --git a/web/oss/src/state/newApps/index.ts b/web/oss/src/state/newApps/index.ts deleted file mode 100644 index dbbe0f2c8b..0000000000 --- a/web/oss/src/state/newApps/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * New Apps State Management - Main Export - * - * Centralized exports for the new optimized apps state management system. - * This follows the same pattern as newVariants for consistency. - */ - -// API functions -export * from "./api/apps" - -// Query atoms -export * from "./atoms/queries" - -// Mutation atoms -export * from "./atoms/mutations" - -// High-level selectors -export * from "./selectors/apps" - -// Type re-exports for convenience -export type {ListAppsItem} from "@/oss/lib/Types" diff --git a/web/oss/src/state/newApps/selectors/apps.ts b/web/oss/src/state/newApps/selectors/apps.ts deleted file mode 100644 index 4e05b0f485..0000000000 --- a/web/oss/src/state/newApps/selectors/apps.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * New Apps Selector Atoms - * - * High-level selector atoms that combine query and mutation atoms - * for common app management use cases: - * - App table data with loading states - * - App selector with current selection - * - App management actions - */ - -import {atom} from "jotai" -import {eagerAtom} from "jotai-eager" - -import {ListAppsItem} from "@/oss/lib/Types" - -import { - createAppMutationAtom, - deleteAppMutationAtom, - updateAppMutationAtom, - switchAppMutationAtom, - anyAppMutationLoadingAtom, -} from "../atoms/mutations" -import { - appsAtom, - appsQueryAtom, - appTableDataAtom, - appSelectorOptionsAtom, - currentAppAtom, - selectedAppIdAtom, - appsLoadingAtom, - appsErrorAtom, - appsCountAtom, -} from "../atoms/queries" - -/** - * App table selector - combines data and loading states - * Used by: AppTable component - */ -export const appTableSelectorAtom = eagerAtom((get) => { - const data = get(appTableDataAtom) - const loading = get(appsLoadingAtom) - const error = get(appsErrorAtom) - const mutationLoading = get(anyAppMutationLoadingAtom) - - return { - data, - loading: loading || mutationLoading, - error, - isEmpty: !loading && !error && Array.isArray(data) && data.length === 0, - } -}) - -/** - * App selector state - combines options and current selection - * Used by: App selector dropdown - */ -export const appSelectorStateAtom = eagerAtom((get) => { - const options = get(appSelectorOptionsAtom) - const selectedId = get(selectedAppIdAtom) - const currentApp = get(currentAppAtom) - const loading = get(appsLoadingAtom) - - return { - options, - selectedId, - currentApp, - loading, - hasSelection: !!selectedId, - } -}) - -/** - * App management actions - combines all mutation atoms - * Used by: App management components - */ -export const appManagementActionsAtom = atom((get) => { - const createMutation = get(createAppMutationAtom) - const deleteMutation = get(deleteAppMutationAtom) - const updateMutation = get(updateAppMutationAtom) - const switchAction = get(switchAppMutationAtom) - - return { - createApp: createMutation.mutate, - deleteApp: deleteMutation.mutate, - updateApp: updateMutation.mutate, - switchApp: (appId: string | null) => switchAction, - - // Loading states - isCreating: createMutation.isPending, - isDeleting: deleteMutation.isPending, - isUpdating: updateMutation.isPending, - - // Error states - createError: createMutation.error, - deleteError: deleteMutation.error, - updateError: updateMutation.error, - - // Success states - createSuccess: createMutation.isSuccess, - deleteSuccess: deleteMutation.isSuccess, - updateSuccess: updateMutation.isSuccess, - } -}) - -/** - * App stats selector - for dashboard/overview - * Used by: Dashboard, app management overview - */ -export const appStatsAtom = eagerAtom((get) => { - const apps = get(appsAtom) - const count = get(appsCountAtom) - const loading = get(appsLoadingAtom) - const error = get(appsErrorAtom) - - if (loading || error || !apps || apps.length === 0) { - return { - total: 0, - byType: {}, - recentlyUpdated: [], - loading, - error, - } - } - - // Calculate stats - const byType = apps.reduce( - (acc: Record, app: any) => { - const type = app.app_type || "custom" - acc[type] = (acc[type] || 0) + 1 - return acc - }, - {} as Record, - ) - - // Get recently updated apps (last 7 days) - const sevenDaysAgo = new Date() - sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7) - - const recentlyUpdated = apps - .filter((app: any) => new Date(app.updated_at) > sevenDaysAgo) - .sort( - (a: any, b: any) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(), - ) - .slice(0, 5) // Top 5 most recent - - return { - total: count, - byType, - recentlyUpdated, - loading: false, - error: null, - } -}) - -/** - * Current app context - provides full context for current app - * Used by: Components that need current app info - */ -export const currentAppContextAtom = eagerAtom((get) => { - const currentApp = get(currentAppAtom) - const selectedId = get(selectedAppIdAtom) - const loading = get(appsLoadingAtom) - - return { - app: currentApp, - appId: selectedId, - appName: currentApp?.app_name || null, - appType: currentApp?.app_type || null, - hasApp: !!currentApp, - loading, - } -}) - -/** - * Re-export commonly used atoms for convenience - */ -export { - // Query atoms - appsAtom, - appsQueryAtom, - currentAppAtom, - selectedAppIdAtom, - appsLoadingAtom, - appsErrorAtom, - - // Mutation atoms - createAppMutationAtom, - deleteAppMutationAtom, - updateAppMutationAtom, - switchAppMutationAtom, -} diff --git a/web/oss/src/state/newApps/selectors/skeleton-apps.ts b/web/oss/src/state/newApps/selectors/skeleton-apps.ts deleted file mode 100644 index 4f4d950a98..0000000000 --- a/web/oss/src/state/newApps/selectors/skeleton-apps.ts +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Skeleton-Enhanced App Selectors - * - * High-level selector atoms that provide skeleton data during loading - * for immediate UI rendering and better perceived performance - */ - -import deepEqual from "fast-deep-equal" -import {selectAtom} from "jotai/utils" -import {eagerAtom} from "jotai-eager" - -import {hasSkeletonItems} from "../../skeleton/generators" -import {anyAppMutationLoadingAtom} from "../atoms/mutations" -import { - appsSkeletonAtom, - appTableSkeletonDataAtom, - appSelectorSkeletonOptionsAtom, - currentAppSkeletonAtom, - appsSkeletonLoadingAtom, - appsSkeletonErrorAtom, - appsSkeletonCountAtom, - appStatsSkeletonAtom, - hasSkeletonDataAtom, - appsLoadingProgressAtom, -} from "../atoms/skeleton-queries" - -/** - * Enhanced app table selector with skeleton support - * Provides immediate rendering with skeleton data - */ -export const appTableSkeletonSelectorAtom = eagerAtom((get) => { - const data = get(appTableSkeletonDataAtom) - const loadingState = get(appsSkeletonLoadingAtom) - const error = get(appsSkeletonErrorAtom) - const mutationLoading = get(anyAppMutationLoadingAtom) - const hasSkeletons = hasSkeletonItems(data) - - return { - data, - loading: loadingState.isLoading || mutationLoading, - isSkeleton: loadingState.isSkeleton, - loadingStage: loadingState.loadingStage, - progress: get(appsLoadingProgressAtom), - error, - isEmpty: !loadingState.isLoading && !error && !hasSkeletons && data.length === 0, - hasSkeletonItems: hasSkeletons, - } -}) - -/** - * Enhanced app selector state with skeleton support - */ -export const appSelectorSkeletonStateAtom = eagerAtom((get) => { - const options = get(appSelectorSkeletonOptionsAtom) - const currentApp = get(currentAppSkeletonAtom) - const loadingState = get(appsSkeletonLoadingAtom) - const hasSkeletons = hasSkeletonItems(options) - - // For skeleton state, use first skeleton option as selected - const selectedId = currentApp?.app_id || (hasSkeletons ? options[0]?.value : null) - - return { - options, - selectedId, - currentApp, - loading: loadingState.isLoading, - isSkeleton: loadingState.isSkeleton, - loadingStage: loadingState.loadingStage, - hasSelection: !!selectedId, - hasSkeletonItems: hasSkeletons, - } -}) - -/** - * Enhanced app management actions with skeleton awareness - */ -export const appManagementSkeletonActionsAtom = eagerAtom((get) => { - const loadingState = get(appsSkeletonLoadingAtom) - const mutationLoading = get(anyAppMutationLoadingAtom) - const hasSkeletons = get(hasSkeletonDataAtom) - - return { - canCreate: !loadingState.isSkeleton && !mutationLoading, - canDelete: !loadingState.isSkeleton && !mutationLoading, - canUpdate: !loadingState.isSkeleton && !mutationLoading, - canSwitch: !hasSkeletons, // Allow switching even during partial loading - isLoading: loadingState.isLoading || mutationLoading, - isSkeleton: loadingState.isSkeleton, - loadingStage: loadingState.loadingStage, - } -}) - -/** - * Enhanced app stats selector with skeleton support - */ -export const appStatsSkeletonSelectorAtom = eagerAtom((get) => { - const stats = get(appStatsSkeletonAtom) - const loadingState = get(appsSkeletonLoadingAtom) - - return { - ...stats, - isSkeleton: loadingState.isSkeleton, - loadingStage: loadingState.loadingStage, - progress: get(appsLoadingProgressAtom), - } -}) - -/** - * Enhanced current app context with skeleton support - */ -export const currentAppSkeletonContextAtom = eagerAtom((get) => { - const currentApp = get(currentAppSkeletonAtom) - const loadingState = get(appsSkeletonLoadingAtom) - - return { - app: currentApp, - appId: currentApp?.app_id || null, - appName: currentApp?.app_name || null, - appType: currentApp?.app_type || null, - hasApp: !!currentApp, - loading: loadingState.isLoading, - isSkeleton: loadingState.isSkeleton, - loadingStage: loadingState.loadingStage, - } -}) - -/** - * Progressive loading indicator atom - * Provides detailed loading progress for complex UI states - */ -export const progressiveLoadingAtom = eagerAtom((get) => { - const loadingState = get(appsSkeletonLoadingAtom) - const tableData = get(appTableSkeletonDataAtom) - const selectorOptions = get(appSelectorSkeletonOptionsAtom) - const stats = get(appStatsSkeletonAtom) - - // Calculate component-level loading states - const components = { - table: { - loaded: !hasSkeletonItems(tableData), - progress: hasSkeletonItems(tableData) ? 30 : 100, - }, - selector: { - loaded: !hasSkeletonItems(selectorOptions), - progress: hasSkeletonItems(selectorOptions) ? 60 : 100, - }, - stats: { - loaded: !stats._skeleton?.isLoading, - progress: stats._skeleton?.isLoading ? 80 : 100, - }, - } - - // Calculate overall progress - const totalProgress = - Object.values(components).reduce((sum, component) => sum + component.progress, 0) / - Object.keys(components).length - - return { - overall: { - progress: totalProgress, - isComplete: totalProgress === 100, - stage: loadingState.loadingStage, - }, - components, - isSkeleton: loadingState.isSkeleton, - } -}) - -/** - * Smart refresh atom that handles skeleton states - */ -export const smartRefreshAtom = eagerAtom((get) => { - const loadingState = get(appsSkeletonLoadingAtom) - const hasSkeletons = get(hasSkeletonDataAtom) - - return { - canRefresh: !loadingState.isSkeleton || loadingState.loadingStage === "partial", - shouldShowRefreshButton: !hasSkeletons, - refreshInProgress: loadingState.isLoading && loadingState.loadingStage === "initial", - } -}) - -/** - * Export all atoms for easy consumption - */ -export { - // Core skeleton atoms - appsSkeletonAtom, - appTableSkeletonDataAtom, - appSelectorSkeletonOptionsAtom, - currentAppSkeletonAtom, - appsSkeletonLoadingAtom, - appsSkeletonErrorAtom, - appsSkeletonCountAtom, - appStatsSkeletonAtom, - hasSkeletonDataAtom, - appsLoadingProgressAtom, -} diff --git a/web/oss/src/state/newPlayground/core/prompts.ts b/web/oss/src/state/newPlayground/core/prompts.ts index e9d93544c2..990101320f 100644 --- a/web/oss/src/state/newPlayground/core/prompts.ts +++ b/web/oss/src/state/newPlayground/core/prompts.ts @@ -9,7 +9,7 @@ import {generateId} from "@/oss/lib/shared/variant/stringUtils" import {derivePromptsFromSpec} from "@/oss/lib/shared/variant/transformer/transformer" import {transformToRequestBody} from "@/oss/lib/shared/variant/transformer/transformToRequestBody" import type {AgentaConfigPrompt, EnhancedVariant} from "@/oss/lib/shared/variant/transformer/types" -import {currentAppContextAtom} from "@/oss/state/newApps/selectors/apps" +import {currentAppContextAtom} from "@/oss/state/app/selectors/app" import { appSchemaAtom, appUriInfoAtom, diff --git a/web/oss/src/state/newPlayground/core/variantFlags.ts b/web/oss/src/state/newPlayground/core/variantFlags.ts index d2dc405441..1f11ac4ae6 100644 --- a/web/oss/src/state/newPlayground/core/variantFlags.ts +++ b/web/oss/src/state/newPlayground/core/variantFlags.ts @@ -1,7 +1,7 @@ import {atom} from "jotai" import {atomFamily} from "jotai/utils" -import {currentAppContextAtom} from "@/oss/state/newApps/selectors/apps" +import {currentAppContextAtom} from "@/oss/state/app/selectors/app" import {requestSchemaMetaAtomFamily} from "@/oss/state/newPlayground/core/requestSchemaMeta" import {getEnhancedRevisionById} from "../../variant/atoms/fetcher" diff --git a/web/oss/src/state/newPlayground/mutations/webWorkerIntegration.ts b/web/oss/src/state/newPlayground/mutations/webWorkerIntegration.ts index 8d39f6cd99..6aab8d9b36 100644 --- a/web/oss/src/state/newPlayground/mutations/webWorkerIntegration.ts +++ b/web/oss/src/state/newPlayground/mutations/webWorkerIntegration.ts @@ -28,7 +28,7 @@ import { messageSchemaMetadataAtom, } from "@/oss/state/generation/entities" import {inputRowAtomFamily, rowVariablesAtomFamily} from "@/oss/state/generation/selectors" -import {currentAppContextAtom} from "@/oss/state/newApps/selectors/apps" +import {currentAppContextAtom} from "@/oss/state/app/selectors/app" import {customPropertiesByRevisionAtomFamily} from "@/oss/state/newPlayground/core/customProperties" import {promptsAtomFamily, promptVariablesAtomFamily} from "@/oss/state/newPlayground/core/prompts" import {variantFlagsAtomFamily} from "@/oss/state/newPlayground/core/variantFlags" diff --git a/web/oss/src/state/skeleton/examples.tsx b/web/oss/src/state/skeleton/examples.tsx deleted file mode 100644 index 0699b1082a..0000000000 --- a/web/oss/src/state/skeleton/examples.tsx +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Skeleton Data System Usage Examples - * - * Demonstrates how to use the skeleton data system in React components - * for better loading states and incremental updates - */ - -import React from "react" - -import {Skeleton, Table, Select, Card, Progress} from "antd" -import {useAtomValue} from "jotai" - -// Import skeleton-enhanced atoms -import { - appTableSkeletonSelectorAtom, - appSelectorSkeletonStateAtom, - appStatsSkeletonSelectorAtom, - progressiveLoadingAtom, - smartRefreshAtom, -} from "../newApps/selectors/skeleton-apps" - -/** - * Example: Enhanced App Table with Skeleton Support - * - * This table renders immediately with skeleton data, then progressively - * enhances as real data loads - */ -export const SkeletonAppTable: React.FC = () => { - const tableState = useAtomValue(appTableSkeletonSelectorAtom) - - const columns = [ - { - title: "App Name", - dataIndex: "app_name", - key: "app_name", - render: (text: string, record: any) => { - // Show skeleton for skeleton data - if (record._skeleton?.isLoading) { - return - } - return text - }, - }, - { - title: "Type", - dataIndex: "app_type", - key: "app_type", - render: (text: string, record: any) => { - if (record._skeleton?.isLoading) { - return - } - return {text} - }, - }, - { - title: "Updated", - dataIndex: "updated_at", - key: "updated_at", - render: (text: string, record: any) => { - if (record._skeleton?.isLoading) { - return - } - return new Date(text).toLocaleDateString() - }, - }, - { - title: "Actions", - key: "actions", - render: (_: any, record: any) => { - if (record._skeleton?.isLoading) { - return - } - return ( -
- - -
- ) - }, - }, - ] - - return ( -
- {/* Progressive loading indicator */} - {tableState.isSkeleton && ( - - )} - - - - {tableState.isEmpty &&
No apps found
} - - ) -} - -/** - * Example: Enhanced App Selector with Skeleton Support - */ -export const SkeletonAppSelector: React.FC = () => { - const selectorState = useAtomValue(appSelectorSkeletonStateAtom) - - return ( - - ) -} - -/** - * Example: Enhanced App Stats with Skeleton Support - */ -export const SkeletonAppStats: React.FC = () => { - const stats = useAtomValue(appStatsSkeletonSelectorAtom) - - return ( -
- -
- Total Apps: - {stats.isSkeleton ? ( - - ) : ( - {stats.total} - )} -
- -
- By Type: - {stats.isSkeleton ? ( -
- - -
- ) : ( -
- {Object.entries(stats.byType).map(([type, count]) => ( - - {type}: {count} - - ))} -
- )} -
- -
-

Recently Updated:

- {stats.recentlyUpdated.map((app: any, index: number) => ( -
- {app._skeleton?.isLoading ? ( - - ) : ( - {app.app_name} - )} -
- ))} -
-
-
- ) -} - -/** - * Example: Progressive Loading Dashboard - * - * Shows overall loading progress and component-level states - */ -export const ProgressiveLoadingDashboard: React.FC = () => { - const progressState = useAtomValue(progressiveLoadingAtom) - const refreshState = useAtomValue(smartRefreshAtom) - - return ( -
- - `${percent}% (${progressState.overall.stage})`} - /> - -
- {Object.entries(progressState.components).map(([name, component]) => ( -
- {name}: - -
- ))} -
- - {refreshState.shouldShowRefreshButton && ( - - )} -
-
- ) -} - -/** - * Example: Complete App Management Page with Skeleton Support - */ -export const SkeletonAppManagementPage: React.FC = () => { - return ( -
-
-

App Management

- -
- -
-
- -
- -
- - -
-
-
- ) -} - -/** - * CSS Classes for Skeleton Styling - */ -export const skeletonStyles = ` -.skeleton-table { - opacity: 0.8; -} - -.skeleton-table .ant-table-row { - animation: skeleton-pulse 1.5s ease-in-out infinite; -} - -@keyframes skeleton-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } -} - -.loading-dashboard .component-item { - display: flex; - align-items: center; - margin-bottom: 8px; -} - -.loading-dashboard .component-item span { - width: 80px; - margin-right: 12px; -} - -.app-stats .stat-item { - display: flex; - justify-content: space-between; - margin-bottom: 8px; -} - -.recent-app { - padding: 4px 0; - border-bottom: 1px solid #f0f0f0; -} -` diff --git a/web/oss/src/state/skeleton/generators.ts b/web/oss/src/state/skeleton/generators.ts deleted file mode 100644 index f6235751eb..0000000000 --- a/web/oss/src/state/skeleton/generators.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Skeleton Data Generators - * - * Functions to generate realistic skeleton data for different entity types - */ - -import {ListAppsItem} from "@/oss/lib/Types" - -import {SkeletonConfig, SkeletonGenerator, SkeletonData, SkeletonMetadata} from "./types" - -/** - * Creates skeleton metadata with default values - */ -export const createSkeletonMetadata = ( - stage: SkeletonMetadata["loadingStage"] = "initial", - priority: SkeletonMetadata["priority"] = "medium", -): SkeletonMetadata => ({ - isSkeleton: true, - loadingStage: stage, - priority, - timestamp: Date.now(), -}) - -/** - * Wraps data with skeleton metadata - */ -export const wrapWithSkeletonMeta = ( - data: T, - meta?: Partial, -): SkeletonData => ({ - data, - meta: { - ...createSkeletonMetadata(), - ...meta, - }, -}) - -/** - * Generates realistic skeleton app data - */ -export const createSkeletonApps: SkeletonGenerator = (config = {}) => { - const {count = 5, realisticValues = true, includeNested = true} = config - - const skeletonApps: ListAppsItem[] = [] - - for (let i = 0; i < count; i++) { - const app: ListAppsItem = { - app_id: `skeleton-app-${i}`, - app_name: realisticValues ? `Loading App ${i + 1}...` : "████████", - app_type: realisticValues ? "custom" : "████", - updated_at: new Date().toISOString(), - created_at: new Date().toISOString(), - // Add skeleton-specific properties if needed - ...(includeNested && { - // Add nested skeleton data for complex structures - _skeleton: { - isLoading: true, - stage: "initial", - }, - }), - } - skeletonApps.push(app) - } - - return skeletonApps -} - -/** - * Generates skeleton table data with realistic structure - */ -export const createSkeletonTableData = (config: SkeletonConfig = {}) => { - const apps = createSkeletonApps(config) - - return apps.map((app, index) => ({ - key: app.app_id, - app_id: app.app_id, - app_name: app.app_name, - app_type: app.app_type, - updated_at: app.updated_at, - displayName: app.app_name, - typeTag: app.app_type, - // Skeleton-specific UI properties - _skeleton: { - isLoading: true, - priority: index < 3 ? "high" : "medium", // First 3 items high priority - }, - })) -} - -/** - * Generates skeleton selector options - */ -export const createSkeletonSelectorOptions = (config: SkeletonConfig = {}) => { - const apps = createSkeletonApps(config) - - return apps.map((app) => ({ - value: app.app_id, - label: app.app_name, - app_type: app.app_type, - updated_at: app.updated_at, - _skeleton: { - isLoading: true, - }, - })) -} - -/** - * Generates skeleton app stats - */ -export const createSkeletonAppStats = () => ({ - total: 0, - byType: { - custom: 0, - template: 0, - }, - recentlyUpdated: createSkeletonApps({count: 3, realisticValues: true}), - loading: true, - error: null, - _skeleton: { - isLoading: true, - stage: "initial" as const, - }, -}) - -/** - * Utility to check if data is skeleton data - */ -export const isSkeletonData = (data: any): data is SkeletonData => { - return data && typeof data === "object" && data.meta?.isSkeleton === true -} - -/** - * Utility to extract data from skeleton wrapper - */ -export const extractData = (skeletonData: SkeletonData | T): T => { - if (isSkeletonData(skeletonData)) { - return skeletonData.data - } - return skeletonData as T -} - -/** - * Utility to check if any item in array has skeleton data - */ -export const hasSkeletonItems = (items: any[]): boolean => { - return items.some((item) => item._skeleton?.isLoading === true) -} diff --git a/web/oss/src/state/skeleton/index.ts b/web/oss/src/state/skeleton/index.ts deleted file mode 100644 index 03867d552a..0000000000 --- a/web/oss/src/state/skeleton/index.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Skeleton Data System - Main Export - * - * A comprehensive system for providing skeleton data during loading states - * to improve perceived performance and enable incremental UI updates - * - * @example Basic Usage - * ```typescript - * import { atomWithSkeletonQuery, createSkeletonApps } from '@/state/skeleton' - * - * const mySkeletonAtom = atomWithSkeletonQuery( - * queryOptions, - * createSkeletonApps, - * { count: 5, realisticValues: true } - * ) - * ``` - * - * @example Component Usage - * ```tsx - * const MyComponent = () => { - * const tableState = useAtomValue(appTableSkeletonSelectorAtom) - * - * return ( - *
- * ) - * } - * ``` - */ - -// Core types -export type { - SkeletonMetadata, - SkeletonData, - SkeletonConfig, - SkeletonGenerator, - IncrementalUpdateConfig, - LoadingState, -} from "./types" - -// Skeleton data generators -export { - createSkeletonMetadata, - wrapWithSkeletonMeta, - createSkeletonApps, - createSkeletonTableData, - createSkeletonSelectorOptions, - createSkeletonAppStats, - isSkeletonData, - extractData, - hasSkeletonItems, -} from "./generators" - -// Enhanced loadable atoms -export { - atomWithSkeletonQuery, - skeletonLoadableAtom, - incrementalLoadingAtom, - skeletonLoadingStateAtom, -} from "./loadable" - -// NewApps integration -export { - appsSkeletonQueryAtom, - appsSkeletonAtom, - appTableSkeletonDataAtom, - appSelectorSkeletonOptionsAtom, - currentAppSkeletonAtom, - appsSkeletonLoadingAtom, - appsSkeletonErrorAtom, - appsSkeletonCountAtom, - appStatsSkeletonAtom, - hasSkeletonDataAtom, - appsLoadingProgressAtom, -} from "../newApps/atoms/skeleton-queries" - -// Enhanced selectors -export { - appTableSkeletonSelectorAtom, - appSelectorSkeletonStateAtom, - appManagementSkeletonActionsAtom, - appStatsSkeletonSelectorAtom, - currentAppSkeletonContextAtom, - progressiveLoadingAtom, - smartRefreshAtom, -} from "../newApps/selectors/skeleton-apps" - -// React components and examples -export { - SkeletonAppTable, - SkeletonAppSelector, - SkeletonAppStats, - ProgressiveLoadingDashboard, - SkeletonAppManagementPage, - skeletonStyles, -} from "./examples" - -/** - * Migration Guide from Regular Atoms to Skeleton Atoms - * - * 1. Replace regular query atoms: - * - `appsAtom` → `appsSkeletonAtom` - * - `appTableDataAtom` → `appTableSkeletonDataAtom` - * - `appSelectorOptionsAtom` → `appSelectorSkeletonOptionsAtom` - * - * 2. Update selectors: - * - `appTableSelectorAtom` → `appTableSkeletonSelectorAtom` - * - `appSelectorStateAtom` → `appSelectorSkeletonStateAtom` - * - * 3. Update components to handle skeleton data: - * - Check `isSkeleton` property in state - * - Render skeleton UI for items with `_skeleton.isLoading` - * - Use progressive loading indicators - * - * 4. Benefits: - * - Immediate UI rendering with realistic placeholder data - * - Better perceived performance - * - Incremental loading with partial data updates - * - Graceful error handling with skeleton fallbacks - */ - -/** - * Performance Considerations - * - * - Skeleton data is generated once and cached - * - Uses eagerAtom for immediate evaluation - * - Minimal re-renders with deep equality checks - * - Progressive enhancement reduces blocking operations - * - Memory efficient with selective skeleton generation - */ - -/** - * Best Practices - * - * 1. Use realistic skeleton data that matches your UI structure - * 2. Implement progressive loading for complex nested data - * 3. Provide visual feedback for loading progress - * 4. Handle skeleton states in your components gracefully - * 5. Use incremental loading for better perceived performance - * 6. Test skeleton states in your component tests - */ diff --git a/web/oss/src/state/skeleton/loadable.ts b/web/oss/src/state/skeleton/loadable.ts deleted file mode 100644 index 39e16ec856..0000000000 --- a/web/oss/src/state/skeleton/loadable.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Enhanced Loadable Atoms with Skeleton Data - * - * Provides loadable atoms that return skeleton data during loading states - * for immediate UI rendering and better perceived performance - */ - -import deepEqual from "fast-deep-equal" -import {atom, Atom} from "jotai" -import {selectAtom} from "jotai/utils" -import {eagerAtom} from "jotai-eager" -import {atomWithQuery, AtomWithQueryOptions} from "jotai-tanstack-query" - -import {wrapWithSkeletonMeta, createSkeletonMetadata} from "./generators" -import { - SkeletonData, - SkeletonConfig, - SkeletonGenerator, - LoadingState, - IncrementalUpdateConfig, -} from "./types" - -/** - * Creates a query atom that returns skeleton data during loading - */ -export function atomWithSkeletonQuery( - queryOptions: AtomWithQueryOptions, - skeletonGenerator: SkeletonGenerator, - config: SkeletonConfig = {}, -) { - const baseQueryAtom = atomWithQuery(queryOptions) - - return eagerAtom((get) => { - const queryResult = get(baseQueryAtom) - - if (queryResult.isLoading && !queryResult.data) { - // Return skeleton data during initial loading - const skeletonData = skeletonGenerator(config) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: config.priority || "medium", - }) - } - - if (queryResult.isError) { - // Return skeleton data during error states for graceful degradation - const skeletonData = skeletonGenerator({...config, count: 1}) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: "low", - }) - } - - if (queryResult.data) { - // Return real data wrapped with completion metadata - return wrapWithSkeletonMeta(queryResult.data, { - isSkeleton: false, - loadingStage: "complete", - priority: "high", - timestamp: Date.now(), - }) - } - - // Fallback to skeleton data - const skeletonData = skeletonGenerator(config) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: config.priority || "medium", - }) - }) -} - -/** - * Creates a loadable atom that provides skeleton data during loading - */ -export function skeletonLoadableAtom( - sourceAtom: Atom, - skeletonGenerator: SkeletonGenerator, - config: SkeletonConfig = {}, -) { - return eagerAtom((get) => { - try { - const data = get(sourceAtom) - - // Check if source is still loading - if (data?.isLoading || data?.isPending) { - const skeletonData = skeletonGenerator(config) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: config.priority || "medium", - }) - } - - // Check if source has error - if (data?.isError || data?.error) { - const skeletonData = skeletonGenerator({...config, count: 1}) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: "low", - }) - } - - // Return real data if available - if (data?.data || Array.isArray(data)) { - const realData = data?.data || data - return wrapWithSkeletonMeta(realData, { - isSkeleton: false, - loadingStage: "complete", - priority: "high", - timestamp: Date.now(), - }) - } - - // Fallback to skeleton - const skeletonData = skeletonGenerator(config) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: config.priority || "medium", - }) - } catch (error) { - // Error handling - return skeleton data - const skeletonData = skeletonGenerator({...config, count: 1}) - return wrapWithSkeletonMeta(skeletonData, { - loadingStage: "initial", - priority: "low", - }) - } - }) -} - -/** - * Creates an incremental loading atom that merges partial data with skeleton - */ -export function incrementalLoadingAtom( - primaryAtom: Atom, - secondaryAtoms: Atom[], - skeletonGenerator: SkeletonGenerator, - mergeConfig: IncrementalUpdateConfig = {}, -) { - return eagerAtom((get) => { - const primary = get(primaryAtom) - const {preserveSkeletonStructure = true, mergeStrategy = "merge"} = mergeConfig - - // Start with skeleton data - let result = skeletonGenerator({count: 5, realisticValues: true}) - let loadingStage: "initial" | "partial" | "complete" = "initial" - let loadedCount = 0 - - // Check primary data - if (primary?.data && !primary.isLoading) { - result = primary.data - loadingStage = "partial" - loadedCount++ - } - - // Incrementally merge secondary data - secondaryAtoms.forEach((atom) => { - try { - const secondary = get(atom) - if (secondary?.data && !secondary.isLoading) { - if (mergeStrategy === "merge" && Array.isArray(result)) { - // Merge additional data into existing structure - result = mergePartialData(result, secondary.data, preserveSkeletonStructure) - } - loadedCount++ - } - } catch (error) { - // Continue with partial data if secondary fails - } - }) - - // Determine final loading stage - if (loadedCount === 0) { - loadingStage = "initial" - } else if (loadedCount < secondaryAtoms.length + 1) { - loadingStage = "partial" - } else { - loadingStage = "complete" - } - - return wrapWithSkeletonMeta(result, { - isSkeleton: loadingStage !== "complete", - loadingStage, - priority: loadingStage === "complete" ? "high" : "medium", - timestamp: Date.now(), - }) - }) -} - -/** - * Creates a loading state atom from skeleton data - */ -export function skeletonLoadingStateAtom( - skeletonDataAtom: Atom>, -): Atom { - return selectAtom( - skeletonDataAtom, - (skeletonData) => ({ - isLoading: skeletonData.meta.isSkeleton, - isSkeleton: skeletonData.meta.isSkeleton, - loadingStage: skeletonData.meta.loadingStage, - progress: calculateProgress(skeletonData.meta.loadingStage), - }), - deepEqual, - ) -} - -/** - * Utility to merge partial data with skeleton structure - */ -function mergePartialData( - skeletonData: T[], - partialData: Partial[], - preserveStructure: boolean, -): T[] { - if (!preserveStructure) { - return partialData as T[] - } - - // Merge partial data into skeleton structure - return skeletonData.map((skeleton, index) => { - const partial = partialData[index] - if (partial) { - return {...skeleton, ...partial} - } - return skeleton - }) -} - -/** - * Calculate loading progress based on stage - */ -function calculateProgress(stage: "initial" | "partial" | "complete"): number { - switch (stage) { - case "initial": - return 0 - case "partial": - return 50 - case "complete": - return 100 - default: - return 0 - } -} diff --git a/web/oss/src/state/skeleton/types.ts b/web/oss/src/state/skeleton/types.ts deleted file mode 100644 index c7209b9bd5..0000000000 --- a/web/oss/src/state/skeleton/types.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Skeleton Data System Types - * - * Provides type-safe skeleton data generation and management - * for better loading states and incremental updates - */ - -export interface SkeletonMetadata { - isSkeleton: boolean - loadingStage: "initial" | "partial" | "complete" - priority: "high" | "medium" | "low" - timestamp: number -} - -export interface SkeletonData { - data: T - meta: SkeletonMetadata -} - -export interface SkeletonConfig { - count?: number - priority?: SkeletonMetadata["priority"] - includeNested?: boolean - realisticValues?: boolean -} - -export type SkeletonGenerator = (config?: SkeletonConfig) => T[] - -export interface IncrementalUpdateConfig { - preserveSkeletonStructure?: boolean - mergeStrategy?: "replace" | "merge" | "append" - priorityOrder?: string[] -} - -export interface LoadingState { - isLoading: boolean - isSkeleton: boolean - loadingStage: SkeletonMetadata["loadingStage"] - progress?: number -} diff --git a/web/oss/src/state/testset/atoms/fetcher.ts b/web/oss/src/state/testset/atoms/fetcher.ts index 8ce793f886..46727a9c11 100644 --- a/web/oss/src/state/testset/atoms/fetcher.ts +++ b/web/oss/src/state/testset/atoms/fetcher.ts @@ -2,10 +2,16 @@ import {atomFamily} from "jotai/utils" import {atomWithQuery} from "jotai-tanstack-query" import {testset} from "@/oss/lib/Types" -import {fetchPreviewTestsets, fetchTestsets} from "@/oss/services/testsets/api" +import {fetchTestsets, fetchPreviewTestsets} from "@/oss/services/testsets/api" +import {PreviewTestsetsQueryPayload} from "@/oss/services/testsets/api/types" import {projectIdAtom} from "../../project" +// Local options type for enabling/disabling queries +interface TestsetsQueryOptions { + enabled?: boolean +} + /** * Atom for fetching regular/legacy testsets */ @@ -25,6 +31,31 @@ export const testsetsQueryAtom = atomWithQuery((get) => { } }) +/** + * Atom family for fetching preview testsets with filters + */ +export const previewTestsetsQueryAtomFamily = atomFamily( + ({ + payload = {}, + enabled = true, + }: {payload?: PreviewTestsetsQueryPayload; enabled?: boolean} = {}) => + atomWithQuery((get) => { + const projectId = get(projectIdAtom) + + const payloadKey = JSON.stringify(payload || {}) + + return { + queryKey: ["preview-testsets", projectId, payloadKey], + queryFn: () => fetchPreviewTestsets(payload), + staleTime: 1000 * 60, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + enabled: enabled && !!projectId, + } + }), +) + export const testsetsQueryAtomFamily = atomFamily(({enabled = true}: TestsetsQueryOptions = {}) => atomWithQuery((get) => { const projectId = get(projectIdAtom) diff --git a/web/oss/src/state/testset/hooks/index.ts b/web/oss/src/state/testset/hooks/index.ts new file mode 100644 index 0000000000..52e5ad05f5 --- /dev/null +++ b/web/oss/src/state/testset/hooks/index.ts @@ -0,0 +1,90 @@ +import {useMemo} from "react" + +import {useAtom} from "jotai" + +import {PreviewTestsetsQueryPayload} from "@/oss/services/testsets/api/types" + +import { + previewTestsetsQueryAtom, + previewTestsetsQueryAtomFamily, + testsetsQueryAtomFamily, +} from "../atoms/fetcher" + +export {useTestset, testsetQueryAtomFamily} from "./useTestset" + +/** + * Hook for regular/legacy testsets. + * + * @param options.enabled - Whether the query is enabled (default: true) + * @returns Object with `testsets`, `isError`, `error`, `isLoading`, `mutate` + */ +export const useTestsetsData = ({enabled = true} = {}) => { + const stableAtom = useMemo(() => testsetsQueryAtomFamily({enabled}), [enabled]) + const [{data: testsets, isPending, refetch, error, isError}] = useAtom(stableAtom) + + return { + testsets: testsets ?? [], + isError, + error, + isLoading: isPending, + mutate: refetch, + } +} + +/** + * Hook for preview testsets (no filters). + * + * @returns Object with `testsets`, `isError`, `error`, `isLoading`, `mutate` + */ +export const usePreviewTestsetsData = () => { + const [{data: testsets, isPending, refetch, error, isError}] = useAtom(previewTestsetsQueryAtom) + + return { + testsets: testsets ?? [], + isError, + error, + isLoading: isPending, + mutate: refetch, + } +} + +/** + * Hook for preview testsets with filters. + * + * Use `useMemo` to pass a stable `payload` object so the query key remains stable. + * + * @param payload - Filter payload matching PreviewTestsetsQueryPayload + * @param options.enabled - Whether the query is enabled (default: true) + * @returns Object with `testsets`, `isError`, `error`, `isLoading`, `mutate` + */ +export const usePreviewTestsetsDataWithFilters = ( + payload: PreviewTestsetsQueryPayload = {}, + {enabled = true}: {enabled?: boolean} = {}, +) => { + const stableAtom = useMemo( + () => previewTestsetsQueryAtomFamily({payload, enabled}), + [payload, enabled], + ) + const [{data: testsets, isPending, refetch, error, isError}] = useAtom(stableAtom) + + return { + testsets: testsets ?? [], + isError, + error, + isLoading: isPending, + mutate: refetch, + } +} + +/** + * Combined hook that supports both regular and preview testsets. + * + * @param preview - If true, returns preview testsets; otherwise regular testsets + * @returns Same shape as the underlying hook + */ +export const useTestsetsDataWithPreview = (preview = false) => { + const regularData = useTestsetsData() + const previewData = usePreviewTestsetsData() + + return preview ? previewData : regularData +} diff --git a/web/oss/src/state/testset/index.tsx b/web/oss/src/state/testset/index.tsx index 5fa7368092..351e313d9d 100644 --- a/web/oss/src/state/testset/index.tsx +++ b/web/oss/src/state/testset/index.tsx @@ -1,55 +1 @@ -import {useMemo} from "react" - -import {useAtom, getDefaultStore} from "jotai" - -import {previewTestsetsQueryAtom, testsetsQueryAtomFamily} from "./atoms/fetcher" -import {useTestset} from "./hooks/useTestset" - -/** - * Hook for regular/legacy testsets - */ -export const useTestsetsData = ({enabled = true} = {}) => { - const stableAtom = useMemo(() => testsetsQueryAtomFamily({enabled}), [enabled]) - const [{data: testsets, isPending, refetch, error, isError}] = useAtom(stableAtom) - - return { - testsets: testsets ?? [], - isError, - error, - isLoading: isPending, - mutate: refetch, - } -} - -/** - * Hook for preview testsets - */ -export const usePreviewTestsetsData = () => { - const store = getDefaultStore() - const [{data: testsets, isPending, refetch, error, isError}] = useAtom( - previewTestsetsQueryAtom, - {store}, - ) - - return { - testsets: testsets ?? [], - isError, - error, - isLoading: isPending, - mutate: refetch, - } -} - -/** - * Combined hook that supports both regular and preview testsets - * @param preview - Whether to fetch preview testsets (default: false) - */ -export const useTestsetsDataWithPreview = (preview = false) => { - const regularData = useTestsetsData() - const previewData = usePreviewTestsetsData() - - return preview ? previewData : regularData -} - -// Export the single testset hook -export {useTestset} +export * from "./hooks" diff --git a/web/oss/src/state/variant/atoms/fetcher.ts b/web/oss/src/state/variant/atoms/fetcher.ts index 89ea734396..f9110a1248 100644 --- a/web/oss/src/state/variant/atoms/fetcher.ts +++ b/web/oss/src/state/variant/atoms/fetcher.ts @@ -17,7 +17,7 @@ import {fetchSingleProfile} from "@/oss/services/api" import {fetchVariants as fetchAppVariants} from "@/oss/services/api" import {routerAppIdAtom, recentAppIdAtom} from "@/oss/state/app/atoms/fetcher" import {environmentsAtom} from "@/oss/state/environment/atoms/fetcher" -import {currentAppContextAtom, selectedAppIdAtom} from "@/oss/state/newApps/selectors/apps" +import {currentAppContextAtom, selectedAppIdAtom} from "@/oss/state/app/selectors/app" import {projectIdAtom} from "@/oss/state/project/selectors/project" // Utility: check if a string is a canonical UUID (v1–v5) diff --git a/web/package.json b/web/package.json index 351d237cb3..4cec32420b 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "agenta-web", - "version": "0.55.2", + "version": "0.56.0", "workspaces": [ "ee", "oss",