From 8ca9172b9e30712fd8968fd5876b9462d41c9336 Mon Sep 17 00:00:00 2001 From: subnetmarco <88.marco@gmail.com> Date: Mon, 19 Oct 2026 12:19:32 -0700 Subject: [PATCH 1/6] chore(openapi): sync the durable callback contract Co-authored-by: Cursor --- openapi/openapi.yaml | 1039 ++++++++++++++++- .../authentication/auth_forgot_password.py | 40 + .../auth_get_password_policy.py | 52 +- .../api/authentication/auth_logout.py | 117 +- .../api/authentication/auth_refresh.py | 32 + .../api/authentication/auth_reset_password.py | 33 + .../api/authentication/auth_signin.py | 33 + .../api/authentication/auth_signup.py | 33 + .../authentication/auth_signup_anonymous.py | 60 +- .../_generated/api/autofix/get_autofix_run.py | 224 ++++ .../api/autofix/list_autofix_runs.py | 294 +++++ .../api/autofix/start_autofix_run.py | 291 +++++ .../fail_durable_callback.py | 256 ++++ .../heartbeat_durable_callback.py | 248 ++++ .../publish_durable_message.py | 16 +- .../register_durable_callback.py | 276 +++++ .../succeed_durable_callback.py | 260 +++++ .../api/frontends/create_frontend.py | 16 +- .../api/functions/invoke_function.py | 24 + .../auth_o_auth_exchange.py | 33 + .../api/projects/get_project_health.py | 12 +- .../storage_policies/delete_storage_policy.py | 86 +- src/volcano_sdk/_generated/models/__init__.py | 44 + .../_generated/models/autofix_diagnosis.py | 132 +++ .../models/autofix_diagnosis_confidence.py | 10 + .../_generated/models/autofix_edit.py | 75 ++ .../_generated/models/autofix_evidence.py | 82 ++ .../models/autofix_evidence_kind.py | 10 + .../_generated/models/autofix_patch.py | 86 ++ .../_generated/models/autofix_pull_request.py | 131 +++ .../models/autofix_pull_request_skipped.py | 10 + .../models/autofix_pull_request_state.py | 10 + .../_generated/models/autofix_run.py | 362 ++++++ .../_generated/models/autofix_run_status.py | 10 + .../_generated/models/autofix_run_trigger.py | 10 + .../_generated/models/autofix_run_usage.py | 83 ++ .../models/create_service_key_body.py | 4 +- .../models/durable_callback_error.py | 70 ++ .../models/durable_callback_outcome.py | 85 ++ .../models/durable_callback_registration.py | 110 ++ .../models/durable_callback_status.py | 10 + .../_generated/models/durable_execution.py | 4 +- .../models/fail_durable_callback_request.py | 75 ++ .../models/function_invocation_request.py | 3 + .../function_invocation_request_payload.py | 3 + .../heartbeat_durable_callback_request.py | 59 + .../models/paginated_autofix_runs.py | 120 ++ .../_generated/models/project_health_check.py | 7 +- .../models/project_health_resource_type.py | 4 +- .../models/project_health_response.py | 7 +- .../models/project_health_status.py | 4 +- .../models/project_health_summary.py | 7 +- .../register_durable_callback_request.py | 74 ++ .../succeed_durable_callback_request.py | 69 ++ 54 files changed, 5171 insertions(+), 74 deletions(-) create mode 100644 src/volcano_sdk/_generated/api/autofix/get_autofix_run.py create mode 100644 src/volcano_sdk/_generated/api/autofix/list_autofix_runs.py create mode 100644 src/volcano_sdk/_generated/api/autofix/start_autofix_run.py create mode 100644 src/volcano_sdk/_generated/api/durable_functions/fail_durable_callback.py create mode 100644 src/volcano_sdk/_generated/api/durable_functions/heartbeat_durable_callback.py create mode 100644 src/volcano_sdk/_generated/api/durable_functions/register_durable_callback.py create mode 100644 src/volcano_sdk/_generated/api/durable_functions/succeed_durable_callback.py create mode 100644 src/volcano_sdk/_generated/models/autofix_diagnosis.py create mode 100644 src/volcano_sdk/_generated/models/autofix_diagnosis_confidence.py create mode 100644 src/volcano_sdk/_generated/models/autofix_edit.py create mode 100644 src/volcano_sdk/_generated/models/autofix_evidence.py create mode 100644 src/volcano_sdk/_generated/models/autofix_evidence_kind.py create mode 100644 src/volcano_sdk/_generated/models/autofix_patch.py create mode 100644 src/volcano_sdk/_generated/models/autofix_pull_request.py create mode 100644 src/volcano_sdk/_generated/models/autofix_pull_request_skipped.py create mode 100644 src/volcano_sdk/_generated/models/autofix_pull_request_state.py create mode 100644 src/volcano_sdk/_generated/models/autofix_run.py create mode 100644 src/volcano_sdk/_generated/models/autofix_run_status.py create mode 100644 src/volcano_sdk/_generated/models/autofix_run_trigger.py create mode 100644 src/volcano_sdk/_generated/models/autofix_run_usage.py create mode 100644 src/volcano_sdk/_generated/models/durable_callback_error.py create mode 100644 src/volcano_sdk/_generated/models/durable_callback_outcome.py create mode 100644 src/volcano_sdk/_generated/models/durable_callback_registration.py create mode 100644 src/volcano_sdk/_generated/models/durable_callback_status.py create mode 100644 src/volcano_sdk/_generated/models/fail_durable_callback_request.py create mode 100644 src/volcano_sdk/_generated/models/heartbeat_durable_callback_request.py create mode 100644 src/volcano_sdk/_generated/models/paginated_autofix_runs.py create mode 100644 src/volcano_sdk/_generated/models/register_durable_callback_request.py create mode 100644 src/volcano_sdk/_generated/models/succeed_durable_callback_request.py diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 78f3bd4c..db86efd7 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -2252,7 +2252,8 @@ paths: Returns a fast control-plane health snapshot for the project and its deployed resources. The endpoint does not run live provider probes. A successful request returns 200 even when the project status is - `unhealthy`; transport and authorization failures use HTTP errors. + `degraded` or `critical`; transport and authorization failures use HTTP + errors. operationId: getProjectHealth security: - UserToken: [] @@ -4188,6 +4189,12 @@ paths: - Function must have `visibility: public` - Function receives payload only (no `__volcano_auth`) + **Reserved payload keys:** the top-level `__volcano_auth` and + `__volcano_http` keys are reserved. Volcano removes caller-supplied + values for both before the function runs; only an auth user token adds + a server-built `__volcano_auth`, and only HTTP-mode ingress sets + `__volcano_http: true`. + **Transport and CORS:** - This operation is the authenticated direct RPC endpoint and always uses the POST `{payload: ...}` contract, including for functions whose DNS ingress is @@ -4462,7 +4469,9 @@ paths: description: | Function is not deployed yet, or has no deployed region. Also returned when two starts under the same execution name raced and - both released it, which is retryable as it stands. + both released it, which is retryable as it stands, and with `code` + `execution_name_held` when an unfinished start this caller may not + resume holds the execution name, which needs a name of its own. content: application/json: schema: @@ -4606,8 +4615,9 @@ paths: $ref: '#/components/schemas/Error' '429': description: | - Too many messages sent to this execution, or the function's rate - limit, which sends share with its invocations and starts. + Too many messages sent to this execution, the function's or the + project's function rate limit, which sends share with invocations + and starts, or the project is over its bandwidth cap. content: application/json: schema: @@ -4681,8 +4691,9 @@ paths: $ref: '#/components/schemas/Error' '429': description: | - The function's rate limit, which lists share with its invocations - and starts. + The function's or the project's function rate limit, which lists + share with invocations and starts, or the project is over its + bandwidth cap. content: application/json: schema: @@ -6250,7 +6261,7 @@ paths: schema: $ref: '#/components/schemas/Error' '429': - description: Too many requests from this execution or address + description: Too many requests from this execution content: application/json: schema: @@ -6329,7 +6340,7 @@ paths: schema: $ref: '#/components/schemas/Error' '429': - description: Too many requests from this execution or address + description: Too many requests from this execution content: application/json: schema: @@ -6357,7 +6368,9 @@ paths: The SDK sends an `Idempotency-Key` that a rerun of the publishing step repeats, so a step that runs again returns the first message with - `200` and publishes nothing. + `200` and stores nothing new. That message is delivered live again, + and counts again, because the first run may have stopped before + subscribers got it. The SDKs deliver it to a subscriber once. operationId: publishDurableMessage security: [] parameters: @@ -6415,6 +6428,318 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /durable-callbacks: + post: + tags: + - Durable Functions + summary: Get a callback token from inside a workflow + description: | + Registers a waiting step and returns its callback token. The Volcano + SDK calls this from `ctx.waitForCallback`; applications do not call it + directly. + + `execution_ref` and `callback_id` are opaque values the SDK reads from + the running workflow. Together they are the request's credential, so + the operation takes no other authentication. The token works until + the step's own timeout, and only for that step. + + Retrying is safe: a request for a step that is already registered + returns the same token with `200`. + operationId: registerDurableCallback + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterDurableCallbackRequest' + responses: + '200': + description: The step was already registered; its token is returned + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackRegistration' + '201': + description: Token issued + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackRegistration' + '400': + description: The request body is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: No running durable execution matches `execution_ref` + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: | + The token cannot be issued now. `code` says why: + + - `callback_not_ready`: the workflow has not finished opening the + step yet. Retry with a short backoff; the SDK does. + - `callback_closed`: the workflow is no longer waiting on it. + - `callback_taken`: the step is already registered as an approval + or a message wait. + - `execution_ended`: the execution has finished. + - `too_many_callback_tokens`: the execution already holds the + maximum number of open tokens. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '413': + description: The request body is larger than 8 KiB + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + description: Too many requests from this execution + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '503': + description: | + The step could not be confirmed with the workflow right now. Retry + with backoff. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /durable-callbacks/succeed: + post: + tags: + - Durable Functions + summary: Complete a waiting step with a result + description: | + Resumes the workflow step waiting on the token with `result`. Call it + from the system the workflow handed the token to. + + The token is the only credential. A token takes one outcome: once it + has succeeded or failed, every later request is refused with + `409 callback_completed`, including a retry of the request that + succeeded it. + operationId: succeedDurableCallback + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SucceedDurableCallbackRequest' + responses: + '200': + description: The workflow has the result + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackOutcome' + '202': + description: | + The result is recorded and on its way to the workflow, which takes + longer than this request waits. Volcano finishes the delivery; do + not send it again. + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackOutcome' + '400': + description: The token is missing, or `result` is missing, null, not JSON, or larger than 64 KiB + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: | + No callback has this token. `code` is `callback_not_found`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: | + The token already has an outcome. `code` is `callback_completed`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '410': + description: | + The workflow no longer waits on the token. `code` says why: + + - `callback_expired`: the step timed out, on its own timeout or + its heartbeat timeout. + - `execution_ended`: the execution finished or stopped. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '413': + description: The request body is larger than 72 KiB + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + description: Too many callback requests for the token's project. Retry later. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /durable-callbacks/fail: + post: + tags: + - Durable Functions + summary: Fail a waiting step + description: | + Resumes the workflow step waiting on the token by raising `error`. The + SDKs raise it as `CallbackFailedError`, carrying its `type` and + `message`. + + The token is the only credential, and takes one outcome, as for + succeeding it. + operationId: failDurableCallback + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FailDurableCallbackRequest' + responses: + '200': + description: The workflow has the error + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackOutcome' + '202': + description: | + The error is recorded and on its way to the workflow. Volcano + finishes the delivery; do not send it again. + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackOutcome' + '400': + description: The token is missing, or the error's `type` or `message` is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: | + No callback has this token. `code` is `callback_not_found`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: | + The token already has an outcome. `code` is `callback_completed`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '410': + description: | + The workflow no longer waits on the token. `code` is + `callback_expired` or `execution_ended`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '413': + description: The request body is larger than 72 KiB + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + description: Too many callback requests for the token's project. Retry later. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /durable-callbacks/heartbeat: + post: + tags: + - Durable Functions + summary: Keep a waiting step alive + description: | + Tells the workflow step waiting on the token that the work is still + going on, restarting its heartbeat timeout. A heartbeat never extends + the step's own timeout, and a step without a heartbeat timeout needs + none. + operationId: heartbeatDurableCallback + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HeartbeatDurableCallbackRequest' + responses: + '200': + description: The step is still waiting + content: + application/json: + schema: + $ref: '#/components/schemas/DurableCallbackOutcome' + '400': + description: The token is missing + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: | + No callback has this token. `code` is `callback_not_found`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: | + The token already has an outcome. `code` is `callback_completed`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '410': + description: | + The workflow no longer waits on the token. `code` is + `callback_expired` or `execution_ended`. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '413': + description: The request body is larger than 8 KiB + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + description: Too many callback requests for the token's project. Retry later. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '503': + description: The heartbeat could not reach the workflow right now. Retry with backoff. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /durable-approvals: post: tags: @@ -6795,6 +7120,173 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /projects/{id}/issues/{issueId}/autofix-runs: + post: + tags: + - Autofix + summary: Diagnose an issue + description: | + Queues a run that diagnoses the issue with AI. The run reads the + issue's runtime logs and, when a push to the project's connected + repository built the deployment that was live when the issue was + last seen, its source at that commit. Poll the run until its status is + `succeeded`, `failed`, or `canceled`. + + Autofix must be on for the project. An issue has at most one queued or + running run, a project runs one at a time, and a project starts at most + 25 runs, manual and automatic, in any 24 hours. + + Project access tokens are refused. A run spends model tokens on the + project's provider, so a person starts it with a platform token or in + the dashboard. + operationId: startAutofixRun + security: + - UserToken: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/IssueId' + responses: + '202': + description: Queued + content: + application/json: + schema: + $ref: '#/components/schemas/AutofixRun' + '400': + description: Code `feature_unavailable` means the project's provider is `volcano` and Volcano-managed models are not available in this environment. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: The owner's plan does not include autofix, or the request used a project access token. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Project or issue not found. Code `feature_unavailable` means autofix is not available in this environment. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: Autofix is off for the project, or the issue already has a queued or running run. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '429': + description: The project started its limit of runs in the last 24 hours. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '501': + description: Autofix is not available in local mode. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '503': + description: The owner's plan could not be resolved. Retry later. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /projects/{id}/autofix-runs: + get: + tags: + - Autofix + summary: List autofix runs + description: | + Lists the project's autofix runs, newest first. Runs stay readable after + autofix is turned off, and where autofix is not available. + + Project access tokens can list runs, including read-only ones. + operationId: listAutofixRuns + security: + - UserToken: [] + - ProjectAccessToken: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/Limit' + - $ref: '#/components/parameters/IssueCursor' + - name: issue_id + in: query + required: false + description: Return only runs for this issue. + schema: + type: string + format: uuid + - name: status + in: query + required: false + description: Return only runs in this status. + schema: + $ref: '#/components/schemas/AutofixRunStatus' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedAutofixRuns' + '400': + description: A filter or the cursor is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Project not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '501': + description: Autofix is not available in local mode. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /projects/{id}/autofix-runs/{runId}: + get: + tags: + - Autofix + summary: Get an autofix run + description: | + Returns one run: its status, the diagnosis once it succeeds, why it + failed or was canceled, and the model tokens it used. Runs stay readable + where autofix is not available. + + Project access tokens can read runs, including read-only ones. + operationId: getAutofixRun + security: + - UserToken: [] + - ProjectAccessToken: [] + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/AutofixRunId' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/AutofixRun' + '404': + description: Project or run not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '501': + description: Autofix is not available in local mode. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /projects/{id}/durable-approvals: get: tags: @@ -7150,7 +7642,7 @@ paths: 22.x or 24.x. The Node.js runtime is inferred from `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x. The selected Node.js family must also satisfy the installed Next.js package's - `engines.node` constraint. Volcano tests Next 15.5.27 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next 16.4.0 (`>=20.9.0`). + `engines.node` constraint. Volcano tests Next 15.5.28 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next 16.4.2 (`>=20.9.0`). Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI does not apply its own source archive size limit. After the final container images are built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing. @@ -10404,6 +10896,9 @@ paths: operationId: authGetPasswordPolicy security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' responses: '200': description: Effective password policy @@ -10412,7 +10907,7 @@ paths: schema: $ref: '#/components/schemas/AuthPasswordPolicy' '401': - description: Invalid, missing, or revoked anon key + description: Invalid, missing, or revoked anon key; or invalid auth proxy metadata content: application/json: schema: @@ -10444,6 +10939,9 @@ paths: operationId: authSignup security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: true content: @@ -10478,7 +10976,9 @@ paths: description: Invalid input (bad email/password format) '401': description: | - Unauthorized - Invalid, tampered, revoked, or wrong-project anon key + Unauthorized - Invalid, tampered, revoked, or wrong-project anon key. + Also returned for invalid, repeated, or under-scoped auth proxy metadata + (`invalid auth proxy metadata`). '403': description: | Forbidden - Signups disabled, anon key lacks signup permission, or the @@ -10510,6 +11010,9 @@ paths: operationId: authSignin security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: true content: @@ -10540,7 +11043,7 @@ paths: '401': description: | Unauthorized - Invalid credentials, invalid/tampered/revoked anon key, - or account banned/deleted + or account banned/deleted; or invalid auth proxy metadata '403': description: | Forbidden - Anon key lacks signin permission, or the email domain is @@ -10566,6 +11069,9 @@ paths: operationId: authRefresh security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: false content: @@ -10587,7 +11093,7 @@ paths: schema: $ref: '#/components/schemas/AuthTokenResponse' '401': - description: Invalid or expired refresh token + description: Invalid or expired refresh token; or invalid auth proxy metadata content: application/json: schema: @@ -10621,6 +11127,9 @@ paths: operationId: authLogout security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: false content: @@ -10637,6 +11146,12 @@ paths: responses: '204': description: Logged out successfully + '401': + description: Missing, invalid, or revoked anon key; or invalid, repeated, or under-scoped auth proxy metadata + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /auth/forgot-password: post: tags: @@ -10649,6 +11164,9 @@ paths: operationId: authForgotPassword security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: true content: @@ -10672,6 +11190,12 @@ paths: message: type: string example: If the email exists, a password reset link has been sent + '401': + description: Missing, invalid, or revoked anon key; or invalid, repeated, or under-scoped auth proxy metadata + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '403': description: Password reset is disabled for this project content: @@ -10695,6 +11219,9 @@ paths: operationId: authResetPassword security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: true content: @@ -10726,7 +11253,7 @@ paths: '400': description: Password doesn't meet requirements '401': - description: Invalid or expired token + description: Invalid or expired token; or invalid auth proxy metadata '503': description: Compromised-password screening is temporarily unavailable content: @@ -10837,6 +11364,9 @@ paths: operationId: authSignupAnonymous security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: description: Optional user metadata content: @@ -10858,6 +11388,12 @@ paths: application/json: schema: $ref: '#/components/schemas/AuthTokenResponse' + '401': + description: Missing, invalid, or revoked anon key; or invalid, repeated, or under-scoped auth proxy metadata + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '403': description: Anonymous signins disabled /auth/user/convert-anonymous: @@ -13292,6 +13828,9 @@ paths: operationId: authOAuthExchange security: - AnonKey: [] + parameters: + - $ref: '#/components/parameters/AuthProxyAuthorization' + - $ref: '#/components/parameters/AuthProxyClientIP' requestBody: required: true content: @@ -13317,7 +13856,7 @@ paths: '400': description: Invalid, expired, consumed, or redirect-mismatched code '401': - description: Missing or invalid project anon key + description: Missing or invalid project anon key; or invalid auth proxy metadata '403': description: | Email confirmation is now required, or the account's email domain is not @@ -14572,7 +15111,9 @@ paths: for backward compatibility. Provide an explicit list (e.g. ["functions.invoke", "locks.manage"]) to restrict the key; "*" grants everything. Scope enforcement applies to function invocation, - storage object operations, and project locks. + storage object operations, project locks, and server-side auth proxy + metadata (auth.signin, auth.signup, auth.refresh, auth.logout, + auth.password_reset). example: - functions.invoke - locks.manage @@ -14846,6 +15387,12 @@ paths: responses: '200': description: Policy deleted + '404': + description: Project, bucket, or policy not found. Also returned when the policy ID is malformed or belongs to another bucket. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /projects/{id}/storage/objects: get: tags: @@ -16167,6 +16714,32 @@ components: project access token cannot create, list, read, or revoke project access tokens. parameters: + AuthProxyAuthorization: + name: X-Volcano-Proxy-Authorization + in: header + required: false + description: | + Trusted server-side auth proxy credential in `Bearer SERVICE_KEY` + form. When supplied, `X-Volcano-Client-IP` is also required. The service + key must belong to the anon key's project and hold this operation's scope: + `auth.signup` for signup and anonymous signup, `auth.signin` for signin + and OAuth code exchange, `auth.refresh` for refresh, `auth.logout` for + logout, and `auth.password_reset` for forgot-password and reset-password. + The password policy accepts any service key for the project. Never expose + a service key to browser code. + schema: + type: string + AuthProxyClientIP: + name: X-Volcano-Client-IP + in: header + required: false + description: | + Original browser IPv4 or IPv6 address supplied by a trusted server-side + auth proxy. The proxy must derive this value from its platform-verified + request metadata, never from a browser-supplied header. Requires a valid + `X-Volcano-Proxy-Authorization` header. + schema: + type: string BackupName: name: backupName in: path @@ -16313,6 +16886,14 @@ components: description: | Opaque cursor from a previous response's `next_cursor`. Pages forward; keep the same filters while paging. + AutofixRunId: + name: runId + in: path + required: true + schema: + type: string + format: uuid + description: Autofix run ID DurableApprovalFunctionFilter: name: function in: query @@ -16332,11 +16913,13 @@ components: description: | Makes a send safe to retry. A repeat with a key the same sender has used on the execution returns the first message and sends nothing. - Keys of different senders never collide. Up to 255 characters. + Keys of different senders never collide. Up to 255 printable ASCII + characters. schema: type: string minLength: 1 maxLength: 255 + pattern: ^[ -~]+$ DurableClientToken: name: X-Volcano-Client-Token in: header @@ -21032,8 +21615,8 @@ components: type: string description: | Lets a browser read and send the execution's messages. Returned only - by the start that created the execution, never by a repeat under the - same name or by a read. Send it as `X-Volcano-Client-Token`. + by the start whose input the execution runs, never by a repeat under + the same name or by a read. Send it as `X-Volcano-Client-Token`. required: - id - function_id @@ -21394,6 +21977,9 @@ components: } } ``` + + The top-level `__volcano_auth` and `__volcano_http` keys are reserved; + Volcano removes caller-supplied values for both. FunctionInvocationResponse: type: object description: Raw function response body returned by the invoked function. @@ -22388,6 +22974,9 @@ components: enum: - in - out + x-enum-varnames: + - DurableMessageDirectionIn + - DurableMessageDirectionOut description: | `in` is a message sent to the execution. `out` is one the execution published. @@ -22582,6 +23171,89 @@ components: required: - name additionalProperties: false + RegisterDurableCallbackRequest: + type: object + properties: + execution_ref: + type: string + minLength: 1 + maxLength: 2048 + description: Opaque reference to the running execution, supplied by the SDK. + callback_id: + type: string + minLength: 1 + maxLength: 1024 + description: Opaque reference to the waiting step, supplied by the SDK. + name: + type: string + minLength: 1 + maxLength: 237 + pattern: ^[ -~]+$ + description: The waiting step's name, up to 237 printable ASCII characters. + required: + - execution_ref + - callback_id + - name + additionalProperties: false + DurableCallbackRegistration: + type: object + description: The token for the waiting step, as the workflow receives it. + properties: + token: + $ref: '#/components/schemas/DurableCallbackToken' + expires_at: + type: string + format: date-time + nullable: true + description: | + When the waiting step times out and the token stops working. Null + when the workflow set no timeout, in which case the token works + until the execution ends. + required: + - token + - expires_at + SucceedDurableCallbackRequest: + type: object + properties: + token: + $ref: '#/components/schemas/DurableCallbackToken' + result: + x-go-type: json.RawMessage + x-go-type-skip-optional-pointer: true + description: | + Any JSON value other than null, up to 64 KiB. The waiting step + resolves with it. Null is refused because a timeout resolves the + step with null. + required: + - token + - result + additionalProperties: false + FailDurableCallbackRequest: + type: object + properties: + token: + $ref: '#/components/schemas/DurableCallbackToken' + error: + $ref: '#/components/schemas/DurableCallbackError' + required: + - token + - error + additionalProperties: false + HeartbeatDurableCallbackRequest: + type: object + properties: + token: + $ref: '#/components/schemas/DurableCallbackToken' + required: + - token + additionalProperties: false + DurableCallbackOutcome: + type: object + properties: + status: + $ref: '#/components/schemas/DurableCallbackStatus' + required: + - status Issue: type: object description: Runtime errors from one function or frontend, grouped by error type and stack. @@ -22795,6 +23467,289 @@ components: description: Your own API key for `provider`, such as an Anthropic key for `anthropic`. Omit it to keep the stored key. Encrypted at rest and never returned. required: - enabled + AutofixRunStatus: + type: string + enum: + - queued + - running + - succeeded + - failed + - canceled + x-enum-varnames: + - AutofixRunStatusQueued + - AutofixRunStatusRunning + - AutofixRunStatusSucceeded + - AutofixRunStatusFailed + - AutofixRunStatusCanceled + description: Where a run is. `canceled` means the project's settings or plan stopped allowing the run before it finished; `failure_code` says which. + AutofixRunTrigger: + type: string + enum: + - manual + - new_issue + - regression + x-enum-varnames: + - AutofixRunTriggerManual + - AutofixRunTriggerNewIssue + - AutofixRunTriggerRegression + description: What started the run. `manual` is a request; `new_issue` and `regression` are automatic runs for an issue that appeared or reopened. + AutofixEvidence: + type: object + additionalProperties: false + properties: + kind: + type: string + enum: + - log + - code + - other + x-enum-varnames: + - AutofixEvidenceKindLog + - AutofixEvidenceKindCode + - AutofixEvidenceKindOther + detail: + type: string + location: + type: string + description: A file and line, or a log timestamp. Absent when the evidence has none. + required: + - kind + - detail + AutofixDiagnosis: + type: object + additionalProperties: false + properties: + summary: + type: string + description: One or two sentences on what goes wrong. + root_cause: + type: string + evidence: + type: array + items: + $ref: '#/components/schemas/AutofixEvidence' + suggested_fix: + type: string + confidence: + type: string + enum: + - low + - medium + - high + x-enum-varnames: + - AutofixConfidenceLow + - AutofixConfidenceMedium + - AutofixConfidenceHigh + fixable: + type: boolean + description: Whether a change to the project's code would fix the issue. + files: + type: array + description: Repository paths a fix would change. + items: + type: string + required: + - summary + - root_cause + - evidence + - suggested_fix + - confidence + - fixable + - files + AutofixRunUsage: + type: object + additionalProperties: false + description: Model tokens the run used across every attempt. + properties: + input_tokens: + type: integer + format: int64 + output_tokens: + type: integer + format: int64 + cache_read_input_tokens: + type: integer + format: int64 + cache_creation_input_tokens: + type: integer + format: int64 + required: + - input_tokens + - output_tokens + - cache_read_input_tokens + - cache_creation_input_tokens + AutofixEdit: + type: object + additionalProperties: false + description: One exact text replacement. An empty `old_text` creates the file. + properties: + path: + type: string + old_text: + type: string + new_text: + type: string + required: + - path + - old_text + - new_text + AutofixPatch: + type: object + additionalProperties: false + description: The fix the model proposed, applied to the deployed commit. Present whenever the run opened a pull request, and when a valid fix could not be published. + properties: + title: + type: string + edits: + type: array + items: + $ref: '#/components/schemas/AutofixEdit' + required: + - title + - edits + AutofixPullRequestState: + type: string + enum: + - open + - merged + - closed + x-enum-varnames: + - AutofixPullRequestStateOpen + - AutofixPullRequestStateMerged + - AutofixPullRequestStateClosed + AutofixPullRequest: + type: object + additionalProperties: false + description: The draft pull request the run opened on the connected repository. + properties: + number: + type: integer + format: int64 + url: + type: string + format: uri + branch: + type: string + description: The branch the fix is on, `volcano/autofix/`. + state: + $ref: '#/components/schemas/AutofixPullRequestState' + closed_at: + type: string + format: date-time + description: When the pull request was merged or closed. + deployed_at: + type: string + format: date-time + description: When a deployment that includes the merged fix went live. The issue resolves then, unless it occurred again since. When more than 20 deployments went live after the merge before Volcano checked, it is the merge time instead. + required: + - number + - url + - branch + - state + AutofixPullRequestSkipped: + type: string + enum: + - source_unavailable + - pull_request_open + - not_fixable + - low_confidence + - no_patch + - permission_missing + - repository_changed + - publish_failed + x-enum-varnames: + - AutofixPullRequestSkippedSourceUnavailable + - AutofixPullRequestSkippedPullRequestOpen + - AutofixPullRequestSkippedNotFixable + - AutofixPullRequestSkippedLowConfidence + - AutofixPullRequestSkippedNoPatch + - AutofixPullRequestSkippedPermissionMissing + - AutofixPullRequestSkippedRepositoryChanged + - AutofixPullRequestSkippedPublishFailed + description: Why a run whose `max_stage` is `pull_request` opened no pull request. The diagnosis stands either way. + AutofixRun: + type: object + additionalProperties: false + properties: + id: + type: string + format: uuid + project_id: + type: string + format: uuid + issue_id: + type: string + format: uuid + trigger: + $ref: '#/components/schemas/AutofixRunTrigger' + status: + $ref: '#/components/schemas/AutofixRunStatus' + max_stage: + $ref: '#/components/schemas/AutofixStage' + stage_reached: + $ref: '#/components/schemas/AutofixStage' + provider: + $ref: '#/components/schemas/AutofixProvider' + model: + $ref: '#/components/schemas/AutofixModel' + diagnosis: + $ref: '#/components/schemas/AutofixDiagnosis' + patch: + $ref: '#/components/schemas/AutofixPatch' + pull_request: + $ref: '#/components/schemas/AutofixPullRequest' + pull_request_skipped: + $ref: '#/components/schemas/AutofixPullRequestSkipped' + commit_sha: + type: string + description: The commit the diagnosis read, when a push to the connected repository deployed the resource. Absent when the run read logs only. + failure_code: + type: string + description: Why the run failed or was canceled, or why its last attempt will be retried. One of `api_key_rejected`, `model_request_rejected`, `model_unavailable`, `model_refused`, `no_diagnosis`, `budget_exhausted`, `timed_out`, `attempts_exhausted`, `autofix_disabled`, `plan_required`, `managed_models_unavailable`, `settings_changed`, `issue_not_found`, or `internal_error`. + failure_message: + type: string + usage: + $ref: '#/components/schemas/AutofixRunUsage' + created_at: + type: string + format: date-time + started_at: + type: string + format: date-time + completed_at: + type: string + format: date-time + required: + - id + - project_id + - issue_id + - trigger + - status + - max_stage + - provider + - model + - usage + - created_at + PaginatedAutofixRuns: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/AutofixRun' + limit: + type: integer + description: Number of items per page + has_more: + type: boolean + description: Whether a next page exists + next_cursor: + type: string + description: Opaque cursor for the next page (present if has_more is true) + required: + - data + - limit + - has_more PaginatedProjectCustomDomains: type: object properties: @@ -23977,6 +24932,7 @@ components: - function - frontend - database + - database_branch id: type: string format: uuid @@ -24065,8 +25021,11 @@ components: - findings ProjectHealthStatus: type: string + description: empty means no functions, frontends, databases, storage, Volcano Auth users, or Sandbox resources are listed. needs_attention means an operation needs review without confirmed service impairment, or a serving frontend whose edge recovery gave up needs a redeploy. degraded means a resource is only partly available or is unavailable for an expected, temporary reason, such as a frontend whose edge synchronization did not complete or a database being restored; critical means use is blocked. A failed workflow or deployment record alone does not establish an outage. Findings remain available for empty projects. enum: + - empty - healthy + - needs_attention - degraded - critical - unknown @@ -25923,6 +26882,46 @@ components: message is handed over, then `delivered`. `expired` means the wait's timeout passed first, and `cancelled` that the execution stopped waiting. + DurableCallbackToken: + type: string + minLength: 5 + maxLength: 128 + pattern: ^vcb_[A-Za-z0-9_-]+$ + description: | + The `vcb_` token the workflow handed out. It is the only credential + the callback operations take, so treat it as a secret. + DurableCallbackStatus: + type: string + enum: + - waiting + - delivering + - succeeded + - failed + description: | + `waiting` until the token is succeeded or failed. `delivering` while + the outcome is on its way to the workflow; Volcano finishes the + delivery on its own. `succeeded` or `failed` once the workflow has the + outcome. + DurableCallbackError: + type: object + description: The error the waiting step raises. + properties: + type: + type: string + minLength: 1 + maxLength: 256 + pattern: ^[^\x00-\x1F\x7F]+$ + description: | + A name workflows can branch on, such as `PaymentDeclined`. Up to + 256 characters, without control characters. + message: + type: string + maxLength: 32768 + pattern: ^[^\x00]*$ + description: A human-readable explanation, up to 32 KiB. + required: + - type + additionalProperties: false DurableApprovalFunction: type: object description: | diff --git a/src/volcano_sdk/_generated/api/authentication/auth_forgot_password.py b/src/volcano_sdk/_generated/api/authentication/auth_forgot_password.py index 8e99a5c9..570079cd 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_forgot_password.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_forgot_password.py @@ -11,6 +11,7 @@ from ...models.auth_forgot_password_body import AuthForgotPasswordBody from ...models.auth_forgot_password_response_200 import AuthForgotPasswordResponse200 from ...models.error import Error +from ...types import UNSET, Unset from typing import cast @@ -18,9 +19,17 @@ def request_kwargs( *, body: AuthForgotPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -49,6 +58,13 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res return response_200 + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + + + return response_401 + if response.status_code == 403: response_403 = Error.from_dict(response.json()) @@ -82,6 +98,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthForgotPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[AuthForgotPasswordResponse200 | Error]: """ Request password reset @@ -91,6 +109,8 @@ def sync_detailed( Project is identified via the anon key. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthForgotPasswordBody): Raises: @@ -104,6 +124,8 @@ def sync_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -117,6 +139,8 @@ def sync( *, client: AuthenticatedClient, body: AuthForgotPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> AuthForgotPasswordResponse200 | Error | None: """ Request password reset @@ -126,6 +150,8 @@ def sync( Project is identified via the anon key. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthForgotPasswordBody): Raises: @@ -140,6 +166,8 @@ def sync( return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -147,6 +175,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthForgotPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[AuthForgotPasswordResponse200 | Error]: """ Request password reset @@ -156,6 +186,8 @@ async def asyncio_detailed( Project is identified via the anon key. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthForgotPasswordBody): Raises: @@ -169,6 +201,8 @@ async def asyncio_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -182,6 +216,8 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthForgotPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> AuthForgotPasswordResponse200 | Error | None: """ Request password reset @@ -191,6 +227,8 @@ async def asyncio( Project is identified via the anon key. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthForgotPasswordBody): Raises: @@ -205,5 +243,7 @@ async def asyncio( return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_get_password_policy.py b/src/volcano_sdk/_generated/api/authentication/auth_get_password_policy.py index e54e0304..0afc3245 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_get_password_policy.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_get_password_policy.py @@ -10,14 +10,25 @@ from ...models.auth_password_policy import AuthPasswordPolicy from ...models.error import Error +from ...types import UNSET, Unset from typing import cast def request_kwargs( - + *, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, + ) -> dict[str, Any]: - + headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + + @@ -29,6 +40,7 @@ def request_kwargs( } + _kwargs["headers"] = headers return _kwargs @@ -73,6 +85,8 @@ def build_response(*, client: AuthenticatedClient | Client, response: httpx.Resp def sync_detailed( *, client: AuthenticatedClient, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[AuthPasswordPolicy | Error]: """ Get the effective password policy @@ -81,6 +95,10 @@ def sync_detailed( screening status for the project identified by the anon key. A valid anon key is required, but no route-specific auth permission is needed. + Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -91,7 +109,9 @@ def sync_detailed( kwargs = request_kwargs( - + x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, + ) response = client.get_httpx_client().request( @@ -103,6 +123,8 @@ def sync_detailed( def sync( *, client: AuthenticatedClient, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> AuthPasswordPolicy | Error | None: """ Get the effective password policy @@ -111,6 +133,10 @@ def sync( screening status for the project identified by the anon key. A valid anon key is required, but no route-specific auth permission is needed. + Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -122,12 +148,16 @@ def sync( return sync_detailed( client=client, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed async def asyncio_detailed( *, client: AuthenticatedClient, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[AuthPasswordPolicy | Error]: """ Get the effective password policy @@ -136,6 +166,10 @@ async def asyncio_detailed( screening status for the project identified by the anon key. A valid anon key is required, but no route-specific auth permission is needed. + Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -146,7 +180,9 @@ async def asyncio_detailed( kwargs = request_kwargs( - + x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, + ) response = await client.get_async_httpx_client().request( @@ -158,6 +194,8 @@ async def asyncio_detailed( async def asyncio( *, client: AuthenticatedClient, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> AuthPasswordPolicy | Error | None: """ Get the effective password policy @@ -166,6 +204,10 @@ async def asyncio( screening status for the project identified by the anon key. A valid anon key is required, but no route-specific auth permission is needed. + Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): + Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. @@ -177,5 +219,7 @@ async def asyncio( return (await asyncio_detailed( client=client, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_logout.py b/src/volcano_sdk/_generated/api/authentication/auth_logout.py index 39c21187..8a0e74c9 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_logout.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_logout.py @@ -9,6 +9,7 @@ from ... import errors from ...models.auth_logout_body import AuthLogoutBody +from ...models.error import Error from ...types import UNSET, Unset from typing import cast @@ -17,9 +18,17 @@ def request_kwargs( *, body: AuthLogoutBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -42,9 +51,17 @@ def request_kwargs( -def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | None: +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None: if response.status_code == 204: - return None + response_204 = cast(Any, None) + return response_204 + + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + + + return response_401 if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) @@ -52,7 +69,7 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res return None -def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]: +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -65,8 +82,10 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthLogoutBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, -) -> Response[Any]: +) -> Response[Any | Error]: """ Logout (revoke refresh token) Invalidate a refresh token. Requires an anon key. @@ -76,6 +95,8 @@ def sync_detailed( token; logout remains idempotent when the cookie is missing or expired. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthLogoutBody | Unset): Raises: @@ -83,12 +104,14 @@ def sync_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any] + Response[Any | Error] """ kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -98,13 +121,52 @@ def sync_detailed( return build_response(client=client, response=response) +def sync( + *, + client: AuthenticatedClient, + body: AuthLogoutBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, + +) -> Any | Error | None: + """ Logout (revoke refresh token) + + Invalidate a refresh token. Requires an anon key. + + Send `refresh_token` for the default flow. An eligible cookie-mode + browser request may instead send `session_mode: cookie` with an empty + token; logout remains idempotent when the cookie is missing or expired. + + Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): + body (AuthLogoutBody | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | Error + """ + + + return sync_detailed( + client=client, +body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, + + ).parsed async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthLogoutBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, -) -> Response[Any]: +) -> Response[Any | Error]: """ Logout (revoke refresh token) Invalidate a refresh token. Requires an anon key. @@ -114,6 +176,8 @@ async def asyncio_detailed( token; logout remains idempotent when the cookie is missing or expired. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthLogoutBody | Unset): Raises: @@ -121,12 +185,14 @@ async def asyncio_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any] + Response[Any | Error] """ kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -136,3 +202,40 @@ async def asyncio_detailed( return build_response(client=client, response=response) +async def asyncio( + *, + client: AuthenticatedClient, + body: AuthLogoutBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, + +) -> Any | Error | None: + """ Logout (revoke refresh token) + + Invalidate a refresh token. Requires an anon key. + + Send `refresh_token` for the default flow. An eligible cookie-mode + browser request may instead send `session_mode: cookie` with an empty + token; logout remains idempotent when the cookie is missing or expired. + + Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): + body (AuthLogoutBody | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | Error + """ + + + return (await asyncio_detailed( + client=client, +body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_refresh.py b/src/volcano_sdk/_generated/api/authentication/auth_refresh.py index bb68bd60..ff639f08 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_refresh.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_refresh.py @@ -19,9 +19,17 @@ def request_kwargs( *, body: AuthRefreshBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -92,6 +100,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthRefreshBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[AuthTokenResponse | Error]: """ Refresh access token @@ -105,6 +115,8 @@ def sync_detailed( response. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthRefreshBody | Unset): Raises: @@ -118,6 +130,8 @@ def sync_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -131,6 +145,8 @@ def sync( *, client: AuthenticatedClient, body: AuthRefreshBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> AuthTokenResponse | Error | None: """ Refresh access token @@ -144,6 +160,8 @@ def sync( response. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthRefreshBody | Unset): Raises: @@ -158,6 +176,8 @@ def sync( return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -165,6 +185,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthRefreshBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[AuthTokenResponse | Error]: """ Refresh access token @@ -178,6 +200,8 @@ async def asyncio_detailed( response. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthRefreshBody | Unset): Raises: @@ -191,6 +215,8 @@ async def asyncio_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -204,6 +230,8 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthRefreshBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> AuthTokenResponse | Error | None: """ Refresh access token @@ -217,6 +245,8 @@ async def asyncio( response. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthRefreshBody | Unset): Raises: @@ -231,5 +261,7 @@ async def asyncio( return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_reset_password.py b/src/volcano_sdk/_generated/api/authentication/auth_reset_password.py index fa12e70a..4147b7cc 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_reset_password.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_reset_password.py @@ -11,6 +11,7 @@ from ...models.auth_reset_password_body import AuthResetPasswordBody from ...models.auth_reset_password_response_200 import AuthResetPasswordResponse200 from ...models.error import Error +from ...types import UNSET, Unset from typing import cast @@ -18,9 +19,17 @@ def request_kwargs( *, body: AuthResetPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -83,6 +92,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthResetPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthResetPasswordResponse200 | Error]: """ Reset password with recovery token @@ -91,6 +102,8 @@ def sync_detailed( Revokes all existing sessions for security. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthResetPasswordBody): Raises: @@ -104,6 +117,8 @@ def sync_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -117,6 +132,8 @@ def sync( *, client: AuthenticatedClient, body: AuthResetPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthResetPasswordResponse200 | Error | None: """ Reset password with recovery token @@ -125,6 +142,8 @@ def sync( Revokes all existing sessions for security. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthResetPasswordBody): Raises: @@ -139,6 +158,8 @@ def sync( return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -146,6 +167,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthResetPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthResetPasswordResponse200 | Error]: """ Reset password with recovery token @@ -154,6 +177,8 @@ async def asyncio_detailed( Revokes all existing sessions for security. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthResetPasswordBody): Raises: @@ -167,6 +192,8 @@ async def asyncio_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -180,6 +207,8 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthResetPasswordBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthResetPasswordResponse200 | Error | None: """ Reset password with recovery token @@ -188,6 +217,8 @@ async def asyncio( Revokes all existing sessions for security. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthResetPasswordBody): Raises: @@ -202,5 +233,7 @@ async def asyncio( return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_signin.py b/src/volcano_sdk/_generated/api/authentication/auth_signin.py index 8bf251a6..bf84ceca 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_signin.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_signin.py @@ -10,6 +10,7 @@ from ...models.auth_signin_body import AuthSigninBody from ...models.auth_token_response import AuthTokenResponse +from ...types import UNSET, Unset from typing import cast @@ -17,9 +18,17 @@ def request_kwargs( *, body: AuthSigninBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -83,6 +92,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthSigninBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthTokenResponse]: """ Sign in an auth user @@ -97,6 +108,8 @@ def sync_detailed( token. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSigninBody): Raises: @@ -110,6 +123,8 @@ def sync_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -123,6 +138,8 @@ def sync( *, client: AuthenticatedClient, body: AuthSigninBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthTokenResponse | None: """ Sign in an auth user @@ -137,6 +154,8 @@ def sync( token. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSigninBody): Raises: @@ -151,6 +170,8 @@ def sync( return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -158,6 +179,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthSigninBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthTokenResponse]: """ Sign in an auth user @@ -172,6 +195,8 @@ async def asyncio_detailed( token. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSigninBody): Raises: @@ -185,6 +210,8 @@ async def asyncio_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -198,6 +225,8 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthSigninBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthTokenResponse | None: """ Sign in an auth user @@ -212,6 +241,8 @@ async def asyncio( token. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSigninBody): Raises: @@ -226,5 +257,7 @@ async def asyncio( return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_signup.py b/src/volcano_sdk/_generated/api/authentication/auth_signup.py index 6db72ccd..6fec9bab 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_signup.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_signup.py @@ -11,6 +11,7 @@ from ...models.auth_signup_body import AuthSignupBody from ...models.auth_signup_response import AuthSignupResponse from ...models.error import Error +from ...types import UNSET, Unset from typing import cast @@ -18,9 +19,17 @@ def request_kwargs( *, body: AuthSignupBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -91,6 +100,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthSignupBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthSignupResponse | Error]: """ Sign up a new auth user @@ -109,6 +120,8 @@ def sync_detailed( response cannot be used to discover which emails are registered. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupBody): Raises: @@ -122,6 +135,8 @@ def sync_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -135,6 +150,8 @@ def sync( *, client: AuthenticatedClient, body: AuthSignupBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthSignupResponse | Error | None: """ Sign up a new auth user @@ -153,6 +170,8 @@ def sync( response cannot be used to discover which emails are registered. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupBody): Raises: @@ -167,6 +186,8 @@ def sync( return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -174,6 +195,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthSignupBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthSignupResponse | Error]: """ Sign up a new auth user @@ -192,6 +215,8 @@ async def asyncio_detailed( response cannot be used to discover which emails are registered. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupBody): Raises: @@ -205,6 +230,8 @@ async def asyncio_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -218,6 +245,8 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthSignupBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthSignupResponse | Error | None: """ Sign up a new auth user @@ -236,6 +265,8 @@ async def asyncio( response cannot be used to discover which emails are registered. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupBody): Raises: @@ -250,5 +281,7 @@ async def asyncio( return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/authentication/auth_signup_anonymous.py b/src/volcano_sdk/_generated/api/authentication/auth_signup_anonymous.py index 620257a7..4ab1e4a2 100644 --- a/src/volcano_sdk/_generated/api/authentication/auth_signup_anonymous.py +++ b/src/volcano_sdk/_generated/api/authentication/auth_signup_anonymous.py @@ -10,6 +10,7 @@ from ...models.auth_signup_anonymous_body import AuthSignupAnonymousBody from ...models.auth_token_response import AuthTokenResponse +from ...models.error import Error from ...types import UNSET, Unset from typing import cast @@ -18,9 +19,17 @@ def request_kwargs( *, body: AuthSignupAnonymousBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -43,7 +52,7 @@ def request_kwargs( -def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | AuthTokenResponse | None: +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | AuthTokenResponse | Error | None: if response.status_code == 201: response_201 = AuthTokenResponse.from_dict(response.json()) @@ -51,6 +60,13 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res return response_201 + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + + + return response_401 + if response.status_code == 403: response_403 = cast(Any, None) return response_403 @@ -61,7 +77,7 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res return None -def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | AuthTokenResponse]: +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | AuthTokenResponse | Error]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -74,8 +90,10 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthSignupAnonymousBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, -) -> Response[Any | AuthTokenResponse]: +) -> Response[Any | AuthTokenResponse | Error]: """ Create anonymous user Create guest user without email/password. @@ -84,6 +102,8 @@ def sync_detailed( Requires enable_anonymous_signins to be true. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupAnonymousBody | Unset): Raises: @@ -91,12 +111,14 @@ def sync_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any | AuthTokenResponse] + Response[Any | AuthTokenResponse | Error] """ kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -110,8 +132,10 @@ def sync( *, client: AuthenticatedClient, body: AuthSignupAnonymousBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, -) -> Any | AuthTokenResponse | None: +) -> Any | AuthTokenResponse | Error | None: """ Create anonymous user Create guest user without email/password. @@ -120,6 +144,8 @@ def sync( Requires enable_anonymous_signins to be true. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupAnonymousBody | Unset): Raises: @@ -127,13 +153,15 @@ def sync( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Any | AuthTokenResponse + Any | AuthTokenResponse | Error """ return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -141,8 +169,10 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthSignupAnonymousBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, -) -> Response[Any | AuthTokenResponse]: +) -> Response[Any | AuthTokenResponse | Error]: """ Create anonymous user Create guest user without email/password. @@ -151,6 +181,8 @@ async def asyncio_detailed( Requires enable_anonymous_signins to be true. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupAnonymousBody | Unset): Raises: @@ -158,12 +190,14 @@ async def asyncio_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any | AuthTokenResponse] + Response[Any | AuthTokenResponse | Error] """ kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -177,8 +211,10 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthSignupAnonymousBody | Unset = UNSET, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, -) -> Any | AuthTokenResponse | None: +) -> Any | AuthTokenResponse | Error | None: """ Create anonymous user Create guest user without email/password. @@ -187,6 +223,8 @@ async def asyncio( Requires enable_anonymous_signins to be true. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthSignupAnonymousBody | Unset): Raises: @@ -194,12 +232,14 @@ async def asyncio( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Any | AuthTokenResponse + Any | AuthTokenResponse | Error """ return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/autofix/get_autofix_run.py b/src/volcano_sdk/_generated/api/autofix/get_autofix_run.py new file mode 100644 index 00000000..d79f218c --- /dev/null +++ b/src/volcano_sdk/_generated/api/autofix/get_autofix_run.py @@ -0,0 +1,224 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.autofix_run import AutofixRun +from ...models.error import Error +from typing import cast +from uuid import UUID + + + +def request_kwargs( + id: UUID | str, + run_id: UUID | str, + +) -> dict[str, Any]: + + + + + + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/projects/{id}/autofix-runs/{run_id}".format(id=quote(str(id), safe=""),run_id=quote(str(run_id), safe=""),), + } + + + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> AutofixRun | Error | None: + if response.status_code == 200: + response_200 = AutofixRun.from_dict(response.json()) + + + + return response_200 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 501: + response_501 = Error.from_dict(response.json()) + + + + return response_501 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[AutofixRun | Error]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + id: UUID | str, + run_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> Response[AutofixRun | Error]: + """ Get an autofix run + + Returns one run: its status, the diagnosis once it succeeds, why it + failed or was canceled, and the model tokens it used. Runs stay readable + where autofix is not available. + + Project access tokens can read runs, including read-only ones. + + Args: + id (UUID): + run_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AutofixRun | Error] + """ + + + kwargs = request_kwargs( + id=id, +run_id=run_id, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + id: UUID | str, + run_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> AutofixRun | Error | None: + """ Get an autofix run + + Returns one run: its status, the diagnosis once it succeeds, why it + failed or was canceled, and the model tokens it used. Runs stay readable + where autofix is not available. + + Project access tokens can read runs, including read-only ones. + + Args: + id (UUID): + run_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AutofixRun | Error + """ + + + return sync_detailed( + id=id, +run_id=run_id, +client=client, + + ).parsed + +async def asyncio_detailed( + id: UUID | str, + run_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> Response[AutofixRun | Error]: + """ Get an autofix run + + Returns one run: its status, the diagnosis once it succeeds, why it + failed or was canceled, and the model tokens it used. Runs stay readable + where autofix is not available. + + Project access tokens can read runs, including read-only ones. + + Args: + id (UUID): + run_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AutofixRun | Error] + """ + + + kwargs = request_kwargs( + id=id, +run_id=run_id, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + id: UUID | str, + run_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> AutofixRun | Error | None: + """ Get an autofix run + + Returns one run: its status, the diagnosis once it succeeds, why it + failed or was canceled, and the model tokens it used. Runs stay readable + where autofix is not available. + + Project access tokens can read runs, including read-only ones. + + Args: + id (UUID): + run_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AutofixRun | Error + """ + + + return (await asyncio_detailed( + id=id, +run_id=run_id, +client=client, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/autofix/list_autofix_runs.py b/src/volcano_sdk/_generated/api/autofix/list_autofix_runs.py new file mode 100644 index 00000000..0d90897a --- /dev/null +++ b/src/volcano_sdk/_generated/api/autofix/list_autofix_runs.py @@ -0,0 +1,294 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.autofix_run_status import AutofixRunStatus +from ...models.autofix_run_status import check_autofix_run_status +from ...models.error import Error +from ...models.paginated_autofix_runs import PaginatedAutofixRuns +from ...types import UNSET, Unset +from typing import cast +from uuid import UUID + + + +def request_kwargs( + id: UUID | str, + *, + limit: int | Unset = 10, + cursor: str | Unset = UNSET, + issue_id: UUID | str | Unset = UNSET, + status: AutofixRunStatus | Unset = UNSET, + +) -> dict[str, Any]: + + + + + params: dict[str, Any] = {} + + params["limit"] = limit + + params["cursor"] = cursor + + json_issue_id: str | Unset = UNSET + if not isinstance(issue_id, Unset): + json_issue_id = str(issue_id) + params["issue_id"] = json_issue_id + + json_status: str | Unset = UNSET + if not isinstance(status, Unset): + json_status = status + + params["status"] = json_status + + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/projects/{id}/autofix-runs".format(id=quote(str(id), safe=""),), + "params": params, + } + + + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Error | PaginatedAutofixRuns | None: + if response.status_code == 200: + response_200 = PaginatedAutofixRuns.from_dict(response.json()) + + + + return response_200 + + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + + + return response_400 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 501: + response_501 = Error.from_dict(response.json()) + + + + return response_501 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Error | PaginatedAutofixRuns]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + id: UUID | str, + *, + client: AuthenticatedClient, + limit: int | Unset = 10, + cursor: str | Unset = UNSET, + issue_id: UUID | str | Unset = UNSET, + status: AutofixRunStatus | Unset = UNSET, + +) -> Response[Error | PaginatedAutofixRuns]: + """ List autofix runs + + Lists the project's autofix runs, newest first. Runs stay readable after + autofix is turned off, and where autofix is not available. + + Project access tokens can list runs, including read-only ones. + + Args: + id (UUID): + limit (int | Unset): Default: 10. + cursor (str | Unset): + issue_id (UUID | Unset): + status (AutofixRunStatus | Unset): Where a run is. `canceled` means the project's settings + or plan stopped allowing the run before it finished; `failure_code` says which. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Error | PaginatedAutofixRuns] + """ + + + kwargs = request_kwargs( + id=id, +limit=limit, +cursor=cursor, +issue_id=issue_id, +status=status, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + id: UUID | str, + *, + client: AuthenticatedClient, + limit: int | Unset = 10, + cursor: str | Unset = UNSET, + issue_id: UUID | str | Unset = UNSET, + status: AutofixRunStatus | Unset = UNSET, + +) -> Error | PaginatedAutofixRuns | None: + """ List autofix runs + + Lists the project's autofix runs, newest first. Runs stay readable after + autofix is turned off, and where autofix is not available. + + Project access tokens can list runs, including read-only ones. + + Args: + id (UUID): + limit (int | Unset): Default: 10. + cursor (str | Unset): + issue_id (UUID | Unset): + status (AutofixRunStatus | Unset): Where a run is. `canceled` means the project's settings + or plan stopped allowing the run before it finished; `failure_code` says which. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Error | PaginatedAutofixRuns + """ + + + return sync_detailed( + id=id, +client=client, +limit=limit, +cursor=cursor, +issue_id=issue_id, +status=status, + + ).parsed + +async def asyncio_detailed( + id: UUID | str, + *, + client: AuthenticatedClient, + limit: int | Unset = 10, + cursor: str | Unset = UNSET, + issue_id: UUID | str | Unset = UNSET, + status: AutofixRunStatus | Unset = UNSET, + +) -> Response[Error | PaginatedAutofixRuns]: + """ List autofix runs + + Lists the project's autofix runs, newest first. Runs stay readable after + autofix is turned off, and where autofix is not available. + + Project access tokens can list runs, including read-only ones. + + Args: + id (UUID): + limit (int | Unset): Default: 10. + cursor (str | Unset): + issue_id (UUID | Unset): + status (AutofixRunStatus | Unset): Where a run is. `canceled` means the project's settings + or plan stopped allowing the run before it finished; `failure_code` says which. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Error | PaginatedAutofixRuns] + """ + + + kwargs = request_kwargs( + id=id, +limit=limit, +cursor=cursor, +issue_id=issue_id, +status=status, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + id: UUID | str, + *, + client: AuthenticatedClient, + limit: int | Unset = 10, + cursor: str | Unset = UNSET, + issue_id: UUID | str | Unset = UNSET, + status: AutofixRunStatus | Unset = UNSET, + +) -> Error | PaginatedAutofixRuns | None: + """ List autofix runs + + Lists the project's autofix runs, newest first. Runs stay readable after + autofix is turned off, and where autofix is not available. + + Project access tokens can list runs, including read-only ones. + + Args: + id (UUID): + limit (int | Unset): Default: 10. + cursor (str | Unset): + issue_id (UUID | Unset): + status (AutofixRunStatus | Unset): Where a run is. `canceled` means the project's settings + or plan stopped allowing the run before it finished; `failure_code` says which. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Error | PaginatedAutofixRuns + """ + + + return (await asyncio_detailed( + id=id, +client=client, +limit=limit, +cursor=cursor, +issue_id=issue_id, +status=status, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/autofix/start_autofix_run.py b/src/volcano_sdk/_generated/api/autofix/start_autofix_run.py new file mode 100644 index 00000000..0fee78e8 --- /dev/null +++ b/src/volcano_sdk/_generated/api/autofix/start_autofix_run.py @@ -0,0 +1,291 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.autofix_run import AutofixRun +from ...models.error import Error +from typing import cast +from uuid import UUID + + + +def request_kwargs( + id: UUID | str, + issue_id: UUID | str, + +) -> dict[str, Any]: + + + + + + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/projects/{id}/issues/{issue_id}/autofix-runs".format(id=quote(str(id), safe=""),issue_id=quote(str(issue_id), safe=""),), + } + + + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> AutofixRun | Error | None: + if response.status_code == 202: + response_202 = AutofixRun.from_dict(response.json()) + + + + return response_202 + + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + + + return response_400 + + if response.status_code == 403: + response_403 = Error.from_dict(response.json()) + + + + return response_403 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + + + return response_409 + + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + + + return response_429 + + if response.status_code == 501: + response_501 = Error.from_dict(response.json()) + + + + return response_501 + + if response.status_code == 503: + response_503 = Error.from_dict(response.json()) + + + + return response_503 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[AutofixRun | Error]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + id: UUID | str, + issue_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> Response[AutofixRun | Error]: + """ Diagnose an issue + + Queues a run that diagnoses the issue with AI. The run reads the + issue's runtime logs and, when a push to the project's connected + repository built the deployment that was live when the issue was + last seen, its source at that commit. Poll the run until its status is + `succeeded`, `failed`, or `canceled`. + + Autofix must be on for the project. An issue has at most one queued or + running run, a project runs one at a time, and a project starts at most + 25 runs, manual and automatic, in any 24 hours. + + Project access tokens are refused. A run spends model tokens on the + project's provider, so a person starts it with a platform token or in + the dashboard. + + Args: + id (UUID): + issue_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AutofixRun | Error] + """ + + + kwargs = request_kwargs( + id=id, +issue_id=issue_id, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + id: UUID | str, + issue_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> AutofixRun | Error | None: + """ Diagnose an issue + + Queues a run that diagnoses the issue with AI. The run reads the + issue's runtime logs and, when a push to the project's connected + repository built the deployment that was live when the issue was + last seen, its source at that commit. Poll the run until its status is + `succeeded`, `failed`, or `canceled`. + + Autofix must be on for the project. An issue has at most one queued or + running run, a project runs one at a time, and a project starts at most + 25 runs, manual and automatic, in any 24 hours. + + Project access tokens are refused. A run spends model tokens on the + project's provider, so a person starts it with a platform token or in + the dashboard. + + Args: + id (UUID): + issue_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AutofixRun | Error + """ + + + return sync_detailed( + id=id, +issue_id=issue_id, +client=client, + + ).parsed + +async def asyncio_detailed( + id: UUID | str, + issue_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> Response[AutofixRun | Error]: + """ Diagnose an issue + + Queues a run that diagnoses the issue with AI. The run reads the + issue's runtime logs and, when a push to the project's connected + repository built the deployment that was live when the issue was + last seen, its source at that commit. Poll the run until its status is + `succeeded`, `failed`, or `canceled`. + + Autofix must be on for the project. An issue has at most one queued or + running run, a project runs one at a time, and a project starts at most + 25 runs, manual and automatic, in any 24 hours. + + Project access tokens are refused. A run spends model tokens on the + project's provider, so a person starts it with a platform token or in + the dashboard. + + Args: + id (UUID): + issue_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AutofixRun | Error] + """ + + + kwargs = request_kwargs( + id=id, +issue_id=issue_id, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + id: UUID | str, + issue_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> AutofixRun | Error | None: + """ Diagnose an issue + + Queues a run that diagnoses the issue with AI. The run reads the + issue's runtime logs and, when a push to the project's connected + repository built the deployment that was live when the issue was + last seen, its source at that commit. Poll the run until its status is + `succeeded`, `failed`, or `canceled`. + + Autofix must be on for the project. An issue has at most one queued or + running run, a project runs one at a time, and a project starts at most + 25 runs, manual and automatic, in any 24 hours. + + Project access tokens are refused. A run spends model tokens on the + project's provider, so a person starts it with a platform token or in + the dashboard. + + Args: + id (UUID): + issue_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AutofixRun | Error + """ + + + return (await asyncio_detailed( + id=id, +issue_id=issue_id, +client=client, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/durable_functions/fail_durable_callback.py b/src/volcano_sdk/_generated/api/durable_functions/fail_durable_callback.py new file mode 100644 index 00000000..eaf8fb76 --- /dev/null +++ b/src/volcano_sdk/_generated/api/durable_functions/fail_durable_callback.py @@ -0,0 +1,256 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.durable_callback_outcome import DurableCallbackOutcome +from ...models.error import Error +from ...models.fail_durable_callback_request import FailDurableCallbackRequest +from typing import cast + + + +def request_kwargs( + *, + body: FailDurableCallbackRequest, + +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + + + + + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/durable-callbacks/fail", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> DurableCallbackOutcome | Error | None: + if response.status_code == 200: + response_200 = DurableCallbackOutcome.from_dict(response.json()) + + + + return response_200 + + if response.status_code == 202: + response_202 = DurableCallbackOutcome.from_dict(response.json()) + + + + return response_202 + + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + + + return response_400 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + + + return response_409 + + if response.status_code == 410: + response_410 = Error.from_dict(response.json()) + + + + return response_410 + + if response.status_code == 413: + response_413 = Error.from_dict(response.json()) + + + + return response_413 + + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + + + return response_429 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[DurableCallbackOutcome | Error]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: FailDurableCallbackRequest, + +) -> Response[DurableCallbackOutcome | Error]: + """ Fail a waiting step + + Resumes the workflow step waiting on the token by raising `error`. The + SDKs raise it as `CallbackFailedError`, carrying its `type` and + `message`. + + The token is the only credential, and takes one outcome, as for + succeeding it. + + Args: + body (FailDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackOutcome | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + *, + client: AuthenticatedClient | Client, + body: FailDurableCallbackRequest, + +) -> DurableCallbackOutcome | Error | None: + """ Fail a waiting step + + Resumes the workflow step waiting on the token by raising `error`. The + SDKs raise it as `CallbackFailedError`, carrying its `type` and + `message`. + + The token is the only credential, and takes one outcome, as for + succeeding it. + + Args: + body (FailDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackOutcome | Error + """ + + + return sync_detailed( + client=client, +body=body, + + ).parsed + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: FailDurableCallbackRequest, + +) -> Response[DurableCallbackOutcome | Error]: + """ Fail a waiting step + + Resumes the workflow step waiting on the token by raising `error`. The + SDKs raise it as `CallbackFailedError`, carrying its `type` and + `message`. + + The token is the only credential, and takes one outcome, as for + succeeding it. + + Args: + body (FailDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackOutcome | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: FailDurableCallbackRequest, + +) -> DurableCallbackOutcome | Error | None: + """ Fail a waiting step + + Resumes the workflow step waiting on the token by raising `error`. The + SDKs raise it as `CallbackFailedError`, carrying its `type` and + `message`. + + The token is the only credential, and takes one outcome, as for + succeeding it. + + Args: + body (FailDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackOutcome | Error + """ + + + return (await asyncio_detailed( + client=client, +body=body, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/durable_functions/heartbeat_durable_callback.py b/src/volcano_sdk/_generated/api/durable_functions/heartbeat_durable_callback.py new file mode 100644 index 00000000..476ec88a --- /dev/null +++ b/src/volcano_sdk/_generated/api/durable_functions/heartbeat_durable_callback.py @@ -0,0 +1,248 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.durable_callback_outcome import DurableCallbackOutcome +from ...models.error import Error +from ...models.heartbeat_durable_callback_request import HeartbeatDurableCallbackRequest +from typing import cast + + + +def request_kwargs( + *, + body: HeartbeatDurableCallbackRequest, + +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + + + + + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/durable-callbacks/heartbeat", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> DurableCallbackOutcome | Error | None: + if response.status_code == 200: + response_200 = DurableCallbackOutcome.from_dict(response.json()) + + + + return response_200 + + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + + + return response_400 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + + + return response_409 + + if response.status_code == 410: + response_410 = Error.from_dict(response.json()) + + + + return response_410 + + if response.status_code == 413: + response_413 = Error.from_dict(response.json()) + + + + return response_413 + + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + + + return response_429 + + if response.status_code == 503: + response_503 = Error.from_dict(response.json()) + + + + return response_503 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[DurableCallbackOutcome | Error]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: HeartbeatDurableCallbackRequest, + +) -> Response[DurableCallbackOutcome | Error]: + """ Keep a waiting step alive + + Tells the workflow step waiting on the token that the work is still + going on, restarting its heartbeat timeout. A heartbeat never extends + the step's own timeout, and a step without a heartbeat timeout needs + none. + + Args: + body (HeartbeatDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackOutcome | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + *, + client: AuthenticatedClient | Client, + body: HeartbeatDurableCallbackRequest, + +) -> DurableCallbackOutcome | Error | None: + """ Keep a waiting step alive + + Tells the workflow step waiting on the token that the work is still + going on, restarting its heartbeat timeout. A heartbeat never extends + the step's own timeout, and a step without a heartbeat timeout needs + none. + + Args: + body (HeartbeatDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackOutcome | Error + """ + + + return sync_detailed( + client=client, +body=body, + + ).parsed + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: HeartbeatDurableCallbackRequest, + +) -> Response[DurableCallbackOutcome | Error]: + """ Keep a waiting step alive + + Tells the workflow step waiting on the token that the work is still + going on, restarting its heartbeat timeout. A heartbeat never extends + the step's own timeout, and a step without a heartbeat timeout needs + none. + + Args: + body (HeartbeatDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackOutcome | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: HeartbeatDurableCallbackRequest, + +) -> DurableCallbackOutcome | Error | None: + """ Keep a waiting step alive + + Tells the workflow step waiting on the token that the work is still + going on, restarting its heartbeat timeout. A heartbeat never extends + the step's own timeout, and a step without a heartbeat timeout needs + none. + + Args: + body (HeartbeatDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackOutcome | Error + """ + + + return (await asyncio_detailed( + client=client, +body=body, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/durable_functions/publish_durable_message.py b/src/volcano_sdk/_generated/api/durable_functions/publish_durable_message.py index de29d303..c2227628 100644 --- a/src/volcano_sdk/_generated/api/durable_functions/publish_durable_message.py +++ b/src/volcano_sdk/_generated/api/durable_functions/publish_durable_message.py @@ -126,7 +126,9 @@ def sync_detailed( The SDK sends an `Idempotency-Key` that a rerun of the publishing step repeats, so a step that runs again returns the first message with - `200` and publishes nothing. + `200` and stores nothing new. That message is delivered live again, + and counts again, because the first run may have stopped before + subscribers got it. The SDKs deliver it to a subscriber once. Args: x_volcano_durable_session (str): @@ -174,7 +176,9 @@ def sync( The SDK sends an `Idempotency-Key` that a rerun of the publishing step repeats, so a step that runs again returns the first message with - `200` and publishes nothing. + `200` and stores nothing new. That message is delivered live again, + and counts again, because the first run may have stopped before + subscribers got it. The SDKs deliver it to a subscriber once. Args: x_volcano_durable_session (str): @@ -217,7 +221,9 @@ async def asyncio_detailed( The SDK sends an `Idempotency-Key` that a rerun of the publishing step repeats, so a step that runs again returns the first message with - `200` and publishes nothing. + `200` and stores nothing new. That message is delivered live again, + and counts again, because the first run may have stopped before + subscribers got it. The SDKs deliver it to a subscriber once. Args: x_volcano_durable_session (str): @@ -265,7 +271,9 @@ async def asyncio( The SDK sends an `Idempotency-Key` that a rerun of the publishing step repeats, so a step that runs again returns the first message with - `200` and publishes nothing. + `200` and stores nothing new. That message is delivered live again, + and counts again, because the first run may have stopped before + subscribers got it. The SDKs deliver it to a subscriber once. Args: x_volcano_durable_session (str): diff --git a/src/volcano_sdk/_generated/api/durable_functions/register_durable_callback.py b/src/volcano_sdk/_generated/api/durable_functions/register_durable_callback.py new file mode 100644 index 00000000..bc734510 --- /dev/null +++ b/src/volcano_sdk/_generated/api/durable_functions/register_durable_callback.py @@ -0,0 +1,276 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.durable_callback_registration import DurableCallbackRegistration +from ...models.error import Error +from ...models.register_durable_callback_request import RegisterDurableCallbackRequest +from typing import cast + + + +def request_kwargs( + *, + body: RegisterDurableCallbackRequest, + +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + + + + + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/durable-callbacks", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> DurableCallbackRegistration | Error | None: + if response.status_code == 200: + response_200 = DurableCallbackRegistration.from_dict(response.json()) + + + + return response_200 + + if response.status_code == 201: + response_201 = DurableCallbackRegistration.from_dict(response.json()) + + + + return response_201 + + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + + + return response_400 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + + + return response_409 + + if response.status_code == 413: + response_413 = Error.from_dict(response.json()) + + + + return response_413 + + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + + + return response_429 + + if response.status_code == 503: + response_503 = Error.from_dict(response.json()) + + + + return response_503 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[DurableCallbackRegistration | Error]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: RegisterDurableCallbackRequest, + +) -> Response[DurableCallbackRegistration | Error]: + """ Get a callback token from inside a workflow + + Registers a waiting step and returns its callback token. The Volcano + SDK calls this from `ctx.waitForCallback`; applications do not call it + directly. + + `execution_ref` and `callback_id` are opaque values the SDK reads from + the running workflow. Together they are the request's credential, so + the operation takes no other authentication. The token works until + the step's own timeout, and only for that step. + + Retrying is safe: a request for a step that is already registered + returns the same token with `200`. + + Args: + body (RegisterDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackRegistration | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + *, + client: AuthenticatedClient | Client, + body: RegisterDurableCallbackRequest, + +) -> DurableCallbackRegistration | Error | None: + """ Get a callback token from inside a workflow + + Registers a waiting step and returns its callback token. The Volcano + SDK calls this from `ctx.waitForCallback`; applications do not call it + directly. + + `execution_ref` and `callback_id` are opaque values the SDK reads from + the running workflow. Together they are the request's credential, so + the operation takes no other authentication. The token works until + the step's own timeout, and only for that step. + + Retrying is safe: a request for a step that is already registered + returns the same token with `200`. + + Args: + body (RegisterDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackRegistration | Error + """ + + + return sync_detailed( + client=client, +body=body, + + ).parsed + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: RegisterDurableCallbackRequest, + +) -> Response[DurableCallbackRegistration | Error]: + """ Get a callback token from inside a workflow + + Registers a waiting step and returns its callback token. The Volcano + SDK calls this from `ctx.waitForCallback`; applications do not call it + directly. + + `execution_ref` and `callback_id` are opaque values the SDK reads from + the running workflow. Together they are the request's credential, so + the operation takes no other authentication. The token works until + the step's own timeout, and only for that step. + + Retrying is safe: a request for a step that is already registered + returns the same token with `200`. + + Args: + body (RegisterDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackRegistration | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: RegisterDurableCallbackRequest, + +) -> DurableCallbackRegistration | Error | None: + """ Get a callback token from inside a workflow + + Registers a waiting step and returns its callback token. The Volcano + SDK calls this from `ctx.waitForCallback`; applications do not call it + directly. + + `execution_ref` and `callback_id` are opaque values the SDK reads from + the running workflow. Together they are the request's credential, so + the operation takes no other authentication. The token works until + the step's own timeout, and only for that step. + + Retrying is safe: a request for a step that is already registered + returns the same token with `200`. + + Args: + body (RegisterDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackRegistration | Error + """ + + + return (await asyncio_detailed( + client=client, +body=body, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/durable_functions/succeed_durable_callback.py b/src/volcano_sdk/_generated/api/durable_functions/succeed_durable_callback.py new file mode 100644 index 00000000..96f92390 --- /dev/null +++ b/src/volcano_sdk/_generated/api/durable_functions/succeed_durable_callback.py @@ -0,0 +1,260 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.durable_callback_outcome import DurableCallbackOutcome +from ...models.error import Error +from ...models.succeed_durable_callback_request import SucceedDurableCallbackRequest +from typing import cast + + + +def request_kwargs( + *, + body: SucceedDurableCallbackRequest, + +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + + + + + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/durable-callbacks/succeed", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> DurableCallbackOutcome | Error | None: + if response.status_code == 200: + response_200 = DurableCallbackOutcome.from_dict(response.json()) + + + + return response_200 + + if response.status_code == 202: + response_202 = DurableCallbackOutcome.from_dict(response.json()) + + + + return response_202 + + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + + + return response_400 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 + + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + + + return response_409 + + if response.status_code == 410: + response_410 = Error.from_dict(response.json()) + + + + return response_410 + + if response.status_code == 413: + response_413 = Error.from_dict(response.json()) + + + + return response_413 + + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + + + return response_429 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[DurableCallbackOutcome | Error]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + body: SucceedDurableCallbackRequest, + +) -> Response[DurableCallbackOutcome | Error]: + """ Complete a waiting step with a result + + Resumes the workflow step waiting on the token with `result`. Call it + from the system the workflow handed the token to. + + The token is the only credential. A token takes one outcome: once it + has succeeded or failed, every later request is refused with + `409 callback_completed`, including a retry of the request that + succeeded it. + + Args: + body (SucceedDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackOutcome | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return build_response(client=client, response=response) + +def sync( + *, + client: AuthenticatedClient | Client, + body: SucceedDurableCallbackRequest, + +) -> DurableCallbackOutcome | Error | None: + """ Complete a waiting step with a result + + Resumes the workflow step waiting on the token with `result`. Call it + from the system the workflow handed the token to. + + The token is the only credential. A token takes one outcome: once it + has succeeded or failed, every later request is refused with + `409 callback_completed`, including a retry of the request that + succeeded it. + + Args: + body (SucceedDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackOutcome | Error + """ + + + return sync_detailed( + client=client, +body=body, + + ).parsed + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + body: SucceedDurableCallbackRequest, + +) -> Response[DurableCallbackOutcome | Error]: + """ Complete a waiting step with a result + + Resumes the workflow step waiting on the token with `result`. Call it + from the system the workflow handed the token to. + + The token is the only credential. A token takes one outcome: once it + has succeeded or failed, every later request is refused with + `409 callback_completed`, including a retry of the request that + succeeded it. + + Args: + body (SucceedDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[DurableCallbackOutcome | Error] + """ + + + kwargs = request_kwargs( + body=body, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return build_response(client=client, response=response) + +async def asyncio( + *, + client: AuthenticatedClient | Client, + body: SucceedDurableCallbackRequest, + +) -> DurableCallbackOutcome | Error | None: + """ Complete a waiting step with a result + + Resumes the workflow step waiting on the token with `result`. Call it + from the system the workflow handed the token to. + + The token is the only credential. A token takes one outcome: once it + has succeeded or failed, every later request is refused with + `409 callback_completed`, including a retry of the request that + succeeded it. + + Args: + body (SucceedDurableCallbackRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + DurableCallbackOutcome | Error + """ + + + return (await asyncio_detailed( + client=client, +body=body, + + )).parsed diff --git a/src/volcano_sdk/_generated/api/frontends/create_frontend.py b/src/volcano_sdk/_generated/api/frontends/create_frontend.py index 7c94b624..1509d39f 100644 --- a/src/volcano_sdk/_generated/api/frontends/create_frontend.py +++ b/src/volcano_sdk/_generated/api/frontends/create_frontend.py @@ -152,8 +152,8 @@ def sync_detailed( 22.x or 24.x. The Node.js runtime is inferred from `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x. The selected Node.js family must also satisfy the installed Next.js package's - `engines.node` constraint. Volcano tests Next 15.5.27 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next - 16.4.0 (`>=20.9.0`). + `engines.node` constraint. Volcano tests Next 15.5.28 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next + 16.4.2 (`>=20.9.0`). Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI does not apply its own source archive size limit. After the final container images are built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing. @@ -216,8 +216,8 @@ def sync( 22.x or 24.x. The Node.js runtime is inferred from `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x. The selected Node.js family must also satisfy the installed Next.js package's - `engines.node` constraint. Volcano tests Next 15.5.27 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next - 16.4.0 (`>=20.9.0`). + `engines.node` constraint. Volcano tests Next 15.5.28 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next + 16.4.2 (`>=20.9.0`). Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI does not apply its own source archive size limit. After the final container images are built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing. @@ -275,8 +275,8 @@ async def asyncio_detailed( 22.x or 24.x. The Node.js runtime is inferred from `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x. The selected Node.js family must also satisfy the installed Next.js package's - `engines.node` constraint. Volcano tests Next 15.5.27 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next - 16.4.0 (`>=20.9.0`). + `engines.node` constraint. Volcano tests Next 15.5.28 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next + 16.4.2 (`>=20.9.0`). Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI does not apply its own source archive size limit. After the final container images are built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing. @@ -339,8 +339,8 @@ async def asyncio( 22.x or 24.x. The Node.js runtime is inferred from `package.json` `engines.node`; if omitted, Volcano uses Node.js 22.x. The selected Node.js family must also satisfy the installed Next.js package's - `engines.node` constraint. Volcano tests Next 15.5.27 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next - 16.4.0 (`>=20.9.0`). + `engines.node` constraint. Volcano tests Next 15.5.28 (`^18.18.0 || ^19.8.0 || >=20.0.0`) and Next + 16.4.2 (`>=20.9.0`). Source archive size is enforced by the API with `SOURCE_ARCHIVE_SIZE_LIMIT_MB`; the CLI does not apply its own source archive size limit. After the final container images are built, the publish build enforces `LAMBDA_TARGET_CONTAINER_SIZE_LIMIT_MB` before pushing. diff --git a/src/volcano_sdk/_generated/api/functions/invoke_function.py b/src/volcano_sdk/_generated/api/functions/invoke_function.py index 55bf0ed8..bed45f2a 100644 --- a/src/volcano_sdk/_generated/api/functions/invoke_function.py +++ b/src/volcano_sdk/_generated/api/functions/invoke_function.py @@ -151,6 +151,12 @@ def sync_detailed( - Function must have `visibility: public` - Function receives payload only (no `__volcano_auth`) + **Reserved payload keys:** the top-level `__volcano_auth` and + `__volcano_http` keys are reserved. Volcano removes caller-supplied + values for both before the function runs; only an auth user token adds + a server-built `__volcano_auth`, and only HTTP-mode ingress sets + `__volcano_http: true`. + **Transport and CORS:** - This operation is the authenticated direct RPC endpoint and always uses the POST `{payload: ...}` contract, including for functions whose DNS ingress is @@ -236,6 +242,12 @@ def sync( - Function must have `visibility: public` - Function receives payload only (no `__volcano_auth`) + **Reserved payload keys:** the top-level `__volcano_auth` and + `__volcano_http` keys are reserved. Volcano removes caller-supplied + values for both before the function runs; only an auth user token adds + a server-built `__volcano_auth`, and only HTTP-mode ingress sets + `__volcano_http: true`. + **Transport and CORS:** - This operation is the authenticated direct RPC endpoint and always uses the POST `{payload: ...}` contract, including for functions whose DNS ingress is @@ -316,6 +328,12 @@ async def asyncio_detailed( - Function must have `visibility: public` - Function receives payload only (no `__volcano_auth`) + **Reserved payload keys:** the top-level `__volcano_auth` and + `__volcano_http` keys are reserved. Volcano removes caller-supplied + values for both before the function runs; only an auth user token adds + a server-built `__volcano_auth`, and only HTTP-mode ingress sets + `__volcano_http: true`. + **Transport and CORS:** - This operation is the authenticated direct RPC endpoint and always uses the POST `{payload: ...}` contract, including for functions whose DNS ingress is @@ -401,6 +419,12 @@ async def asyncio( - Function must have `visibility: public` - Function receives payload only (no `__volcano_auth`) + **Reserved payload keys:** the top-level `__volcano_auth` and + `__volcano_http` keys are reserved. Volcano removes caller-supplied + values for both before the function runs; only an auth user token adds + a server-built `__volcano_auth`, and only HTTP-mode ingress sets + `__volcano_http: true`. + **Transport and CORS:** - This operation is the authenticated direct RPC endpoint and always uses the POST `{payload: ...}` contract, including for functions whose DNS ingress is diff --git a/src/volcano_sdk/_generated/api/o_auth_authentication/auth_o_auth_exchange.py b/src/volcano_sdk/_generated/api/o_auth_authentication/auth_o_auth_exchange.py index 27018310..24d84ce8 100644 --- a/src/volcano_sdk/_generated/api/o_auth_authentication/auth_o_auth_exchange.py +++ b/src/volcano_sdk/_generated/api/o_auth_authentication/auth_o_auth_exchange.py @@ -10,6 +10,7 @@ from ...models.auth_o_auth_exchange_body import AuthOAuthExchangeBody from ...models.auth_token_response import AuthTokenResponse +from ...types import UNSET, Unset from typing import cast @@ -17,9 +18,17 @@ def request_kwargs( *, body: AuthOAuthExchangeBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> dict[str, Any]: headers: dict[str, Any] = {} + if not isinstance(x_volcano_proxy_authorization, Unset): + headers["X-Volcano-Proxy-Authorization"] = x_volcano_proxy_authorization + + if not isinstance(x_volcano_client_ip, Unset): + headers["X-Volcano-Client-IP"] = x_volcano_client_ip + @@ -83,6 +92,8 @@ def sync_detailed( *, client: AuthenticatedClient, body: AuthOAuthExchangeBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthTokenResponse]: """ Exchange OAuth authorization code @@ -92,6 +103,8 @@ def sync_detailed( redirect_url that initiated the flow. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthOAuthExchangeBody): Raises: @@ -105,6 +118,8 @@ def sync_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -118,6 +133,8 @@ def sync( *, client: AuthenticatedClient, body: AuthOAuthExchangeBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthTokenResponse | None: """ Exchange OAuth authorization code @@ -127,6 +144,8 @@ def sync( redirect_url that initiated the flow. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthOAuthExchangeBody): Raises: @@ -141,6 +160,8 @@ def sync( return sync_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ).parsed @@ -148,6 +169,8 @@ async def asyncio_detailed( *, client: AuthenticatedClient, body: AuthOAuthExchangeBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Response[Any | AuthTokenResponse]: """ Exchange OAuth authorization code @@ -157,6 +180,8 @@ async def asyncio_detailed( redirect_url that initiated the flow. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthOAuthExchangeBody): Raises: @@ -170,6 +195,8 @@ async def asyncio_detailed( kwargs = request_kwargs( body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, ) @@ -183,6 +210,8 @@ async def asyncio( *, client: AuthenticatedClient, body: AuthOAuthExchangeBody, + x_volcano_proxy_authorization: str | Unset = UNSET, + x_volcano_client_ip: str | Unset = UNSET, ) -> Any | AuthTokenResponse | None: """ Exchange OAuth authorization code @@ -192,6 +221,8 @@ async def asyncio( redirect_url that initiated the flow. Args: + x_volcano_proxy_authorization (str | Unset): + x_volcano_client_ip (str | Unset): body (AuthOAuthExchangeBody): Raises: @@ -206,5 +237,7 @@ async def asyncio( return (await asyncio_detailed( client=client, body=body, +x_volcano_proxy_authorization=x_volcano_proxy_authorization, +x_volcano_client_ip=x_volcano_client_ip, )).parsed diff --git a/src/volcano_sdk/_generated/api/projects/get_project_health.py b/src/volcano_sdk/_generated/api/projects/get_project_health.py index d7d5ba24..baabcc82 100644 --- a/src/volcano_sdk/_generated/api/projects/get_project_health.py +++ b/src/volcano_sdk/_generated/api/projects/get_project_health.py @@ -97,7 +97,8 @@ def sync_detailed( Returns a fast control-plane health snapshot for the project and its deployed resources. The endpoint does not run live provider probes. A successful request returns 200 even when the project status is - `unhealthy`; transport and authorization failures use HTTP errors. + `degraded` or `critical`; transport and authorization failures use HTTP + errors. Args: id (UUID): @@ -133,7 +134,8 @@ def sync( Returns a fast control-plane health snapshot for the project and its deployed resources. The endpoint does not run live provider probes. A successful request returns 200 even when the project status is - `unhealthy`; transport and authorization failures use HTTP errors. + `degraded` or `critical`; transport and authorization failures use HTTP + errors. Args: id (UUID): @@ -164,7 +166,8 @@ async def asyncio_detailed( Returns a fast control-plane health snapshot for the project and its deployed resources. The endpoint does not run live provider probes. A successful request returns 200 even when the project status is - `unhealthy`; transport and authorization failures use HTTP errors. + `degraded` or `critical`; transport and authorization failures use HTTP + errors. Args: id (UUID): @@ -200,7 +203,8 @@ async def asyncio( Returns a fast control-plane health snapshot for the project and its deployed resources. The endpoint does not run live provider probes. A successful request returns 200 even when the project status is - `unhealthy`; transport and authorization failures use HTTP errors. + `degraded` or `critical`; transport and authorization failures use HTTP + errors. Args: id (UUID): diff --git a/src/volcano_sdk/_generated/api/storage_policies/delete_storage_policy.py b/src/volcano_sdk/_generated/api/storage_policies/delete_storage_policy.py index 5e572294..4b9c0404 100644 --- a/src/volcano_sdk/_generated/api/storage_policies/delete_storage_policy.py +++ b/src/volcano_sdk/_generated/api/storage_policies/delete_storage_policy.py @@ -8,6 +8,8 @@ from ...types import Response, UNSET from ... import errors +from ...models.error import Error +from typing import cast from uuid import UUID @@ -34,9 +36,17 @@ def request_kwargs( -def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | None: +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | Error | None: if response.status_code == 200: - return None + response_200 = cast(Any, None) + return response_200 + + if response.status_code == 404: + response_404 = Error.from_dict(response.json()) + + + + return response_404 if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) @@ -44,7 +54,7 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res return None -def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]: +def build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any | Error]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -60,7 +70,7 @@ def sync_detailed( *, client: AuthenticatedClient, -) -> Response[Any]: +) -> Response[Any | Error]: """ Delete a storage policy Args: @@ -73,7 +83,7 @@ def sync_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any] + Response[Any | Error] """ @@ -90,6 +100,37 @@ def sync_detailed( return build_response(client=client, response=response) +def sync( + id: UUID | str, + bucket_name: str, + policy_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> Any | Error | None: + """ Delete a storage policy + + Args: + id (UUID): + bucket_name (str): + policy_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | Error + """ + + + return sync_detailed( + id=id, +bucket_name=bucket_name, +policy_id=policy_id, +client=client, + + ).parsed async def asyncio_detailed( id: UUID | str, @@ -98,7 +139,7 @@ async def asyncio_detailed( *, client: AuthenticatedClient, -) -> Response[Any]: +) -> Response[Any | Error]: """ Delete a storage policy Args: @@ -111,7 +152,7 @@ async def asyncio_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any] + Response[Any | Error] """ @@ -128,3 +169,34 @@ async def asyncio_detailed( return build_response(client=client, response=response) +async def asyncio( + id: UUID | str, + bucket_name: str, + policy_id: UUID | str, + *, + client: AuthenticatedClient, + +) -> Any | Error | None: + """ Delete a storage policy + + Args: + id (UUID): + bucket_name (str): + policy_id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Any | Error + """ + + + return (await asyncio_detailed( + id=id, +bucket_name=bucket_name, +policy_id=policy_id, +client=client, + + )).parsed diff --git a/src/volcano_sdk/_generated/models/__init__.py b/src/volcano_sdk/_generated/models/__init__.py index a49fb1ac..31ee40ce 100644 --- a/src/volcano_sdk/_generated/models/__init__.py +++ b/src/volcano_sdk/_generated/models/__init__.py @@ -90,8 +90,21 @@ from .auth_user_app_metadata import AuthUserAppMetadata from .auth_user_status import AuthUserStatus from .auth_user_user_metadata import AuthUserUserMetadata +from .autofix_diagnosis import AutofixDiagnosis +from .autofix_diagnosis_confidence import AutofixDiagnosisConfidence +from .autofix_edit import AutofixEdit +from .autofix_evidence import AutofixEvidence +from .autofix_evidence_kind import AutofixEvidenceKind from .autofix_model import AutofixModel +from .autofix_patch import AutofixPatch from .autofix_provider import AutofixProvider +from .autofix_pull_request import AutofixPullRequest +from .autofix_pull_request_skipped import AutofixPullRequestSkipped +from .autofix_pull_request_state import AutofixPullRequestState +from .autofix_run import AutofixRun +from .autofix_run_status import AutofixRunStatus +from .autofix_run_trigger import AutofixRunTrigger +from .autofix_run_usage import AutofixRunUsage from .autofix_settings import AutofixSettings from .autofix_stage import AutofixStage from .ban_auth_user_body import BanAuthUserBody @@ -222,6 +235,10 @@ from .durable_approval_registration import DurableApprovalRegistration from .durable_approval_stats import DurableApprovalStats from .durable_approval_status import DurableApprovalStatus +from .durable_callback_error import DurableCallbackError +from .durable_callback_outcome import DurableCallbackOutcome +from .durable_callback_registration import DurableCallbackRegistration +from .durable_callback_status import DurableCallbackStatus from .durable_execution import DurableExecution from .durable_execution_error import DurableExecutionError from .durable_execution_invocation import DurableExecutionInvocation @@ -251,6 +268,7 @@ from .email_template_template_type import EmailTemplateTemplateType from .error import Error from .export_project_source_request import ExportProjectSourceRequest +from .fail_durable_callback_request import FailDurableCallbackRequest from .frontend import Frontend from .frontend_custom_domain_conflict_error import FrontendCustomDomainConflictError from .frontend_custom_domain_response import FrontendCustomDomainResponse @@ -318,6 +336,7 @@ from .git_installations_response import GitInstallationsResponse from .git_repositories_response import GitRepositoriesResponse from .git_repository import GitRepository +from .heartbeat_durable_callback_request import HeartbeatDurableCallbackRequest from .hosted_auth_page_type import HostedAuthPageType from .hosted_login_email_check_request import HostedLoginEmailCheckRequest from .hosted_login_email_check_response import HostedLoginEmailCheckResponse @@ -384,6 +403,7 @@ from .open_api_spec_document import OpenAPISpecDocument from .open_durable_message_session_request import OpenDurableMessageSessionRequest from .paginated_auth_users import PaginatedAuthUsers +from .paginated_autofix_runs import PaginatedAutofixRuns from .paginated_databases import PaginatedDatabases from .paginated_durable_approvals import PaginatedDurableApprovals from .paginated_durable_executions import PaginatedDurableExecutions @@ -545,6 +565,7 @@ from .realtime_stats import RealtimeStats from .refresh_o_auth_provider_token_provider import RefreshOAuthProviderTokenProvider from .refresh_o_auth_provider_token_response_200 import RefreshOAuthProviderTokenResponse200 +from .register_durable_callback_request import RegisterDurableCallbackRequest from .register_durable_message_wait_request import RegisterDurableMessageWaitRequest from .rename_variable_environment_request import RenameVariableEnvironmentRequest from .render_default_managed_auth_page_action import RenderDefaultManagedAuthPageAction @@ -600,6 +621,7 @@ from .storage_policy_operation import StoragePolicyOperation from .storage_stats import StorageStats from .storage_visibility_request import StorageVisibilityRequest +from .succeed_durable_callback_request import SucceedDurableCallbackRequest from .summarize_project_deployments_resource_type import SummarizeProjectDeploymentsResourceType from .test_email_request import TestEmailRequest from .test_email_response import TestEmailResponse @@ -736,8 +758,21 @@ "AuthUserAppMetadata", "AuthUserStatus", "AuthUserUserMetadata", + "AutofixDiagnosis", + "AutofixDiagnosisConfidence", + "AutofixEdit", + "AutofixEvidence", + "AutofixEvidenceKind", "AutofixModel", + "AutofixPatch", "AutofixProvider", + "AutofixPullRequest", + "AutofixPullRequestSkipped", + "AutofixPullRequestState", + "AutofixRun", + "AutofixRunStatus", + "AutofixRunTrigger", + "AutofixRunUsage", "AutofixSettings", "AutofixStage", "BanAuthUserBody", @@ -868,6 +903,10 @@ "DurableApprovalRegistration", "DurableApprovalStats", "DurableApprovalStatus", + "DurableCallbackError", + "DurableCallbackOutcome", + "DurableCallbackRegistration", + "DurableCallbackStatus", "DurableExecution", "DurableExecutionError", "DurableExecutionInvocation", @@ -897,6 +936,7 @@ "EmailTemplateTemplateType", "Error", "ExportProjectSourceRequest", + "FailDurableCallbackRequest", "Frontend", "FrontendCustomDomainConflictError", "FrontendCustomDomainResponse", @@ -964,6 +1004,7 @@ "GitInstallationsResponse", "GitRepositoriesResponse", "GitRepository", + "HeartbeatDurableCallbackRequest", "HostedAuthPageType", "HostedLoginEmailCheckRequest", "HostedLoginEmailCheckResponse", @@ -1030,6 +1071,7 @@ "OpenAPISpecDocument", "OpenDurableMessageSessionRequest", "PaginatedAuthUsers", + "PaginatedAutofixRuns", "PaginatedDatabases", "PaginatedDurableApprovals", "PaginatedDurableExecutions", @@ -1191,6 +1233,7 @@ "RealtimeStats", "RefreshOAuthProviderTokenProvider", "RefreshOAuthProviderTokenResponse200", + "RegisterDurableCallbackRequest", "RegisterDurableMessageWaitRequest", "RenameVariableEnvironmentRequest", "RenderDefaultManagedAuthPageAction", @@ -1246,6 +1289,7 @@ "StoragePolicyOperation", "StorageStats", "StorageVisibilityRequest", + "SucceedDurableCallbackRequest", "SummarizeProjectDeploymentsResourceType", "TestEmailRequest", "TestEmailResponse", diff --git a/src/volcano_sdk/_generated/models/autofix_diagnosis.py b/src/volcano_sdk/_generated/models/autofix_diagnosis.py new file mode 100644 index 00000000..26bacf89 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_diagnosis.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..models.autofix_diagnosis_confidence import AutofixDiagnosisConfidence +from ..models.autofix_diagnosis_confidence import check_autofix_diagnosis_confidence +from typing import cast + +if TYPE_CHECKING: + from ..models.autofix_evidence import AutofixEvidence + + + + + +T = TypeVar("T", bound="AutofixDiagnosis") + + + +@_attrs_define +class AutofixDiagnosis: + """ + Attributes: + summary (str): One or two sentences on what goes wrong. + root_cause (str): + evidence (list[AutofixEvidence]): + suggested_fix (str): + confidence (AutofixDiagnosisConfidence): + fixable (bool): Whether a change to the project's code would fix the issue. + files (list[str]): Repository paths a fix would change. + """ + + summary: str + root_cause: str + evidence: list[AutofixEvidence] + suggested_fix: str + confidence: AutofixDiagnosisConfidence + fixable: bool + files: list[str] + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.autofix_evidence import AutofixEvidence # noqa: PLC0415 + summary = self.summary + + root_cause = self.root_cause + + evidence = [] + for evidence_item_data in self.evidence: + evidence_item = evidence_item_data.to_dict() + evidence.append(evidence_item) + + + + suggested_fix = self.suggested_fix + + confidence: str = self.confidence + + fixable = self.fixable + + files = self.files + + + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "summary": summary, + "root_cause": root_cause, + "evidence": evidence, + "suggested_fix": suggested_fix, + "confidence": confidence, + "fixable": fixable, + "files": files, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.autofix_evidence import AutofixEvidence # noqa: PLC0415 + d = dict(src_dict) + summary = d.pop("summary") + + root_cause = d.pop("root_cause") + + evidence = [] + _evidence = d.pop("evidence") + for evidence_item_data in (_evidence): + evidence_item = AutofixEvidence.from_dict(evidence_item_data) + + + + evidence.append(evidence_item) + + + suggested_fix = d.pop("suggested_fix") + + confidence = check_autofix_diagnosis_confidence(d.pop("confidence")) + + + + + fixable = d.pop("fixable") + + files = cast(list[str], d.pop("files")) + + + autofix_diagnosis = cls( + summary=summary, + root_cause=root_cause, + evidence=evidence, + suggested_fix=suggested_fix, + confidence=confidence, + fixable=fixable, + files=files, + ) + + return autofix_diagnosis + diff --git a/src/volcano_sdk/_generated/models/autofix_diagnosis_confidence.py b/src/volcano_sdk/_generated/models/autofix_diagnosis_confidence.py new file mode 100644 index 00000000..eeec57dd --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_diagnosis_confidence.py @@ -0,0 +1,10 @@ +from typing import Literal + +AutofixDiagnosisConfidence = Literal['high', 'low', 'medium'] + +AUTOFIX_DIAGNOSIS_CONFIDENCE_VALUES: set[AutofixDiagnosisConfidence] = { 'high', 'low', 'medium', } + +def check_autofix_diagnosis_confidence(value: str) -> AutofixDiagnosisConfidence: + if value in AUTOFIX_DIAGNOSIS_CONFIDENCE_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {AUTOFIX_DIAGNOSIS_CONFIDENCE_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/autofix_edit.py b/src/volcano_sdk/_generated/models/autofix_edit.py new file mode 100644 index 00000000..2bcb071d --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_edit.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + + + + + + + +T = TypeVar("T", bound="AutofixEdit") + + + +@_attrs_define +class AutofixEdit: + """ One exact text replacement. An empty `old_text` creates the file. + + Attributes: + path (str): + old_text (str): + new_text (str): + """ + + path: str + old_text: str + new_text: str + + + + + + def to_dict(self) -> dict[str, Any]: + path = self.path + + old_text = self.old_text + + new_text = self.new_text + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "path": path, + "old_text": old_text, + "new_text": new_text, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + path = d.pop("path") + + old_text = d.pop("old_text") + + new_text = d.pop("new_text") + + autofix_edit = cls( + path=path, + old_text=old_text, + new_text=new_text, + ) + + return autofix_edit + diff --git a/src/volcano_sdk/_generated/models/autofix_evidence.py b/src/volcano_sdk/_generated/models/autofix_evidence.py new file mode 100644 index 00000000..4acd5a3d --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_evidence.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..models.autofix_evidence_kind import AutofixEvidenceKind +from ..models.autofix_evidence_kind import check_autofix_evidence_kind +from ..types import UNSET, Unset +from typing import cast + + + + + + +T = TypeVar("T", bound="AutofixEvidence") + + + +@_attrs_define +class AutofixEvidence: + """ + Attributes: + kind (AutofixEvidenceKind): + detail (str): + location (str | Unset): A file and line, or a log timestamp. Absent when the evidence has none. + """ + + kind: AutofixEvidenceKind + detail: str + location: str | Unset = UNSET + + + + + + def to_dict(self) -> dict[str, Any]: + kind: str = self.kind + + detail = self.detail + + location = self.location + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "kind": kind, + "detail": detail, + }) + if location is not UNSET: + field_dict["location"] = location + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + kind = check_autofix_evidence_kind(d.pop("kind")) + + + + + detail = d.pop("detail") + + location = d.pop("location", UNSET) + + autofix_evidence = cls( + kind=kind, + detail=detail, + location=location, + ) + + return autofix_evidence + diff --git a/src/volcano_sdk/_generated/models/autofix_evidence_kind.py b/src/volcano_sdk/_generated/models/autofix_evidence_kind.py new file mode 100644 index 00000000..0723fe78 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_evidence_kind.py @@ -0,0 +1,10 @@ +from typing import Literal + +AutofixEvidenceKind = Literal['code', 'log', 'other'] + +AUTOFIX_EVIDENCE_KIND_VALUES: set[AutofixEvidenceKind] = { 'code', 'log', 'other', } + +def check_autofix_evidence_kind(value: str) -> AutofixEvidenceKind: + if value in AUTOFIX_EVIDENCE_KIND_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {AUTOFIX_EVIDENCE_KIND_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/autofix_patch.py b/src/volcano_sdk/_generated/models/autofix_patch.py new file mode 100644 index 00000000..62ee2b03 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_patch.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from typing import cast + +if TYPE_CHECKING: + from ..models.autofix_edit import AutofixEdit + + + + + +T = TypeVar("T", bound="AutofixPatch") + + + +@_attrs_define +class AutofixPatch: + """ The fix the model proposed, applied to the deployed commit. Present whenever the run opened a pull request, and when + a valid fix could not be published. + + Attributes: + title (str): + edits (list[AutofixEdit]): + """ + + title: str + edits: list[AutofixEdit] + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.autofix_edit import AutofixEdit # noqa: PLC0415 + title = self.title + + edits = [] + for edits_item_data in self.edits: + edits_item = edits_item_data.to_dict() + edits.append(edits_item) + + + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "title": title, + "edits": edits, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.autofix_edit import AutofixEdit # noqa: PLC0415 + d = dict(src_dict) + title = d.pop("title") + + edits = [] + _edits = d.pop("edits") + for edits_item_data in (_edits): + edits_item = AutofixEdit.from_dict(edits_item_data) + + + + edits.append(edits_item) + + + autofix_patch = cls( + title=title, + edits=edits, + ) + + return autofix_patch + diff --git a/src/volcano_sdk/_generated/models/autofix_pull_request.py b/src/volcano_sdk/_generated/models/autofix_pull_request.py new file mode 100644 index 00000000..a8b58ae9 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_pull_request.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..models.autofix_pull_request_state import AutofixPullRequestState +from ..models.autofix_pull_request_state import check_autofix_pull_request_state +from ..types import UNSET, Unset +from typing import cast +import datetime + + + + + + +T = TypeVar("T", bound="AutofixPullRequest") + + + +@_attrs_define +class AutofixPullRequest: + """ The draft pull request the run opened on the connected repository. + + Attributes: + number (int): + url (str): + branch (str): The branch the fix is on, `volcano/autofix/`. + state (AutofixPullRequestState): + closed_at (datetime.datetime | Unset): When the pull request was merged or closed. + deployed_at (datetime.datetime | Unset): When a deployment that includes the merged fix went live. The issue + resolves then, unless it occurred again since. When more than 20 deployments went live after the merge before + Volcano checked, it is the merge time instead. + """ + + number: int + url: str + branch: str + state: AutofixPullRequestState + closed_at: datetime.datetime | Unset = UNSET + deployed_at: datetime.datetime | Unset = UNSET + + + + + + def to_dict(self) -> dict[str, Any]: + number = self.number + + url = self.url + + branch = self.branch + + state: str = self.state + + closed_at: str | Unset = UNSET + if not isinstance(self.closed_at, Unset): + closed_at = self.closed_at.isoformat() + + deployed_at: str | Unset = UNSET + if not isinstance(self.deployed_at, Unset): + deployed_at = self.deployed_at.isoformat() + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "number": number, + "url": url, + "branch": branch, + "state": state, + }) + if closed_at is not UNSET: + field_dict["closed_at"] = closed_at + if deployed_at is not UNSET: + field_dict["deployed_at"] = deployed_at + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + number = d.pop("number") + + url = d.pop("url") + + branch = d.pop("branch") + + state = check_autofix_pull_request_state(d.pop("state")) + + + + + _closed_at = d.pop("closed_at", UNSET) + closed_at: datetime.datetime | Unset + if isinstance(_closed_at, Unset): + closed_at = UNSET + else: + closed_at = datetime.datetime.fromisoformat(_closed_at) + + + + + _deployed_at = d.pop("deployed_at", UNSET) + deployed_at: datetime.datetime | Unset + if isinstance(_deployed_at, Unset): + deployed_at = UNSET + else: + deployed_at = datetime.datetime.fromisoformat(_deployed_at) + + + + + autofix_pull_request = cls( + number=number, + url=url, + branch=branch, + state=state, + closed_at=closed_at, + deployed_at=deployed_at, + ) + + return autofix_pull_request + diff --git a/src/volcano_sdk/_generated/models/autofix_pull_request_skipped.py b/src/volcano_sdk/_generated/models/autofix_pull_request_skipped.py new file mode 100644 index 00000000..81b39fad --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_pull_request_skipped.py @@ -0,0 +1,10 @@ +from typing import Literal + +AutofixPullRequestSkipped = Literal['low_confidence', 'no_patch', 'not_fixable', 'permission_missing', 'publish_failed', 'pull_request_open', 'repository_changed', 'source_unavailable'] + +AUTOFIX_PULL_REQUEST_SKIPPED_VALUES: set[AutofixPullRequestSkipped] = { 'low_confidence', 'no_patch', 'not_fixable', 'permission_missing', 'publish_failed', 'pull_request_open', 'repository_changed', 'source_unavailable', } + +def check_autofix_pull_request_skipped(value: str) -> AutofixPullRequestSkipped: + if value in AUTOFIX_PULL_REQUEST_SKIPPED_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {AUTOFIX_PULL_REQUEST_SKIPPED_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/autofix_pull_request_state.py b/src/volcano_sdk/_generated/models/autofix_pull_request_state.py new file mode 100644 index 00000000..5a7764b9 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_pull_request_state.py @@ -0,0 +1,10 @@ +from typing import Literal + +AutofixPullRequestState = Literal['closed', 'merged', 'open'] + +AUTOFIX_PULL_REQUEST_STATE_VALUES: set[AutofixPullRequestState] = { 'closed', 'merged', 'open', } + +def check_autofix_pull_request_state(value: str) -> AutofixPullRequestState: + if value in AUTOFIX_PULL_REQUEST_STATE_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {AUTOFIX_PULL_REQUEST_STATE_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/autofix_run.py b/src/volcano_sdk/_generated/models/autofix_run.py new file mode 100644 index 00000000..4fb1f1cb --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_run.py @@ -0,0 +1,362 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..models.autofix_model import AutofixModel +from ..models.autofix_model import check_autofix_model +from ..models.autofix_provider import AutofixProvider +from ..models.autofix_provider import check_autofix_provider +from ..models.autofix_pull_request_skipped import AutofixPullRequestSkipped +from ..models.autofix_pull_request_skipped import check_autofix_pull_request_skipped +from ..models.autofix_run_status import AutofixRunStatus +from ..models.autofix_run_status import check_autofix_run_status +from ..models.autofix_run_trigger import AutofixRunTrigger +from ..models.autofix_run_trigger import check_autofix_run_trigger +from ..models.autofix_stage import AutofixStage +from ..models.autofix_stage import check_autofix_stage +from ..types import UNSET, Unset +from typing import cast +from uuid import UUID +import datetime + +if TYPE_CHECKING: + from ..models.autofix_diagnosis import AutofixDiagnosis + from ..models.autofix_patch import AutofixPatch + from ..models.autofix_pull_request import AutofixPullRequest + from ..models.autofix_run_usage import AutofixRunUsage + + + + + +T = TypeVar("T", bound="AutofixRun") + + + +@_attrs_define +class AutofixRun: + """ + Attributes: + id (UUID): + project_id (UUID): + issue_id (UUID): + trigger (AutofixRunTrigger): What started the run. `manual` is a request; `new_issue` and `regression` are + automatic runs for an issue that appeared or reopened. + status (AutofixRunStatus): Where a run is. `canceled` means the project's settings or plan stopped allowing the + run before it finished; `failure_code` says which. + max_stage (AutofixStage): How far a run may go. `diagnose` stops at a root-cause diagnosis. `pull_request` may + also open a draft pull request with a fix in the project's connected repository. + provider (AutofixProvider): Who serves the model. `volcano` runs on Volcano's models. `anthropic` runs on your + own Anthropic API key. + model (AutofixModel): The Claude model a run uses. `claude-opus-5-5` is the default. + usage (AutofixRunUsage): Model tokens the run used across every attempt. + created_at (datetime.datetime): + stage_reached (AutofixStage | Unset): How far a run may go. `diagnose` stops at a root-cause diagnosis. + `pull_request` may also open a draft pull request with a fix in the project's connected repository. + diagnosis (AutofixDiagnosis | Unset): + patch (AutofixPatch | Unset): The fix the model proposed, applied to the deployed commit. Present whenever the + run opened a pull request, and when a valid fix could not be published. + pull_request (AutofixPullRequest | Unset): The draft pull request the run opened on the connected repository. + pull_request_skipped (AutofixPullRequestSkipped | Unset): Why a run whose `max_stage` is `pull_request` opened + no pull request. The diagnosis stands either way. + commit_sha (str | Unset): The commit the diagnosis read, when a push to the connected repository deployed the + resource. Absent when the run read logs only. + failure_code (str | Unset): Why the run failed or was canceled, or why its last attempt will be retried. One of + `api_key_rejected`, `model_request_rejected`, `model_unavailable`, `model_refused`, `no_diagnosis`, + `budget_exhausted`, `timed_out`, `attempts_exhausted`, `autofix_disabled`, `plan_required`, + `managed_models_unavailable`, `settings_changed`, `issue_not_found`, or `internal_error`. + failure_message (str | Unset): + started_at (datetime.datetime | Unset): + completed_at (datetime.datetime | Unset): + """ + + id: UUID + project_id: UUID + issue_id: UUID + trigger: AutofixRunTrigger + status: AutofixRunStatus + max_stage: AutofixStage + provider: AutofixProvider + model: AutofixModel + usage: AutofixRunUsage + created_at: datetime.datetime + stage_reached: AutofixStage | Unset = UNSET + diagnosis: AutofixDiagnosis | Unset = UNSET + patch: AutofixPatch | Unset = UNSET + pull_request: AutofixPullRequest | Unset = UNSET + pull_request_skipped: AutofixPullRequestSkipped | Unset = UNSET + commit_sha: str | Unset = UNSET + failure_code: str | Unset = UNSET + failure_message: str | Unset = UNSET + started_at: datetime.datetime | Unset = UNSET + completed_at: datetime.datetime | Unset = UNSET + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.autofix_diagnosis import AutofixDiagnosis # noqa: PLC0415 + from ..models.autofix_patch import AutofixPatch # noqa: PLC0415 + from ..models.autofix_pull_request import AutofixPullRequest # noqa: PLC0415 + from ..models.autofix_run_usage import AutofixRunUsage # noqa: PLC0415 + id = str(self.id) + + project_id = str(self.project_id) + + issue_id = str(self.issue_id) + + trigger: str = self.trigger + + status: str = self.status + + max_stage: str = self.max_stage + + provider: str = self.provider + + model: str = self.model + + usage = self.usage.to_dict() + + created_at = self.created_at.isoformat() + + stage_reached: str | Unset = UNSET + if not isinstance(self.stage_reached, Unset): + stage_reached = self.stage_reached + + + diagnosis: dict[str, Any] | Unset = UNSET + if not isinstance(self.diagnosis, Unset): + diagnosis = self.diagnosis.to_dict() + + patch: dict[str, Any] | Unset = UNSET + if not isinstance(self.patch, Unset): + patch = self.patch.to_dict() + + pull_request: dict[str, Any] | Unset = UNSET + if not isinstance(self.pull_request, Unset): + pull_request = self.pull_request.to_dict() + + pull_request_skipped: str | Unset = UNSET + if not isinstance(self.pull_request_skipped, Unset): + pull_request_skipped = self.pull_request_skipped + + + commit_sha = self.commit_sha + + failure_code = self.failure_code + + failure_message = self.failure_message + + started_at: str | Unset = UNSET + if not isinstance(self.started_at, Unset): + started_at = self.started_at.isoformat() + + completed_at: str | Unset = UNSET + if not isinstance(self.completed_at, Unset): + completed_at = self.completed_at.isoformat() + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "id": id, + "project_id": project_id, + "issue_id": issue_id, + "trigger": trigger, + "status": status, + "max_stage": max_stage, + "provider": provider, + "model": model, + "usage": usage, + "created_at": created_at, + }) + if stage_reached is not UNSET: + field_dict["stage_reached"] = stage_reached + if diagnosis is not UNSET: + field_dict["diagnosis"] = diagnosis + if patch is not UNSET: + field_dict["patch"] = patch + if pull_request is not UNSET: + field_dict["pull_request"] = pull_request + if pull_request_skipped is not UNSET: + field_dict["pull_request_skipped"] = pull_request_skipped + if commit_sha is not UNSET: + field_dict["commit_sha"] = commit_sha + if failure_code is not UNSET: + field_dict["failure_code"] = failure_code + if failure_message is not UNSET: + field_dict["failure_message"] = failure_message + if started_at is not UNSET: + field_dict["started_at"] = started_at + if completed_at is not UNSET: + field_dict["completed_at"] = completed_at + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.autofix_diagnosis import AutofixDiagnosis # noqa: PLC0415 + from ..models.autofix_patch import AutofixPatch # noqa: PLC0415 + from ..models.autofix_pull_request import AutofixPullRequest # noqa: PLC0415 + from ..models.autofix_run_usage import AutofixRunUsage # noqa: PLC0415 + d = dict(src_dict) + id = UUID(d.pop("id")) + + + + + project_id = UUID(d.pop("project_id")) + + + + + issue_id = UUID(d.pop("issue_id")) + + + + + trigger = check_autofix_run_trigger(d.pop("trigger")) + + + + + status = check_autofix_run_status(d.pop("status")) + + + + + max_stage = check_autofix_stage(d.pop("max_stage")) + + + + + provider = check_autofix_provider(d.pop("provider")) + + + + + model = check_autofix_model(d.pop("model")) + + + + + usage = AutofixRunUsage.from_dict(d.pop("usage")) + + + + + created_at = datetime.datetime.fromisoformat(d.pop("created_at")) + + + + + _stage_reached = d.pop("stage_reached", UNSET) + stage_reached: AutofixStage | Unset + if isinstance(_stage_reached, Unset): + stage_reached = UNSET + else: + stage_reached = check_autofix_stage(_stage_reached) + + + + + _diagnosis = d.pop("diagnosis", UNSET) + diagnosis: AutofixDiagnosis | Unset + if isinstance(_diagnosis, Unset): + diagnosis = UNSET + else: + diagnosis = AutofixDiagnosis.from_dict(_diagnosis) + + + + + _patch = d.pop("patch", UNSET) + patch: AutofixPatch | Unset + if isinstance(_patch, Unset): + patch = UNSET + else: + patch = AutofixPatch.from_dict(_patch) + + + + + _pull_request = d.pop("pull_request", UNSET) + pull_request: AutofixPullRequest | Unset + if isinstance(_pull_request, Unset): + pull_request = UNSET + else: + pull_request = AutofixPullRequest.from_dict(_pull_request) + + + + + _pull_request_skipped = d.pop("pull_request_skipped", UNSET) + pull_request_skipped: AutofixPullRequestSkipped | Unset + if isinstance(_pull_request_skipped, Unset): + pull_request_skipped = UNSET + else: + pull_request_skipped = check_autofix_pull_request_skipped(_pull_request_skipped) + + + + + commit_sha = d.pop("commit_sha", UNSET) + + failure_code = d.pop("failure_code", UNSET) + + failure_message = d.pop("failure_message", UNSET) + + _started_at = d.pop("started_at", UNSET) + started_at: datetime.datetime | Unset + if isinstance(_started_at, Unset): + started_at = UNSET + else: + started_at = datetime.datetime.fromisoformat(_started_at) + + + + + _completed_at = d.pop("completed_at", UNSET) + completed_at: datetime.datetime | Unset + if isinstance(_completed_at, Unset): + completed_at = UNSET + else: + completed_at = datetime.datetime.fromisoformat(_completed_at) + + + + + autofix_run = cls( + id=id, + project_id=project_id, + issue_id=issue_id, + trigger=trigger, + status=status, + max_stage=max_stage, + provider=provider, + model=model, + usage=usage, + created_at=created_at, + stage_reached=stage_reached, + diagnosis=diagnosis, + patch=patch, + pull_request=pull_request, + pull_request_skipped=pull_request_skipped, + commit_sha=commit_sha, + failure_code=failure_code, + failure_message=failure_message, + started_at=started_at, + completed_at=completed_at, + ) + + return autofix_run + diff --git a/src/volcano_sdk/_generated/models/autofix_run_status.py b/src/volcano_sdk/_generated/models/autofix_run_status.py new file mode 100644 index 00000000..e74d65c8 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_run_status.py @@ -0,0 +1,10 @@ +from typing import Literal + +AutofixRunStatus = Literal['canceled', 'failed', 'queued', 'running', 'succeeded'] + +AUTOFIX_RUN_STATUS_VALUES: set[AutofixRunStatus] = { 'canceled', 'failed', 'queued', 'running', 'succeeded', } + +def check_autofix_run_status(value: str) -> AutofixRunStatus: + if value in AUTOFIX_RUN_STATUS_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {AUTOFIX_RUN_STATUS_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/autofix_run_trigger.py b/src/volcano_sdk/_generated/models/autofix_run_trigger.py new file mode 100644 index 00000000..c4a8be9f --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_run_trigger.py @@ -0,0 +1,10 @@ +from typing import Literal + +AutofixRunTrigger = Literal['manual', 'new_issue', 'regression'] + +AUTOFIX_RUN_TRIGGER_VALUES: set[AutofixRunTrigger] = { 'manual', 'new_issue', 'regression', } + +def check_autofix_run_trigger(value: str) -> AutofixRunTrigger: + if value in AUTOFIX_RUN_TRIGGER_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {AUTOFIX_RUN_TRIGGER_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/autofix_run_usage.py b/src/volcano_sdk/_generated/models/autofix_run_usage.py new file mode 100644 index 00000000..a93a2e78 --- /dev/null +++ b/src/volcano_sdk/_generated/models/autofix_run_usage.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + + + + + + + +T = TypeVar("T", bound="AutofixRunUsage") + + + +@_attrs_define +class AutofixRunUsage: + """ Model tokens the run used across every attempt. + + Attributes: + input_tokens (int): + output_tokens (int): + cache_read_input_tokens (int): + cache_creation_input_tokens (int): + """ + + input_tokens: int + output_tokens: int + cache_read_input_tokens: int + cache_creation_input_tokens: int + + + + + + def to_dict(self) -> dict[str, Any]: + input_tokens = self.input_tokens + + output_tokens = self.output_tokens + + cache_read_input_tokens = self.cache_read_input_tokens + + cache_creation_input_tokens = self.cache_creation_input_tokens + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "cache_read_input_tokens": cache_read_input_tokens, + "cache_creation_input_tokens": cache_creation_input_tokens, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + input_tokens = d.pop("input_tokens") + + output_tokens = d.pop("output_tokens") + + cache_read_input_tokens = d.pop("cache_read_input_tokens") + + cache_creation_input_tokens = d.pop("cache_creation_input_tokens") + + autofix_run_usage = cls( + input_tokens=input_tokens, + output_tokens=output_tokens, + cache_read_input_tokens=cache_read_input_tokens, + cache_creation_input_tokens=cache_creation_input_tokens, + ) + + return autofix_run_usage + diff --git a/src/volcano_sdk/_generated/models/create_service_key_body.py b/src/volcano_sdk/_generated/models/create_service_key_body.py index f4ce6430..875d490a 100644 --- a/src/volcano_sdk/_generated/models/create_service_key_body.py +++ b/src/volcano_sdk/_generated/models/create_service_key_body.py @@ -32,7 +32,9 @@ class CreateServiceKeyBody: for backward compatibility. Provide an explicit list (e.g. ["functions.invoke", "locks.manage"]) to restrict the key; "*" grants everything. Scope enforcement applies to function invocation, - storage object operations, and project locks. + storage object operations, project locks, and server-side auth proxy + metadata (auth.signin, auth.signup, auth.refresh, auth.logout, + auth.password_reset). Example: ['functions.invoke', 'locks.manage']. """ diff --git a/src/volcano_sdk/_generated/models/durable_callback_error.py b/src/volcano_sdk/_generated/models/durable_callback_error.py new file mode 100644 index 00000000..427d4465 --- /dev/null +++ b/src/volcano_sdk/_generated/models/durable_callback_error.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..types import UNSET, Unset + + + + + + +T = TypeVar("T", bound="DurableCallbackError") + + + +@_attrs_define +class DurableCallbackError: + """ The error the waiting step raises. + + Attributes: + type_ (str): A name workflows can branch on, such as `PaymentDeclined`. Up to + 256 characters, without control characters. + message (str | Unset): A human-readable explanation, up to 32 KiB. + """ + + type_: str + message: str | Unset = UNSET + + + + + + def to_dict(self) -> dict[str, Any]: + type_ = self.type_ + + message = self.message + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "type": type_, + }) + if message is not UNSET: + field_dict["message"] = message + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + type_ = d.pop("type") + + message = d.pop("message", UNSET) + + durable_callback_error = cls( + type_=type_, + message=message, + ) + + return durable_callback_error + diff --git a/src/volcano_sdk/_generated/models/durable_callback_outcome.py b/src/volcano_sdk/_generated/models/durable_callback_outcome.py new file mode 100644 index 00000000..899be20b --- /dev/null +++ b/src/volcano_sdk/_generated/models/durable_callback_outcome.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..models.durable_callback_status import check_durable_callback_status +from ..models.durable_callback_status import DurableCallbackStatus +from typing import cast + + + + + + +T = TypeVar("T", bound="DurableCallbackOutcome") + + + +@_attrs_define +class DurableCallbackOutcome: + """ + Attributes: + status (DurableCallbackStatus): `waiting` until the token is succeeded or failed. `delivering` while + the outcome is on its way to the workflow; Volcano finishes the + delivery on its own. `succeeded` or `failed` once the workflow has the + outcome. + """ + + status: DurableCallbackStatus + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + status: str = self.status + + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "status": status, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + status = check_durable_callback_status(d.pop("status")) + + + + + durable_callback_outcome = cls( + status=status, + ) + + + durable_callback_outcome.additional_properties = d + return durable_callback_outcome + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/volcano_sdk/_generated/models/durable_callback_registration.py b/src/volcano_sdk/_generated/models/durable_callback_registration.py new file mode 100644 index 00000000..a34633d6 --- /dev/null +++ b/src/volcano_sdk/_generated/models/durable_callback_registration.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from typing import cast +import datetime + + + + + + +T = TypeVar("T", bound="DurableCallbackRegistration") + + + +@_attrs_define +class DurableCallbackRegistration: + """ The token for the waiting step, as the workflow receives it. + + Attributes: + token (str): The `vcb_` token the workflow handed out. It is the only credential + the callback operations take, so treat it as a secret. + expires_at (datetime.datetime | None): When the waiting step times out and the token stops working. Null + when the workflow set no timeout, in which case the token works + until the execution ends. + """ + + token: str + expires_at: datetime.datetime | None + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + token = self.token + + expires_at: None | str + if isinstance(self.expires_at, datetime.datetime): + expires_at = self.expires_at.isoformat() + else: + expires_at = self.expires_at + + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "token": token, + "expires_at": expires_at, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + token = d.pop("token") + + def _parse_expires_at(data: object) -> datetime.datetime | None: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + expires_at_type_0 = datetime.datetime.fromisoformat(data) + + + + return expires_at_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(datetime.datetime | None, data) + + expires_at = _parse_expires_at(d.pop("expires_at")) + + + durable_callback_registration = cls( + token=token, + expires_at=expires_at, + ) + + + durable_callback_registration.additional_properties = d + return durable_callback_registration + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/volcano_sdk/_generated/models/durable_callback_status.py b/src/volcano_sdk/_generated/models/durable_callback_status.py new file mode 100644 index 00000000..82a4a174 --- /dev/null +++ b/src/volcano_sdk/_generated/models/durable_callback_status.py @@ -0,0 +1,10 @@ +from typing import Literal + +DurableCallbackStatus = Literal['delivering', 'failed', 'succeeded', 'waiting'] + +DURABLE_CALLBACK_STATUS_VALUES: set[DurableCallbackStatus] = { 'delivering', 'failed', 'succeeded', 'waiting', } + +def check_durable_callback_status(value: str) -> DurableCallbackStatus: + if value in DURABLE_CALLBACK_STATUS_VALUES: + return value + raise TypeError(f"Unexpected value {value!r}. Expected one of {DURABLE_CALLBACK_STATUS_VALUES!r}") diff --git a/src/volcano_sdk/_generated/models/durable_execution.py b/src/volcano_sdk/_generated/models/durable_execution.py index a7bee0e8..c45737cf 100644 --- a/src/volcano_sdk/_generated/models/durable_execution.py +++ b/src/volcano_sdk/_generated/models/durable_execution.py @@ -69,8 +69,8 @@ class DurableExecution: error (DurableExecutionError | Unset): Why a failed or timed-out execution ended. completed_at (datetime.datetime | Unset): Present once the execution has reached a terminal status. client_token (str | Unset): Lets a browser read and send the execution's messages. Returned only - by the start that created the execution, never by a repeat under the - same name or by a read. Send it as `X-Volcano-Client-Token`. + by the start whose input the execution runs, never by a repeat under + the same name or by a read. Send it as `X-Volcano-Client-Token`. """ id: UUID diff --git a/src/volcano_sdk/_generated/models/fail_durable_callback_request.py b/src/volcano_sdk/_generated/models/fail_durable_callback_request.py new file mode 100644 index 00000000..7d1364af --- /dev/null +++ b/src/volcano_sdk/_generated/models/fail_durable_callback_request.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from typing import cast + +if TYPE_CHECKING: + from ..models.durable_callback_error import DurableCallbackError + + + + + +T = TypeVar("T", bound="FailDurableCallbackRequest") + + + +@_attrs_define +class FailDurableCallbackRequest: + """ + Attributes: + token (str): The `vcb_` token the workflow handed out. It is the only credential + the callback operations take, so treat it as a secret. + error (DurableCallbackError): The error the waiting step raises. + """ + + token: str + error: DurableCallbackError + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.durable_callback_error import DurableCallbackError # noqa: PLC0415 + token = self.token + + error = self.error.to_dict() + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "token": token, + "error": error, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.durable_callback_error import DurableCallbackError # noqa: PLC0415 + d = dict(src_dict) + token = d.pop("token") + + error = DurableCallbackError.from_dict(d.pop("error")) + + + + + fail_durable_callback_request = cls( + token=token, + error=error, + ) + + return fail_durable_callback_request + diff --git a/src/volcano_sdk/_generated/models/function_invocation_request.py b/src/volcano_sdk/_generated/models/function_invocation_request.py index 6bc4909b..2853a9af 100644 --- a/src/volcano_sdk/_generated/models/function_invocation_request.py +++ b/src/volcano_sdk/_generated/models/function_invocation_request.py @@ -40,6 +40,9 @@ class FunctionInvocationRequest: } } ``` + + The top-level `__volcano_auth` and `__volcano_http` keys are reserved; + Volcano removes caller-supplied values for both. """ payload: FunctionInvocationRequestPayload | Unset = UNSET diff --git a/src/volcano_sdk/_generated/models/function_invocation_request_payload.py b/src/volcano_sdk/_generated/models/function_invocation_request_payload.py index f9422124..7457d0fd 100644 --- a/src/volcano_sdk/_generated/models/function_invocation_request_payload.py +++ b/src/volcano_sdk/_generated/models/function_invocation_request_payload.py @@ -35,6 +35,9 @@ class FunctionInvocationRequestPayload: } ``` + The top-level `__volcano_auth` and `__volcano_http` keys are reserved; + Volcano removes caller-supplied values for both. + """ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) diff --git a/src/volcano_sdk/_generated/models/heartbeat_durable_callback_request.py b/src/volcano_sdk/_generated/models/heartbeat_durable_callback_request.py new file mode 100644 index 00000000..883487e3 --- /dev/null +++ b/src/volcano_sdk/_generated/models/heartbeat_durable_callback_request.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + + + + + + + +T = TypeVar("T", bound="HeartbeatDurableCallbackRequest") + + + +@_attrs_define +class HeartbeatDurableCallbackRequest: + """ + Attributes: + token (str): The `vcb_` token the workflow handed out. It is the only credential + the callback operations take, so treat it as a secret. + """ + + token: str + + + + + + def to_dict(self) -> dict[str, Any]: + token = self.token + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "token": token, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + token = d.pop("token") + + heartbeat_durable_callback_request = cls( + token=token, + ) + + return heartbeat_durable_callback_request + diff --git a/src/volcano_sdk/_generated/models/paginated_autofix_runs.py b/src/volcano_sdk/_generated/models/paginated_autofix_runs.py new file mode 100644 index 00000000..a61325e1 --- /dev/null +++ b/src/volcano_sdk/_generated/models/paginated_autofix_runs.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from ..types import UNSET, Unset +from typing import cast + +if TYPE_CHECKING: + from ..models.autofix_run import AutofixRun + + + + + +T = TypeVar("T", bound="PaginatedAutofixRuns") + + + +@_attrs_define +class PaginatedAutofixRuns: + """ + Attributes: + data (list[AutofixRun]): + limit (int): Number of items per page + has_more (bool): Whether a next page exists + next_cursor (str | Unset): Opaque cursor for the next page (present if has_more is true) + """ + + data: list[AutofixRun] + limit: int + has_more: bool + next_cursor: str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.autofix_run import AutofixRun # noqa: PLC0415 + data = [] + for data_item_data in self.data: + data_item = data_item_data.to_dict() + data.append(data_item) + + + + limit = self.limit + + has_more = self.has_more + + next_cursor = self.next_cursor + + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "data": data, + "limit": limit, + "has_more": has_more, + }) + if next_cursor is not UNSET: + field_dict["next_cursor"] = next_cursor + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.autofix_run import AutofixRun # noqa: PLC0415 + d = dict(src_dict) + data = [] + _data = d.pop("data") + for data_item_data in (_data): + data_item = AutofixRun.from_dict(data_item_data) + + + + data.append(data_item) + + + limit = d.pop("limit") + + has_more = d.pop("has_more") + + next_cursor = d.pop("next_cursor", UNSET) + + paginated_autofix_runs = cls( + data=data, + limit=limit, + has_more=has_more, + next_cursor=next_cursor, + ) + + + paginated_autofix_runs.additional_properties = d + return paginated_autofix_runs + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/volcano_sdk/_generated/models/project_health_check.py b/src/volcano_sdk/_generated/models/project_health_check.py index 00998a52..f6046fb6 100644 --- a/src/volcano_sdk/_generated/models/project_health_check.py +++ b/src/volcano_sdk/_generated/models/project_health_check.py @@ -32,7 +32,12 @@ class ProjectHealthCheck: Attributes: id (str): category (ProjectHealthCategory): - status (ProjectHealthStatus): + status (ProjectHealthStatus): empty means no functions, frontends, databases, storage, Volcano Auth users, or + Sandbox resources are listed. needs_attention means an operation needs review without confirmed service + impairment, or a serving frontend whose edge recovery gave up needs a redeploy. degraded means a resource is + only partly available or is unavailable for an expected, temporary reason, such as a frontend whose edge + synchronization did not complete or a database being restored; critical means use is blocked. A failed workflow + or deployment record alone does not establish an outage. Findings remain available for empty projects. reason_code (str): scope (ProjectHealthScope): evidence (ProjectHealthEvidence): diff --git a/src/volcano_sdk/_generated/models/project_health_resource_type.py b/src/volcano_sdk/_generated/models/project_health_resource_type.py index 89cf9f74..f925e539 100644 --- a/src/volcano_sdk/_generated/models/project_health_resource_type.py +++ b/src/volcano_sdk/_generated/models/project_health_resource_type.py @@ -1,8 +1,8 @@ from typing import Literal -ProjectHealthResourceType = Literal['database', 'frontend', 'function', 'project'] +ProjectHealthResourceType = Literal['database', 'database_branch', 'frontend', 'function', 'project'] -PROJECT_HEALTH_RESOURCE_TYPE_VALUES: set[ProjectHealthResourceType] = { 'database', 'frontend', 'function', 'project', } +PROJECT_HEALTH_RESOURCE_TYPE_VALUES: set[ProjectHealthResourceType] = { 'database', 'database_branch', 'frontend', 'function', 'project', } def check_project_health_resource_type(value: str) -> ProjectHealthResourceType: if value in PROJECT_HEALTH_RESOURCE_TYPE_VALUES: diff --git a/src/volcano_sdk/_generated/models/project_health_response.py b/src/volcano_sdk/_generated/models/project_health_response.py index 6887d281..e156ee10 100644 --- a/src/volcano_sdk/_generated/models/project_health_response.py +++ b/src/volcano_sdk/_generated/models/project_health_response.py @@ -32,7 +32,12 @@ class ProjectHealthResponse: """ Attributes: project_id (UUID): - status (ProjectHealthStatus): + status (ProjectHealthStatus): empty means no functions, frontends, databases, storage, Volcano Auth users, or + Sandbox resources are listed. needs_attention means an operation needs review without confirmed service + impairment, or a serving frontend whose edge recovery gave up needs a redeploy. degraded means a resource is + only partly available or is unavailable for an expected, temporary reason, such as a frontend whose edge + synchronization did not complete or a database being restored; critical means use is blocked. A failed workflow + or deployment record alone does not establish an outage. Findings remain available for empty projects. observed_at (datetime.datetime): fresh_through (datetime.datetime): data_status (ProjectHealthDataStatus): diff --git a/src/volcano_sdk/_generated/models/project_health_status.py b/src/volcano_sdk/_generated/models/project_health_status.py index c299dfb9..4d4ff43f 100644 --- a/src/volcano_sdk/_generated/models/project_health_status.py +++ b/src/volcano_sdk/_generated/models/project_health_status.py @@ -1,8 +1,8 @@ from typing import Literal -ProjectHealthStatus = Literal['critical', 'degraded', 'healthy', 'unknown'] +ProjectHealthStatus = Literal['critical', 'degraded', 'empty', 'healthy', 'needs_attention', 'unknown'] -PROJECT_HEALTH_STATUS_VALUES: set[ProjectHealthStatus] = { 'critical', 'degraded', 'healthy', 'unknown', } +PROJECT_HEALTH_STATUS_VALUES: set[ProjectHealthStatus] = { 'critical', 'degraded', 'empty', 'healthy', 'needs_attention', 'unknown', } def check_project_health_status(value: str) -> ProjectHealthStatus: if value in PROJECT_HEALTH_STATUS_VALUES: diff --git a/src/volcano_sdk/_generated/models/project_health_summary.py b/src/volcano_sdk/_generated/models/project_health_summary.py index 804a732f..1553141c 100644 --- a/src/volcano_sdk/_generated/models/project_health_summary.py +++ b/src/volcano_sdk/_generated/models/project_health_summary.py @@ -25,7 +25,12 @@ class ProjectHealthSummary: """ Attributes: - status (ProjectHealthStatus): + status (ProjectHealthStatus): empty means no functions, frontends, databases, storage, Volcano Auth users, or + Sandbox resources are listed. needs_attention means an operation needs review without confirmed service + impairment, or a serving frontend whose edge recovery gave up needs a redeploy. degraded means a resource is + only partly available or is unavailable for an expected, temporary reason, such as a frontend whose edge + synchronization did not complete or a database being restored; critical means use is blocked. A failed workflow + or deployment record alone does not establish an outage. Findings remain available for empty projects. """ status: ProjectHealthStatus diff --git a/src/volcano_sdk/_generated/models/register_durable_callback_request.py b/src/volcano_sdk/_generated/models/register_durable_callback_request.py new file mode 100644 index 00000000..cc3a3b7f --- /dev/null +++ b/src/volcano_sdk/_generated/models/register_durable_callback_request.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + + + + + + + +T = TypeVar("T", bound="RegisterDurableCallbackRequest") + + + +@_attrs_define +class RegisterDurableCallbackRequest: + """ + Attributes: + execution_ref (str): Opaque reference to the running execution, supplied by the SDK. + callback_id (str): Opaque reference to the waiting step, supplied by the SDK. + name (str): The waiting step's name, up to 237 printable ASCII characters. + """ + + execution_ref: str + callback_id: str + name: str + + + + + + def to_dict(self) -> dict[str, Any]: + execution_ref = self.execution_ref + + callback_id = self.callback_id + + name = self.name + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "execution_ref": execution_ref, + "callback_id": callback_id, + "name": name, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + execution_ref = d.pop("execution_ref") + + callback_id = d.pop("callback_id") + + name = d.pop("name") + + register_durable_callback_request = cls( + execution_ref=execution_ref, + callback_id=callback_id, + name=name, + ) + + return register_durable_callback_request + diff --git a/src/volcano_sdk/_generated/models/succeed_durable_callback_request.py b/src/volcano_sdk/_generated/models/succeed_durable_callback_request.py new file mode 100644 index 00000000..30c7a1b8 --- /dev/null +++ b/src/volcano_sdk/_generated/models/succeed_durable_callback_request.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + + + + + + + +T = TypeVar("T", bound="SucceedDurableCallbackRequest") + + + +@_attrs_define +class SucceedDurableCallbackRequest: + """ + Attributes: + token (str): The `vcb_` token the workflow handed out. It is the only credential + the callback operations take, so treat it as a secret. + result (Any): Any JSON value other than null, up to 64 KiB. The waiting step + resolves with it. Null is refused because a timeout resolves the + step with null. + """ + + token: str + result: Any + + + + + + def to_dict(self) -> dict[str, Any]: + token = self.token + + result = self.result + + + field_dict: dict[str, Any] = {} + + field_dict.update({ + "token": token, + "result": result, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + token = d.pop("token") + + result = d.pop("result") + + succeed_durable_callback_request = cls( + token=token, + result=result, + ) + + return succeed_durable_callback_request + From 046ffa1f91a30f7f09eaa351c5fd52cefd1f5048 Mon Sep 17 00:00:00 2001 From: subnetmarco <88.marco@gmail.com> Date: Mon, 19 Oct 2026 12:19:32 -0700 Subject: [PATCH 2/6] feat(durable): wait for callbacks with ctx.wait_for_callback Co-authored-by: Cursor --- README.md | 1 + docs/functions.md | 51 ++++ src/volcano_sdk/_durable_callback_token.py | 144 ++++++++++++ src/volcano_sdk/_durable_engine.py | 19 +- src/volcano_sdk/_durable_modules.py | 6 +- src/volcano_sdk/_durable_protocols.py | 7 +- .../_tests/fixtures/durable_callbacks.py | 41 ++++ .../_tests/fixtures/invalid_callbacks.py | 4 + .../_tests/test_durable_authoring.py | 131 +++++++++++ .../_tests/test_durable_callback_authoring.py | 213 +++++++++++++++++ .../_tests/test_durable_callback_token.py | 217 ++++++++++++++++++ src/volcano_sdk/durable_authoring.py | 84 ++++++- 12 files changed, 908 insertions(+), 10 deletions(-) create mode 100644 src/volcano_sdk/_durable_callback_token.py create mode 100644 src/volcano_sdk/_tests/fixtures/durable_callbacks.py create mode 100644 src/volcano_sdk/_tests/test_durable_callback_authoring.py create mode 100644 src/volcano_sdk/_tests/test_durable_callback_token.py diff --git a/README.md b/README.md index e01e9b06..bf6a0168 100644 --- a/README.md +++ b/README.md @@ -388,6 +388,7 @@ the clock or a random value. | `ctx.wait_until(check, options, name=None)` | Polls your own state until `options.until` holds, suspending between checks. `options.initial_state` is required. | | `ctx.wait_for_approval(name, title=..., description=None, details=None, timeout=None)` | Suspends until a person approves or denies, and returns an `ApprovalDecision`. A timeout returns `status="expired"` rather than raising. | | `ctx.wait_for_message(name, timeout=None)` | Suspends until a message with that name is sent to the execution, and returns a `ReceivedMessage`, or `None` once the timeout passes. | +| `ctx.wait_for_callback(name, submitter, timeout=None, heartbeat_timeout=None)` | Hands a token to another system and suspends until it succeeds or fails the callback. Returns the result, or `None` once a timeout passes; a failure raises `CallbackFailedError`. | | `ctx.publish(name, data=None)` | Records a message for applications to list or receive live, and returns a `PublishedMessage` with `id` and `seq`. | | `ctx.map(items, fn, name=None, options=None)` | Runs the same work over every item, each in its own child context. | | `ctx.parallel(branches, name=None, options=None)` | Runs independent branches at the same time. | diff --git a/docs/functions.md b/docs/functions.md index 5e09753f..6a844bb1 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -209,6 +209,55 @@ def handler(event, ctx): Names follow the approval rules: up to 237 printable ASCII characters, checked before anything is recorded, as is the timeout. Both need `VOLCANO_PLATFORM_API_URL`. A wait Volcano does not answer, cannot see yet, throttles, or fails on is retried for the same 30 seconds an approval gets, then raises `CallbackSubmitterError`. A wait Volcano refuses, for example with `too_many_message_waits` when 100 are already open, raises it at once. A refused publish fails its step without a retry, which fails the execution unless the handler catches it. +## Wait for another system with a callback token + +`ctx.wait_for_callback()` hands a token to another system and pauses the execution until that system answers with it. Use it when the work happens somewhere you do not run code, such as a payment provider, a job queue, or a partner's API, and that system can call back once it is done: + +```python +from volcano_sdk.durable_authoring import CallbackFailedError, durable + + +@durable +def handler(event, ctx): + try: + payment = ctx.wait_for_callback( + "payment", + lambda token: payments.start( + order_id=event["order_id"], callback_token=token + ), + timeout="1h", + heartbeat_timeout="10m", + ) + except CallbackFailedError as error: + return {"status": "declined", "reason": error.type, "message": error.message} + + if payment is None: + return {"status": "abandoned"} + return ctx.step( + "fulfil", lambda scope: fulfil(event["order_id"], payment["charge_id"]) + ) +``` + +The submitter receives a `vcb_` token. The system holding it answers with the token alone and needs no Volcano credential: + +```bash +curl -X POST "https://api.volcano.dev/durable-callbacks/succeed" \ + -H 'Content-Type: application/json' \ + -d '{"token":"vcb_...","result":{"charge_id":"ch_1"}}' + +curl -X POST "https://api.volcano.dev/durable-callbacks/fail" \ + -H 'Content-Type: application/json' \ + -d '{"token":"vcb_...","error":{"type":"CardDeclined","message":"Insufficient funds"}}' + +curl -X POST "https://api.volcano.dev/durable-callbacks/heartbeat" \ + -H 'Content-Type: application/json' \ + -d '{"token":"vcb_..."}' +``` + +`wait_for_callback(name, submitter, timeout=None, heartbeat_timeout=None)` returns the JSON value the token was succeeded with, or `None` once `timeout` passes, or `heartbeat_timeout` passes without a heartbeat. A result is never `None`, so the two cannot be confused. A failure raises `CallbackFailedError` with the `type` and `message` it was failed with. A token takes one answer: a second gets `409 callback_completed`, and one after the wait timed out or the execution ended gets `410`. It works only for this wait, so treat it as a secret. + +The submitter runs inside a checkpointed step that is not retried, so a replay does not run it again. An attempt interrupted before the step is recorded can run it twice, with the same token both times, so make the hand-off idempotent. When it raises, `wait_for_callback()` raises the runtime's `CallbackSubmitterError`. Getting the token needs `VOLCANO_PLATFORM_API_URL` and is retried for the same 30 seconds an approval gets. See [Durable callbacks](/platform/functions/durable-callbacks) for every request, response, and limit. + ## Decide approvals from a backend `client.durable.approvals` lists, reads, and decides a project's approvals: @@ -322,4 +371,6 @@ Approvals work the same locally. Decide one with `volcano durable approvals appr Messages work the same locally too. Send one with `volcano durable messages send`, or point the client at the local server. Message wait timeouts run in real time. +Callback tokens work locally as well: send the same requests to the local server. A callback's timeout and heartbeat timeout run in real time. + Running a decorated handler directly in a Python process still needs the optional test runtime: `python -m pip install 'volcano-sdk-python[durable]'`. diff --git a/src/volcano_sdk/_durable_callback_token.py b/src/volcano_sdk/_durable_callback_token.py new file mode 100644 index 00000000..3ebf54e1 --- /dev/null +++ b/src/volcano_sdk/_durable_callback_token.py @@ -0,0 +1,144 @@ +"""Hand a waiting step to another system, with a token Volcano issues for it.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._durable_callback import is_mapping +from ._durable_platform_request import PlatformRequest, post_to_platform +from ._durable_response import json_value +from ._transport_response import plain_json +from ._transport_types import decode_json +from .errors import ConflictError + +if TYPE_CHECKING: + from collections.abc import Callable + + from .models import JSONValue + +_NOT_READY = "callback_not_ready" +_CLOSED = "callback_closed" +_UNREADABLE_REGISTRATION = ( + "Volcano registered the callback with a token this SDK cannot read" +) +_UNREADABLE_RESULT = "Volcano resumed the callback with a result this SDK cannot read" + + +class CallbackFailedError(Exception): + """Raised by `ctx.wait_for_callback` when the token's holder fails it. + + `type` and `message` are what it sent. `type` is a name to branch on, such + as `CardDeclined`. + """ + + def __init__(self, error_type: str, message: str) -> None: + """Carry the failure's type and message.""" + super().__init__(message) + self.type: str = error_type + self.message: str = message + + +def token_submitter( + api_url: str, + execution_ref: str, + name: str, + hand_off: Callable[[str], object], +) -> Callable[[str, object], None]: + """Build the runtime's submitter: register the callback, then hand its token on. + + It runs inside the runtime's checkpointed submitter step, so a replay does + not register the callback again, and a step that runs again registers the + same callback and gets the same token. + + Returns: + The submitter the runtime calls with the callback's id. + + """ + + def submit(callback_id: str, _context: object) -> None: + token = register_callback(api_url, execution_ref, callback_id, name) + # A closed callback already timed out; the wait resumes with that. + if token is not None: + _ = hand_off(token) + + return submit + + +def register_callback( + api_url: str, execution_ref: str, callback_id: str, name: str +) -> str | None: + """Ask Volcano for the token that completes the callback. + + Returns: + The token, or None when the callback stopped waiting before the + request landed, normally because its timeout passed first. + + Raises: + ConflictError: Volcano refused the callback for another reason, such + as the step being registered for something else. + TypeError: Volcano answered without a token this SDK can read. + + """ + body: dict[str, JSONValue] = { + "execution_ref": execution_ref, + "callback_id": callback_id, + "name": name, + } + try: + answer = post_to_platform( + api_url, PlatformRequest("/durable-callbacks", body, not_ready=_NOT_READY) + ) + except ConflictError as error: + if error.code != _CLOSED: + raise + return None + token = answer.get("token") if is_mapping(answer) else None + if not isinstance(token, str) or not token: + raise TypeError(_UNREADABLE_REGISTRATION) + return token + + +def callback_result(result: object) -> JSONValue: + """Read the result the token's holder succeeded the callback with. + + The runtime hands it back as the JSON document Volcano sent, or already + decoded. + + Returns: + The result, as plain JSON. + + Raises: + TypeError: The result is not JSON. + + """ + if isinstance(result, (str, bytes)): + document = result.encode() if isinstance(result, str) else result + try: + result = decode_json(document) + except ValueError as error: + raise TypeError(_UNREADABLE_RESULT) from error + return plain_json(json_value(result, _UNREADABLE_RESULT)) + + +def callback_failure(data: str | None) -> tuple[str, str] | None: + """Read the failure Volcano delivered from the runtime's error data. + + Volcano sends the failure's type and message as the error's data, because + on replay the runtime rebuilds the error from its message and data alone. + + Returns: + The failure's type and message, or None when the data is not one + Volcano sent, as for a callback that ended because its execution + stopped. + + """ + if data is None: + return None + try: + value = decode_json(data.encode()) + except ValueError: + return None + if not is_mapping(value) or not isinstance(error_type := value.get("type"), str): + return None + message = value.get("message") + return error_type, message if isinstance(message, str) else "" diff --git a/src/volcano_sdk/_durable_engine.py b/src/volcano_sdk/_durable_engine.py index ef199481..e7cda85e 100644 --- a/src/volcano_sdk/_durable_engine.py +++ b/src/volcano_sdk/_durable_engine.py @@ -31,6 +31,7 @@ WaitForCallbackConfig, ) from aws_durable_execution_sdk_python.config import Duration as EngineDuration + from aws_durable_execution_sdk_python.exceptions import CallbackExternalError from aws_durable_execution_sdk_python.retries import ( RetryDecision, RetryStrategyConfig, @@ -121,6 +122,9 @@ def __init__(self) -> None: config.WaitForCallbackConfig ) self.callback_timeout_error: type[Exception] = exceptions.CallbackTimeoutError + self.callback_external_error: type[CallbackExternalError] = ( + exceptions.CallbackExternalError + ) def seconds(self, value: int) -> EngineDuration: """Build the runtime's duration from whole seconds. @@ -254,7 +258,9 @@ def _set_wait_timing( if options.backoff_rate is not None: config.backoff_rate = options.backoff_rate - def callback_options(self, timeout: int | None) -> WaitForCallbackConfig: + def callback_options( + self, timeout: int | None, heartbeat_timeout: int | None = None + ) -> WaitForCallbackConfig: """Build the runtime's callback options. The submitter is not retried by the runtime: it already retries what is @@ -264,11 +270,12 @@ def callback_options(self, timeout: int | None) -> WaitForCallbackConfig: The runtime wait-for-callback configuration. """ - if timeout is None: - return self.wait_for_callback_config(retry_strategy=self._never_retry()) - return self.wait_for_callback_config( - timeout=self.seconds(timeout), retry_strategy=self._never_retry() - ) + config = self.wait_for_callback_config(retry_strategy=self._never_retry()) + if timeout is not None: + config = replace(config, timeout=self.seconds(timeout)) + if heartbeat_timeout is not None: + config = replace(config, heartbeat_timeout=self.seconds(heartbeat_timeout)) + return config def map_options(self, options: BatchOptions | None) -> object: """Build the runtime's map options. diff --git a/src/volcano_sdk/_durable_modules.py b/src/volcano_sdk/_durable_modules.py index 2cfff4fe..0cf2bd66 100644 --- a/src/volcano_sdk/_durable_modules.py +++ b/src/volcano_sdk/_durable_modules.py @@ -20,7 +20,10 @@ StepSemantics, WaitForCallbackConfig, ) - from aws_durable_execution_sdk_python.exceptions import CallbackTimeoutError + from aws_durable_execution_sdk_python.exceptions import ( + CallbackExternalError, + CallbackTimeoutError, + ) from aws_durable_execution_sdk_python.retries import ( RetryDecision, RetryStrategyConfig, @@ -97,6 +100,7 @@ class ExceptionsModule(Protocol): """Runtime errors the authoring adapter turns into values.""" CallbackTimeoutError: type[CallbackTimeoutError] + CallbackExternalError: type[CallbackExternalError] @runtime_checkable diff --git a/src/volcano_sdk/_durable_protocols.py b/src/volcano_sdk/_durable_protocols.py index 74dbabf5..87da740f 100644 --- a/src/volcano_sdk/_durable_protocols.py +++ b/src/volcano_sdk/_durable_protocols.py @@ -9,6 +9,8 @@ if TYPE_CHECKING: from collections.abc import Callable, Mapping + from aws_durable_execution_sdk_python.exceptions import CallbackExternalError + from ._durable_options import BatchOptions, Retry, WaitUntilOptions T = TypeVar("T", default=object) @@ -53,10 +55,13 @@ class DurableEngine(Protocol): """The operations the Volcano facade needs from the optional runtime.""" callback_timeout_error: type[Exception] + callback_external_error: type[CallbackExternalError] def seconds(self, value: int) -> object: ... - def callback_options(self, timeout: int | None) -> object: ... + def callback_options( + self, timeout: int | None, heartbeat_timeout: int | None = None + ) -> object: ... def step_options(self, *, retry: Retry, at_most_once: bool) -> object: ... diff --git a/src/volcano_sdk/_tests/fixtures/durable_callbacks.py b/src/volcano_sdk/_tests/fixtures/durable_callbacks.py new file mode 100644 index 00000000..30f9d77f --- /dev/null +++ b/src/volcano_sdk/_tests/fixtures/durable_callbacks.py @@ -0,0 +1,41 @@ +"""Stand-ins for the callback-token route and the systems tokens go to.""" + +from __future__ import annotations + +import json +import threading + +REGISTER = "volcano_sdk._durable_callback_token.register_callback" +FAILURE_DATA = json.dumps({"type": "CardDeclined", "message": "Insufficient funds"}) + + +class CallbackRegistrations: + """Stand in for Volcano's token route, from the runtime's thread.""" + + def __init__(self) -> None: + self.calls: list[tuple[str, str, str, str]] = [] + self.closed: bool = False + self._lock: threading.Lock = threading.Lock() + + def register( + self, api_url: str, execution_ref: str, callback_id: str, name: str + ) -> str | None: + with self._lock: + self.calls.append((api_url, execution_ref, callback_id, name)) + return None if self.closed else f"vcb_{callback_id}" + + def callback_ids(self) -> list[str]: + with self._lock: + return [callback_id for _, _, callback_id, _ in self.calls] + + +class Handoffs: + """Record the tokens a submitter is handed.""" + + def __init__(self) -> None: + self.tokens: list[str] = [] + self._lock: threading.Lock = threading.Lock() + + def __call__(self, token: str) -> None: + with self._lock: + self.tokens.append(token) diff --git a/src/volcano_sdk/_tests/fixtures/invalid_callbacks.py b/src/volcano_sdk/_tests/fixtures/invalid_callbacks.py index cf24c0f7..ea41df34 100644 --- a/src/volcano_sdk/_tests/fixtures/invalid_callbacks.py +++ b/src/volcano_sdk/_tests/fixtures/invalid_callbacks.py @@ -40,3 +40,7 @@ def run_non_callable_operation(context: DurableContext, operation: str) -> objec def use_non_callable_retry(context: DurableContext) -> None: context.step("charge", lambda _scope: None, retry="aggressively") # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + + +def wait_for_callback_without_submitter(context: DurableContext) -> None: + _ = context.wait_for_callback("payment", None) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] diff --git a/src/volcano_sdk/_tests/test_durable_authoring.py b/src/volcano_sdk/_tests/test_durable_authoring.py index 6d2c22e4..4cc7acf3 100644 --- a/src/volcano_sdk/_tests/test_durable_authoring.py +++ b/src/volcano_sdk/_tests/test_durable_authoring.py @@ -35,6 +35,7 @@ CallbackTimeoutError, WaitForConditionError, ) +from aws_durable_execution_sdk_python.lambda_service import ErrorObject from aws_durable_execution_sdk_python.retries import RetryDecision from aws_durable_execution_sdk_python.waits import ( WaitForConditionConfig, @@ -55,6 +56,7 @@ BatchItem, BatchOptions, BatchResult, + CallbackFailedError, DurableContext, DurableRuntimeMissingError, FunctionHandler, @@ -67,6 +69,9 @@ ) from volcano_sdk.models import DurableApprovalDecider, DurableMessageSender +from .fixtures.durable_callbacks import FAILURE_DATA as CALLBACK_FAILURE_DATA +from .fixtures.durable_callbacks import REGISTER as REGISTER_CALLBACK +from .fixtures.durable_callbacks import CallbackRegistrations, Handoffs from .fixtures.durable_context import ( RecordedBatch, RecordedFailure, @@ -1761,6 +1766,132 @@ def handler(_event: object, ctx: DurableContext) -> object: assert result.result is None +@pytest.fixture +def callback_registrations(monkeypatch: pytest.MonkeyPatch) -> CallbackRegistrations: + recorder = CallbackRegistrations() + monkeypatch.setattr(REGISTER_CALLBACK, recorder.register) + monkeypatch.setenv("VOLCANO_PLATFORM_API_URL", PLATFORM_API_URL) + return recorder + + +def pay( + handoffs: Handoffs, + *, + timeout: str | None = None, + heartbeat_timeout: str | None = None, +) -> FunctionHandler: + @durable + def handler(_event: object, ctx: DurableContext) -> object: + try: + payment = ctx.wait_for_callback( + "payment", + handoffs, + timeout=timeout, + heartbeat_timeout=heartbeat_timeout, + ) + except CallbackFailedError as error: + return {"declined": error.type, "message": error.message} + return {"payment": payment} + + return handler + + +def answer_callback( + registrations: CallbackRegistrations, + handler: FunctionHandler, + respond: Callable[[DurableFunctionTestRunner, str], None], +) -> object: + """Run a handler, answer its callback once it suspends, and return its result. + + As with approvals, the answer goes out once the invocation that registered + the callback has suspended. + + Returns: + The decoded result. + + Raises: + AssertionError: If the handler fails. + + """ + invocations = Invocations(handler) + with local_runner(invocations) as runner: + execution_ref = runner.run_async(input=json.dumps({})) + invocations.wait_for_one() + [callback] = registrations.callback_ids() + respond(runner, callback) + result = runner.wait_for_result(execution_ref, timeout=60) + if result.error is not None: + raise AssertionError(result.error.message) + return None if result.result is None else json.loads(result.result) + + +def test_a_succeeded_callback_resumes_with_its_result( + callback_registrations: CallbackRegistrations, +) -> None: + handoffs = Handoffs() + + def succeed(runner: DurableFunctionTestRunner, callback: str) -> None: + runner.send_callback_success(callback, b'{"charge": "ch_1"}') + + result = answer_callback( + callback_registrations, pay(handoffs, timeout="1h"), succeed + ) + + assert result == {"payment": {"charge": "ch_1"}} + # The replay that resumed the execution registered nothing again. + [callback] = callback_registrations.callback_ids() + assert handoffs.tokens == [f"vcb_{callback}"] + + +def test_a_callback_failure_keeps_its_type_through_the_replay( + callback_registrations: CallbackRegistrations, +) -> None: + def fail(runner: DurableFunctionTestRunner, callback: str) -> None: + runner.send_callback_failure( + callback, + ErrorObject( + message="Insufficient funds", + type="CardDeclined", + data=CALLBACK_FAILURE_DATA, + stack_trace=None, + ), + ) + + result = answer_callback(callback_registrations, pay(Handoffs()), fail) + + assert result == {"declined": "CardDeclined", "message": "Insufficient funds"} + + +def test_a_heartbeat_keeps_the_callback_waiting( + callback_registrations: CallbackRegistrations, +) -> None: + def beat_then_succeed(runner: DurableFunctionTestRunner, callback: str) -> None: + runner.send_callback_heartbeat(callback) + runner.send_callback_success(callback, b'"paid"') + + result = answer_callback( + callback_registrations, + pay(Handoffs(), heartbeat_timeout="1h"), + beat_then_succeed, + ) + + assert result == {"payment": "paid"} + + +@pytest.mark.usefixtures("callback_registrations") +@pytest.mark.parametrize( + ("timeout", "heartbeat_timeout"), + [("1s", None), (None, "1s")], + ids=["timeout", "heartbeat-timeout"], +) +def test_a_callback_nobody_answers_resumes_with_none( + timeout: str | None, heartbeat_timeout: str | None +) -> None: + handler = pay(Handoffs(), timeout=timeout, heartbeat_timeout=heartbeat_timeout) + + assert run_handler(handler) == {"payment": None} + + @pytest.mark.usefixtures("registrations") def test_a_missing_platform_api_url_fails_before_opening_a_callback( monkeypatch: pytest.MonkeyPatch, diff --git a/src/volcano_sdk/_tests/test_durable_callback_authoring.py b/src/volcano_sdk/_tests/test_durable_callback_authoring.py new file mode 100644 index 00000000..d98b18e0 --- /dev/null +++ b/src/volcano_sdk/_tests/test_durable_callback_authoring.py @@ -0,0 +1,213 @@ +"""Callback-token authoring tests against a recording runtime context. + +The handler tests that run on the local runner are in test_durable_authoring. +""" + +from __future__ import annotations + +import json +from typing import TYPE_CHECKING + +import pytest +from aws_durable_execution_sdk_python.config import WaitForCallbackConfig +from aws_durable_execution_sdk_python.exceptions import ( + CallbackError, + CallbackExternalError, + CallbackTimeoutError, +) + +from volcano_sdk._durable_engine import Engine +from volcano_sdk.durable_authoring import CallbackFailedError, DurableContext + +from .fixtures.durable_callbacks import ( + FAILURE_DATA, + REGISTER, + CallbackRegistrations, + Handoffs, +) +from .fixtures.durable_context import RecordingContext +from .fixtures.invalid_callbacks import wait_for_callback_without_submitter +from .test_durable_authoring import PLATFORM_API_URL + +if TYPE_CHECKING: + from volcano_sdk.durable_authoring import Duration + + +@pytest.fixture +def registrations(monkeypatch: pytest.MonkeyPatch) -> CallbackRegistrations: + recorder = CallbackRegistrations() + monkeypatch.setattr(REGISTER, recorder.register) + monkeypatch.setenv("VOLCANO_PLATFORM_API_URL", PLATFORM_API_URL) + return recorder + + +def test_wait_for_callback_forwards_its_name_timeouts_and_submitter( + registrations: CallbackRegistrations, +) -> None: + runtime = RecordingContext() + runtime.callback_result = json.dumps({"charge": "ch_1"}) + context = DurableContext(runtime, Engine()) + handoffs = Handoffs() + + result = context.wait_for_callback( + "payment", handoffs, timeout="2h", heartbeat_timeout="5m" + ) + + assert result == {"charge": "ch_1"} + assert runtime.name == "payment" + assert isinstance(runtime.config, WaitForCallbackConfig) + assert runtime.config.timeout.to_seconds() == 7200 + assert runtime.config.heartbeat_timeout.to_seconds() == 300 + retry = runtime.config.retry_strategy + assert retry is not None + assert retry(RuntimeError("failed"), 1).should_retry is False + assert runtime.submitter is not None + assert registrations.calls == [] + runtime.submitter("callback-9", None) + assert registrations.calls == [ + (PLATFORM_API_URL, "arn:execution", "callback-9", "payment") + ] + assert handoffs.tokens == ["vcb_callback-9"] + + +@pytest.mark.usefixtures("registrations") +def test_a_callback_wait_without_timeouts_lasts_as_long_as_the_execution() -> None: + runtime = RecordingContext() + runtime.callback_result = '"done"' + context = DurableContext(runtime, Engine()) + + assert context.wait_for_callback("payment", Handoffs()) == "done" + assert isinstance(runtime.config, WaitForCallbackConfig) + assert runtime.config.timeout.to_seconds() == 0 + assert runtime.config.heartbeat_timeout.to_seconds() == 0 + + +def test_a_callback_that_already_closed_is_not_handed_on( + registrations: CallbackRegistrations, +) -> None: + registrations.closed = True + runtime = RecordingContext() + runtime.callback_result = '"unused"' + context = DurableContext(runtime, Engine()) + handoffs = Handoffs() + _ = context.wait_for_callback("payment", handoffs) + assert runtime.submitter is not None + + runtime.submitter("callback-9", None) + + assert len(registrations.calls) == 1 + assert handoffs.tokens == [] + + +@pytest.mark.usefixtures("registrations") +def test_only_a_callback_timeout_ends_the_wait_with_none() -> None: + runtime = RecordingContext() + runtime.callback_error = CallbackTimeoutError("timed out") + context = DurableContext(runtime, Engine()) + + assert context.wait_for_callback("payment", Handoffs(), timeout=60) is None + runtime.callback_error = CallbackError("callback failed") + with pytest.raises(CallbackError, match="callback failed"): + _ = context.wait_for_callback("payment", Handoffs()) + + +@pytest.mark.usefixtures("registrations") +def test_a_failure_raises_its_type_and_message() -> None: + runtime = RecordingContext() + failure = CallbackExternalError("Insufficient funds", data=FAILURE_DATA) + runtime.callback_error = failure + context = DurableContext(runtime, Engine()) + + with pytest.raises(CallbackFailedError, match=r"^Insufficient funds$") as caught: + _ = context.wait_for_callback("payment", Handoffs()) + + assert caught.value.type == "CardDeclined" + assert caught.value.message == "Insufficient funds" + assert caught.value.__cause__ is failure + + +@pytest.mark.usefixtures("registrations") +@pytest.mark.parametrize("data", [None, "not json", json.dumps({"message": "x"})]) +def test_a_failure_volcano_did_not_send_is_raised_as_the_runtime_raised_it( + data: str | None, +) -> None: + runtime = RecordingContext() + failure = CallbackExternalError("Callback failed", data=data) + runtime.callback_error = failure + context = DurableContext(runtime, Engine()) + + with pytest.raises(CallbackExternalError) as caught: + _ = context.wait_for_callback("payment", Handoffs()) + + assert caught.value is failure + + +@pytest.mark.usefixtures("registrations") +def test_an_unreadable_result_fails_the_wait() -> None: + runtime = RecordingContext() + runtime.callback_result = "{not json" + context = DurableContext(runtime, Engine()) + + with pytest.raises(TypeError, match="result this SDK cannot read"): + _ = context.wait_for_callback("payment", Handoffs()) + + +@pytest.mark.usefixtures("registrations") +@pytest.mark.parametrize( + ("name", "timeout", "heartbeat_timeout", "error", "match"), + [ + ("", None, None, ValueError, "^name must not be empty$"), + ("pay\tment", None, None, ValueError, "^name must be printable ASCII$"), + ("n" * 238, None, None, ValueError, "^name must be at most 237 characters$"), + ("payment", "0s", None, TypeError, "^timeout must be at least 1 second$"), + ("payment", "367d", None, TypeError, "^timeout must be at most 31622400"), + ( + "payment", + None, + "0s", + TypeError, + "^heartbeat_timeout must be at least 1 second$", + ), + ("payment", None, "soon", ValueError, "^heartbeat_timeout must be a duration"), + ], +) +def test_a_callback_wait_is_checked_before_its_callback_opens( + name: str, + timeout: Duration | None, + heartbeat_timeout: Duration | None, + error: type[Exception], + match: str, +) -> None: + runtime = RecordingContext() + context = DurableContext(runtime, Engine()) + + with pytest.raises(error, match=match): + _ = context.wait_for_callback( + name, Handoffs(), timeout=timeout, heartbeat_timeout=heartbeat_timeout + ) + assert runtime.submitter is None + + +@pytest.mark.usefixtures("registrations") +def test_a_callback_wait_requires_a_submitter() -> None: + runtime = RecordingContext() + context = DurableContext(runtime, Engine()) + + with pytest.raises( + TypeError, + match=r"^wait_for_callback\(\) requires a submitter function that receives", + ): + wait_for_callback_without_submitter(context) + assert runtime.submitter is None + + +def test_waiting_for_a_callback_needs_the_platform_api_url( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("VOLCANO_PLATFORM_API_URL", raising=False) + runtime = RecordingContext() + context = DurableContext(runtime, Engine()) + + with pytest.raises(RuntimeError, match=r"^wait_for_callback\(\) needs VOLCANO"): + _ = context.wait_for_callback("payment", Handoffs()) + assert runtime.submitter is None diff --git a/src/volcano_sdk/_tests/test_durable_callback_token.py b/src/volcano_sdk/_tests/test_durable_callback_token.py new file mode 100644 index 00000000..36fcfbaf --- /dev/null +++ b/src/volcano_sdk/_tests/test_durable_callback_token.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +import json +from typing import TYPE_CHECKING, Protocol + +import httpx +import pytest + +from volcano_sdk import ConflictError +from volcano_sdk._durable_callback_token import ( + CallbackFailedError, + callback_failure, + callback_result, + register_callback, + token_submitter, +) +from volcano_sdk._transport_types import decode_json + +from .fixtures.durable_platform import Answer, Platform, install_platform, refused + +if TYPE_CHECKING: + from volcano_sdk.models import JSONValue + +API_URL = "https://api.volcano.test" +TOKEN = "vcb_Zm9yIHRoZSBwYXltZW50IHdvcmtmbG93" +UNREADABLE_REGISTRATION = ( + "Volcano registered the callback with a token this SDK cannot read" +) +UNREADABLE_RESULT = "Volcano resumed the callback with a result this SDK cannot read" + + +class InstallPlatform(Protocol): + def __call__(self, *answers: Answer) -> Platform: ... + + +@pytest.fixture +def platform(monkeypatch: pytest.MonkeyPatch) -> InstallPlatform: + def install(*answers: Answer) -> Platform: + return install_platform(monkeypatch, *answers) + + return install + + +def issued(status: int = 201, token: object = TOKEN) -> httpx.Response: + return httpx.Response(status, json={"token": token, "expires_at": None}) + + +def register() -> str | None: + return register_callback(API_URL, "arn:execution/order-1", "callback-1", "payment") + + +def test_a_callback_registers_for_its_token(platform: InstallPlatform) -> None: + fake = platform(issued()) + + assert register() == TOKEN + + [request] = fake.requests + assert request.method == "POST" + assert str(request.url) == f"{API_URL}/durable-callbacks" + assert request.headers["Content-Type"] == "application/json" + assert decode_json(request.content) == { + "execution_ref": "arn:execution/order-1", + "callback_id": "callback-1", + "name": "payment", + } + + +def test_a_callback_registered_before_returns_its_token( + platform: InstallPlatform, +) -> None: + _ = platform(issued(status=200)) + + assert register() == TOKEN + + +def test_a_callback_the_runtime_has_not_recorded_yet_is_retried( + platform: InstallPlatform, +) -> None: + fake = platform(refused(409, "callback_not_ready"), issued()) + + assert register() == TOKEN + + assert len(fake.requests) == 2 + assert fake.sleeps == [0.5] + + +def test_a_callback_that_already_closed_has_no_token( + platform: InstallPlatform, +) -> None: + fake = platform(refused(409, "callback_closed")) + + assert register() is None + + assert len(fake.requests) == 1 + assert fake.sleeps == [] + + +@pytest.mark.parametrize( + "code", + ["callback_taken", "too_many_callback_tokens", "execution_ended", None], +) +def test_a_callback_refused_for_another_reason_raises_at_once( + platform: InstallPlatform, code: str | None +) -> None: + fake = platform(refused(409, code)) + + with pytest.raises(ConflictError, match="refused with 409") as caught: + _ = register() + + assert caught.value.code == code + assert len(fake.requests) == 1 + + +@pytest.mark.parametrize( + "answer", + [ + issued(token=""), + issued(token=7), + httpx.Response(201, json={"expires_at": None}), + httpx.Response(201, json=[TOKEN]), + httpx.Response(201), + ], + ids=["empty", "number", "missing", "list", "no-body"], +) +def test_a_registration_without_a_readable_token_is_refused( + platform: InstallPlatform, answer: httpx.Response +) -> None: + _ = platform(answer) + + with pytest.raises(TypeError, match=f"^{UNREADABLE_REGISTRATION}$"): + _ = register() + + +def test_the_submitter_hands_the_token_on(platform: InstallPlatform) -> None: + fake = platform(issued()) + handed: list[str] = [] + submit = token_submitter(API_URL, "arn:execution", "payment", handed.append) + + submit("callback-4", None) + + assert handed == [TOKEN] + assert decode_json(fake.requests[0].content) == { + "execution_ref": "arn:execution", + "callback_id": "callback-4", + "name": "payment", + } + + +def test_the_submitter_skips_a_callback_that_already_closed( + platform: InstallPlatform, +) -> None: + _ = platform(refused(409, "callback_closed")) + handed: list[str] = [] + submit = token_submitter(API_URL, "arn:execution", "payment", handed.append) + + submit("callback-4", None) + + assert handed == [] + + +@pytest.mark.parametrize( + ("result", "expected"), + [ + ('{"charge": "ch_1"}', {"charge": "ch_1"}), + (b'{"charge": "ch_1"}', {"charge": "ch_1"}), + ('"done"', "done"), + ("[1, 2]", [1, 2]), + ({"items": (1, 2)}, {"items": [1, 2]}), + ], + ids=["text", "bytes", "string", "list", "decoded"], +) +def test_a_result_is_read_from_the_callback( + result: object, expected: JSONValue +) -> None: + assert callback_result(result) == expected + + +@pytest.mark.parametrize("result", ["{not json", b"\xff", {"a": {1, 2}}]) +def test_an_unreadable_result_is_refused(result: object) -> None: + with pytest.raises(TypeError, match=f"^{UNREADABLE_RESULT}$"): + _ = callback_result(result) + + +@pytest.mark.parametrize( + ("data", "expected"), + [ + ( + {"type": "CardDeclined", "message": "Insufficient funds"}, + ("CardDeclined", "Insufficient funds"), + ), + ({"type": "Cancelled"}, ("Cancelled", "")), + ({"type": "Cancelled", "message": 42}, ("Cancelled", "")), + ({"type": "Cancelled", "message": ""}, ("Cancelled", "")), + ], + ids=["both", "no-message", "message-not-text", "empty-message"], +) +def test_a_failure_is_read_from_its_data( + data: dict[str, JSONValue], expected: tuple[str, str] +) -> None: + assert callback_failure(json.dumps(data)) == expected + + +@pytest.mark.parametrize( + "data", + [None, "not json", "[]", json.dumps({"message": "x"}), json.dumps({"type": 7})], + ids=["none", "not-json", "list", "no-type", "type-not-text"], +) +def test_data_volcano_did_not_send_is_no_failure(data: str | None) -> None: + assert callback_failure(data) is None + + +def test_a_failed_callback_error_carries_its_type_and_message() -> None: + error = CallbackFailedError("CardDeclined", "Insufficient funds") + + assert error.type == "CardDeclined" + assert error.message == "Insufficient funds" + assert str(error) == "Insufficient funds" diff --git a/src/volcano_sdk/durable_authoring.py b/src/volcano_sdk/durable_authoring.py index 3cabc717..4275ae03 100644 --- a/src/volcano_sdk/durable_authoring.py +++ b/src/volcano_sdk/durable_authoring.py @@ -47,6 +47,12 @@ def handler(event, ctx): register_approval, ) from ._durable_callback import operation_name +from ._durable_callback_token import ( + CallbackFailedError, + callback_failure, + callback_result, + token_submitter, +) from ._durable_duration import to_seconds from ._durable_engine import DurableRuntimeMissingError, load_engine from ._durable_message import ( @@ -95,6 +101,7 @@ def handler(event, ctx): "BatchItem", "BatchOptions", "BatchResult", + "CallbackFailedError", "DurableContext", "DurableHandler", "DurableLogger", @@ -130,6 +137,9 @@ def handler(event, ctx): _MAX_WAIT_SECONDS = 31622400 _REQUIRES_HANDLER = "durable(handler) requires a callable" _REQUIRES_UNTIL = "wait_until() requires an `until` predicate" +_REQUIRES_SUBMITTER = ( + "wait_for_callback() requires a submitter function that receives the token" +) # A condition is bounded by how many times it is checked, not by a deadline: # the platform holds the wait between checks and has no clock to compare # against when it resumes. Refused rather than ignored, because a wait meant to @@ -176,10 +186,11 @@ class DurableContext: the operations Volcano supports, in Volcano's vocabulary, and lets durations be written as `"30s"` rather than `Duration.from_seconds(30)`. - Raw callbacks are the deliberate omission: completing one takes a call + Raw callback ids are the deliberate omission: completing one takes a call nothing in a project can make. `wait_for_approval` and `wait_for_message` are the callbacks Volcano completes, when a person decides or a message - arrives. + arrives, and `wait_for_callback` hands out a token any system can complete + one with. """ __slots__: tuple[str, ...] = ( @@ -489,6 +500,75 @@ def submit(callback_id: str, _context: object) -> None: return None return received_message(result) + def wait_for_callback( + self, + name: str, + submitter: Callable[[str], object], + *, + timeout: Duration | None = None, + heartbeat_timeout: Duration | None = None, + ) -> JSONValue: + """Hand a token to another system and suspend until it answers. + + The submitter receives a `vcb_` token and passes it to whatever does + the work: a payment provider, a job queue, a partner's API. That + system succeeds the step with a result, fails it, or sends heartbeats, + with the token as its only credential. The execution is not running, + and not billed, while it waits. + + Without a timeout the wait lasts as long as the execution. With a + heartbeat timeout, it also ends once that long passes without a + heartbeat. Either timeout returns None rather than raising; a result is + never None, so the two cannot be confused. + + The submitter runs inside a checkpointed step that is not retried, so + a replay does not run it again. An attempt interrupted before the step + is recorded can run it twice, with the same token both times. When it + raises, the call raises the runtime's `CallbackSubmitterError`. The + name, submitter, timeouts, and `VOLCANO_PLATFORM_API_URL` are checked + before the callback opens, and getting the token is retried for up to + 30 seconds. + + Returns: + The result the callback was succeeded with, or None once a timeout + passes, restored from its checkpoint during replay. + + Raises: + CallbackFailedError: The token's holder failed the callback. + + """ + callback_name = operation_name(name) + require_callable(submitter, _REQUIRES_SUBMITTER) + config = self._callback_timeouts(timeout, heartbeat_timeout) + api_url = platform_api_url("wait_for_callback()") + submit = token_submitter( + api_url, + self._context.execution_context.durable_execution_arn, + callback_name, + submitter, + ) + try: + result = self._context.wait_for_callback(submit, callback_name, config) + except self._engine.callback_timeout_error: + return None + except self._engine.callback_external_error as error: + failure = callback_failure(error.data) + if failure is None: + raise + raise CallbackFailedError(*failure) from error + return callback_result(result) + + def _callback_timeouts( + self, timeout: Duration | None, heartbeat_timeout: Duration | None + ) -> object: + seconds = None if timeout is None else self._approval_timeout(timeout) + heartbeat = ( + None + if heartbeat_timeout is None + else _bounded_seconds(heartbeat_timeout, "heartbeat_timeout") + ) + return self._engine.callback_options(seconds, heartbeat) + def publish(self, name: str, data: JSONValue = None) -> PublishedMessage: """Record a message for applications to list, or receive live. From eb05911e36732da64b84f02b479a825e45dd3fdf Mon Sep 17 00:00:00 2001 From: subnetmarco <88.marco@gmail.com> Date: Mon, 19 Oct 2026 15:25:06 -0700 Subject: [PATCH 3/6] chore(openapi): sync the per-token callback rate limit Co-authored-by: Cursor --- openapi/openapi.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index db86efd7..5d91d6f4 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -6589,7 +6589,7 @@ paths: schema: $ref: '#/components/schemas/Error' '429': - description: Too many callback requests for the token's project. Retry later. + description: Too many callback requests for the token or its project. Retry later. content: application/json: schema: @@ -6664,7 +6664,7 @@ paths: schema: $ref: '#/components/schemas/Error' '429': - description: Too many callback requests for the token's project. Retry later. + description: Too many callback requests for the token or its project. Retry later. content: application/json: schema: @@ -6729,7 +6729,7 @@ paths: schema: $ref: '#/components/schemas/Error' '429': - description: Too many callback requests for the token's project. Retry later. + description: Too many callback requests for the token or its project. Retry later. content: application/json: schema: From 246da0a54495b092b8e7bb70e542c38decb87133 Mon Sep 17 00:00:00 2001 From: subnetmarco <88.marco@gmail.com> Date: Tue, 20 Oct 2026 10:56:51 -0700 Subject: [PATCH 4/6] fix(durable): refuse an async callback submitter The runtime calls the submitter on a thread without an event loop, and its result was discarded, so an async def submitter returned a coroutine that never ran: the wait opened and nothing received the token. Refuse an async def submitter with a TypeError before the callback opens, and fail the hand-off when a submitter returns any other awaitable, closing a coroutine so it does not warn later. The submitter stays typed as returning object, as in the JavaScript SDK, so one written as an expression that returns what it started still type-checks. The token fixture now reads as the fake it is. Co-authored-by: Cursor --- docs/functions.md | 2 +- src/volcano_sdk/_durable_callback_token.py | 38 ++++++++++- .../_tests/test_durable_callback_authoring.py | 18 +++++ .../_tests/test_durable_callback_token.py | 65 ++++++++++++++++++- src/volcano_sdk/durable_authoring.py | 13 ++-- 5 files changed, 128 insertions(+), 8 deletions(-) diff --git a/docs/functions.md b/docs/functions.md index 6a844bb1..787dc1e0 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -256,7 +256,7 @@ curl -X POST "https://api.volcano.dev/durable-callbacks/heartbeat" \ `wait_for_callback(name, submitter, timeout=None, heartbeat_timeout=None)` returns the JSON value the token was succeeded with, or `None` once `timeout` passes, or `heartbeat_timeout` passes without a heartbeat. A result is never `None`, so the two cannot be confused. A failure raises `CallbackFailedError` with the `type` and `message` it was failed with. A token takes one answer: a second gets `409 callback_completed`, and one after the wait timed out or the execution ended gets `410`. It works only for this wait, so treat it as a secret. -The submitter runs inside a checkpointed step that is not retried, so a replay does not run it again. An attempt interrupted before the step is recorded can run it twice, with the same token both times, so make the hand-off idempotent. When it raises, `wait_for_callback()` raises the runtime's `CallbackSubmitterError`. Getting the token needs `VOLCANO_PLATFORM_API_URL` and is retried for the same 30 seconds an approval gets. See [Durable callbacks](/platform/functions/durable-callbacks) for every request, response, and limit. +The submitter runs inside a checkpointed step that is not retried, so a replay does not run it again. An attempt interrupted before the step is recorded can run it twice, with the same token both times, so make the hand-off idempotent; if the token was answered or the wait timed out in between, it is not run again. When it raises, `wait_for_callback()` raises the runtime's `CallbackSubmitterError`. It must be synchronous: an `async def` submitter raises `TypeError` before the callback opens, and one that returns an awaitable fails the wait instead of handing off. Getting the token needs `VOLCANO_PLATFORM_API_URL` and is retried for the same 30 seconds an approval gets. See [Durable callbacks](/platform/functions/durable-callbacks) for every request, response, and limit. ## Decide approvals from a backend diff --git a/src/volcano_sdk/_durable_callback_token.py b/src/volcano_sdk/_durable_callback_token.py index 3ebf54e1..7c9b33b4 100644 --- a/src/volcano_sdk/_durable_callback_token.py +++ b/src/volcano_sdk/_durable_callback_token.py @@ -2,6 +2,7 @@ from __future__ import annotations +import inspect from typing import TYPE_CHECKING from ._durable_callback import is_mapping @@ -22,6 +23,10 @@ "Volcano registered the callback with a token this SDK cannot read" ) _UNREADABLE_RESULT = "Volcano resumed the callback with a result this SDK cannot read" +SYNCHRONOUS_SUBMITTER = ( + "wait_for_callback() requires a synchronous submitter; " + "nothing would run an async one's hand-off" +) class CallbackFailedError(Exception): @@ -57,13 +62,42 @@ def token_submitter( def submit(callback_id: str, _context: object) -> None: token = register_callback(api_url, execution_ref, callback_id, name) - # A closed callback already timed out; the wait resumes with that. + # A closed callback already timed out or was answered; the wait + # resumes as it did. if token is not None: - _ = hand_off(token) + handed_off(hand_off(token)) return submit +def require_synchronous(submitter: object) -> None: + """Refuse an `async def` submitter before its callback opens. + + Raises: + TypeError: The submitter is a coroutine function. + + """ + if inspect.iscoroutinefunction(submitter): + raise TypeError(SYNCHRONOUS_SUBMITTER) + + +def handed_off(result: object) -> None: + """Refuse a hand-off that returned an awaitable rather than running. + + The runtime calls the submitter on a thread without an event loop, so an + awaitable it returns would never run. + + Raises: + TypeError: The submitter returned an awaitable. + + """ + if not inspect.isawaitable(result): + return + if inspect.iscoroutine(result): + result.close() + raise TypeError(SYNCHRONOUS_SUBMITTER) + + def register_callback( api_url: str, execution_ref: str, callback_id: str, name: str ) -> str | None: diff --git a/src/volcano_sdk/_tests/test_durable_callback_authoring.py b/src/volcano_sdk/_tests/test_durable_callback_authoring.py index d98b18e0..297fbe84 100644 --- a/src/volcano_sdk/_tests/test_durable_callback_authoring.py +++ b/src/volcano_sdk/_tests/test_durable_callback_authoring.py @@ -5,6 +5,7 @@ from __future__ import annotations +import asyncio import json from typing import TYPE_CHECKING @@ -201,6 +202,23 @@ def test_a_callback_wait_requires_a_submitter() -> None: assert runtime.submitter is None +@pytest.mark.usefixtures("registrations") +def test_a_callback_wait_refuses_an_async_submitter() -> None: + runtime = RecordingContext() + context = DurableContext(runtime, Engine()) + handoffs = Handoffs() + + async def submit(token: str) -> None: + await asyncio.sleep(0) + handoffs(token) + + with pytest.raises( + TypeError, match=r"^wait_for_callback\(\) requires a synchronous submitter" + ): + _ = context.wait_for_callback("payment", submit) + assert runtime.submitter is None + + def test_waiting_for_a_callback_needs_the_platform_api_url( monkeypatch: pytest.MonkeyPatch, ) -> None: diff --git a/src/volcano_sdk/_tests/test_durable_callback_token.py b/src/volcano_sdk/_tests/test_durable_callback_token.py index 36fcfbaf..6f191bca 100644 --- a/src/volcano_sdk/_tests/test_durable_callback_token.py +++ b/src/volcano_sdk/_tests/test_durable_callback_token.py @@ -1,5 +1,7 @@ from __future__ import annotations +import asyncio +import inspect import json from typing import TYPE_CHECKING, Protocol @@ -19,14 +21,17 @@ from .fixtures.durable_platform import Answer, Platform, install_platform, refused if TYPE_CHECKING: + from collections.abc import Coroutine, Generator + from volcano_sdk.models import JSONValue API_URL = "https://api.volcano.test" -TOKEN = "vcb_Zm9yIHRoZSBwYXltZW50IHdvcmtmbG93" +TOKEN = "vcb_fake-token-for-tests" UNREADABLE_REGISTRATION = ( "Volcano registered the callback with a token this SDK cannot read" ) UNREADABLE_RESULT = "Volcano resumed the callback with a result this SDK cannot read" +SYNCHRONOUS = r"^wait_for_callback\(\) requires a synchronous submitter" class InstallPlatform(Protocol): @@ -146,6 +151,64 @@ def test_the_submitter_hands_the_token_on(platform: InstallPlatform) -> None: } +def test_the_submitter_ignores_what_the_hand_off_returns( + platform: InstallPlatform, +) -> None: + _ = platform(issued()) + handed: list[str] = [] + + def hand_off(token: str) -> int: + handed.append(token) + return len(handed) + + token_submitter(API_URL, "arn:execution", "payment", hand_off)("callback-4", None) + + assert handed == [TOKEN] + + +def test_the_submitter_refuses_a_hand_off_that_returns_an_awaitable( + platform: InstallPlatform, +) -> None: + _ = platform(issued()) + handed: list[str] = [] + started: list[Coroutine[object, object, None]] = [] + + async def deliver(token: str) -> None: + await asyncio.sleep(0) + handed.append(token) + + def hand_off(token: str) -> Coroutine[object, object, None]: + started.append(deliver(token)) + return started[-1] + + submit = token_submitter(API_URL, "arn:execution", "payment", hand_off) + + with pytest.raises(TypeError, match=SYNCHRONOUS): + submit("callback-4", None) + assert handed == [] + [coroutine] = started + assert inspect.getcoroutinestate(coroutine) == inspect.CORO_CLOSED + + +class Pending: + """An awaitable that is not a coroutine, as a future is.""" + + def __await__(self) -> Generator[None]: + yield + + +def test_the_submitter_refuses_an_awaitable_that_is_not_a_coroutine( + platform: InstallPlatform, +) -> None: + _ = platform(issued()) + submit = token_submitter( + API_URL, "arn:execution", "payment", lambda _token: Pending() + ) + + with pytest.raises(TypeError, match=SYNCHRONOUS): + submit("callback-4", None) + + def test_the_submitter_skips_a_callback_that_already_closed( platform: InstallPlatform, ) -> None: diff --git a/src/volcano_sdk/durable_authoring.py b/src/volcano_sdk/durable_authoring.py index 4275ae03..94cee09d 100644 --- a/src/volcano_sdk/durable_authoring.py +++ b/src/volcano_sdk/durable_authoring.py @@ -51,6 +51,7 @@ def handler(event, ctx): CallbackFailedError, callback_failure, callback_result, + require_synchronous, token_submitter, ) from ._durable_duration import to_seconds @@ -521,10 +522,13 @@ def wait_for_callback( heartbeat. Either timeout returns None rather than raising; a result is never None, so the two cannot be confused. - The submitter runs inside a checkpointed step that is not retried, so - a replay does not run it again. An attempt interrupted before the step - is recorded can run it twice, with the same token both times. When it - raises, the call raises the runtime's `CallbackSubmitterError`. The + The submitter is synchronous, and what it returns is ignored. It runs + inside a checkpointed step that is not retried, so a replay does not + run it again. An attempt interrupted before the step is recorded can + run it twice, with the same token both times, unless the token was + answered or the wait timed out in between. When it raises, or returns + an awaitable, the call raises the runtime's `CallbackSubmitterError`; + an `async def` submitter raises `TypeError` at once. The name, submitter, timeouts, and `VOLCANO_PLATFORM_API_URL` are checked before the callback opens, and getting the token is retried for up to 30 seconds. @@ -539,6 +543,7 @@ def wait_for_callback( """ callback_name = operation_name(name) require_callable(submitter, _REQUIRES_SUBMITTER) + require_synchronous(submitter) config = self._callback_timeouts(timeout, heartbeat_timeout) api_url = platform_api_url("wait_for_callback()") submit = token_submitter( From 5e3ca8d84c92f602930e953a545d25fd3c13f9d2 Mon Sep 17 00:00:00 2001 From: subnetmarco <88.marco@gmail.com> Date: Thu, 22 Oct 2026 12:19:33 -0700 Subject: [PATCH 5/6] chore(openapi): sync the callback registration's operation id Co-authored-by: Cursor --- openapi/openapi.yaml | 10 ++++++++++ .../models/register_durable_callback_request.py | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 5d91d6f4..641fe9aa 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -23190,6 +23190,16 @@ components: maxLength: 237 pattern: ^[ -~]+$ description: The waiting step's name, up to 237 printable ASCII characters. + operation_id: + type: string + minLength: 1 + maxLength: 64 + pattern: ^[0-9a-f]+$ + description: | + The operation an earlier run of the workflow opened the waiting step + under, supplied by the SDK when a replay registers it. Volcano then + looks for the step further back in the execution's history than a + first registration reads. required: - execution_ref - callback_id diff --git a/src/volcano_sdk/_generated/models/register_durable_callback_request.py b/src/volcano_sdk/_generated/models/register_durable_callback_request.py index cc3a3b7f..10a2116e 100644 --- a/src/volcano_sdk/_generated/models/register_durable_callback_request.py +++ b/src/volcano_sdk/_generated/models/register_durable_callback_request.py @@ -8,6 +8,7 @@ from ..types import UNSET, Unset +from ..types import UNSET, Unset @@ -25,11 +26,16 @@ class RegisterDurableCallbackRequest: execution_ref (str): Opaque reference to the running execution, supplied by the SDK. callback_id (str): Opaque reference to the waiting step, supplied by the SDK. name (str): The waiting step's name, up to 237 printable ASCII characters. + operation_id (str | Unset): The operation an earlier run of the workflow opened the waiting step + under, supplied by the SDK when a replay registers it. Volcano then + looks for the step further back in the execution's history than a + first registration reads. """ execution_ref: str callback_id: str name: str + operation_id: str | Unset = UNSET @@ -42,6 +48,8 @@ def to_dict(self) -> dict[str, Any]: name = self.name + operation_id = self.operation_id + field_dict: dict[str, Any] = {} @@ -50,6 +58,8 @@ def to_dict(self) -> dict[str, Any]: "callback_id": callback_id, "name": name, }) + if operation_id is not UNSET: + field_dict["operation_id"] = operation_id return field_dict @@ -64,10 +74,13 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: name = d.pop("name") + operation_id = d.pop("operation_id", UNSET) + register_durable_callback_request = cls( execution_ref=execution_ref, callback_id=callback_id, name=name, + operation_id=operation_id, ) return register_durable_callback_request From 0f5fd6ea0b5c6d0d90d657ccf3d274b5943e7aec Mon Sep 17 00:00:00 2001 From: subnetmarco <88.marco@gmail.com> Date: Thu, 22 Oct 2026 12:31:09 -0700 Subject: [PATCH 6/6] fix(durable): name the callback's operation when a replay registers it Co-authored-by: Cursor --- src/volcano_sdk/_durable_callback_token.py | 52 ++++++++++- .../_tests/fixtures/durable_callbacks.py | 9 +- .../_tests/test_durable_authoring.py | 39 ++++++++ .../_tests/test_durable_callback_token.py | 91 +++++++++++++++++++ src/volcano_sdk/durable_authoring.py | 2 + 5 files changed, 189 insertions(+), 4 deletions(-) diff --git a/src/volcano_sdk/_durable_callback_token.py b/src/volcano_sdk/_durable_callback_token.py index 7c9b33b4..803917bd 100644 --- a/src/volcano_sdk/_durable_callback_token.py +++ b/src/volcano_sdk/_durable_callback_token.py @@ -2,6 +2,7 @@ from __future__ import annotations +import hashlib import inspect from typing import TYPE_CHECKING @@ -48,12 +49,14 @@ def token_submitter( execution_ref: str, name: str, hand_off: Callable[[str], object], + operation_id: str | None = None, ) -> Callable[[str, object], None]: """Build the runtime's submitter: register the callback, then hand its token on. It runs inside the runtime's checkpointed submitter step, so a replay does not register the callback again, and a step that runs again registers the - same callback and gets the same token. + same callback and gets the same token. `operation_id` names the operation + an earlier invocation opened the callback under; see `replayed_operation`. Returns: The submitter the runtime calls with the callback's id. @@ -61,7 +64,9 @@ def token_submitter( """ def submit(callback_id: str, _context: object) -> None: - token = register_callback(api_url, execution_ref, callback_id, name) + token = register_callback( + api_url, execution_ref, callback_id, name, operation_id + ) # A closed callback already timed out or was answered; the wait # resumes as it did. if token is not None: @@ -70,6 +75,41 @@ def submit(callback_id: str, _context: object) -> None: return submit +def replayed_operation(context: object) -> str | None: + """Name the operation an earlier invocation opened the next callback under. + + Volcano then reads back to that callback rather than only through recent + history. Call it just before the runtime's `wait_for_callback`: the wait + runs in a child context named by the operation about to start, and opens + the callback as that context's first operation. Neither the next operation + nor the checkpoint is public, so a runtime without them names no operation + and the registration reads as a first run's does. + + Returns: + The callback's operation id, or None when this invocation opens it. + + """ + peek = getattr(context, "_peek_next_operation_id", None) + checkpoint = getattr(getattr(context, "state", None), "get_checkpoint_result", None) + if not callable(peek) or not callable(checkpoint): + return None + operation_id = callback_operation_id(str(peek())) + existent = getattr(checkpoint(operation_id), "is_existent", None) + if not callable(existent) or not existent(): + return None + return operation_id + + +def callback_operation_id(child_id: str) -> str: + """Name the callback a wait's child context `child_id` opens first. + + Returns: + The id the runtime checkpoints the callback under. + + """ + return hashlib.blake2b(f"{child_id}-1".encode()).hexdigest()[:64] + + def require_synchronous(submitter: object) -> None: """Refuse an `async def` submitter before its callback opens. @@ -99,7 +139,11 @@ def handed_off(result: object) -> None: def register_callback( - api_url: str, execution_ref: str, callback_id: str, name: str + api_url: str, + execution_ref: str, + callback_id: str, + name: str, + operation_id: str | None = None, ) -> str | None: """Ask Volcano for the token that completes the callback. @@ -118,6 +162,8 @@ def register_callback( "callback_id": callback_id, "name": name, } + if operation_id is not None: + body["operation_id"] = operation_id try: answer = post_to_platform( api_url, PlatformRequest("/durable-callbacks", body, not_ready=_NOT_READY) diff --git a/src/volcano_sdk/_tests/fixtures/durable_callbacks.py b/src/volcano_sdk/_tests/fixtures/durable_callbacks.py index 30f9d77f..90e4e6ce 100644 --- a/src/volcano_sdk/_tests/fixtures/durable_callbacks.py +++ b/src/volcano_sdk/_tests/fixtures/durable_callbacks.py @@ -14,14 +14,21 @@ class CallbackRegistrations: def __init__(self) -> None: self.calls: list[tuple[str, str, str, str]] = [] + self.operations: list[str | None] = [] self.closed: bool = False self._lock: threading.Lock = threading.Lock() def register( - self, api_url: str, execution_ref: str, callback_id: str, name: str + self, + api_url: str, + execution_ref: str, + callback_id: str, + name: str, + operation_id: str | None = None, ) -> str | None: with self._lock: self.calls.append((api_url, execution_ref, callback_id, name)) + self.operations.append(operation_id) return None if self.closed else f"vcb_{callback_id}" def callback_ids(self) -> list[str]: diff --git a/src/volcano_sdk/_tests/test_durable_authoring.py b/src/volcano_sdk/_tests/test_durable_authoring.py index 4cc7acf3..df6d31ab 100644 --- a/src/volcano_sdk/_tests/test_durable_authoring.py +++ b/src/volcano_sdk/_tests/test_durable_authoring.py @@ -44,8 +44,13 @@ create_wait_strategy, ) from aws_durable_execution_sdk_python_testing import DurableFunctionTestRunner +from aws_durable_execution_sdk_python_testing.runner import CallbackOperation from volcano_sdk import ValidationError +from volcano_sdk._durable_callback_token import ( + callback_operation_id, + replayed_operation, +) from volcano_sdk._durable_duration import to_seconds from volcano_sdk._durable_engine import Engine, load_engine from volcano_sdk._durable_results import completion_reason @@ -1843,6 +1848,40 @@ def succeed(runner: DurableFunctionTestRunner, callback: str) -> None: assert handoffs.tokens == [f"vcb_{callback}"] +def test_a_replay_names_the_operation_the_first_invocation_opened_the_callback_under( + callback_registrations: CallbackRegistrations, + monkeypatch: pytest.MonkeyPatch, +) -> None: + named: list[str | None] = [] + + def recorded(context: object) -> str | None: + operation_id = replayed_operation(context) + named.append(operation_id) + return operation_id + + monkeypatch.setattr("volcano_sdk.durable_authoring.replayed_operation", recorded) + invocations = Invocations(pay(Handoffs(), timeout="1h")) + with local_runner(invocations) as runner: + execution_ref = runner.run_async(input=json.dumps({})) + invocations.wait_for_one() + [callback] = callback_registrations.callback_ids() + runner.send_callback_success(callback, b'"paid"') + result = runner.wait_for_result(execution_ref, timeout=60) + + assert result.error is None + wait = result.get_context("payment") + [opened] = [ + operation + for operation in wait.child_operations + if isinstance(operation, CallbackOperation) + ] + assert opened.operation_id == callback_operation_id(wait.operation_id) + # The first invocation opened the callback; the replay that resumed it + # finds it checkpointed. + assert named == [None, opened.operation_id] + assert callback_registrations.operations == [None] + + def test_a_callback_failure_keeps_its_type_through_the_replay( callback_registrations: CallbackRegistrations, ) -> None: diff --git a/src/volcano_sdk/_tests/test_durable_callback_token.py b/src/volcano_sdk/_tests/test_durable_callback_token.py index 6f191bca..f490323c 100644 --- a/src/volcano_sdk/_tests/test_durable_callback_token.py +++ b/src/volcano_sdk/_tests/test_durable_callback_token.py @@ -1,8 +1,10 @@ from __future__ import annotations import asyncio +import hashlib import inspect import json +from dataclasses import dataclass, field from typing import TYPE_CHECKING, Protocol import httpx @@ -14,6 +16,7 @@ callback_failure, callback_result, register_callback, + replayed_operation, token_submitter, ) from volcano_sdk._transport_types import decode_json @@ -151,6 +154,94 @@ def test_the_submitter_hands_the_token_on(platform: InstallPlatform) -> None: } +def test_a_replayed_submitter_names_the_callbacks_operation( + platform: InstallPlatform, +) -> None: + fake = platform(issued()) + handed: list[str] = [] + submit = token_submitter(API_URL, "arn:execution", "payment", handed.append, "ab12") + + submit("callback-4", None) + + assert decode_json(fake.requests[0].content) == { + "execution_ref": "arn:execution", + "callback_id": "callback-4", + "name": "payment", + "operation_id": "ab12", + } + + +@dataclass(frozen=True) +class Checkpoint: + exists: bool + + def is_existent(self) -> bool: + return self.exists + + +@dataclass +class Checkpoints: + recorded: set[str] = field(default_factory=set[str]) + + def get_checkpoint_result(self, operation_id: str) -> Checkpoint: + return Checkpoint(operation_id in self.recorded) + + +@dataclass +class OpaqueCheckpoints: + result: object + + def get_checkpoint_result(self, operation_id: str) -> object: + _ = operation_id + return self.result + + +@dataclass +class RuntimeInternals: + """Stand in for the runtime's private operation cursor and checkpoint.""" + + next_operation: str + state: object + + def _peek_next_operation_id(self) -> str: + return self.next_operation + + +@dataclass +class NoCursor: + state: object + + +def callback_of(child_id: str) -> str: + return hashlib.blake2b(f"{child_id}-1".encode()).hexdigest()[:64] + + +def test_a_replay_names_the_operation_an_earlier_invocation_opened() -> None: + context = RuntimeInternals("child-7", Checkpoints({callback_of("child-7")})) + + assert replayed_operation(context) == callback_of("child-7") + + +def test_the_invocation_that_opens_the_callback_names_no_operation() -> None: + context = RuntimeInternals("child-7", Checkpoints({callback_of("child-6")})) + + assert replayed_operation(context) is None + + +@pytest.mark.parametrize( + "context", + [ + NoCursor(Checkpoints({callback_of("child-7")})), + RuntimeInternals("child-7", None), + RuntimeInternals("child-7", OpaqueCheckpoints(object())), + object(), + ], + ids=["no cursor", "no checkpoint", "unreadable checkpoint", "plain object"], +) +def test_a_runtime_without_its_internals_names_no_operation(context: object) -> None: + assert replayed_operation(context) is None + + def test_the_submitter_ignores_what_the_hand_off_returns( platform: InstallPlatform, ) -> None: diff --git a/src/volcano_sdk/durable_authoring.py b/src/volcano_sdk/durable_authoring.py index 94cee09d..10fb58c2 100644 --- a/src/volcano_sdk/durable_authoring.py +++ b/src/volcano_sdk/durable_authoring.py @@ -51,6 +51,7 @@ def handler(event, ctx): CallbackFailedError, callback_failure, callback_result, + replayed_operation, require_synchronous, token_submitter, ) @@ -551,6 +552,7 @@ def wait_for_callback( self._context.execution_context.durable_execution_arn, callback_name, submitter, + replayed_operation(self._context), ) try: result = self._context.wait_for_callback(submit, callback_name, config)