diff --git a/.env b/.env index 8c5a3dd8..f2a7e888 100644 --- a/.env +++ b/.env @@ -44,7 +44,7 @@ NATS_SERVER_URL=nats://nats1:4222 NATS_SERVER_URL_IN_CONTAINER=nats://host.containers.internal:4222 # Generate this using ./nats-conf/generate-auth-nkey.sh -NKEYS_SEED_FILE=/path/to/app_user.nk +NKEYS_SEED_FILE=nats-conf/out_nkey/app_user.nk SENTRY_DSN= @@ -58,5 +58,5 @@ DOCKER_IMAGE_OPERATOR=operator DOCKER_IMAGE_CALLOUT=callout # For operators -GITHUB_USERNAME= -GITHUB_PASSWORD= \ No newline at end of file +GITHUB_USERNAME=changeme +GITHUB_TOKEN=changeme \ No newline at end of file diff --git a/.ruff.toml b/.ruff.toml index 4f43be19..7ddc41d0 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,5 +1,11 @@ target-version = "py310" -exclude = ["backend/app/interactem/app/alembic/**", "backend/agent/thirdparty/**", "conftest.py"] +exclude = [ + "backend/app/interactem/app/alembic/**", + "backend/agent/thirdparty/**", + "conftest.py", + "operators/**", + "tests/**" +] [lint] exclude = ["**/__init__.py"] @@ -21,4 +27,7 @@ ignore = [ [lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. -keep-runtime-typing = true \ No newline at end of file +keep-runtime-typing = true + +[lint.isort] +known-first-party = ["interactem"] \ No newline at end of file diff --git a/backend/agent/interactem/agent/agent.py b/backend/agent/interactem/agent/agent.py index 4873f3d0..7f1201b9 100644 --- a/backend/agent/interactem/agent/agent.py +++ b/backend/agent/interactem/agent/agent.py @@ -48,6 +48,7 @@ create_agent_parameter_consumer, ) from interactem.core.pipeline import Pipeline +from interactem.core.util import create_task_with_ref from .config import cfg @@ -686,8 +687,3 @@ async def handle_name_conflict(client: PodmanClient, container_name: str) -> Non logger.info(f"Conflicting container {conflicting_container.id} removed. ") -def create_task_with_ref(task_refs: set[asyncio.Task], coro: Coroutine) -> asyncio.Task: - task = asyncio.create_task(coro) - task_refs.add(task) - task.add_done_callback(task_refs.discard) # Clean up after completion - return task diff --git a/backend/agent/interactem/agent/config.py b/backend/agent/interactem/agent/config.py index 7815b20f..d8f492e1 100644 --- a/backend/agent/interactem/agent/config.py +++ b/backend/agent/interactem/agent/config.py @@ -4,7 +4,7 @@ class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") - LOCAL: bool = True + LOCAL: bool = False DOCKER_COMPATIBILITY_MODE: bool = False PODMAN_SERVICE_URI: str | None = None NATS_SERVER_URL: NatsDsn = Field(default="nats://localhost:4222") diff --git a/backend/agent/run.py b/backend/agent/interactem/agent/entrypoint.py similarity index 67% rename from backend/agent/run.py rename to backend/agent/interactem/agent/entrypoint.py index 3c70bd97..aa1f66a8 100644 --- a/backend/agent/run.py +++ b/backend/agent/interactem/agent/entrypoint.py @@ -2,11 +2,16 @@ from interactem.agent.agent import Agent -if __name__ == "__main__": + +async def main(): agent = Agent() try: - asyncio.run(agent.run()) + await agent.run() except KeyboardInterrupt: pass finally: print("Application terminated.") + + +def entrypoint(): + asyncio.run(main()) diff --git a/backend/agent/poetry.lock b/backend/agent/poetry.lock index fd866f7d..8748e92c 100644 --- a/backend/agent/poetry.lock +++ b/backend/agent/poetry.lock @@ -296,6 +296,7 @@ develop = true coloredlogs = "^15.0.1" nats-py = "^2.9.0" networkx = "^3.3" +nkeys = "^0.2.1" pydantic = "^2.9" [package.source] @@ -806,4 +807,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "1f997d607d1fb86c8d0abec4b68c5f6032a7e59335d3f42355d3fea50dc94a00" +content-hash = "62ad558b72cbf5bcc6154a873f1abe868d6b6c2273049ac85376c9cc53dc2f64" diff --git a/backend/agent/pyproject.toml b/backend/agent/pyproject.toml index 604e9ee4..d13c32c8 100644 --- a/backend/agent/pyproject.toml +++ b/backend/agent/pyproject.toml @@ -1,9 +1,19 @@ -[tool.poetry] +[project] name = "interactem-agent" -version = "0.1.0" -description = "" -authors = ["Sam Welborn "] +dynamic = [ "version", "dependencies"] +description = "Agent for interactem" readme = "README.md" +authors = [ + {name = "Sam Welborn", email = "swelborn@lbl.gov"}, + {name = "Chris Harris", email = "cjh@lbl.gov"} +] +requires-python = ">=3.10" + +[project.scripts] +interactem-agent = "interactem.agent.entrypoint:entrypoint" + +[tool.poetry] +version = "0.1.0" packages = [{ include = "interactem" }] [tool.poetry.dependencies] diff --git a/backend/app/Dockerfile b/backend/app/Dockerfile index 7946c4ea..9a3d1eb4 100644 --- a/backend/app/Dockerfile +++ b/backend/app/Dockerfile @@ -1,3 +1,4 @@ +# TODO: this is deprecated. We should use the core base image... FROM tiangolo/uvicorn-gunicorn-fastapi:python3.10 WORKDIR /app/ @@ -15,6 +16,7 @@ COPY ./app/pyproject.toml ./app/poetry.lock* /app/ ARG INSTALL_DEV=false COPY ./core/ /core/ +COPY ./sfapi_models/ /sfapi_models/ RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --only main ; fi" diff --git a/backend/app/interactem/app/api/main.py b/backend/app/interactem/app/api/main.py index 95f34572..95fad8d5 100644 --- a/backend/app/interactem/app/api/main.py +++ b/backend/app/interactem/app/api/main.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from interactem.app.api.routes import login, operators, pipelines, users, utils +from interactem.app.api.routes import agents, login, operators, pipelines, users, utils api_router = APIRouter() api_router.include_router(login.router, tags=["login"]) @@ -8,3 +8,4 @@ api_router.include_router(utils.router, prefix="/utils", tags=["utils"]) api_router.include_router(pipelines.router, prefix="/pipelines", tags=["pipelines"]) api_router.include_router(operators.router, prefix="/operators", tags=["operators"]) +api_router.include_router(agents.router, prefix="/agents", tags=["agents"]) diff --git a/backend/app/interactem/app/api/routes/agents.py b/backend/app/interactem/app/api/routes/agents.py new file mode 100644 index 00000000..36780b56 --- /dev/null +++ b/backend/app/interactem/app/api/routes/agents.py @@ -0,0 +1,17 @@ + +from fastapi import APIRouter + +from interactem.app.api.deps import CurrentUser +from interactem.app.events.producer import publish_sfapi_submit_event +from interactem.core.logger import get_logger +from interactem.sfapi_models import AgentCreateEvent + +logger = get_logger() +router = APIRouter() + +@router.post("/launch") +async def launch_agent(current_user: CurrentUser, agent_req: AgentCreateEvent) -> None: + """ + Launch an agent remotely. + """ + await publish_sfapi_submit_event(agent_req) diff --git a/backend/app/interactem/app/api/routes/pipelines.py b/backend/app/interactem/app/api/routes/pipelines.py index fd3dd07e..90dfe5f1 100644 --- a/backend/app/interactem/app/api/routes/pipelines.py +++ b/backend/app/interactem/app/api/routes/pipelines.py @@ -68,7 +68,7 @@ def read_pipeline(session: SessionDep, current_user: CurrentUser, id: uuid.UUID) @router.post("/", response_model=PipelinePublic) def create_pipeline( *, session: SessionDep, current_user: CurrentUser, pipeline_in: PipelineCreate -) -> Any: +) -> PipelinePublic: """ Create new pipeline. """ @@ -78,9 +78,24 @@ def create_pipeline( session.add(pipeline) session.commit() session.refresh(pipeline) + pipeline = PipelinePublic.model_validate(pipeline) return pipeline +@router.post("/run", response_model=PipelinePublic) +async def create_and_run_pipeline( + *, session: SessionDep, current_user: CurrentUser, pipeline_in: PipelineCreate +) -> PipelinePublic: + """ + Create new pipeline and run it. + """ + pipeline = create_pipeline( + session=session, current_user=current_user, pipeline_in=pipeline_in + ) + + return await run_pipeline(session, current_user, pipeline.id) + + @router.delete("/{id}") def delete_pipeline( session: SessionDep, current_user: CurrentUser, id: uuid.UUID diff --git a/backend/app/interactem/app/events/producer.py b/backend/app/interactem/app/events/producer.py index 94d76a28..b70b6428 100644 --- a/backend/app/interactem/app/events/producer.py +++ b/backend/app/interactem/app/events/producer.py @@ -1,18 +1,38 @@ +import asyncio import logging +from fastapi import HTTPException +from nats.aio.msg import Msg as NatsMessage +from nats.errors import NoRespondersError as NatsNoRespondersError +from nats.errors import TimeoutError as NatsTimeoutError +from nats.js.api import StreamInfo +from nats.js.errors import APIError, NoStreamResponseError from pydantic import BaseModel from sqlmodel import SQLModel -from interactem.core.constants import STREAM_PIPELINES, SUBJECT_PIPELINES_RUN +from interactem.core.constants import ( + SFAPI_GROUP_NAME, + SFAPI_STATUS_ENDPOINT, + STREAM_PIPELINES, + STREAM_SFAPI, + SUBJECT_PIPELINES_RUN, + SUBJECT_SFAPI_JOBS_SUBMIT, +) from interactem.core.events.pipelines import PipelineRunEvent from interactem.core.nats import create_or_update_stream, nc -from interactem.core.nats.config import PIPELINES_STREAM_CONFIG +from interactem.core.nats.config import PIPELINES_STREAM_CONFIG, SFAPI_STREAM_CONFIG +from interactem.sfapi_models import AgentCreateEvent, StatusRequest from ..core.config import settings logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +nats_tasks: list[asyncio.Task] = [] + +NATS_REQ_TIMEOUT_DEFAULT = 5 +NATS_REQ_TIMEOUT_SFAPI = 10 + async def start(): global nats_client @@ -20,15 +40,20 @@ async def start(): logger.info(f"Connecting to NATS server: {settings.NATS_SERVER_URL}") nats_client = await nc([str(settings.NATS_SERVER_URL)], "api") nats_jetstream = nats_client.jetstream() - info = await create_or_update_stream(PIPELINES_STREAM_CONFIG, nats_jetstream) - logger.info(f"Stream information: {info}") + stream_infos: list[StreamInfo] = [] + stream_infos.append( + await create_or_update_stream(PIPELINES_STREAM_CONFIG, nats_jetstream) + ) + stream_infos.append( + await create_or_update_stream(SFAPI_STREAM_CONFIG, nats_jetstream) + ) + logger.info(f"Streams information:\n {stream_infos}") async def stop(): if nats_client: await nats_client.close() - async def publish_jetstream_event( stream: str, subject: str, @@ -45,9 +70,54 @@ async def publish_jetstream_event( stream=stream, headers=None, ) - except: # noqa - logger.exception(f"Exception send on subject: {subject}") + except NoStreamResponseError as e: + raise HTTPException( + status_code=503, + detail=f"Failed to publish event on stream: {stream}. \nNats error: {e}.", + ) + except APIError as e: + if not e.code: + raise HTTPException(status_code=500, detail=str(e)) + raise HTTPException(status_code=e.code, detail=str(e)) + + +async def nats_req_rep( + subject: str, + payload: BaseModel | SQLModel, + timeout: int = NATS_REQ_TIMEOUT_DEFAULT, +) -> NatsMessage: + try: + rep: NatsMessage = await nats_client.request( + subject=subject, + payload=payload.model_dump_json().encode(), + headers=None, + timeout=timeout, + ) + except NatsNoRespondersError as e: + raise HTTPException( + status_code=503, + detail=f"No responders found for subject: {subject}. Nats error: {e}.", + ) + except NatsTimeoutError as e: + logger.exception(f"Timeout for subject: {subject}") + raise HTTPException( + status_code=504, + detail=f"Timeout for subject: {subject}. Nats error: {e}.", + ) + + return rep async def publish_pipeline_run_event(event: PipelineRunEvent) -> None: await publish_jetstream_event(STREAM_PIPELINES, SUBJECT_PIPELINES_RUN, event) + + +async def request_machine_status(payload: StatusRequest) -> NatsMessage: + return await nats_req_rep( + f"{SFAPI_GROUP_NAME}.{SFAPI_STATUS_ENDPOINT}", + payload, + timeout=NATS_REQ_TIMEOUT_SFAPI, # longer timeout for sfapi calls + ) + +async def publish_sfapi_submit_event(event: AgentCreateEvent) -> None: + await publish_jetstream_event(STREAM_SFAPI, SUBJECT_SFAPI_JOBS_SUBMIT, event) diff --git a/backend/app/interactem/app/tests/api/routes/test.http b/backend/app/interactem/app/tests/api/routes/test.http index ae9fb60e..e8b22f8e 100644 --- a/backend/app/interactem/app/tests/api/routes/test.http +++ b/backend/app/interactem/app/tests/api/routes/test.http @@ -9,918 +9,20 @@ POST http://localhost:8080/api/v1/login/external-token Content-Type: application/json Authorization: bearer -### - -POST http://localhost:80/api/v1/pipelines -Content-Type: application/json -Authorization: bearer - - -{ - "data": { - "edges": [ - { - "input_id": "12345678-1234-1234-1234-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ab" - }, - { - "input_id": "12345678-1234-1234-1234-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890cd" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ef" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890cd", - "num_connections": 2, - "output_id": "87654321-4321-4321-4321-1234567890ff" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ef", - "num_connections": 1, - "output_id": "12345678-1234-1234-1234-1234567890cd" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ff", - "num_connections": 1, - "output_id": "12345678-1234-1234-1234-1234567890ef" - } - ], - "operators": [ - { - "id": "12345678-1234-1234-1234-1234567890ab", - "image": "interactem/image-generator", - "inputs": [], - "node_type": "operator", - "outputs": [ - "87654321-4321-4321-4321-1234567890ab", - "87654321-4321-4321-4321-1234567890cd" - ], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - } - ], - "params": { - "hello": "world" - }, - "machine_name": "mothership6" - }, - { - "id": "12345678-1234-1234-1234-1234567890cd", - "image": "interactem/image-receiver", - "inputs": [ - "87654321-4321-4321-4321-1234567890ef" - ], - "node_type": "operator", - "outputs": [], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - } - ], - "params": { - "hello": "world" - }, - "machine_name": "perlmutter" - }, - { - "id": "12345678-1234-1234-1234-1234567890ef", - "image": "interactem/image-receiver", - "inputs": [ - "87654321-4321-4321-4321-1234567890ff" - ], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - } - ], - "node_type": "operator", - "outputs": [], - "params": { - "hello": "world" - }, - "machine_name": "perlmutter" - } - ], - "ports": [ - { - "id": "87654321-4321-4321-4321-1234567890ab", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890ab", - "port_type": "output", - "portkey": "out1" - }, - { - "id": "87654321-4321-4321-4321-1234567890cd", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890ab", - "port_type": "output", - "portkey": "out1" - }, - { - "id": "87654321-4321-4321-4321-1234567890ef", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "input", - "portkey": "in1" - }, - { - "id": "87654321-4321-4321-4321-1234567890ff", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890ef", - "port_type": "input", - "portkey": "in1" - } - ] - } -} - - -### - -POST http://localhost:80/api/v1/pipelines -Content-Type: application/json -Authorization: bearer - - -{ - "data": { - "edges": [ - { - "input_id": "12345678-1234-1234-1234-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ab" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ef" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ef", - "num_connections": 1, - "output_id": "12345678-1234-1234-1234-1234567890cd" - } - ], - "operators": [ - { - "id": "12345678-1234-1234-1234-1234567890ab", - "image": "interactem/image-generator", - "inputs": [], - "node_type": "operator", - "outputs": [ - "87654321-4321-4321-4321-1234567890ab" - ], - "params": { - "hello": "world" - }, - "machine_name": "mothership6", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - } - ], - "env": { - "TEST_ENV": "ENV_VAR" - } - }, - { - "id": "12345678-1234-1234-1234-1234567890cd", - "image": "samwelborn/count-electrons:latest", - "inputs": [ - "87654321-4321-4321-4321-1234567890ef" - ], - "node_type": "operator", - "outputs": [], - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - } - ], - "env": { - "TEST_ENV": "ENV_VAR" - } - } - ], - "ports": [ - { - "id": "87654321-4321-4321-4321-1234567890ab", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890ab", - "port_type": "output", - "portkey": "out1" - }, - { - "id": "87654321-4321-4321-4321-1234567890ef", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "input", - "portkey": "in1" - } - ] - } -} - -### DATA READ/COUNT/SUBTRACT/SAVE - -POST http://localhost:80/api/v1/pipelines +### Create an agent +POST http://localhost:8080/api/v1/agents/launch Content-Type: application/json Authorization: bearer put_the_access_token_here { - "data": { - "edges": [ - { - "input_id": "12345678-1234-1234-1234-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ab" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ef" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ef", - "num_connections": 1, - "output_id": "12345678-1234-1234-1234-1234567890cd" - }, - { - "input_id": "12345678-1234-1234-1234-1234567890cd", - "num_connections": 1, - "output_id": "10000008-1234-1234-1234-1000000890ab" - }, - { - "input_id": "10000008-1234-1234-1234-1000000890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4322-1234567890af" - }, - { - "input_id": "87654321-4321-4321-4322-1234567890af", - "num_connections": 1, - "output_id": "12345678-1234-1234-1235-1432567890cd" - }, - { - "input_id": "12345678-1234-1234-1235-1432567890cd", - "num_connections": 1, - "output_id": "87611121-4321-4321-4322-1234567890af" - }, - { - "input_id": "87611121-4321-4321-4322-1234567890af", - "num_connections": 1, - - "output_id": "87654321-4321-4321-4321-1234567890af" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890af", - "num_connections": 1, - "output_id": "12345678-1234-1234-1234-1432567890cd" - } - ], - "operators": [ - { - "id": "12345678-1234-1234-1234-1234567890ab", - "image": "interactem/data-replay", - "inputs": [], - "node_type": "operator", - "outputs": [ - "87654321-4321-4321-4321-1234567890ab" - ], - "params": { - "hello": "world" - }, - "machine_name": "mothership6", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/data-replay/run.py", - "target": "/app/run.py", - "read_only": true - }, - { - "type": "bind", - "source": "~/ncem_raw_data/", - "target": "/raw_data/", - "read_only": true - } - ], - "env": { - "TEST_ENV": "ENV_VAR" - } - }, - { - "id": "12345678-1234-1234-1234-1234567890cd", - "image": "interactem/count-electrons:latest", - "inputs": [ - "87654321-4321-4321-4321-1234567890ef" - ], - "outputs": [ - "10000008-1234-1234-1234-1000000890ab" - ], - "node_type": "operator", - "outputs": [], - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/electron-count/run.py", - "target": "/app/run.py", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - } - ], - "env": { - "TEST_ENV": "ENV_VAR" - } - }, - { - "id": "12345678-1234-1234-1235-1432567890cd", - "image": "interactem/background-subtract:latest", - "inputs": [ - "87654321-4321-4321-4322-1234567890af" - ], - "outputs": [ - "87611121-4321-4321-4322-1234567890af" - ], - "node_type": "operator", - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/background-subtract/run.py", - "target": "/app/run.py", - "read_only": true - }, - { - "type": "bind", - "source": "~/FOURD_241002_0852_20132_00714_offsets.emd", - "target": "/vacuum_scan/FOURD_241002_0852_20132_00714_offsets.emd", - "read_only": true - } - ] - }, - { - "id": "12345678-1234-1234-1234-1432567890cd", - "image": "interactem/electron-count-save:latest", - "inputs": [ - "87654321-4321-4321-4321-1234567890af" - ], - "node_type": "operator", - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/electron-count-save/run.py", - "target": "/app/run.py", - "read_only": true - }, - { - "type": "bind", - "source": "~/electron-count-h5-test/", - "target": "/output/", - "read_only": false - } - ] - } - ], - "ports": [ - { - "id": "87654321-4321-4321-4321-1234567890ab", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890ab", - "port_type": "output", - "portkey": "out1" - }, - { - "id": "87654321-4321-4321-4321-1234567890ef", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "input", - "portkey": "in1" - }, - { - "id": "10000008-1234-1234-1234-1000000890ab", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "output", - "portkey": "out2" - }, - { - "id": "87654321-4321-4321-4321-1234567890af", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1432567890cd", - "port_type": "input", - "portkey": "in2" - }, - { - "id": "87654321-4321-4321-4322-1234567890af", - "node_type": "port", - "operator_id": "12345678-1234-1234-1235-1432567890cd", - "port_type": "input", - "portkey": "in3" - }, - { - "id": "87611121-4321-4321-4322-1234567890af", - "node_type": "port", - "operator_id": "12345678-1234-1234-1235-1432567890cd", - "port_type": "output", - "portkey": "out3" - } - ] - } -} - -### FULL PIPELINE - -POST http://localhost:80/api/v1/pipelines -Content-Type: application/json -{ - "data": { - "edges": [ - { - "input_id": "12345678-1234-1234-1234-1234567890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890ab" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890ab", - "num_connections": 1, - "output_id": "10000008-4321-4321-4321-1000000890ab" - }, - { - "input_id":"10000008-4321-4321-4321-1000000890ab", - "num_connections":1, - "output_id":"10000008-1234-1234-1234-1000000890ab"}, - { - "input_id":"10000008-1234-1234-1234-1000000890ab", - "num_connections":1, - "output_id":"87654321-4321-4321-4321-1000000890ab"}, - { - "input_id":"87654321-4321-4321-4321-1000000890ab", - "num_connections":1, - "output_id":"87654321-4321-4321-4321-1234567890ef"}, - { - "input_id": "87654321-4321-4321-4321-1234567890ef", - "num_connections": 1, - "output_id": "12345678-1234-1234-1234-1234567890cd" - }, - { - "input_id": "12345678-1234-1234-1234-1234567890cd", - "num_connections": 1, - "output_id": "87654123-4321-4321-4321-1000000890ab" - }, - { - "input_id": "87654123-4321-4321-4321-1000000890ab", - "num_connections": 1, - "output_id": "87654321-4321-4321-4321-1234567890af" - }, - { - "input_id": "87654321-4321-4321-4321-1234567890af", - "num_connections": 1, - "output_id":"12345678-1234-1234-1234-1432567890cd" - }, - { - "input_id": "12345678-1234-1234-1234-1234567890cd", - "num_connections": 1, - "output_id":"87654321-4321-4321-4323-1234567890af" - }, - { - "input_id": "87654321-4321-4321-4323-1234567890af", - "num_connections": 1, - "output_id":"87654321-4321-4321-4322-1234567890af" - } - ], - "operators": [ - { - "id": "12345678-0000-0001-0000-1234567890ab", - "image": "interactem/detstream-state-server:latest", - "node_type": "operator", - "machine_name": "mothership6", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-state-server/config.json", - "target": "/usr/local/etc/config.json", - "read_only": true - } - ] - }, - { - "id": "12345678-0000-0000-0000-1234567890ab", - "image": "interactem/detstream-producer", - "node_type": "operator", - "machine_name": "mothership6", - "command": ["0"], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-producer/config.json", - "target": "/usr/local/etc/config.json", - "read_only": true - } - ], - "network_mode": "host" - }, - { - "id": "12345678-0000-0000-0001-1234567890ab", - "image": "interactem/detstream-producer", - "node_type": "operator", - "machine_name": "mothership6", - "command": ["1"], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-producer/config.json", - "target": "/usr/local/etc/config.json", - "read_only": true - } - ], - "network_mode": "host" - }, - { - "id": "12345678-0000-0000-0002-1234567890ab", - "image": "interactem/detstream-producer", - "node_type": "operator", - "machine_name": "mothership6", - "command": ["2"], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-producer/config.json", - "target": "/usr/local/etc/config.json", - "read_only": true - } - ], - "network_mode": "host" - }, - { - "id": "12345678-0000-0000-0003-1234567890ab", - "image": "interactem/detstream-producer", - "node_type": "operator", - "machine_name": "mothership6", - "command": ["3"], - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-producer/config.json", - "target": "/usr/local/etc/config.json", - "read_only": true - } - ], - "network_mode": "host" - }, - { - "id": "12345678-1234-1234-1234-1234567890ab", - "image": "interactem/detstream-aggregator", - "node_type": "operator", - "outputs": [ - "87654321-4321-4321-4321-1234567890ab" - ], - "params": { - "hello": "world" - }, - "machine_name": "mothership6", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-aggregator/run.py", - "target": "/app/run.py", - "read_only": true - } - ] - }, - { - "id": "10000008-1234-1234-1234-1000000890ab", - "image": "interactem/detstream-assembler", - "node_type": "operator", - "inputs": ["10000008-4321-4321-4321-1000000890ab"], - "outputs": [ - "87654321-4321-4321-4321-1000000890ab" - ], - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/detstream-assembler/run.py", - "target": "/app/run.py", - "read_only": true - } - ] - }, - { - "id": "12345678-1234-1234-1234-1234567890cd", - "image": "interactem/count-electrons:latest", - "inputs": [ - "87654321-4321-4321-4321-1234567890ef" - ], - "node_type": "operator", - "outputs": [ - "87654123-4321-4321-4321-1000000890ab", - "87654321-4321-4321-4323-1234567890af" - ], - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/electron-count/run.py", - "target": "/app/run.py", - "read_only": true - } - ] - }, - { - "id": "12345678-1234-1234-1234-1432567890cd", - "image": "interactem/electron-count-save:latest", - "inputs": [ - "87654321-4321-4321-4321-1234567890af" - ], - "node_type": "operator", - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/electron-count-save/run.py", - "target": "/app/run.py", - "read_only": true - }, - { - "type": "bind", - "source": "~/electron-count-h5-test/", - "target": "/output/", - "read_only": false - } - ] - }, - { - "id": "12345678-1234-1234-1235-1432567890cd", - "image": "interactem/background-subtract:latest", - "inputs": [ - "87654321-4321-4321-4322-1234567890af" - ], - "node_type": "operator", - "params": { - "hello": "world" - }, - "machine_name": "perlmutter", - "mounts": [ - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/operators/", - "target": "/app/operators/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/core/core/", - "target": "/core/core/", - "read_only": true - }, - { - "type": "bind", - "source": "~/Documents/gits/interactEM/backend/operators/containerfiles/background-subtract/run.py", - "target": "/app/run.py", - "read_only": true - }, - { - "type": "bind", - "source": "~/FOURD_241002_0852_20132_00714_offsets.emd", - "target": "/vacuum_scan/FOURD_241002_0852_20132_00714_offsets.emd", - "read_only": true - } - ] - } - ], - "ports": [ - { - "id": "87654321-4321-4321-4321-1234567890ab", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890ab", - "port_type": "output", - "portkey": "out1" - }, - { - "id": "87654321-4321-4321-4321-1234567890ef", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "input", - "portkey": "in1" - }, - { - "id": "10000008-4321-4321-4321-1000000890ab", - "node_type": "port", - "operator_id": "10000008-1234-1234-1234-1000000890ab", - "port_type": "input", - "portkey": "in2" - }, - { - "id": "87654321-4321-4321-4321-1000000890ab", - "node_type": "port", - "operator_id": "10000008-1234-1234-1234-1000000890ab", - "port_type": "output", - "portkey": "out2" - }, - { - "id": "87654123-4321-4321-4321-1000000890ab", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "output", - "portkey": "out3" - }, - { - "id": "87654321-4321-4321-4321-1234567890af", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1432567890cd", - "port_type": "input", - "portkey": "in3" - }, - { - "id": "87654321-4321-4321-4323-1234567890af", - "node_type": "port", - "operator_id": "12345678-1234-1234-1234-1234567890cd", - "port_type": "output", - "portkey": "out4" - }, - { - "id": "87654321-4321-4321-4322-1234567890af", - "node_type": "port", - "operator_id": "12345678-1234-1234-1235-1432567890cd", - "port_type": "input", - "portkey": "in4" - } - ] - } + "machine": "perlmutter", + "num_nodes": 1, + "qos": "debug", + "constraint": "cpu", + "walltime": "00:00:01", + "account": "PUT_AN_ACCOUNT_HERE" } ### GET http://localhost:80/api/v1/pipelines -Authorization: bearer put_the_access_token_here - -POST http://localhost:80/api/v1/pipelines/A-PIPELINE-ID-FROM-GET-REQUEST-ABOVE/run Authorization: bearer put_the_access_token_here \ No newline at end of file diff --git a/backend/app/poetry.lock b/backend/app/poetry.lock index 55dd6c07..e7a8307d 100644 --- a/backend/app/poetry.lock +++ b/backend/app/poetry.lock @@ -214,6 +214,21 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi 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 = "authlib" +version = "1.4.1" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "Authlib-1.4.1-py2.py3-none-any.whl", hash = "sha256:edc29c3f6a3e72cd9e9f45fff67fc663a2c364022eb0371c003f22d5405915c1"}, + {file = "authlib-1.4.1.tar.gz", hash = "sha256:30ead9ea4993cdbab821dc6e01e818362f92da290c04c7f6a1940f86507a790d"}, +] + +[package.dependencies] +cryptography = "*" + [[package]] name = "bcrypt" version = "4.0.1" @@ -600,6 +615,56 @@ files = [ [package.extras] toml = ["tomli"] +[[package]] +name = "cryptography" +version = "44.0.0" +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"] +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "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)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "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 = "cssselect" version = "1.2.0" @@ -1145,7 +1210,7 @@ optional = false python-versions = "^3.10" groups = ["main"] files = [] -develop = false +develop = true [package.dependencies] coloredlogs = "^15.0.1" @@ -1158,6 +1223,23 @@ pydantic = "^2.9" type = "directory" url = "../core" +[[package]] +name = "interactem-sfapi-models" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.10" +groups = ["main"] +files = [] +develop = true + +[package.dependencies] +sfapi-client = "^0.3.2" + +[package.source] +type = "directory" +url = "../sfapi_models" + [[package]] name = "jinja2" version = "3.1.5" @@ -2458,6 +2540,29 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] +[[package]] +name = "sfapi-client" +version = "0.3.2" +description = "Python client for NERSC SF API" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sfapi_client-0.3.2-py3-none-any.whl", hash = "sha256:408f2b27530cbc3f06e5bbe2873b48a03a10c047c06aa47f0f1030a8bc4184f9"}, + {file = "sfapi_client-0.3.2.tar.gz", hash = "sha256:ea6080cf113ba93ccccdaebd2ebdab288241f3e9d969ab9954fca588361b35f7"}, +] + +[package.dependencies] +authlib = "*" +httpx = "*" +pydantic = ">=2.0,<3.0" +pydantic-settings = "*" +tenacity = "*" + +[package.extras] +docs = ["mkdocs-gen-files", "mkdocs-jupyter", "mkdocs-literate-nav", "mkdocs-material", "mkdocs-section-index", "mkdocstrings[python]"] +test = ["pydantic[dotenv]", "pytest", "pytest-asyncio", "pytest-mock"] + [[package]] name = "six" version = "1.17.0" @@ -3090,4 +3195,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "de7ce0682bdc868fde9467140061520d144b201f0c6a79c301d598bb557a19cc" +content-hash = "fef4c1e0c30e298831d3bb0f29f8524aa3d0849980876dc81c8b13b36c3ca552" diff --git a/backend/app/pyproject.toml b/backend/app/pyproject.toml index e28e68a7..65940668 100644 --- a/backend/app/pyproject.toml +++ b/backend/app/pyproject.toml @@ -27,7 +27,8 @@ bcrypt = "4.0.1" pydantic-settings = "^2.2.1" sentry-sdk = {extras = ["fastapi"], version = "^1.40.6"} pyjwt = "^2.8.0" -interactem-core = {path = "../core"} +interactem-core = {path = "../core", develop = true} +interactem-sfapi-models = {path = "../sfapi_models", develop = true} aiohttp = "^3.10.5" jsonpath-ng = "^1.7.0" @@ -50,27 +51,4 @@ exclude = ["venv", ".venv", "alembic"] [tool.ruff] target-version = "py310" exclude = ["alembic"] - -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "UP", # pyupgrade - "ARG001", # unused arguments in functions -] -ignore = [ - "E501", # line too long, handled by black - "B008", # do not perform function calls in argument defaults - "W191", # indentation contains tabs - "B904", # Allow raising exceptions without from e, for HTTPException -] - -isort = { known-first-party = ['operators', 'core'] } - -[tool.ruff.lint.pyupgrade] -# Preserve types, even if a file imports `from __future__ import annotations`. -keep-runtime-typing = true +extend = "../../.ruff.toml" \ No newline at end of file diff --git a/backend/core/interactem/core/constants/__init__.py b/backend/core/interactem/core/constants/__init__.py index 22e7818a..4a9a3605 100644 --- a/backend/core/interactem/core/constants/__init__.py +++ b/backend/core/interactem/core/constants/__init__.py @@ -8,6 +8,17 @@ BUCKET_AGENTS = "agents" BUCKET_AGENTS_TTL = 30 +STREAM_SFAPI = "sfapi" +SUBJECT_SFAPI_JOBS_SUBMIT = f"{STREAM_SFAPI}.jobs.submit" + +SFAPI_SERVICE_NAME = "sfapi-service" +SFAPI_GROUP_NAME = f"{STREAM_SFAPI}-micro" +SFAPI_STATUS_ENDPOINT = "status" + +STREAM_NOTIFICATIONS = "notifications" +SUBJECT_NOTIFICATIONS_INFO = f"{STREAM_NOTIFICATIONS}.info" +SUBJECT_NOTIFICATIONS_ERRORS = f"{STREAM_NOTIFICATIONS}.errors" + STREAM_OPERATORS = "operators" BUCKET_OPERATORS = "operators" diff --git a/backend/core/interactem/core/nats/__init__.py b/backend/core/interactem/core/nats/__init__.py index 0b4a3df1..80fbf505 100644 --- a/backend/core/interactem/core/nats/__init__.py +++ b/backend/core/interactem/core/nats/__init__.py @@ -1,5 +1,7 @@ +import asyncio from typing import Awaitable, Callable +from interactem.core.util import create_task_with_ref import nats from interactem.core.constants import ( @@ -114,10 +116,11 @@ async def consume_messages( num_msgs: int = 1, ): logger.info(f"Consuming messages on pull subscription {await psub.consumer_info()}") + handler_tasks: set[asyncio.Task] = set() while True: msgs = await psub.fetch(num_msgs, timeout=None) for msg in msgs: - await handler(msg, js) + create_task_with_ref(handler_tasks, handler(msg, js)) async def create_or_update_stream( diff --git a/backend/core/interactem/core/nats/config.py b/backend/core/interactem/core/nats/config.py index 4d1dafe7..b0cdb654 100644 --- a/backend/core/interactem/core/nats/config.py +++ b/backend/core/interactem/core/nats/config.py @@ -1,14 +1,14 @@ -from nats.js.api import ( - StreamConfig, -) +from nats.js.api import RetentionPolicy, StreamConfig from interactem.core.constants import ( STREAM_AGENTS, STREAM_IMAGES, STREAM_METRICS, + STREAM_NOTIFICATIONS, STREAM_OPERATORS, STREAM_PARAMETERS, STREAM_PIPELINES, + STREAM_SFAPI, ) PARAMETERS_STREAM_CONFIG = StreamConfig( @@ -42,6 +42,19 @@ subjects=[f"{STREAM_AGENTS}.>"], ) +SFAPI_STREAM_CONFIG = StreamConfig( + name=STREAM_SFAPI, + description="A stream for messages to the SFAPI.", + subjects=[f"{STREAM_SFAPI}.>"], +) + +NOTIFICATIONS_STREAM_CONFIG = StreamConfig( + name=STREAM_NOTIFICATIONS, + description="A stream for notifications.", + subjects=[f"{STREAM_NOTIFICATIONS}.>"], + retention=RetentionPolicy.INTEREST, +) + OPERATORS_STREAM_CONFIG = StreamConfig( name=STREAM_OPERATORS, description="A stream for messages for operators.", diff --git a/backend/core/interactem/core/nats/consumers.py b/backend/core/interactem/core/nats/consumers.py index 4c1dbab3..62018259 100644 --- a/backend/core/interactem/core/nats/consumers.py +++ b/backend/core/interactem/core/nats/consumers.py @@ -13,7 +13,9 @@ STREAM_OPERATORS, STREAM_PARAMETERS, STREAM_PIPELINES, + STREAM_SFAPI, SUBJECT_PIPELINES_RUN, + SUBJECT_SFAPI_JOBS_SUBMIT, ) from interactem.core.logger import get_logger from interactem.core.models.operators import OperatorParameter @@ -26,6 +28,11 @@ inactive_threshold=30, ) +SFAPI_CONSUMER_CONFIG = ConsumerConfig( + deliver_policy=DeliverPolicy.NEW, + inactive_threshold=30, +) + PARAMETER_CONSUMER_CONFIG = ConsumerConfig( deliver_policy=DeliverPolicy.LAST_PER_SUBJECT, ) @@ -145,3 +152,20 @@ async def create_metrics_consumer( ) logger.info(f"Subscribed to {subject}") return psub + + +async def create_sfapi_submit_consumer( + js: JetStreamContext, +) -> JetStreamContext.PullSubscription: + subject = SUBJECT_SFAPI_JOBS_SUBMIT + cfg = replace( + SFAPI_CONSUMER_CONFIG, + description="sfapi_submit_consumer", + ) + psub = await js.pull_subscribe( + stream=STREAM_SFAPI, + subject=subject, + config=cfg, + ) + logger.info(f"Subscribed to {subject}") + return psub diff --git a/backend/core/interactem/core/util.py b/backend/core/interactem/core/util.py new file mode 100644 index 00000000..8d47415b --- /dev/null +++ b/backend/core/interactem/core/util.py @@ -0,0 +1,9 @@ +import asyncio +from collections.abc import Coroutine + + +def create_task_with_ref(task_refs: set[asyncio.Task], coro: Coroutine) -> asyncio.Task: + task = asyncio.create_task(coro) + task_refs.add(task) + task.add_done_callback(task_refs.discard) # Clean up after completion + return task diff --git a/backend/micro/launcher/.env b/backend/micro/launcher/.env new file mode 100644 index 00000000..e819c97b --- /dev/null +++ b/backend/micro/launcher/.env @@ -0,0 +1,6 @@ +NATS_SERVER_URL="nats://localhost:4222" +SFAPI_KEY_PATH="~/.superfacility/key.pem" +CONDA_ENV="interactem" +ENV_FILE_PATH="/path/to/.env/file" +SFAPI_ACCOUNT="PUT_THE_ACCOUNT_HERE" +SFAPI_QOS="debug" \ No newline at end of file diff --git a/backend/micro/launcher/.python-version b/backend/micro/launcher/.python-version new file mode 100644 index 00000000..e4fba218 --- /dev/null +++ b/backend/micro/launcher/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/backend/micro/launcher/README.md b/backend/micro/launcher/README.md new file mode 100644 index 00000000..e69de29b diff --git a/backend/micro/launcher/interactem/__init__.py b/backend/micro/launcher/interactem/__init__.py new file mode 100644 index 00000000..8db66d3d --- /dev/null +++ b/backend/micro/launcher/interactem/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/backend/micro/launcher/interactem/launcher/__init__.py b/backend/micro/launcher/interactem/launcher/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/micro/launcher/interactem/launcher/config.py b/backend/micro/launcher/interactem/launcher/config.py new file mode 100644 index 00000000..f268082a --- /dev/null +++ b/backend/micro/launcher/interactem/launcher/config.py @@ -0,0 +1,25 @@ +from pathlib import Path +from typing import Self + +from pydantic import NatsDsn, model_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env") + NATS_SERVER_URL: NatsDsn = NatsDsn("nats://localhost:4222") + SFAPI_KEY_PATH: Path = Path("/secrets/sfapi.pem") + CONDA_ENV: Path | str + ENV_FILE_PATH: Path + SFAPI_ACCOUNT: str + SFAPI_QOS: str + + @model_validator(mode="after") + def resolve_path(self) -> Self: + self.SFAPI_KEY_PATH = self.SFAPI_KEY_PATH.expanduser().resolve() + if not self.SFAPI_KEY_PATH.is_file(): + raise ValueError(f"File not found: {self.SFAPI_KEY_PATH}") + return self + + +cfg = Settings() # type: ignore diff --git a/backend/micro/launcher/interactem/launcher/constants.py b/backend/micro/launcher/interactem/launcher/constants.py new file mode 100644 index 00000000..a4446097 --- /dev/null +++ b/backend/micro/launcher/interactem/launcher/constants.py @@ -0,0 +1,2 @@ +HEADER_TEMPLATE = "header.sh.j2" +LAUNCH_AGENT_TEMPLATE = "launch_agent.sh.j2" diff --git a/backend/micro/launcher/interactem/launcher/launcher.py b/backend/micro/launcher/interactem/launcher/launcher.py new file mode 100644 index 00000000..588f5ae8 --- /dev/null +++ b/backend/micro/launcher/interactem/launcher/launcher.py @@ -0,0 +1,226 @@ +import asyncio +import contextlib +import json +import signal +from pprint import pformat + +import nats +import nats.micro +from jinja2 import Environment, PackageLoader +from nats.aio.msg import Msg as NATSMsg +from nats.js import JetStreamContext +from nats.micro.request import Request +from nats.micro.service import EndpointConfig, GroupConfig, Service, ServiceConfig +from pydantic import ValidationError +from sfapi_client._models import StatusValue +from sfapi_client.client import AsyncClient as SFApiClient +from sfapi_client.compute import Machine +from sfapi_client.exceptions import SfApiError +from sfapi_client.jobs import AsyncJobSqueue + +from interactem.core.constants import ( + SFAPI_GROUP_NAME, + SFAPI_SERVICE_NAME, + SFAPI_STATUS_ENDPOINT, + SUBJECT_NOTIFICATIONS_ERRORS, + SUBJECT_NOTIFICATIONS_INFO, +) +from interactem.core.logger import get_logger +from interactem.core.nats import consume_messages, create_or_update_stream, nc +from interactem.core.nats.config import NOTIFICATIONS_STREAM_CONFIG, SFAPI_STREAM_CONFIG +from interactem.core.nats.consumers import create_sfapi_submit_consumer +from interactem.core.util import create_task_with_ref +from interactem.sfapi_models import ( + AgentCreateEvent, + JobSubmitEvent, + StatusRequest, + StatusResponse, +) + +from .config import cfg +from .constants import LAUNCH_AGENT_TEMPLATE + +logger = get_logger() + +sfapi_client = SFApiClient(key=cfg.SFAPI_KEY_PATH) +jinja_env = Environment(loader=PackageLoader("interactem.launcher"), enable_async=True) + +task_refs: set[asyncio.Task] = set() + + +async def status(req: Request) -> None: + logger.info("Received status request") + try: + status_req = StatusRequest.model_validate_json(req.data) + except Exception as e: + logger.error(f"Failed to parse machine type: {e}") + await req.respond_error( + code="400", + description=f"Invalid machine type: {e}, choices are {[t.value for t in Machine]}", + ) + try: + perlmutter = await sfapi_client.compute(status_req.machine) + await req.respond(perlmutter.status.encode()) + logger.info(f"Status request completed, responded with: {perlmutter.status}") + except SfApiError as e: + logger.error(f"Failed to get status: {e}") + await req.respond_error(code="500", description=e.message) + + +def publish_error(js: JetStreamContext, msg: str) -> None: + create_task_with_ref( + task_refs, + js.publish( + f"{SUBJECT_NOTIFICATIONS_ERRORS}", + payload=msg.encode(), + ), + ) + + +def publish_notification(js: JetStreamContext, msg: str) -> None: + create_task_with_ref( + task_refs, + js.publish( + f"{SUBJECT_NOTIFICATIONS_INFO}", + payload=msg.encode(), + ), + ) + + +async def monitor_job(job: AsyncJobSqueue, js: JetStreamContext) -> None: + msg = f"Job {job.jobid} has been submitted to SFAPI. Waiting for it to run..." + logger.info(msg) + publish_notification(js, msg) + + try: + await job.running() + except SfApiError as e: + # Occurs when the job goes into terminal state. The state is updated in the job object + # here, so we can publish its state directly + logger.error(f"SFAPI Error: {e.message}.") + publish_error(js, e.message) + return + + await job.complete() + msg = f"Job {job.jobid} has completed. Job state: {job.state}." + logger.info(msg) + publish_notification(js, msg) + + +async def submit(msg: NATSMsg, js: JetStreamContext) -> None: + logger.info("Received job submission request") + # Before we get into job submission, we want to let the server + # know that we are working on it + create_task_with_ref(task_refs, msg.in_progress()) + try: + agent_event = AgentCreateEvent.model_validate_json(msg.data) + + # Convert to JobSubmitEvent because we want to limit the frontend + # to not knowing what a job is... + job_submit_event = JobSubmitEvent( + machine=agent_event.machine, + account=cfg.SFAPI_ACCOUNT, + qos=cfg.SFAPI_QOS, + constraint=agent_event.compute_type.value, + walltime=agent_event.duration, + reservation=agent_event.reservation, + num_nodes=agent_event.num_agents, + ) + except ValidationError as e: + logger.error(f"Failed to parse job request: {e}") + create_task_with_ref(task_refs, msg.term()) + return + + compute = await sfapi_client.compute(job_submit_event.machine) + + if compute.status != StatusValue.active: + logger.error(f"Machine is not active: {compute.status}") + create_task_with_ref(task_refs, msg.term()) + return + + # Render job script + template = jinja_env.get_template(LAUNCH_AGENT_TEMPLATE) + script = await template.render_async( + job=job_submit_event.model_dump(), settings=cfg.model_dump() + ) + + create_task_with_ref(task_refs, msg.in_progress()) + try: + job: AsyncJobSqueue = await compute.submit_job(script) + logger.info(f"Job {job.jobid} submitted") + logger.info(f"Script: \n{script}") + create_task_with_ref(task_refs, msg.ack()) + create_task_with_ref(task_refs, monitor_job(job, js)) + except SfApiError as e: + _msg = f"Failed to submit job: {e.message}" + logger.error(_msg) + publish_error(js, _msg) + create_task_with_ref(task_refs, msg.term()) + + +# taken from +# https://github.com/nats-io/nats.py/blob/main/examples/micro/service.py +async def main(): + quit_event = asyncio.Event() + loop = asyncio.get_event_loop() + for sig in (signal.Signals.SIGINT, signal.Signals.SIGTERM): + loop.add_signal_handler(sig, lambda *_: quit_event.set()) + + async with contextlib.AsyncExitStack() as stack: + nats_client = await stack.enter_async_context( + await nc(servers=[str(cfg.NATS_SERVER_URL)], name="sfapi-launcher") + ) + js: JetStreamContext = nats_client.jetstream() + + startup_tasks: list[asyncio.Task] = [] + startup_tasks.append( + asyncio.create_task(create_or_update_stream(SFAPI_STREAM_CONFIG, js)) + ) + startup_tasks.append( + asyncio.create_task( + create_or_update_stream(NOTIFICATIONS_STREAM_CONFIG, js) + ) + ) + await asyncio.gather(*startup_tasks) + + config = ServiceConfig( + name=SFAPI_SERVICE_NAME, + version="0.0.1", + description="A service for getting SFAPI status.", + metadata={}, + queue_group=None, + ) + + service: Service = await stack.enter_async_context( + await nats.micro.add_service(nats_client, config=config) + ) + + sfapi_group = service.add_group( + config=GroupConfig( + name=SFAPI_GROUP_NAME, + queue_group=None, + ) + ) + + await sfapi_group.add_endpoint( + config=EndpointConfig( + name=SFAPI_STATUS_ENDPOINT, + handler=status, + metadata={ + "request_schema": json.dumps(StatusRequest.model_json_schema()), + "response_schema": json.dumps(StatusResponse.model_json_schema()), + }, + ) + ) + + logger.info("SFAPI service is running...") + logger.info(f"Service Info:\n{pformat(service.info(), depth=3)}") + + sfapi_submit_psub = await create_sfapi_submit_consumer(js) + submit_task = asyncio.create_task( + consume_messages(sfapi_submit_psub, submit, js) + ) + # Wait for the quit event + await quit_event.wait() + submit_task.cancel() + await sfapi_client.close() diff --git a/backend/micro/launcher/interactem/launcher/templates/ascii_header.sh.j2 b/backend/micro/launcher/interactem/launcher/templates/ascii_header.sh.j2 new file mode 100644 index 00000000..a96091c5 --- /dev/null +++ b/backend/micro/launcher/interactem/launcher/templates/ascii_header.sh.j2 @@ -0,0 +1,8 @@ +#!/bin/bash + +# ██╗███╗ ██╗████████╗███████╗██████╗ █████╗ ██████╗████████╗███████╗███╗ ███╗ +# ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝████╗ ████║ +# ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝███████║██║ ██║ █████╗ ██╔████╔██║ +# ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔══██║██║ ██║ ██╔══╝ ██║╚██╔╝██║ +# ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║╚██████╗ ██║ ███████╗██║ ╚═╝ ██║ +# ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ \ No newline at end of file diff --git a/backend/micro/launcher/interactem/launcher/templates/header.sh.j2 b/backend/micro/launcher/interactem/launcher/templates/header.sh.j2 new file mode 100644 index 00000000..95773b91 --- /dev/null +++ b/backend/micro/launcher/interactem/launcher/templates/header.sh.j2 @@ -0,0 +1,11 @@ +{% include "ascii_header.sh.j2" %} + +#SBATCH --qos={{job.qos}} +#SBATCH --constraint={{job.constraint}} +#SBATCH --time={{job.walltime}} +#SBATCH --account={{job.account}} +#SBATCH --nodes={{job.num_nodes}} +#SBATCH --exclusive +{%- if job.reservation %} +#SBATCH --reservation={{job.reservation}} +{%- endif %} \ No newline at end of file diff --git a/backend/micro/launcher/interactem/launcher/templates/launch_agent.sh.j2 b/backend/micro/launcher/interactem/launcher/templates/launch_agent.sh.j2 new file mode 100644 index 00000000..3a57c40b --- /dev/null +++ b/backend/micro/launcher/interactem/launcher/templates/launch_agent.sh.j2 @@ -0,0 +1,5 @@ +{% include "header.sh.j2" %} + +module load conda +conda activate {{settings.CONDA_ENV}} +srun --nodes={{job.num_nodes}} --ntasks-per-node=1 dotenv -f {{settings.ENV_FILE_PATH}} run interactem-agent \ No newline at end of file diff --git a/backend/micro/launcher/poetry.lock b/backend/micro/launcher/poetry.lock new file mode 100644 index 00000000..26a08fa0 --- /dev/null +++ b/backend/micro/launcher/poetry.lock @@ -0,0 +1,880 @@ +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.8.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "authlib" +version = "1.4.0" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, + {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, +] + +[package.dependencies] +cryptography = "*" + +[[package]] +name = "certifi" +version = "2024.12.14" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +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"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +description = "Colored terminal output for Python's logging module" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, + {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, +] + +[package.dependencies] +humanfriendly = ">=9.1" + +[package.extras] +cron = ["capturer (>=2.4)"] + +[[package]] +name = "cryptography" +version = "44.0.0" +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"] +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "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)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "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 = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "humanfriendly" +version = "10.0" +description = "Human friendly output for text interfaces using Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] + +[package.dependencies] +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "interactem-core" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.10" +groups = ["main"] +files = [] +develop = true + +[package.dependencies] +coloredlogs = "^15.0.1" +nats-py = "^2.9.0" +networkx = "^3.3" +nkeys = "^0.2.1" +pydantic = "^2.9" + +[package.source] +type = "directory" +url = "../../core" + +[[package]] +name = "interactem-sfapi-models" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.10" +groups = ["main"] +files = [] +develop = true + +[package.dependencies] +sfapi-client = "^0.3.2" + +[package.source] +type = "directory" +url = "../../sfapi_models" + +[[package]] +name = "jinja2" +version = "3.1.5" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +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"}, +] + +[[package]] +name = "nats-py" +version = "2.9.0" +description = "NATS client for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "nats_py-2.9.0.tar.gz", hash = "sha256:01886eb9e0a87f0ec630652cf1fae65d2a8556378a609bc6cc07d2ea60c8d0dd"}, +] + +[package.extras] +aiohttp = ["aiohttp"] +fast-parse = ["fast-mail-parser"] +nkeys = ["nkeys"] + +[[package]] +name = "networkx" +version = "3.4.2" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, +] + +[package.extras] +default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "nkeys" +version = "0.2.1" +description = "A public-key signature system based on Ed25519 for the NATS ecosystem." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "nkeys-0.2.1.tar.gz", hash = "sha256:3a201dcd203d8bb05ba2884d441b2c92918b2a537a10d324e73738887dde9da3"}, +] + +[package.dependencies] +pynacl = "*" + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.10.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.7.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd"}, + {file = "pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" + +[package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +description = "A python implementation of GNU readline." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, + {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, +] + +[package.extras] +dev = ["build", "flake8", "mypy", "pytest", "twine"] + +[[package]] +name = "pytest" +version = "8.3.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.25.3" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, + {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.dependencies] +click = {version = ">=5.0", optional = true, markers = "extra == \"cli\""} + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "sfapi-client" +version = "0.3.2" +description = "Python client for NERSC SF API" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sfapi_client-0.3.2-py3-none-any.whl", hash = "sha256:408f2b27530cbc3f06e5bbe2873b48a03a10c047c06aa47f0f1030a8bc4184f9"}, + {file = "sfapi_client-0.3.2.tar.gz", hash = "sha256:ea6080cf113ba93ccccdaebd2ebdab288241f3e9d969ab9954fca588361b35f7"}, +] + +[package.dependencies] +authlib = "*" +httpx = "*" +pydantic = ">=2.0,<3.0" +pydantic-settings = "*" +tenacity = "*" + +[package.extras] +docs = ["mkdocs-gen-files", "mkdocs-jupyter", "mkdocs-literate-nav", "mkdocs-material", "mkdocs-section-index", "mkdocstrings[python]"] +test = ["pydantic[dotenv]", "pytest", "pytest-asyncio", "pytest-mock"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "tenacity" +version = "9.0.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.12" +content-hash = "a6c3a02692217444c6a13f291d0e506c8491d7119a906ed763fa36a12e52c874" diff --git a/backend/micro/launcher/pyproject.toml b/backend/micro/launcher/pyproject.toml new file mode 100644 index 00000000..d928fc35 --- /dev/null +++ b/backend/micro/launcher/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "launcher" +description = "" +dynamic = ["version", "dependencies"] +authors = [ + {name = "Sam Welborn", email = "swelborn@lbl.gov"}, + {name = "Chris Harris", email = "cjh@lbl.gov"} +] +requires-python = ">=3.12" +readme = "README.md" + +[tool.poetry] +packages = [{ include = "interactem" }] +version = "0.1.0" + +[tool.poetry.dependencies] +python = "^3.12" +interactem-core = {path = "../../core", develop = true} +interactem-sfapi-models = {path = "../../sfapi_models", develop = true} +pydantic-settings = "^2.4.0" +sfapi-client = "^0.3.2" +jinja2 = "^3.1.5" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.4" +pytest-asyncio = "^0.25.3" +python-dotenv = { version = "^1.0.1", extras = ["cli"] } + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +target-version = "py312" +extend = "../../../.ruff.toml" + +isort = { known-first-party = ["interactem"] } + +[tool.ruff.lint.pyupgrade] +# Preserve types, even if a file imports `from __future__ import annotations`. +keep-runtime-typing = true diff --git a/backend/micro/launcher/run.py b/backend/micro/launcher/run.py new file mode 100644 index 00000000..04c1513f --- /dev/null +++ b/backend/micro/launcher/run.py @@ -0,0 +1,6 @@ +import asyncio + +from interactem.launcher.launcher import main + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/micro/launcher/scripts/test.sh b/backend/micro/launcher/scripts/test.sh new file mode 100755 index 00000000..398032d8 --- /dev/null +++ b/backend/micro/launcher/scripts/test.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +THIS_DIR=$(dirname $0) +dotenv -f $THIS_DIR/../tests/.env run pytest -s -vv ./tests \ No newline at end of file diff --git a/backend/micro/launcher/tests/.env b/backend/micro/launcher/tests/.env new file mode 100644 index 00000000..81780288 --- /dev/null +++ b/backend/micro/launcher/tests/.env @@ -0,0 +1,4 @@ +NATS_SERVER_URL="nats://localhost:4222" +SFAPI_KEY_PATH="~/.superfacility/key.pem" +CONDA_ENV="interactem" +ENV_FILE_PATH="/path/to/.env/file" \ No newline at end of file diff --git a/backend/micro/launcher/tests/expected_script.sh b/backend/micro/launcher/tests/expected_script.sh new file mode 100644 index 00000000..cf4b1574 --- /dev/null +++ b/backend/micro/launcher/tests/expected_script.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# ██╗███╗ ██╗████████╗███████╗██████╗ █████╗ ██████╗████████╗███████╗███╗ ███╗ +# ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝████╗ ████║ +# ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝███████║██║ ██║ █████╗ ██╔████╔██║ +# ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔══██║██║ ██║ ██╔══╝ ██║╚██╔╝██║ +# ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║╚██████╗ ██║ ███████╗██║ ╚═╝ ██║ +# ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ + +#SBATCH --qos=normal +#SBATCH --constraint=gpu +#SBATCH --time=01:30:00 +#SBATCH --account=test_account +#SBATCH --nodes=2 +#SBATCH --exclusive + +module load conda +conda activate interactem +srun --nodes=2 --ntasks-per-node=1 dotenv -f /path/to/.env/file run interactem-agent \ No newline at end of file diff --git a/backend/micro/launcher/tests/test_rendering.py b/backend/micro/launcher/tests/test_rendering.py new file mode 100644 index 00000000..c616d196 --- /dev/null +++ b/backend/micro/launcher/tests/test_rendering.py @@ -0,0 +1,42 @@ +from datetime import timedelta +from pathlib import Path + +import pytest +from jinja2 import Environment, PackageLoader +from sfapi_client.compute import Machine + +from interactem.launcher.config import cfg +from interactem.launcher.constants import LAUNCH_AGENT_TEMPLATE +from interactem.sfapi_models import JobSubmitEvent + +HERE = Path(__file__).parent + + +@pytest.fixture +def expected_script() -> str: + with open(HERE / "expected_script.sh") as f: + return f.read() + + +@pytest.mark.asyncio +async def test_submit_rendering(expected_script: str): + job_req = JobSubmitEvent( + machine=Machine.perlmutter, + account="test_account", + qos="normal", + constraint="gpu", + walltime=timedelta(hours=1, minutes=30), + reservation=None, + num_nodes=2, + ) + + jinja_env = Environment( + loader=PackageLoader("interactem.launcher"), enable_async=True + ) + template = jinja_env.get_template(LAUNCH_AGENT_TEMPLATE) + + script = await template.render_async( + job=job_req.model_dump(), settings=cfg.model_dump() + ) + + assert script == expected_script diff --git a/backend/operators/startup.py b/backend/operators/startup.py index 63743599..2a554edb 100644 --- a/backend/operators/startup.py +++ b/backend/operators/startup.py @@ -5,14 +5,15 @@ import sys from pathlib import Path +from pydantic import model_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + from interactem.core.constants import ( OPERATOR_CLASS_NAME, OPERATOR_ID_ENV_VAR, OPERATOR_RUN_LOCATION, ) from interactem.core.logger import get_logger -from pydantic import model_validator -from pydantic_settings import BaseSettings, SettingsConfigDict logger = get_logger() diff --git a/backend/sfapi_models/.python-version b/backend/sfapi_models/.python-version new file mode 100644 index 00000000..7c7a975f --- /dev/null +++ b/backend/sfapi_models/.python-version @@ -0,0 +1 @@ +3.10 \ No newline at end of file diff --git a/backend/sfapi_models/README.md b/backend/sfapi_models/README.md new file mode 100644 index 00000000..e69de29b diff --git a/backend/sfapi_models/interactem/__init__.py b/backend/sfapi_models/interactem/__init__.py new file mode 100644 index 00000000..8db66d3d --- /dev/null +++ b/backend/sfapi_models/interactem/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/backend/sfapi_models/interactem/sfapi_models/__init__.py b/backend/sfapi_models/interactem/sfapi_models/__init__.py new file mode 100644 index 00000000..bc44c19c --- /dev/null +++ b/backend/sfapi_models/interactem/sfapi_models/__init__.py @@ -0,0 +1,56 @@ +import datetime +from enum import Enum + +from pydantic import BaseModel, model_validator +from sfapi_client._models import StatusValue +from sfapi_client.compute import Machine + + +class StatusRequest(BaseModel): + machine: Machine + + +class StatusResponse(BaseModel): + status: StatusValue + + +class ComputeType(str, Enum): + gpu = "gpu" + cpu = "cpu" + + +class AgentCreateEvent(BaseModel): + machine: Machine + duration: datetime.timedelta + compute_type: ComputeType + num_agents: int + reservation: str | None = None + + +class JobSubmitEvent(BaseModel): + machine: Machine + account: str + qos: str + constraint: str + walltime: datetime.timedelta | str + reservation: str | None = None + num_nodes: int = 1 + + @model_validator(mode="after") + def format_walltime(self) -> "JobSubmitEvent": + if isinstance(self.walltime, str): + # Validate the string format HH:MM:SS + parts = self.walltime.split(":") + if len(parts) != 3: + raise ValueError("Walltime must be in the format HH:MM:SS") + hours, minutes, seconds = map(int, parts) + if not (0 <= hours < 24 and 0 <= minutes < 60 and 0 <= seconds < 60): + raise ValueError("Walltime must represent a valid time.") + return self + + # Convert the walltime to HH:MM:SS format + total_seconds = int(self.walltime.total_seconds()) + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + self.walltime = f"{hours:02}:{minutes:02}:{seconds:02}" + return self \ No newline at end of file diff --git a/backend/sfapi_models/poetry.lock b/backend/sfapi_models/poetry.lock new file mode 100644 index 00000000..aa245d56 --- /dev/null +++ b/backend/sfapi_models/poetry.lock @@ -0,0 +1,535 @@ +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.8.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "authlib" +version = "1.4.0" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, + {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, +] + +[package.dependencies] +cryptography = "*" + +[[package]] +name = "certifi" +version = "2024.12.14" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "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"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cryptography" +version = "44.0.0" +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"] +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "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)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "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 = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.10.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.7.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd"}, + {file = "pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" + +[package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "sfapi-client" +version = "0.3.2" +description = "Python client for NERSC SF API" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sfapi_client-0.3.2-py3-none-any.whl", hash = "sha256:408f2b27530cbc3f06e5bbe2873b48a03a10c047c06aa47f0f1030a8bc4184f9"}, + {file = "sfapi_client-0.3.2.tar.gz", hash = "sha256:ea6080cf113ba93ccccdaebd2ebdab288241f3e9d969ab9954fca588361b35f7"}, +] + +[package.dependencies] +authlib = "*" +httpx = "*" +pydantic = ">=2.0,<3.0" +pydantic-settings = "*" +tenacity = "*" + +[package.extras] +docs = ["mkdocs-gen-files", "mkdocs-jupyter", "mkdocs-literate-nav", "mkdocs-material", "mkdocs-section-index", "mkdocstrings[python]"] +test = ["pydantic[dotenv]", "pytest", "pytest-asyncio", "pytest-mock"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "tenacity" +version = "9.0.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.10" +content-hash = "3fd37ff0fff649962b122f6e723efe68957197a8cbf5f09560d4a85ff287a3b6" diff --git a/backend/sfapi_models/pyproject.toml b/backend/sfapi_models/pyproject.toml new file mode 100644 index 00000000..f5bf1640 --- /dev/null +++ b/backend/sfapi_models/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "interactem-sfapi-models" +description = "" +dynamic = ["version", "dependencies"] +authors = [ + {name = "Sam Welborn", email = "swelborn@lbl.gov"}, + {name = "Chris Harris", email = "cjh@lbl.gov"} +] +requires-python = ">=3.10" +readme = "README.md" + +[tool.poetry] +packages = [{ include = "interactem" }] +version = "0.1.0" + +[tool.poetry.dependencies] +python = "^3.10" +sfapi-client = "^0.3.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +target-version = "py310" +extend = "../../.ruff.toml" \ No newline at end of file diff --git a/frontend/interactEM/openapi.json b/frontend/interactEM/openapi.json index 1a526ffe..51236a77 100644 --- a/frontend/interactEM/openapi.json +++ b/frontend/interactEM/openapi.json @@ -677,6 +677,41 @@ } } }, + "/api/v1/pipelines/run": { + "post": { + "tags": ["pipelines"], + "summary": "Create And Run Pipeline", + "description": "Create new pipeline and run it.", + "operationId": "pipelines-create_and_run_pipeline", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/PipelineCreate" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/PipelinePublic" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + }, + "security": [{ "OAuth2PasswordBearer": [] }] + } + }, "/api/v1/pipelines/{id}/run": { "post": { "tags": ["pipelines"], @@ -730,10 +765,60 @@ }, "security": [{ "OAuth2PasswordBearer": [] }] } + }, + "/api/v1/agents/launch": { + "post": { + "tags": ["agents"], + "summary": "Launch Agent", + "description": "Launch an agent remotely.", + "operationId": "agents-launch_agent", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AgentCreateEvent" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + }, + "security": [{ "OAuth2PasswordBearer": [] }] + } } }, "components": { "schemas": { + "AgentCreateEvent": { + "properties": { + "machine": { "$ref": "#/components/schemas/PublicHost" }, + "duration": { + "type": "string", + "format": "duration", + "title": "Duration" + }, + "compute_type": { "$ref": "#/components/schemas/ComputeType" }, + "num_agents": { "type": "integer", "title": "Num Agents" }, + "reservation": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Reservation" + } + }, + "type": "object", + "required": ["machine", "duration", "compute_type", "num_agents"], + "title": "AgentCreateEvent" + }, "Body_login-login_access_token": { "properties": { "grant_type": { @@ -759,6 +844,11 @@ "required": ["username", "password"], "title": "Body_login-login_access_token" }, + "ComputeType": { + "type": "string", + "enum": ["gpu", "cpu"], + "title": "ComputeType" + }, "HTTPValidationError": { "properties": { "detail": { @@ -930,6 +1020,11 @@ "required": ["data", "count"], "title": "PipelinesPublic" }, + "PublicHost": { + "type": "string", + "enum": ["dtn01", "dtns", "perlmutter"], + "title": "PublicHost" + }, "Token": { "properties": { "access_token": { "type": "string", "title": "Access Token" }, diff --git a/frontend/interactEM/package-lock.json b/frontend/interactEM/package-lock.json index a0c63317..9c0ab83d 100644 --- a/frontend/interactEM/package-lock.json +++ b/frontend/interactEM/package-lock.json @@ -13,6 +13,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@hey-api/client-axios": "^0.5.0", + "@hookform/resolvers": "^3.10.0", "@mui/icons-material": "^6.1.4", "@mui/material": "^6.1.4", "@nats-io/jetstream": "^3.0.0-27", @@ -21,6 +22,8 @@ "@radix-ui/react-icons": "^1.3.0", "@xyflow/react": "^12.3.0", "jwt-decode": "^4.0.0", + "react-hook-form": "^7.54.2", + "react-toastify": "^11.0.3", "uuid": "^10.0.0" }, "devDependencies": { @@ -1244,6 +1247,15 @@ "typescript": "^5.5.3" } }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -5483,6 +5495,22 @@ "react": "^18.3.1" } }, + "node_modules/react-hook-form": { + "version": "7.54.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz", + "integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -5499,6 +5527,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-toastify": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz", + "integrity": "sha512-cbPtHJPfc0sGqVwozBwaTrTu1ogB9+BLLjd4dDXd863qYLj7DGrQ2sg5RAChjFUB4yc3w8iXOtWcJqPK/6xqRQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/frontend/interactEM/package.json b/frontend/interactEM/package.json index 2059f4e4..b469313d 100644 --- a/frontend/interactEM/package.json +++ b/frontend/interactEM/package.json @@ -25,6 +25,7 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@hey-api/client-axios": "^0.5.0", + "@hookform/resolvers": "^3.10.0", "@mui/icons-material": "^6.1.4", "@mui/material": "^6.1.4", "@nats-io/jetstream": "^3.0.0-27", @@ -33,13 +34,15 @@ "@radix-ui/react-icons": "^1.3.0", "@xyflow/react": "^12.3.0", "jwt-decode": "^4.0.0", + "react-hook-form": "^7.54.2", + "react-toastify": "^11.0.3", "uuid": "^10.0.0" }, "peerDependencies": { + "@tanstack/react-query": "^5.64.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "zod": "^3.24.1", - "@tanstack/react-query": "^5.64.2" + "zod": "^3.24.1" }, "license": "MIT", "devDependencies": { diff --git a/frontend/interactEM/src/App.tsx b/frontend/interactEM/src/App.tsx index e6ce7f5c..04783546 100644 --- a/frontend/interactEM/src/App.tsx +++ b/frontend/interactEM/src/App.tsx @@ -1,5 +1,20 @@ +import { ToastContainer } from "react-toastify" import InteractEM from "./pages/interactem" export default function App() { - return + return ( + <> + + + + ) } diff --git a/frontend/interactEM/src/client/generated/@tanstack/react-query.gen.ts b/frontend/interactEM/src/client/generated/@tanstack/react-query.gen.ts index c6937a70..8d3138db 100644 --- a/frontend/interactEM/src/client/generated/@tanstack/react-query.gen.ts +++ b/frontend/interactEM/src/client/generated/@tanstack/react-query.gen.ts @@ -8,6 +8,7 @@ import { } from "@tanstack/react-query" import type { AxiosError } from "axios" import { + agentsLaunchAgent, client, loginLoginAccessToken, loginLoginWithExternalToken, @@ -16,6 +17,7 @@ import { loginResetPassword, loginTestToken, operatorsReadOperators, + pipelinesCreateAndRunPipeline, pipelinesCreatePipeline, pipelinesDeletePipeline, pipelinesReadPipeline, @@ -34,6 +36,8 @@ import { utilsTestEmail, } from "../sdk.gen" import type { + AgentsLaunchAgentData, + AgentsLaunchAgentError, LoginLoginAccessTokenData, LoginLoginAccessTokenError, LoginLoginAccessTokenResponse, @@ -51,6 +55,9 @@ import type { LoginTestTokenData, LoginTestTokenResponse, OperatorsReadOperatorsData, + PipelinesCreateAndRunPipelineData, + PipelinesCreateAndRunPipelineError, + PipelinesCreateAndRunPipelineResponse, PipelinesCreatePipelineData, PipelinesCreatePipelineError, PipelinesCreatePipelineResponse, @@ -757,6 +764,47 @@ export const pipelinesReadPipelineOptions = ( }) } +export const pipelinesCreateAndRunPipelineQueryKey = ( + options: Options, +) => [createQueryKey("pipelinesCreateAndRunPipeline", options)] + +export const pipelinesCreateAndRunPipelineOptions = ( + options: Options, +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await pipelinesCreateAndRunPipeline({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }) + return data + }, + queryKey: pipelinesCreateAndRunPipelineQueryKey(options), + }) +} + +export const pipelinesCreateAndRunPipelineMutation = ( + options?: Partial>, +) => { + const mutationOptions: UseMutationOptions< + PipelinesCreateAndRunPipelineResponse, + AxiosError, + Options + > = { + mutationFn: async (localOptions) => { + const { data } = await pipelinesCreateAndRunPipeline({ + ...options, + ...localOptions, + throwOnError: true, + }) + return data + }, + } + return mutationOptions +} + export const pipelinesRunPipelineQueryKey = ( options: Options, ) => [createQueryKey("pipelinesRunPipeline", options)] @@ -818,3 +866,44 @@ export const operatorsReadOperatorsOptions = ( queryKey: operatorsReadOperatorsQueryKey(options), }) } + +export const agentsLaunchAgentQueryKey = ( + options: Options, +) => [createQueryKey("agentsLaunchAgent", options)] + +export const agentsLaunchAgentOptions = ( + options: Options, +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await agentsLaunchAgent({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }) + return data + }, + queryKey: agentsLaunchAgentQueryKey(options), + }) +} + +export const agentsLaunchAgentMutation = ( + options?: Partial>, +) => { + const mutationOptions: UseMutationOptions< + unknown, + AxiosError, + Options + > = { + mutationFn: async (localOptions) => { + const { data } = await agentsLaunchAgent({ + ...options, + ...localOptions, + throwOnError: true, + }) + return data + }, + } + return mutationOptions +} diff --git a/frontend/interactEM/src/client/generated/sdk.gen.ts b/frontend/interactEM/src/client/generated/sdk.gen.ts index 63513345..5eb4c2c7 100644 --- a/frontend/interactEM/src/client/generated/sdk.gen.ts +++ b/frontend/interactEM/src/client/generated/sdk.gen.ts @@ -7,6 +7,8 @@ import { urlSearchParamsBodySerializer, } from "@hey-api/client-axios" import type { + AgentsLaunchAgentData, + AgentsLaunchAgentError, LoginLoginAccessTokenData, LoginLoginAccessTokenError, LoginLoginAccessTokenResponse, @@ -25,6 +27,9 @@ import type { LoginTestTokenResponse, OperatorsReadOperatorsData, OperatorsReadOperatorsResponse, + PipelinesCreateAndRunPipelineData, + PipelinesCreateAndRunPipelineError, + PipelinesCreateAndRunPipelineResponse, PipelinesCreatePipelineData, PipelinesCreatePipelineError, PipelinesCreatePipelineResponse, @@ -572,6 +577,35 @@ export const pipelinesReadPipeline = ( }) } +/** + * Create And Run Pipeline + * Create new pipeline and run it. + */ +export const pipelinesCreateAndRunPipeline = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => { + return (options?.client ?? client).post< + PipelinesCreateAndRunPipelineResponse, + PipelinesCreateAndRunPipelineError, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/api/v1/pipelines/run", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }) +} + /** * Run Pipeline * Run a pipeline. @@ -617,3 +651,30 @@ export const operatorsReadOperators = ( ...options, }) } + +/** + * Launch Agent + * Launch an agent remotely. + */ +export const agentsLaunchAgent = ( + options: Options, +) => { + return (options?.client ?? client).post< + unknown, + AgentsLaunchAgentError, + ThrowOnError + >({ + security: [ + { + scheme: "bearer", + type: "http", + }, + ], + url: "/api/v1/agents/launch", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }) +} diff --git a/frontend/interactEM/src/client/generated/types.gen.ts b/frontend/interactEM/src/client/generated/types.gen.ts index 7a4771f0..1c98bf13 100644 --- a/frontend/interactEM/src/client/generated/types.gen.ts +++ b/frontend/interactEM/src/client/generated/types.gen.ts @@ -1,5 +1,13 @@ // This file is auto-generated by @hey-api/openapi-ts +export type AgentCreateEvent = { + machine: PublicHost + duration: string + compute_type: ComputeType + num_agents: number + reservation?: string | null +} + export type BodyLoginLoginAccessToken = { grant_type?: string | null username: string @@ -9,6 +17,8 @@ export type BodyLoginLoginAccessToken = { client_secret?: string | null } +export type ComputeType = "gpu" | "cpu" + export type HttpValidationError = { detail?: Array } @@ -85,6 +95,8 @@ export type PipelinesPublic = { count: number } +export type PublicHost = "dtn01" | "dtns" | "perlmutter" + export type Token = { access_token: string token_type?: string @@ -693,6 +705,33 @@ export type PipelinesReadPipelineResponses = { export type PipelinesReadPipelineResponse = PipelinesReadPipelineResponses[keyof PipelinesReadPipelineResponses] +export type PipelinesCreateAndRunPipelineData = { + body: PipelineCreate + path?: never + query?: never + url: "/api/v1/pipelines/run" +} + +export type PipelinesCreateAndRunPipelineErrors = { + /** + * Validation Error + */ + 422: HttpValidationError +} + +export type PipelinesCreateAndRunPipelineError = + PipelinesCreateAndRunPipelineErrors[keyof PipelinesCreateAndRunPipelineErrors] + +export type PipelinesCreateAndRunPipelineResponses = { + /** + * Successful Response + */ + 200: PipelinePublic +} + +export type PipelinesCreateAndRunPipelineResponse = + PipelinesCreateAndRunPipelineResponses[keyof PipelinesCreateAndRunPipelineResponses] + export type PipelinesRunPipelineData = { body?: never path: { @@ -738,3 +777,27 @@ export type OperatorsReadOperatorsResponses = { export type OperatorsReadOperatorsResponse = OperatorsReadOperatorsResponses[keyof OperatorsReadOperatorsResponses] + +export type AgentsLaunchAgentData = { + body: AgentCreateEvent + path?: never + query?: never + url: "/api/v1/agents/launch" +} + +export type AgentsLaunchAgentErrors = { + /** + * Validation Error + */ + 422: HttpValidationError +} + +export type AgentsLaunchAgentError = + AgentsLaunchAgentErrors[keyof AgentsLaunchAgentErrors] + +export type AgentsLaunchAgentResponses = { + /** + * Successful Response + */ + 200: unknown +} diff --git a/frontend/interactEM/src/client/generated/zod.gen.ts b/frontend/interactEM/src/client/generated/zod.gen.ts index c3eaed23..ff1213ed 100644 --- a/frontend/interactEM/src/client/generated/zod.gen.ts +++ b/frontend/interactEM/src/client/generated/zod.gen.ts @@ -2,6 +2,14 @@ import { z } from "zod" +export const zAgentCreateEvent = z.object({ + machine: z.enum(["dtn01", "dtns", "perlmutter"]), + duration: z.string(), + compute_type: z.enum(["gpu", "cpu"]), + num_agents: z.number().int(), + reservation: z.union([z.string(), z.null()]).optional(), +}) + export const zBodyLoginLoginAccessToken = z.object({ grant_type: z.union([z.string().regex(/password/), z.null()]).optional(), username: z.string(), @@ -11,6 +19,8 @@ export const zBodyLoginLoginAccessToken = z.object({ client_secret: z.union([z.string(), z.null()]).optional(), }) +export const zComputeType = z.enum(["gpu", "cpu"]) + export const zHttpValidationError = z.object({ detail: z .array( @@ -125,6 +135,8 @@ export const zPipelinesPublic = z.object({ count: z.number().int(), }) +export const zPublicHost = z.enum(["dtn01", "dtns", "perlmutter"]) + export const zToken = z.object({ access_token: z.string(), token_type: z.string().optional().default("bearer"), @@ -226,6 +238,8 @@ export const zPipelinesDeletePipelineResponse = zMessage export const zPipelinesReadPipelineResponse = zPipelinePublic +export const zPipelinesCreateAndRunPipelineResponse = zPipelinePublic + export const zPipelinesRunPipelineResponse = zPipelinePublic export const zOperatorsReadOperatorsResponse = zOperators diff --git a/frontend/interactEM/src/client/index.ts b/frontend/interactEM/src/client/index.ts index d557d45f..e1d5654b 100644 --- a/frontend/interactEM/src/client/index.ts +++ b/frontend/interactEM/src/client/index.ts @@ -1,2 +1,9 @@ export * from "./generated" export * from "./generated/@tanstack/react-query.gen" +import { z } from "zod" +import { zAgentCreateEvent as zAgentCreateEventTmp } from "./generated/zod.gen" + +export const zAgentCreateEvent = zAgentCreateEventTmp.extend({ + duration: z.string().time(), + num_agents: z.coerce.number().int(), +}) diff --git a/frontend/interactEM/src/components/launchagentfab.tsx b/frontend/interactEM/src/components/launchagentfab.tsx new file mode 100644 index 00000000..1f04b9f0 --- /dev/null +++ b/frontend/interactEM/src/components/launchagentfab.tsx @@ -0,0 +1,217 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { SupportAgent } from "@mui/icons-material" +import { Fab } from "@mui/material" +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControl, + InputLabel, + MenuItem, + Select, + TextField, +} from "@mui/material" +import { useMutation } from "@tanstack/react-query" +import { useCallback, useState } from "react" +import { Controller, type SubmitHandler, useForm } from "react-hook-form" +import { + type AgentCreateEvent, + agentsLaunchAgentMutation, + zAgentCreateEvent, +} from "../client" + +// TODO: in the future, consider using: https://github.com/dohomi/react-hook-form-mui/ +export const LaunchAgentFab = () => { + const [open, setOpen] = useState(false) + + const launchAgent = useMutation({ + ...agentsLaunchAgentMutation(), + }) + + const { + control, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(zAgentCreateEvent), + defaultValues: { + machine: "perlmutter", + compute_type: "cpu", + duration: "01:00:00", + reservation: "", + num_agents: 1, + }, + }) + const onSubmit: SubmitHandler = useCallback( + async (formData: AgentCreateEvent) => { + try { + console.log(formData) + launchAgent.mutate({ + body: formData, + }) + setOpen(false) + } catch (error) { + console.error("Failed to launch agent:", error) + } + }, + [launchAgent], + ) + + return ( + <> + setOpen(true)} + sx={{ + position: "relative", + top: "90%", + left: "10%", + }} + > + + Launch Agent + + + setOpen(false)} + maxWidth="sm" + fullWidth + > + Launch Agent + +
+ + Machine + ( + + )} + /> + + + + + Compute Type + + ( + + )} + /> + + + + ( + + )} + /> + + + + ( + + )} + /> + + + + ( + + )} + /> + +
+
+ + + + +
+ + ) +} diff --git a/frontend/interactEM/src/components/launchpipelinefab.tsx b/frontend/interactEM/src/components/launchpipelinefab.tsx new file mode 100644 index 00000000..19225fdf --- /dev/null +++ b/frontend/interactEM/src/components/launchpipelinefab.tsx @@ -0,0 +1,36 @@ +import { Navigation } from "@mui/icons-material" +import { Fab } from "@mui/material" +import { useMutation } from "@tanstack/react-query" +import type { Edge, Node } from "@xyflow/react" +import { pipelinesCreateAndRunPipelineMutation } from "../client" +import { type OperatorNodeData, toJSON } from "../pipeline" + +type CreatePipelineFabProps = { + nodes: Node[] + edges: Edge[] +} + +export const LaunchPipelineFab = ({ nodes, edges }: CreatePipelineFabProps) => { + const launchPipeline = useMutation({ + ...pipelinesCreateAndRunPipelineMutation(), + }) + const onLaunchPipeline = () => { + const pipelineData = toJSON(nodes, edges) + launchPipeline.mutate({ + body: pipelineData, + }) + } + + return ( + + + Launch + + ) +} diff --git a/frontend/interactEM/src/components/notificationstoast.ts b/frontend/interactEM/src/components/notificationstoast.ts new file mode 100644 index 00000000..1f1b24ee --- /dev/null +++ b/frontend/interactEM/src/components/notificationstoast.ts @@ -0,0 +1,45 @@ +import { + AckPolicy, + DeliverPolicy, + type JsMsg, + ReplayPolicy, +} from "@nats-io/jetstream" +import { useCallback, useMemo } from "react" +import { type TypeOptions, toast } from "react-toastify" +import { + NOTIFICATIONS_ERRORS_SUBJECT, + NOTIFICATIONS_STREAM, +} from "../constants/nats" +import { useConsumeMessages } from "../hooks/useConsumeMessages" +import { useConsumer } from "../hooks/useConsumer" + +export default function NotificationsToast() { + const config = useMemo( + () => ({ + filter_subjects: [`${NOTIFICATIONS_STREAM}.*`], + ack_policy: AckPolicy.All, + deliver_policy: DeliverPolicy.New, + replay_policy: ReplayPolicy.Instant, + }), + [], + ) + const consumer = useConsumer({ + stream: "notifications", + config: config, + }) + + const handleMessage = useCallback(async (m: JsMsg) => { + let toastType: TypeOptions = "info" + + if (m.subject === `${NOTIFICATIONS_ERRORS_SUBJECT}`) { + toastType = "error" + } + + const notification = m.string() + toast(notification, { type: toastType }) + }, []) + + useConsumeMessages({ consumer, handleMessage }) + + return null +} diff --git a/frontend/interactEM/src/components/pipelineflow.tsx b/frontend/interactEM/src/components/pipelineflow.tsx index 96197ef1..ff0b4fdb 100644 --- a/frontend/interactEM/src/components/pipelineflow.tsx +++ b/frontend/interactEM/src/components/pipelineflow.tsx @@ -27,17 +27,12 @@ import OperatorNode, { } from "./operatornode" import "@xyflow/react/dist/style.css" - -import AddIcon from "@mui/icons-material/Add" -import NavigationIcon from "@mui/icons-material/Navigation" -import Fab from "@mui/material/Fab" -import type { PipelinePublic } from "../client" import { useDnD } from "../dnd/dndcontext" -import { useCreatePipeline } from "../hooks/useCreatePipeline" import useOperators from "../hooks/useOperators" -import { useRunPipeline } from "../hooks/useRunPipeline" import { layoutElements } from "../layout" import { fromPipelineJSON } from "../pipeline" +import { LaunchAgentFab } from "./launchagentfab" +import { LaunchPipelineFab } from "./launchpipelinefab" export const edgeOptions = { type: "smoothstep", @@ -58,18 +53,8 @@ export const PipelineFlow = () => { console.error("Error loading operators:", error) } - const [currentPipelineId, setCurrentPipelineId] = useState( - null, - ) - - const createPipeline = useCreatePipeline((pipeline: PipelinePublic) => { - setCurrentPipelineId(pipeline.id) - }) - const runPipeline = useRunPipeline() - const handleConnect: OnConnect = useCallback((connection) => { // any new connections should nullify the current pipeline ID - setCurrentPipelineId(null) setEdges((eds) => addEdge(connection, eds)) }, []) @@ -159,15 +144,6 @@ export const PipelineFlow = () => { const handleNodesChange: OnNodesChange> = useCallback( // no need to nullify ID if we are just moving/selecting node (changes) => { - const hasMeaningfulChanges = changes.some( - (change) => - change.type !== "position" && - change.type !== "select" && - change.type !== "dimensions", - ) - if (hasMeaningfulChanges) { - setCurrentPipelineId(null) - } setNodes((nds) => applyNodeChanges>(changes, nds)) }, [], @@ -192,24 +168,6 @@ export const PipelineFlow = () => { URL.revokeObjectURL(url) } - const onCreatePipeline = () => { - const pipelineData = toJSON(nodes, edges) - createPipeline.mutate({ - body: pipelineData, - }) - } - - const onRunPipeline = () => { - if (currentPipelineId === null) { - console.error("No pipeline ID to launch") - return - } - console.log("Launching pipeline:", currentPipelineId) - runPipeline.mutate({ - path: { id: currentPipelineId }, - }) - } - const availableOperators = operators ?? [] const deleteKeyCode: KeyCode = "Delete" @@ -245,29 +203,8 @@ export const PipelineFlow = () => { - - - Create - - - {currentPipelineId && ( - - - Launch - - )} + + diff --git a/frontend/interactEM/src/constants/nats.ts b/frontend/interactEM/src/constants/nats.ts index 37a01567..89faf46e 100644 --- a/frontend/interactEM/src/constants/nats.ts +++ b/frontend/interactEM/src/constants/nats.ts @@ -4,3 +4,6 @@ export const PARAMETERS_QUERYKEY = "parameterValue" export const PARAMETERS_UPDATE_STREAM = `${PARAMETERS_STREAM}.update` export const IMAGES_STREAM = "images" export const OPERATORS_STREAM = "operators" +export const NOTIFICATIONS_STREAM = "notifications" +export const NOTIFICATIONS_ERRORS_SUBJECT = `${NOTIFICATIONS_STREAM}.errors` +export const NOTIFICATIONS_INFO_SUBJECT = `${NOTIFICATIONS_STREAM}.info` diff --git a/frontend/interactEM/src/hooks/useCreatePipeline.ts b/frontend/interactEM/src/hooks/useCreatePipeline.ts deleted file mode 100644 index 5545e079..00000000 --- a/frontend/interactEM/src/hooks/useCreatePipeline.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useMutation } from "@tanstack/react-query" -import type { PipelinePublic } from "../client" -import { pipelinesCreatePipelineMutation } from "../client" - -export const useCreatePipeline = ( - onCreate: (pipeline: PipelinePublic) => void, -) => { - return useMutation({ - ...pipelinesCreatePipelineMutation(), - onError: (error) => { - console.error("Error creating pipeline:", error) - }, - onSuccess: (data) => { - console.log("Pipeline created:", data) - onCreate(data) - // Here we should invalidate the pipeline query to get up to date information from backend - // Currently this is unused, keeping here for reference - // The "queryKey" generated by hey-api is not very good, we may need to do this manually - // NOTE: with updated generated tanstack query, I don't think we need to do all of this.. - - // const queryKeyPipeline = pipelinesReadPipelineQueryKey({ - // path: { id: data.id }, - // }) - // queryClient.invalidateQueries({ - // queryKey: [queryKeyPipeline[0]._id], - // }) - // const queryKeyPipelines = pipelinesReadPipelinesQueryKey() - // queryClient.invalidateQueries({ - // queryKey: [queryKeyPipelines[0]._id], - // }) - }, - onSettled: () => { - console.log("Pipeline creation complete") - }, - }) -} diff --git a/frontend/interactEM/src/hooks/useRunPipeline.ts b/frontend/interactEM/src/hooks/useRunPipeline.ts deleted file mode 100644 index d9393a92..00000000 --- a/frontend/interactEM/src/hooks/useRunPipeline.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useMutation } from "@tanstack/react-query" -import { pipelinesRunPipelineMutation } from "../client" - -export const useRunPipeline = () => { - return useMutation({ - ...pipelinesRunPipelineMutation(), - onError: (error) => { - console.error("Error running pipeline:", error) - }, - onSuccess: (data) => { - console.log("Pipeline launched:", data) - }, - onSettled: () => { - console.log("Pipeline launch settled.") - }, - }) -} diff --git a/frontend/interactEM/src/pages/interactem.tsx b/frontend/interactEM/src/pages/interactem.tsx index 77a1ea83..82969bb4 100644 --- a/frontend/interactEM/src/pages/interactem.tsx +++ b/frontend/interactEM/src/pages/interactem.tsx @@ -10,6 +10,7 @@ import "../index.css" import "@xyflow/react/dist/style.css" import { QueryClientProvider } from "@tanstack/react-query" import { interactemQueryClient } from "../auth/api" +import NotificationsToast from "../components/notificationstoast" interface InteractEMProps { authMode?: "external" | "internal" @@ -29,6 +30,7 @@ export default function InteractEM({ + diff --git a/scripts/generate-client.sh b/scripts/generate-client.sh index ec67cc15..057600eb 100755 --- a/scripts/generate-client.sh +++ b/scripts/generate-client.sh @@ -5,8 +5,12 @@ set -x # activate backend/app environment using poetry shell command before running THIS_DIR=$(dirname "$0") -cd $THIS_DIR/.. -python -c "import interactem.app.main; import json; print(json.dumps(interactem.app.main.app.openapi()))" > openapi.json +ROOT_DIR=$(realpath $THIS_DIR/..) +cd $ROOT_DIR +poetry run -P backend/app -- \ + python -c "import interactem.app.main; import json; print(json.dumps(interactem.app.main.app.openapi()))" \ + > $ROOT_DIR/openapi.json +cd $ROOT_DIR mv openapi.json frontend/interactEM/openapi.json cd frontend/interactEM npm run generate-client