From b211155784135b1377975a2759f2ddca1cffcea2 Mon Sep 17 00:00:00 2001 From: dieriba Date: Wed, 15 Oct 2025 23:43:42 +0200 Subject: [PATCH 01/33] fix: support dyn select for sub flow (#6835) * support subflow * update * Update frontend/src/lib/common.ts Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --------- Co-authored-by: Ruben Fiszel Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- frontend/src/lib/common.ts | 4 +++- .../components/flows/content/FlowModuleComponent.svelte | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/common.ts b/frontend/src/lib/common.ts index f3dc46db7a..e5220a2655 100644 --- a/frontend/src/lib/common.ts +++ b/frontend/src/lib/common.ts @@ -1,4 +1,4 @@ -import type { Script } from './gen' +import type { Script, ScriptLang } from './gen' export type OwnerKind = 'group' | 'user' | 'folder' @@ -109,6 +109,8 @@ export function modalToSchema(schema: ModalSchemaProperty): SchemaProperty { export type Schema = { $schema: string | undefined type: string + "x-windmill-dyn-select-code"?: string + "x-windmill-dyn-select-lang"?: ScriptLang properties: { [name: string]: SchemaProperty } order?: string[] required: string[] diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 3be61accba..01252b6a46 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -325,6 +325,13 @@ lang: value.language } break + case 'flow': + helperScript = { + source: 'deployed', + path: value.path, + runnable_kind: 'flow' + } + break default: helperScript = undefined } From d12c8f34efe5ebbdbbf85ae41bb11307dc5d8ea3 Mon Sep 17 00:00:00 2001 From: dieriba Date: Thu, 16 Oct 2025 00:08:34 +0200 Subject: [PATCH 02/33] fix: gcp script picker (#6837) * fix * remove --- .../lib/components/triggers/gcp/GcpTriggerEditorInner.svelte | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte index abb3ab8782..3a14b27522 100644 --- a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte @@ -31,7 +31,6 @@ import Toggle from '$lib/components/Toggle.svelte' let drawer: Drawer | undefined = $state(undefined) - let is_flow: boolean = $state(false) let initialPath = $state('') let edit = $state(true) let delivery_type: DeliveryType = $state('pull') @@ -132,7 +131,6 @@ drawerLoading = true try { drawer?.openDrawer() - is_flow = nis_flow itemKind = nis_flow ? 'flow' : 'script' initialScriptPath = '' fixedScriptPath = fixedScriptPath_ ?? '' @@ -184,7 +182,6 @@ subscription_id = cfg?.subscription_id delivery_config = cfg?.delivery_config subscription_mode = cfg?.subscription_mode - is_flow = cfg?.is_flow path = cfg?.path enabled = cfg?.enabled topic_id = cfg?.topic_id ?? '' @@ -229,7 +226,7 @@ path, script_path, enabled, - is_flow, + is_flow : itemKind === 'flow', error_handler_path, error_handler_args, retry, From 3d5631938f9dc6da6256868349776b53e1f202d8 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 15 Oct 2025 11:46:46 +0000 Subject: [PATCH 03/33] error --- backend/windmill-common/src/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/windmill-common/src/client.rs b/backend/windmill-common/src/client.rs index a95ccbe292..b05fdfa2f7 100644 --- a/backend/windmill-common/src/client.rs +++ b/backend/windmill-common/src/client.rs @@ -39,8 +39,8 @@ impl AuthedClient { .send() .await .map_err(|e| { - tracing::error!("Error executing get request from authed http client to {url} with query {query:?}: {e}"); - anyhow::anyhow!("Error executing get request from authed http client to {url} with query {query:?}: {e}") + tracing::error!("Error executing get request from authed http client to {url} with query {query:?}: {e:#?}"); + anyhow::anyhow!("Error executing get request from authed http client to {url} with query {query:?}: {e:#?}") }) } From 892ce64ea8550c22d65180c71f57c90a65583832 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 15 Oct 2025 22:24:11 +0000 Subject: [PATCH 04/33] fix: fix concurrency key filter --- backend/windmill-api/src/concurrency_groups.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/windmill-api/src/concurrency_groups.rs b/backend/windmill-api/src/concurrency_groups.rs index ba85043a73..db1341132c 100644 --- a/backend/windmill-api/src/concurrency_groups.rs +++ b/backend/windmill-api/src/concurrency_groups.rs @@ -159,22 +159,22 @@ async fn get_concurrent_intervals( let lq = ListCompletedQuery { order_desc: Some(true), ..lq }; let lqc = lq.clone(); let lqq: ListQueueQuery = lqc.into(); - let mut sqlb_q = SqlBuilder::select_from("v2_as_queue") + let mut sqlb_q = SqlBuilder::select_from("v2_job_queue") .fields(UnifiedJob::queued_job_fields()) .order_by("created_at", lq.order_desc.unwrap_or(true)) .limit(row_limit) .clone(); - let mut sqlb_c = SqlBuilder::select_from("v2_as_completed_job") + let mut sqlb_c = SqlBuilder::select_from("v2_job_completed") .fields(UnifiedJob::completed_job_fields()) .order_by("started_at", lq.order_desc.unwrap_or(true)) .limit(row_limit) .clone(); - let mut sqlb_q_user = SqlBuilder::select_from("v2_as_queue") + let mut sqlb_q_user = SqlBuilder::select_from("v2_job_queue") .fields(&["id"]) .order_by("created_at", lq.order_desc.unwrap_or(true)) .limit(row_limit) .clone(); - let mut sqlb_c_user = SqlBuilder::select_from("v2_as_completed_job") + let mut sqlb_c_user = SqlBuilder::select_from("v2_job_completed") .fields(&["id"]) .order_by("started_at", lq.order_desc.unwrap_or(true)) .limit(row_limit) From 48acc57823792c9e795f9735712e1b2ed6d2b4e2 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Wed, 15 Oct 2025 18:30:09 -0400 Subject: [PATCH 05/33] fix: always create instance groups with uuid (#6826) * fix: always create instance groups with uuid * Update SQLx metadata * repo ref --------- Co-authored-by: windmill-internal-app[bot] --- ...1f7f387f5055c47f493271d26731336257384.json | 10 +-- ...cc6d6bf1df758b30e99bd661da866062ef14f.json | 23 ----- ...b21270131e9e93ca10d195664e7e5a774fe9e.json | 4 +- ...dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c.json | 15 ---- ...ef6bfdecc81700b89962c758c065d8d55f9e2.json | 16 ++++ ...94f91df7e588d4d2431bc85f4d8734920c8bf.json | 51 ----------- ...c38fc64deb1226aab9dc3bc4465324fce37d1.json | 16 ---- ...ca3761d400391f1f46a8294da3e6c9af63887.json | 15 ---- ...5ef756b8e5c1955fbe111df9ee171dc262338.json | 89 ------------------- ...0cb549a34b96554ae1872355b90304f5dcb76.json | 4 +- ...212a5bd4039b57fab20b163617e33a4c9dd46.json | 14 --- ...77afbd8b3a660b3be27514b517c077c63c238.json | 89 ------------------- backend/ee-repo-ref.txt | 2 +- ...922_backfill_instance_group_uuids.down.sql | 4 + ...81922_backfill_instance_group_uuids.up.sql | 7 ++ backend/windmill-api/src/groups.rs | 6 +- 16 files changed, 41 insertions(+), 324 deletions(-) delete mode 100644 backend/.sqlx/query-16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f.json delete mode 100644 backend/.sqlx/query-383b8adaadc6e23952ca8942fe7dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c.json create mode 100644 backend/.sqlx/query-59c63c5e3ce0b4976133cd65258ef6bfdecc81700b89962c758c065d8d55f9e2.json delete mode 100644 backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json delete mode 100644 backend/.sqlx/query-85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1.json delete mode 100644 backend/.sqlx/query-a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887.json delete mode 100644 backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json delete mode 100644 backend/.sqlx/query-b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46.json delete mode 100644 backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json create mode 100644 backend/migrations/20251014181922_backfill_instance_group_uuids.down.sql create mode 100644 backend/migrations/20251014181922_backfill_instance_group_uuids.up.sql diff --git a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json index d29a18c691..e7ed0aee65 100644 --- a/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json +++ b/backend/.sqlx/query-07168aaf14cb6beff0ad4274b441f7f387f5055c47f493271d26731336257384.json @@ -46,11 +46,11 @@ ] }, "nullable": [ - true, - true, - true, - true, - true, + false, + false, + false, + false, + false, true, true ] diff --git a/backend/.sqlx/query-16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f.json b/backend/.sqlx/query-16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f.json deleted file mode 100644 index 1af42ff529..0000000000 --- a/backend/.sqlx/query-16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "hash", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "16e4b1bead9fc77fd98658b8cb8cc6d6bf1df758b30e99bd661da866062ef14f" -} diff --git a/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json b/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json index 66453d2659..9462f9b48b 100644 --- a/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json +++ b/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json @@ -59,9 +59,7 @@ "failure", "command", "approval", - "preprocessor", - "schedule_handler_old", - "dynamic_skip" + "preprocessor" ] } } diff --git a/backend/.sqlx/query-383b8adaadc6e23952ca8942fe7dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c.json b/backend/.sqlx/query-383b8adaadc6e23952ca8942fe7dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c.json deleted file mode 100644 index db96fe6009..0000000000 --- a/backend/.sqlx/query-383b8adaadc6e23952ca8942fe7dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO instance_group (name, summary) VALUES ($1, $2) ON CONFLICT DO NOTHING", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "383b8adaadc6e23952ca8942fe7dbaa7c74ce0cfa1b945b7514e19a70f7a6f1c" -} diff --git a/backend/.sqlx/query-59c63c5e3ce0b4976133cd65258ef6bfdecc81700b89962c758c065d8d55f9e2.json b/backend/.sqlx/query-59c63c5e3ce0b4976133cd65258ef6bfdecc81700b89962c758c065d8d55f9e2.json new file mode 100644 index 0000000000..f2c1cd78bf --- /dev/null +++ b/backend/.sqlx/query-59c63c5e3ce0b4976133cd65258ef6bfdecc81700b89962c758c065d8d55f9e2.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO instance_group (name, summary, id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "59c63c5e3ce0b4976133cd65258ef6bfdecc81700b89962c758c065d8d55f9e2" +} diff --git a/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json b/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json deleted file mode 100644 index 6f08d98113..0000000000 --- a/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO script (summary, description, dedicated_worker, content, workspace_id, path, hash, language, tag, created_by, lock) VALUES ('', '', true, $1, $2, $3, $4, $5, $6, $7, '') ON CONFLICT (workspace_id, hash) DO NOTHING", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Varchar", - "Varchar", - "Int8", - { - "Custom": { - "name": "script_lang", - "kind": { - "Enum": [ - "python3", - "deno", - "go", - "bash", - "postgresql", - "nativets", - "bun", - "mysql", - "bigquery", - "snowflake", - "graphql", - "powershell", - "mssql", - "php", - "bunnative", - "rust", - "ansible", - "csharp", - "oracledb", - "nu", - "java", - "duckdb", - "ruby" - ] - } - } - }, - "Varchar", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf" -} diff --git a/backend/.sqlx/query-85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1.json b/backend/.sqlx/query-85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1.json deleted file mode 100644 index bce7324fb6..0000000000 --- a/backend/.sqlx/query-85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "UuidArray", - "Varchar", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "85705fc3d7f8ba5f1b12d5fb222c38fc64deb1226aab9dc3bc4465324fce37d1" -} diff --git a/backend/.sqlx/query-a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887.json b/backend/.sqlx/query-a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887.json deleted file mode 100644 index 1afc61978e..0000000000 --- a/backend/.sqlx/query-a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "UuidArray", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "a0b3e10e077d30c1da135dff9feca3761d400391f1f46a8294da3e6c9af63887" -} diff --git a/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json b/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json deleted file mode 100644 index fe1a67b0d4..0000000000 --- a/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, 1)) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Int8", - "Varchar", - { - "Custom": { - "name": "job_kind", - "kind": { - "Enum": [ - "script", - "preview", - "flow", - "dependencies", - "flowpreview", - "script_hub", - "identity", - "flowdependencies", - "http", - "graphql", - "postgresql", - "noop", - "appdependencies", - "deploymentcallback", - "singlestepflow", - "flowscript", - "flownode", - "appscript", - "aiagent" - ] - } - } - }, - { - "Custom": { - "name": "script_lang", - "kind": { - "Enum": [ - "python3", - "deno", - "go", - "bash", - "postgresql", - "nativets", - "bun", - "mysql", - "bigquery", - "snowflake", - "graphql", - "powershell", - "mssql", - "php", - "bunnative", - "rust", - "ansible", - "csharp", - "oracledb", - "nu", - "java", - "duckdb", - "ruby" - ] - } - } - }, - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Jsonb" - ] - }, - "nullable": [ - false - ] - }, - "hash": "ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338" -} diff --git a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json index 99269c9851..54e94cfb8f 100644 --- a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json +++ b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json @@ -18,8 +18,8 @@ "Left": [] }, "nullable": [ - true, - false + false, + true ] }, "hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76" diff --git a/backend/.sqlx/query-b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46.json b/backend/.sqlx/query-b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46.json deleted file mode 100644 index a49baeefaf..0000000000 --- a/backend/.sqlx/query-b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "UuidArray" - ] - }, - "nullable": [] - }, - "hash": "b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46" -} diff --git a/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json b/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json deleted file mode 100644 index 00662e2fd2..0000000000 --- a/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Int8", - "Varchar", - { - "Custom": { - "name": "job_kind", - "kind": { - "Enum": [ - "script", - "preview", - "flow", - "dependencies", - "flowpreview", - "script_hub", - "identity", - "flowdependencies", - "http", - "graphql", - "postgresql", - "noop", - "appdependencies", - "deploymentcallback", - "singlestepflow", - "flowscript", - "flownode", - "appscript", - "aiagent" - ] - } - } - }, - { - "Custom": { - "name": "script_lang", - "kind": { - "Enum": [ - "python3", - "deno", - "go", - "bash", - "postgresql", - "nativets", - "bun", - "mysql", - "bigquery", - "snowflake", - "graphql", - "powershell", - "mssql", - "php", - "bunnative", - "rust", - "ansible", - "csharp", - "oracledb", - "nu", - "java", - "duckdb", - "ruby" - ] - } - } - }, - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Int4" - ] - }, - "nullable": [ - false - ] - }, - "hash": "ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238" -} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index cac3213232..12cf35dc1b 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -19909371503adfa12106dbf261d7f4134989a204 +c7d34190819c83b4dfe62498a47ab9b439a321a9 diff --git a/backend/migrations/20251014181922_backfill_instance_group_uuids.down.sql b/backend/migrations/20251014181922_backfill_instance_group_uuids.down.sql new file mode 100644 index 0000000000..d130324837 --- /dev/null +++ b/backend/migrations/20251014181922_backfill_instance_group_uuids.down.sql @@ -0,0 +1,4 @@ +-- Add down migration script here + +-- This migration is irreversible as we cannot safely remove UUIDs +-- that may already be in use by SCIM clients diff --git a/backend/migrations/20251014181922_backfill_instance_group_uuids.up.sql b/backend/migrations/20251014181922_backfill_instance_group_uuids.up.sql new file mode 100644 index 0000000000..d947b80fad --- /dev/null +++ b/backend/migrations/20251014181922_backfill_instance_group_uuids.up.sql @@ -0,0 +1,7 @@ +-- Add up migration script here + +-- Backfill UUIDs for instance groups that don't have one +-- This is needed for SCIM compatibility where groups must have stable UUIDs +UPDATE instance_group +SET id = gen_random_uuid()::text +WHERE id IS NULL; diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index 4c526ffe26..afe73f3bc8 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -286,13 +286,17 @@ async fn create_igroup( Extension(db): Extension, Json(ng): Json, ) -> Result { + use uuid::Uuid; + require_super_admin(&db, &authed.email).await?; let mut tx = db.begin().await?; + let id = Uuid::new_v4().to_string(); sqlx::query!( - "INSERT INTO instance_group (name, summary) VALUES ($1, $2) ON CONFLICT DO NOTHING", + "INSERT INTO instance_group (name, summary, id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", ng.name, ng.summary, + id, ) .execute(&mut *tx) .await?; From d75e9e3d92d43f449a6296b367018f8fa3da6507 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Wed, 15 Oct 2025 18:32:54 -0400 Subject: [PATCH 06/33] feat: build pydoc for wmill python client and mount in container image (#6828) --- Dockerfile | 1 + python-client/.gitignore | 1 + python-client/DOCS.md | 128 + python-client/build.sh | 5 +- python-client/build_pdoc.sh | 19 + python-client/docs/index.html | 7 + python-client/docs/search.js | 46 + python-client/docs/wmill.html | 240 + python-client/docs/wmill/client.html | 6459 +++++++++++++++++++++++ python-client/docs/wmill/s3_reader.html | 550 ++ python-client/docs/wmill/s3_types.html | 841 +++ 11 files changed, 8296 insertions(+), 1 deletion(-) create mode 100644 python-client/DOCS.md create mode 100755 python-client/build_pdoc.sh create mode 100644 python-client/docs/index.html create mode 100644 python-client/docs/search.js create mode 100644 python-client/docs/wmill.html create mode 100644 python-client/docs/wmill/client.html create mode 100644 python-client/docs/wmill/s3_reader.html create mode 100644 python-client/docs/wmill/s3_types.html diff --git a/Dockerfile b/Dockerfile index 4f3bb4f7d6..b3f18b4e17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,7 @@ COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi. RUN cd /backend/windmill-api && . ./build_openapi.sh COPY /backend/parsers/windmill-parser-wasm/pkg/ /backend/parsers/windmill-parser-wasm/pkg/ COPY /typescript-client/docs/ /frontend/static/tsdocs/ +COPY /python-client/docs/ /frontend/static/pydocs/ RUN npm run generate-backend-client ENV NODE_OPTIONS "--max-old-space-size=8192" diff --git a/python-client/.gitignore b/python-client/.gitignore index 0a6e1f82df..3b7cf22936 100644 --- a/python-client/.gitignore +++ b/python-client/.gitignore @@ -1 +1,2 @@ windmill-api/ +.venv/ diff --git a/python-client/DOCS.md b/python-client/DOCS.md new file mode 100644 index 0000000000..58a0a2c6ab --- /dev/null +++ b/python-client/DOCS.md @@ -0,0 +1,128 @@ +# Python Client Documentation + +This document describes how the Python client documentation is generated and deployed, similar to the TypeScript client. + +## Overview + +The Python client uses **pdoc** to automatically generate API documentation from docstrings in the code, similar to how the TypeScript client uses TypeDoc. + +## Architecture + +Following the same pattern as the TypeScript client: + +1. **Documentation Generator**: pdoc (Python) vs TypeDoc (TypeScript) +2. **Build Script**: `build_pdoc.sh` (similar to `build_typedoc.sh` for TS) +3. **Output Directory**: `docs/` containing static HTML +4. **Deployment**: Copied to `/frontend/static/pydocs/` during Docker build +5. **URL**: Accessible at `https://app.windmill.dev/pydocs/wmill.html` + +## Building Documentation + +### How It Works + +The Python client documentation follows the same pattern as the TypeScript client: + +1. **Docs are checked into git** - The `docs/` directory is committed to the repository +2. **Built during releases** - When `./build.sh` runs (on releases or manually), it calls `./build_pdoc.sh` +3. **Copied during Docker build** - The Dockerfile copies pre-built docs to the frontend + +### Locally + +To build/update the documentation: + +```bash +cd /path/to/windmill/python-client +./build_pdoc.sh +``` + +This will: +1. Create a virtual environment if needed (`.venv/` - gitignored) +2. Install pdoc and dependencies +3. Generate HTML documentation in `./docs/` +4. Documentation will be available at `file://$(pwd)/docs/wmill.html` + +After building, you should commit the updated docs: +```bash +git add docs/ +git commit -m "Update Python client documentation" +``` + +### In CI/CD + +The documentation is built automatically during the release process: + +1. **On release** (`pypi_on_release.yml` workflow triggers on version tags) +2. Runs `./publish.sh` → calls `./build.sh` → calls `./build_pdoc.sh` +3. Docs are generated and should be committed separately or before the release + +### In Docker Build + +The documentation is copied during Docker build (see `Dockerfile` line 51): + +```dockerfile +COPY /python-client/docs/ /frontend/static/pydocs/ +``` + +This makes the docs available at `https://app.windmill.dev/pydocs/` in production. + +## Documentation Structure + +The generated documentation includes: + +- **Main Module** (`wmill.html`): Overview and module-level functions +- **Client Class** (`wmill/client.html`): Full Windmill class API reference +- **S3 Types** (`wmill/s3_types.html`): S3 integration types and helpers +- **S3 Reader** (`wmill/s3_reader.html`): S3 file reading utilities + +All documentation is automatically generated from: +- Function/class docstrings +- Type hints +- Parameter descriptions +- Return type annotations + +## Writing Good Documentation + +To maintain quality documentation: + +1. **Use clear docstrings** following Google or NumPy style: + ```python + def my_function(param1: str, param2: int = 0) -> dict: + """Short description of function. + + Longer description with more details about what the function does + and any important notes. + + Args: + param1: Description of param1 + param2: Description of param2 (default: 0) + + Returns: + Description of return value + + Example: + >>> result = my_function("test", 5) + >>> print(result) + {'status': 'ok'} + """ + ``` + +2. **Add type hints** - pdoc uses them to generate better documentation +3. **Include examples** in docstrings where helpful +4. **Keep descriptions concise** but complete + +## Comparison with TypeScript Client + +| Aspect | TypeScript | Python | +|--------|-----------|--------| +| Generator | TypeDoc | pdoc | +| Build Script | `build_typedoc.sh` | `build_pdoc.sh` | +| Output Dir | `docs/` | `docs/` | +| Hosted At | `/tsdocs/` | `/pydocs/` | +| Entry Point | `modules.html` | `wmill.html` | + +## References + +- Windmill Docs: https://windmilldocs/docs/advanced/2_clients/python_client.md +- TypeScript Client Docs: https://app.windmill.dev/tsdocs/modules.html +- Python Client Docs: https://app.windmill.dev/pydocs/wmill.html +- pdoc Documentation: https://pdoc.dev/ diff --git a/python-client/build.sh b/python-client/build.sh index 780806388e..4224f77bad 100755 --- a/python-client/build.sh +++ b/python-client/build.sh @@ -74,4 +74,7 @@ mv windmill-api/README.md.tmp windmill-api/README.md cd windmill-api && poetry build cd ../wmill && poetry build cd ../wmill_pg && poetry build -cd .. && echo "windmill-api/" >> .gitignore +cd .. && echo "windmill-api/" >> .gitignore + +# Build documentation (similar to typescript-client/build_typedoc.sh) +./build_pdoc.sh diff --git a/python-client/build_pdoc.sh b/python-client/build_pdoc.sh new file mode 100755 index 0000000000..53df696331 --- /dev/null +++ b/python-client/build_pdoc.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Build Python documentation with pdoc +# Similar to typescript-client/build_typedoc.sh + +set -e + +# Create/activate virtual environment if needed +if [ ! -d ".venv" ]; then + python3 -m venv .venv + .venv/bin/pip install -q pdoc httpx +fi + +# Install the package +.venv/bin/pip install -q ./wmill + +# Generate documentation +.venv/bin/pdoc wmill -o docs + +echo "Python documentation built successfully in ./docs/" diff --git a/python-client/docs/index.html b/python-client/docs/index.html new file mode 100644 index 0000000000..6cb034b833 --- /dev/null +++ b/python-client/docs/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/python-client/docs/search.js b/python-client/docs/search.js new file mode 100644 index 0000000000..960f5fe457 --- /dev/null +++ b/python-client/docs/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, "wmill.client": {"fullname": "wmill.client", "modulename": "wmill.client", "kind": "module", "doc": "

\n"}, "wmill.client.logger": {"fullname": "wmill.client.logger", "modulename": "wmill.client", "qualname": "logger", "kind": "variable", "doc": "

\n", "default_value": "<Logger windmill_client (WARNING)>"}, "wmill.client.JobStatus": {"fullname": "wmill.client.JobStatus", "modulename": "wmill.client", "qualname": "JobStatus", "kind": "variable", "doc": "

\n", "default_value": "typing.Literal['RUNNING', 'WAITING', 'COMPLETED']"}, "wmill.client.Windmill": {"fullname": "wmill.client.Windmill", "modulename": "wmill.client", "qualname": "Windmill", "kind": "class", "doc": "

\n"}, "wmill.client.Windmill.__init__": {"fullname": "wmill.client.Windmill.__init__", "modulename": "wmill.client", "qualname": "Windmill.__init__", "kind": "function", "doc": "

\n", "signature": "(base_url=None, token=None, workspace=None, verify=True)"}, "wmill.client.Windmill.base_url": {"fullname": "wmill.client.Windmill.base_url", "modulename": "wmill.client", "qualname": "Windmill.base_url", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.token": {"fullname": "wmill.client.Windmill.token", "modulename": "wmill.client", "qualname": "Windmill.token", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.headers": {"fullname": "wmill.client.Windmill.headers", "modulename": "wmill.client", "qualname": "Windmill.headers", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.verify": {"fullname": "wmill.client.Windmill.verify", "modulename": "wmill.client", "qualname": "Windmill.verify", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.client": {"fullname": "wmill.client.Windmill.client", "modulename": "wmill.client", "qualname": "Windmill.client", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.workspace": {"fullname": "wmill.client.Windmill.workspace", "modulename": "wmill.client", "qualname": "Windmill.workspace", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.path": {"fullname": "wmill.client.Windmill.path", "modulename": "wmill.client", "qualname": "Windmill.path", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.mocked_api": {"fullname": "wmill.client.Windmill.mocked_api", "modulename": "wmill.client", "qualname": "Windmill.mocked_api", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.get_mocked_api": {"fullname": "wmill.client.Windmill.get_mocked_api", "modulename": "wmill.client", "qualname": "Windmill.get_mocked_api", "kind": "function", "doc": "

\n", "signature": "(self) -> Optional[dict]:", "funcdef": "def"}, "wmill.client.Windmill.get_client": {"fullname": "wmill.client.Windmill.get_client", "modulename": "wmill.client", "qualname": "Windmill.get_client", "kind": "function", "doc": "

\n", "signature": "(self) -> httpx.Client:", "funcdef": "def"}, "wmill.client.Windmill.get": {"fullname": "wmill.client.Windmill.get", "modulename": "wmill.client", "qualname": "Windmill.get", "kind": "function", "doc": "

\n", "signature": "(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:", "funcdef": "def"}, "wmill.client.Windmill.post": {"fullname": "wmill.client.Windmill.post", "modulename": "wmill.client", "qualname": "Windmill.post", "kind": "function", "doc": "

\n", "signature": "(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:", "funcdef": "def"}, "wmill.client.Windmill.create_token": {"fullname": "wmill.client.Windmill.create_token", "modulename": "wmill.client", "qualname": "Windmill.create_token", "kind": "function", "doc": "

\n", "signature": "(self, duration=datetime.timedelta(days=1)) -> str:", "funcdef": "def"}, "wmill.client.Windmill.run_script_async": {"fullname": "wmill.client.Windmill.run_script_async", "modulename": "wmill.client", "qualname": "Windmill.run_script_async", "kind": "function", "doc": "

Create a script job and return its job id.

\n\n

Deprecated since version Use run_script_by_path_async or run_script_by_hash_async instead..

\n", "signature": "(\tself,\tpath: str = None,\thash_: str = None,\targs: dict = None,\tscheduled_in_secs: int = None) -> str:", "funcdef": "def"}, "wmill.client.Windmill.run_script_by_path_async": {"fullname": "wmill.client.Windmill.run_script_by_path_async", "modulename": "wmill.client", "qualname": "Windmill.run_script_by_path_async", "kind": "function", "doc": "

Create a script job by path and return its job id.

\n", "signature": "(self, path: str, args: dict = None, scheduled_in_secs: int = None) -> str:", "funcdef": "def"}, "wmill.client.Windmill.run_script_by_hash_async": {"fullname": "wmill.client.Windmill.run_script_by_hash_async", "modulename": "wmill.client", "qualname": "Windmill.run_script_by_hash_async", "kind": "function", "doc": "

Create a script job by hash and return its job id.

\n", "signature": "(\tself,\thash_: str,\targs: dict = None,\tscheduled_in_secs: int = None) -> str:", "funcdef": "def"}, "wmill.client.Windmill.run_flow_async": {"fullname": "wmill.client.Windmill.run_flow_async", "modulename": "wmill.client", "qualname": "Windmill.run_flow_async", "kind": "function", "doc": "

Create a flow job and return its job id.

\n", "signature": "(\tself,\tpath: str,\targs: dict = None,\tscheduled_in_secs: int = None,\tdo_not_track_in_parent: bool = True) -> str:", "funcdef": "def"}, "wmill.client.Windmill.run_script": {"fullname": "wmill.client.Windmill.run_script", "modulename": "wmill.client", "qualname": "Windmill.run_script", "kind": "function", "doc": "

Run script synchronously and return its result.

\n\n

Deprecated since version Use run_script_by_path or run_script_by_hash instead..

\n", "signature": "(\tself,\tpath: str = None,\thash_: str = None,\targs: dict = None,\ttimeout: datetime.timedelta | int | float | None = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = False) -> Any:", "funcdef": "def"}, "wmill.client.Windmill.run_script_by_path": {"fullname": "wmill.client.Windmill.run_script_by_path", "modulename": "wmill.client", "qualname": "Windmill.run_script_by_path", "kind": "function", "doc": "

Run script by path synchronously and return its result.

\n", "signature": "(\tself,\tpath: str,\targs: dict = None,\ttimeout: datetime.timedelta | int | float | None = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = False) -> Any:", "funcdef": "def"}, "wmill.client.Windmill.run_script_by_hash": {"fullname": "wmill.client.Windmill.run_script_by_hash", "modulename": "wmill.client", "qualname": "Windmill.run_script_by_hash", "kind": "function", "doc": "

Run script by hash synchronously and return its result.

\n", "signature": "(\tself,\thash_: str,\targs: dict = None,\ttimeout: datetime.timedelta | int | float | None = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = False) -> Any:", "funcdef": "def"}, "wmill.client.Windmill.wait_job": {"fullname": "wmill.client.Windmill.wait_job", "modulename": "wmill.client", "qualname": "Windmill.wait_job", "kind": "function", "doc": "

\n", "signature": "(\tself,\tjob_id,\ttimeout: datetime.timedelta | int | float | None = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = False):", "funcdef": "def"}, "wmill.client.Windmill.cancel_running": {"fullname": "wmill.client.Windmill.cancel_running", "modulename": "wmill.client", "qualname": "Windmill.cancel_running", "kind": "function", "doc": "

Cancel currently running executions of the same script.

\n", "signature": "(self) -> dict:", "funcdef": "def"}, "wmill.client.Windmill.get_job": {"fullname": "wmill.client.Windmill.get_job", "modulename": "wmill.client", "qualname": "Windmill.get_job", "kind": "function", "doc": "

\n", "signature": "(self, job_id: str) -> dict:", "funcdef": "def"}, "wmill.client.Windmill.get_root_job_id": {"fullname": "wmill.client.Windmill.get_root_job_id", "modulename": "wmill.client", "qualname": "Windmill.get_root_job_id", "kind": "function", "doc": "

\n", "signature": "(self, job_id: str | None = None) -> dict:", "funcdef": "def"}, "wmill.client.Windmill.get_id_token": {"fullname": "wmill.client.Windmill.get_id_token", "modulename": "wmill.client", "qualname": "Windmill.get_id_token", "kind": "function", "doc": "

\n", "signature": "(self, audience: str) -> str:", "funcdef": "def"}, "wmill.client.Windmill.get_job_status": {"fullname": "wmill.client.Windmill.get_job_status", "modulename": "wmill.client", "qualname": "Windmill.get_job_status", "kind": "function", "doc": "

\n", "signature": "(self, job_id: str) -> Literal['RUNNING', 'WAITING', 'COMPLETED']:", "funcdef": "def"}, "wmill.client.Windmill.get_result": {"fullname": "wmill.client.Windmill.get_result", "modulename": "wmill.client", "qualname": "Windmill.get_result", "kind": "function", "doc": "

\n", "signature": "(self, job_id: str, assert_result_is_not_none: bool = True) -> Any:", "funcdef": "def"}, "wmill.client.Windmill.get_variable": {"fullname": "wmill.client.Windmill.get_variable", "modulename": "wmill.client", "qualname": "Windmill.get_variable", "kind": "function", "doc": "

\n", "signature": "(self, path: str) -> str:", "funcdef": "def"}, "wmill.client.Windmill.set_variable": {"fullname": "wmill.client.Windmill.set_variable", "modulename": "wmill.client", "qualname": "Windmill.set_variable", "kind": "function", "doc": "

\n", "signature": "(self, path: str, value: str, is_secret: bool = False) -> None:", "funcdef": "def"}, "wmill.client.Windmill.get_resource": {"fullname": "wmill.client.Windmill.get_resource", "modulename": "wmill.client", "qualname": "Windmill.get_resource", "kind": "function", "doc": "

\n", "signature": "(self, path: str, none_if_undefined: bool = False) -> dict | None:", "funcdef": "def"}, "wmill.client.Windmill.set_resource": {"fullname": "wmill.client.Windmill.set_resource", "modulename": "wmill.client", "qualname": "Windmill.set_resource", "kind": "function", "doc": "

\n", "signature": "(self, value: Any, path: str, resource_type: str):", "funcdef": "def"}, "wmill.client.Windmill.set_state": {"fullname": "wmill.client.Windmill.set_state", "modulename": "wmill.client", "qualname": "Windmill.set_state", "kind": "function", "doc": "

\n", "signature": "(self, value: Any):", "funcdef": "def"}, "wmill.client.Windmill.set_progress": {"fullname": "wmill.client.Windmill.set_progress", "modulename": "wmill.client", "qualname": "Windmill.set_progress", "kind": "function", "doc": "

\n", "signature": "(self, value: int, job_id: Optional[str] = None):", "funcdef": "def"}, "wmill.client.Windmill.get_progress": {"fullname": "wmill.client.Windmill.get_progress", "modulename": "wmill.client", "qualname": "Windmill.get_progress", "kind": "function", "doc": "

\n", "signature": "(self, job_id: Optional[str] = None) -> Any:", "funcdef": "def"}, "wmill.client.Windmill.set_flow_user_state": {"fullname": "wmill.client.Windmill.set_flow_user_state", "modulename": "wmill.client", "qualname": "Windmill.set_flow_user_state", "kind": "function", "doc": "

Set the user state of a flow at a given key

\n", "signature": "(self, key: str, value: Any) -> None:", "funcdef": "def"}, "wmill.client.Windmill.get_flow_user_state": {"fullname": "wmill.client.Windmill.get_flow_user_state", "modulename": "wmill.client", "qualname": "Windmill.get_flow_user_state", "kind": "function", "doc": "

Get the user state of a flow at a given key

\n", "signature": "(self, key: str) -> Any:", "funcdef": "def"}, "wmill.client.Windmill.version": {"fullname": "wmill.client.Windmill.version", "modulename": "wmill.client", "qualname": "Windmill.version", "kind": "variable", "doc": "

\n"}, "wmill.client.Windmill.get_duckdb_connection_settings": {"fullname": "wmill.client.Windmill.get_duckdb_connection_settings", "modulename": "wmill.client", "qualname": "Windmill.get_duckdb_connection_settings", "kind": "function", "doc": "

Convenient helpers that takes an S3 resource as input and returns the settings necessary to\ninitiate an S3 connection from DuckDB

\n", "signature": "(\tself,\ts3_resource_path: str = '') -> wmill.s3_types.DuckDbConnectionSettings | None:", "funcdef": "def"}, "wmill.client.Windmill.get_polars_connection_settings": {"fullname": "wmill.client.Windmill.get_polars_connection_settings", "modulename": "wmill.client", "qualname": "Windmill.get_polars_connection_settings", "kind": "function", "doc": "

Convenient helpers that takes an S3 resource as input and returns the settings necessary to\ninitiate an S3 connection from Polars

\n", "signature": "(\tself,\ts3_resource_path: str = '') -> wmill.s3_types.PolarsConnectionSettings:", "funcdef": "def"}, "wmill.client.Windmill.get_boto3_connection_settings": {"fullname": "wmill.client.Windmill.get_boto3_connection_settings", "modulename": "wmill.client", "qualname": "Windmill.get_boto3_connection_settings", "kind": "function", "doc": "

Convenient helpers that takes an S3 resource as input and returns the settings necessary to\ninitiate an S3 connection using boto3

\n", "signature": "(\tself,\ts3_resource_path: str = '') -> wmill.s3_types.Boto3ConnectionSettings:", "funcdef": "def"}, "wmill.client.Windmill.load_s3_file": {"fullname": "wmill.client.Windmill.load_s3_file", "modulename": "wmill.client", "qualname": "Windmill.load_s3_file", "kind": "function", "doc": "

Load a file from the workspace s3 bucket and returns its content as bytes.

\n\n

'''python\nfrom wmill import S3Object

\n\n

s3_obj = S3Object(s3=\"/path/to/my_file.txt\")\nmy_obj_content = client.load_s3_file(s3_obj)\nfile_content = my_obj_content.decode(\"utf-8\")\n'''

\n", "signature": "(\tself,\ts3object: wmill.s3_types.S3Object | str,\ts3_resource_path: str | None) -> bytes:", "funcdef": "def"}, "wmill.client.Windmill.load_s3_file_reader": {"fullname": "wmill.client.Windmill.load_s3_file_reader", "modulename": "wmill.client", "qualname": "Windmill.load_s3_file_reader", "kind": "function", "doc": "

Load a file from the workspace s3 bucket and returns the bytes stream.

\n\n

'''python\nfrom wmill import S3Object

\n\n

s3_obj = S3Object(s3=\"/path/to/my_file.txt\")\nwith wmill.load_s3_file_reader(s3object, s3_resource_path) as file_reader:\n print(file_reader.read())\n'''

\n", "signature": "(\tself,\ts3object: wmill.s3_types.S3Object | str,\ts3_resource_path: str | None) -> _io.BufferedReader:", "funcdef": "def"}, "wmill.client.Windmill.write_s3_file": {"fullname": "wmill.client.Windmill.write_s3_file", "modulename": "wmill.client", "qualname": "Windmill.write_s3_file", "kind": "function", "doc": "

Write a file to the workspace S3 bucket

\n\n

'''python\nfrom wmill import S3Object

\n\n

s3_obj = S3Object(s3=\"/path/to/my_file.txt\")

\n\n

for an in memory bytes array:

\n\n

file_content = b'Hello Windmill!'\nclient.write_s3_file(s3_obj, file_content)

\n\n

for a file:

\n\n

with open(\"my_file.txt\", \"rb\") as my_file:\n client.write_s3_file(s3_obj, my_file)\n'''

\n", "signature": "(\tself,\ts3object: wmill.s3_types.S3Object | str | None,\tfile_content: _io.BufferedReader | bytes,\ts3_resource_path: str | None,\tcontent_type: str | None = None,\tcontent_disposition: str | None = None) -> wmill.s3_types.S3Object:", "funcdef": "def"}, "wmill.client.Windmill.sign_s3_objects": {"fullname": "wmill.client.Windmill.sign_s3_objects", "modulename": "wmill.client", "qualname": "Windmill.sign_s3_objects", "kind": "function", "doc": "

\n", "signature": "(\tself,\ts3_objects: list[wmill.s3_types.S3Object | str]) -> list[wmill.s3_types.S3Object]:", "funcdef": "def"}, "wmill.client.Windmill.sign_s3_object": {"fullname": "wmill.client.Windmill.sign_s3_object", "modulename": "wmill.client", "qualname": "Windmill.sign_s3_object", "kind": "function", "doc": "

\n", "signature": "(\tself,\ts3_object: wmill.s3_types.S3Object | str) -> wmill.s3_types.S3Object:", "funcdef": "def"}, "wmill.client.Windmill.whoami": {"fullname": "wmill.client.Windmill.whoami", "modulename": "wmill.client", "qualname": "Windmill.whoami", "kind": "function", "doc": "

\n", "signature": "(self) -> dict:", "funcdef": "def"}, "wmill.client.Windmill.user": {"fullname": "wmill.client.Windmill.user", "modulename": "wmill.client", "qualname": "Windmill.user", "kind": "variable", "doc": "

\n", "annotation": ": dict"}, "wmill.client.Windmill.state_path": {"fullname": "wmill.client.Windmill.state_path", "modulename": "wmill.client", "qualname": "Windmill.state_path", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.client.Windmill.state": {"fullname": "wmill.client.Windmill.state", "modulename": "wmill.client", "qualname": "Windmill.state", "kind": "variable", "doc": "

\n", "annotation": ": Any"}, "wmill.client.Windmill.set_shared_state_pickle": {"fullname": "wmill.client.Windmill.set_shared_state_pickle", "modulename": "wmill.client", "qualname": "Windmill.set_shared_state_pickle", "kind": "function", "doc": "

Set the state in the shared folder using pickle

\n", "signature": "(value: Any, path: str = 'state.pickle') -> None:", "funcdef": "def"}, "wmill.client.Windmill.get_shared_state_pickle": {"fullname": "wmill.client.Windmill.get_shared_state_pickle", "modulename": "wmill.client", "qualname": "Windmill.get_shared_state_pickle", "kind": "function", "doc": "

Get the state in the shared folder using pickle

\n", "signature": "(path: str = 'state.pickle') -> Any:", "funcdef": "def"}, "wmill.client.Windmill.set_shared_state": {"fullname": "wmill.client.Windmill.set_shared_state", "modulename": "wmill.client", "qualname": "Windmill.set_shared_state", "kind": "function", "doc": "

Set the state in the shared folder using pickle

\n", "signature": "(value: Any, path: str = 'state.json') -> None:", "funcdef": "def"}, "wmill.client.Windmill.get_shared_state": {"fullname": "wmill.client.Windmill.get_shared_state", "modulename": "wmill.client", "qualname": "Windmill.get_shared_state", "kind": "function", "doc": "

Get the state in the shared folder using pickle

\n", "signature": "(path: str = 'state.json') -> None:", "funcdef": "def"}, "wmill.client.Windmill.get_resume_urls": {"fullname": "wmill.client.Windmill.get_resume_urls", "modulename": "wmill.client", "qualname": "Windmill.get_resume_urls", "kind": "function", "doc": "

\n", "signature": "(self, approver: str = None) -> dict:", "funcdef": "def"}, "wmill.client.Windmill.request_interactive_slack_approval": {"fullname": "wmill.client.Windmill.request_interactive_slack_approval", "modulename": "wmill.client", "qualname": "Windmill.request_interactive_slack_approval", "kind": "function", "doc": "

Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.

\n\n

[Enterprise Edition Only] To include form fields in the Slack approval request, use the \"Advanced -> Suspend -> Form\" functionality.\nLearn more at: https://www.windmill.dev/docs/flows/flow_approval#form

\n\n
Parameters
\n\n
    \n
  • slack_resource_path: The path to the Slack resource in Windmill.
  • \n
  • channel_id: The Slack channel ID where the approval request will be sent.
  • \n
  • message: Optional custom message to include in the Slack approval request.
  • \n
  • approver: Optional user ID or name of the approver for the request.
  • \n
  • default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
  • \n
  • dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
  • \n
\n\n
Raises
\n\n
    \n
  • Exception: If the function is not called within a flow or flow preview.
  • \n
  • Exception: If the required flow job or flow step environment variables are not set.
  • \n
\n\n
Returns
\n\n
\n

None

\n
\n\n

Usage Example:

\n\n
\n
\n
\n

client.request_interactive_slack_approval(\n ... slack_resource_path=\"/u/alex/my_slack_resource\",\n ... channel_id=\"admins-slack-channel\",\n ... message=\"Please approve this request\",\n ... approver=\"approver123\",\n ... default_args_json={\"key1\": \"value1\", \"key2\": 42},\n ... dynamic_enums_json={\"foo\": [\"choice1\", \"choice2\"], \"bar\": [\"optionA\", \"optionB\"]},\n ... )

\n
\n
\n
\n\n

Notes:

\n\n
    \n
  • This function must be executed within a Windmill flow or flow preview.
  • \n
  • The function checks for required environment variables (WM_FLOW_JOB_ID, WM_FLOW_STEP_ID) to ensure it is run in the appropriate context.
  • \n
\n", "signature": "(\tself,\tslack_resource_path: str,\tchannel_id: str,\tmessage: str = None,\tapprover: str = None,\tdefault_args_json: dict = None,\tdynamic_enums_json: dict = None) -> None:", "funcdef": "def"}, "wmill.client.Windmill.username_to_email": {"fullname": "wmill.client.Windmill.username_to_email", "modulename": "wmill.client", "qualname": "Windmill.username_to_email", "kind": "function", "doc": "

Get email from workspace username\nThis method is particularly useful for apps that require the email address of the viewer.\nIndeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.

\n", "signature": "(self, username: str) -> str:", "funcdef": "def"}, "wmill.client.Windmill.send_teams_message": {"fullname": "wmill.client.Windmill.send_teams_message", "modulename": "wmill.client", "qualname": "Windmill.send_teams_message", "kind": "function", "doc": "

Send a message to a Microsoft Teams conversation with conversation_id, where success is used to style the message

\n", "signature": "(\tself,\tconversation_id: str,\ttext: str,\tsuccess: bool = True,\tcard_block: dict = None):", "funcdef": "def"}, "wmill.client.init_global_client": {"fullname": "wmill.client.init_global_client", "modulename": "wmill.client", "qualname": "init_global_client", "kind": "function", "doc": "

\n", "signature": "(f):", "funcdef": "def"}, "wmill.client.deprecate": {"fullname": "wmill.client.deprecate", "modulename": "wmill.client", "qualname": "deprecate", "kind": "function", "doc": "

\n", "signature": "(in_favor_of: str):", "funcdef": "def"}, "wmill.client.get_workspace": {"fullname": "wmill.client.get_workspace", "modulename": "wmill.client", "qualname": "get_workspace", "kind": "function", "doc": "

\n", "signature": "() -> str:", "funcdef": "def"}, "wmill.client.get_root_job_id": {"fullname": "wmill.client.get_root_job_id", "modulename": "wmill.client", "qualname": "get_root_job_id", "kind": "function", "doc": "

\n", "signature": "(job_id: str | None = None) -> str:", "funcdef": "def"}, "wmill.client.get_version": {"fullname": "wmill.client.get_version", "modulename": "wmill.client", "qualname": "get_version", "kind": "function", "doc": "

\n", "signature": "() -> str:", "funcdef": "def"}, "wmill.client.run_script_async": {"fullname": "wmill.client.run_script_async", "modulename": "wmill.client", "qualname": "run_script_async", "kind": "function", "doc": "

\n", "signature": "(\thash_or_path: str,\targs: Dict[str, Any] = None,\tscheduled_in_secs: int = None) -> str:", "funcdef": "def"}, "wmill.client.run_flow_async": {"fullname": "wmill.client.run_flow_async", "modulename": "wmill.client", "qualname": "run_flow_async", "kind": "function", "doc": "

\n", "signature": "(\tpath: str,\targs: Dict[str, Any] = None,\tscheduled_in_secs: int = None,\tdo_not_track_in_parent: bool = True) -> str:", "funcdef": "def"}, "wmill.client.run_script_sync": {"fullname": "wmill.client.run_script_sync", "modulename": "wmill.client", "qualname": "run_script_sync", "kind": "function", "doc": "

\n", "signature": "(\thash: str,\targs: Dict[str, Any] = None,\tverbose: bool = False,\tassert_result_is_not_none: bool = True,\tcleanup: bool = True,\ttimeout: datetime.timedelta = None) -> Any:", "funcdef": "def"}, "wmill.client.run_script_by_path_async": {"fullname": "wmill.client.run_script_by_path_async", "modulename": "wmill.client", "qualname": "run_script_by_path_async", "kind": "function", "doc": "

\n", "signature": "(\tpath: str,\targs: Dict[str, Any] = None,\tscheduled_in_secs: Optional[int] = None) -> str:", "funcdef": "def"}, "wmill.client.run_script_by_hash_async": {"fullname": "wmill.client.run_script_by_hash_async", "modulename": "wmill.client", "qualname": "run_script_by_hash_async", "kind": "function", "doc": "

\n", "signature": "(\thash_: str,\targs: Dict[str, Any] = None,\tscheduled_in_secs: Optional[int] = None) -> str:", "funcdef": "def"}, "wmill.client.run_script_by_path_sync": {"fullname": "wmill.client.run_script_by_path_sync", "modulename": "wmill.client", "qualname": "run_script_by_path_sync", "kind": "function", "doc": "

\n", "signature": "(\tpath: str,\targs: Dict[str, Any] = None,\tverbose: bool = False,\tassert_result_is_not_none: bool = True,\tcleanup: bool = True,\ttimeout: datetime.timedelta = None) -> Any:", "funcdef": "def"}, "wmill.client.get_id_token": {"fullname": "wmill.client.get_id_token", "modulename": "wmill.client", "qualname": "get_id_token", "kind": "function", "doc": "

Get a JWT token for the given audience for OIDC purposes to login into third parties like AWS, Vault, GCP, etc.

\n", "signature": "(audience: str) -> str:", "funcdef": "def"}, "wmill.client.get_job_status": {"fullname": "wmill.client.get_job_status", "modulename": "wmill.client", "qualname": "get_job_status", "kind": "function", "doc": "

\n", "signature": "(job_id: str) -> Literal['RUNNING', 'WAITING', 'COMPLETED']:", "funcdef": "def"}, "wmill.client.get_result": {"fullname": "wmill.client.get_result", "modulename": "wmill.client", "qualname": "get_result", "kind": "function", "doc": "

\n", "signature": "(job_id: str, assert_result_is_not_none=True) -> Dict[str, Any]:", "funcdef": "def"}, "wmill.client.duckdb_connection_settings": {"fullname": "wmill.client.duckdb_connection_settings", "modulename": "wmill.client", "qualname": "duckdb_connection_settings", "kind": "function", "doc": "

Convenient helpers that takes an S3 resource as input and returns the settings necessary to\ninitiate an S3 connection from DuckDB

\n", "signature": "(s3_resource_path: str = '') -> wmill.s3_types.DuckDbConnectionSettings:", "funcdef": "def"}, "wmill.client.polars_connection_settings": {"fullname": "wmill.client.polars_connection_settings", "modulename": "wmill.client", "qualname": "polars_connection_settings", "kind": "function", "doc": "

Convenient helpers that takes an S3 resource as input and returns the settings necessary to\ninitiate an S3 connection from Polars

\n", "signature": "(s3_resource_path: str = '') -> wmill.s3_types.PolarsConnectionSettings:", "funcdef": "def"}, "wmill.client.boto3_connection_settings": {"fullname": "wmill.client.boto3_connection_settings", "modulename": "wmill.client", "qualname": "boto3_connection_settings", "kind": "function", "doc": "

Convenient helpers that takes an S3 resource as input and returns the settings necessary to\ninitiate an S3 connection using boto3

\n", "signature": "(s3_resource_path: str = '') -> wmill.s3_types.Boto3ConnectionSettings:", "funcdef": "def"}, "wmill.client.load_s3_file": {"fullname": "wmill.client.load_s3_file", "modulename": "wmill.client", "qualname": "load_s3_file", "kind": "function", "doc": "

Load the entire content of a file stored in S3 as bytes

\n", "signature": "(\ts3object: wmill.s3_types.S3Object | str,\ts3_resource_path: str | None = None) -> bytes:", "funcdef": "def"}, "wmill.client.load_s3_file_reader": {"fullname": "wmill.client.load_s3_file_reader", "modulename": "wmill.client", "qualname": "load_s3_file_reader", "kind": "function", "doc": "

Load the content of a file stored in S3

\n", "signature": "(\ts3object: wmill.s3_types.S3Object | str,\ts3_resource_path: str | None = None) -> _io.BufferedReader:", "funcdef": "def"}, "wmill.client.write_s3_file": {"fullname": "wmill.client.write_s3_file", "modulename": "wmill.client", "qualname": "write_s3_file", "kind": "function", "doc": "

Upload a file to S3

\n\n

Content type will be automatically guessed from path extension if left empty

\n\n

See MDN for content_disposition: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition\nand content_type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type

\n", "signature": "(\ts3object: wmill.s3_types.S3Object | str | None,\tfile_content: _io.BufferedReader | bytes,\ts3_resource_path: str | None = None,\tcontent_type: str | None = None,\tcontent_disposition: str | None = None) -> wmill.s3_types.S3Object:", "funcdef": "def"}, "wmill.client.sign_s3_objects": {"fullname": "wmill.client.sign_s3_objects", "modulename": "wmill.client", "qualname": "sign_s3_objects", "kind": "function", "doc": "

Sign S3 objects to be used by anonymous users in public apps\nReturns a list of signed s3 tokens

\n", "signature": "(\ts3_objects: list[wmill.s3_types.S3Object | str]) -> list[wmill.s3_types.S3Object]:", "funcdef": "def"}, "wmill.client.sign_s3_object": {"fullname": "wmill.client.sign_s3_object", "modulename": "wmill.client", "qualname": "sign_s3_object", "kind": "function", "doc": "

Sign S3 object to be used by anonymous users in public apps\nReturns a signed s3 object

\n", "signature": "(s3_object: wmill.s3_types.S3Object | str) -> wmill.s3_types.S3Object:", "funcdef": "def"}, "wmill.client.whoami": {"fullname": "wmill.client.whoami", "modulename": "wmill.client", "qualname": "whoami", "kind": "function", "doc": "

Returns the current user

\n", "signature": "() -> dict:", "funcdef": "def"}, "wmill.client.get_state": {"fullname": "wmill.client.get_state", "modulename": "wmill.client", "qualname": "get_state", "kind": "function", "doc": "

Get the state

\n", "signature": "() -> Any:", "funcdef": "def"}, "wmill.client.get_resource": {"fullname": "wmill.client.get_resource", "modulename": "wmill.client", "qualname": "get_resource", "kind": "function", "doc": "

Get resource from Windmill

\n", "signature": "(path: str, none_if_undefined: bool = False) -> dict | None:", "funcdef": "def"}, "wmill.client.set_resource": {"fullname": "wmill.client.set_resource", "modulename": "wmill.client", "qualname": "set_resource", "kind": "function", "doc": "

Set the resource at a given path as a string, creating it if it does not exist

\n", "signature": "(path: str, value: Any, resource_type: str = 'any') -> None:", "funcdef": "def"}, "wmill.client.set_state": {"fullname": "wmill.client.set_state", "modulename": "wmill.client", "qualname": "set_state", "kind": "function", "doc": "

Set the state

\n", "signature": "(value: Any) -> None:", "funcdef": "def"}, "wmill.client.set_progress": {"fullname": "wmill.client.set_progress", "modulename": "wmill.client", "qualname": "set_progress", "kind": "function", "doc": "

Set the progress

\n", "signature": "(value: int, job_id: Optional[str] = None) -> None:", "funcdef": "def"}, "wmill.client.get_progress": {"fullname": "wmill.client.get_progress", "modulename": "wmill.client", "qualname": "get_progress", "kind": "function", "doc": "

Get the progress

\n", "signature": "(job_id: Optional[str] = None) -> Any:", "funcdef": "def"}, "wmill.client.set_shared_state_pickle": {"fullname": "wmill.client.set_shared_state_pickle", "modulename": "wmill.client", "qualname": "set_shared_state_pickle", "kind": "function", "doc": "

Set the state in the shared folder using pickle

\n", "signature": "(value: Any, path='state.pickle') -> None:", "funcdef": "def"}, "wmill.client.get_shared_state_pickle": {"fullname": "wmill.client.get_shared_state_pickle", "modulename": "wmill.client", "qualname": "get_shared_state_pickle", "kind": "function", "doc": "

Get the state in the shared folder using pickle

\n", "signature": "(path='state.pickle') -> Any:", "funcdef": "def"}, "wmill.client.set_shared_state": {"fullname": "wmill.client.set_shared_state", "modulename": "wmill.client", "qualname": "set_shared_state", "kind": "function", "doc": "

Set the state in the shared folder using pickle

\n", "signature": "(value: Any, path='state.json') -> None:", "funcdef": "def"}, "wmill.client.get_shared_state": {"fullname": "wmill.client.get_shared_state", "modulename": "wmill.client", "qualname": "get_shared_state", "kind": "function", "doc": "

Get the state in the shared folder using pickle

\n", "signature": "(path='state.json') -> None:", "funcdef": "def"}, "wmill.client.get_variable": {"fullname": "wmill.client.get_variable", "modulename": "wmill.client", "qualname": "get_variable", "kind": "function", "doc": "

Returns the variable at a given path as a string

\n", "signature": "(path: str) -> str:", "funcdef": "def"}, "wmill.client.set_variable": {"fullname": "wmill.client.set_variable", "modulename": "wmill.client", "qualname": "set_variable", "kind": "function", "doc": "

Set the variable at a given path as a string, creating it if it does not exist

\n", "signature": "(path: str, value: str, is_secret: bool = False) -> None:", "funcdef": "def"}, "wmill.client.get_flow_user_state": {"fullname": "wmill.client.get_flow_user_state", "modulename": "wmill.client", "qualname": "get_flow_user_state", "kind": "function", "doc": "

Get the user state of a flow at a given key

\n", "signature": "(key: str) -> Any:", "funcdef": "def"}, "wmill.client.set_flow_user_state": {"fullname": "wmill.client.set_flow_user_state", "modulename": "wmill.client", "qualname": "set_flow_user_state", "kind": "function", "doc": "

Set the user state of a flow at a given key

\n", "signature": "(key: str, value: Any) -> None:", "funcdef": "def"}, "wmill.client.get_state_path": {"fullname": "wmill.client.get_state_path", "modulename": "wmill.client", "qualname": "get_state_path", "kind": "function", "doc": "

\n", "signature": "() -> str:", "funcdef": "def"}, "wmill.client.get_resume_urls": {"fullname": "wmill.client.get_resume_urls", "modulename": "wmill.client", "qualname": "get_resume_urls", "kind": "function", "doc": "

\n", "signature": "(approver: str = None) -> dict:", "funcdef": "def"}, "wmill.client.request_interactive_slack_approval": {"fullname": "wmill.client.request_interactive_slack_approval", "modulename": "wmill.client", "qualname": "request_interactive_slack_approval", "kind": "function", "doc": "

\n", "signature": "(\tslack_resource_path: str,\tchannel_id: str,\tmessage: str = None,\tapprover: str = None,\tdefault_args_json: dict = None,\tdynamic_enums_json: dict = None) -> None:", "funcdef": "def"}, "wmill.client.send_teams_message": {"fullname": "wmill.client.send_teams_message", "modulename": "wmill.client", "qualname": "send_teams_message", "kind": "function", "doc": "

\n", "signature": "(\tconversation_id: str,\ttext: str,\tsuccess: bool,\tcard_block: dict = None):", "funcdef": "def"}, "wmill.client.cancel_running": {"fullname": "wmill.client.cancel_running", "modulename": "wmill.client", "qualname": "cancel_running", "kind": "function", "doc": "

Cancel currently running executions of the same script.

\n", "signature": "() -> dict:", "funcdef": "def"}, "wmill.client.run_script": {"fullname": "wmill.client.run_script", "modulename": "wmill.client", "qualname": "run_script", "kind": "function", "doc": "

Run script synchronously and return its result.

\n\n

Deprecated since version Use run_script_by_path or run_script_by_hash instead..

\n", "signature": "(\tpath: str = None,\thash_: str = None,\targs: dict = None,\ttimeout: datetime.timedelta | int | float = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = True) -> Any:", "funcdef": "def"}, "wmill.client.run_script_by_path": {"fullname": "wmill.client.run_script_by_path", "modulename": "wmill.client", "qualname": "run_script_by_path", "kind": "function", "doc": "

Run script by path synchronously and return its result.

\n", "signature": "(\tpath: str,\targs: dict = None,\ttimeout: datetime.timedelta | int | float = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = True) -> Any:", "funcdef": "def"}, "wmill.client.run_script_by_hash": {"fullname": "wmill.client.run_script_by_hash", "modulename": "wmill.client", "qualname": "run_script_by_hash", "kind": "function", "doc": "

Run script by hash synchronously and return its result.

\n", "signature": "(\thash_: str,\targs: dict = None,\ttimeout: datetime.timedelta | int | float = None,\tverbose: bool = False,\tcleanup: bool = True,\tassert_result_is_not_none: bool = True) -> Any:", "funcdef": "def"}, "wmill.client.username_to_email": {"fullname": "wmill.client.username_to_email", "modulename": "wmill.client", "qualname": "username_to_email", "kind": "function", "doc": "

Get email from workspace username\nThis method is particularly useful for apps that require the email address of the viewer.\nIndeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.

\n", "signature": "(username: str) -> str:", "funcdef": "def"}, "wmill.client.task": {"fullname": "wmill.client.task", "modulename": "wmill.client", "qualname": "task", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, "wmill.client.parse_resource_syntax": {"fullname": "wmill.client.parse_resource_syntax", "modulename": "wmill.client", "qualname": "parse_resource_syntax", "kind": "function", "doc": "

Parse resource syntax from string.

\n", "signature": "(s: str) -> Optional[str]:", "funcdef": "def"}, "wmill.client.parse_s3_object": {"fullname": "wmill.client.parse_s3_object", "modulename": "wmill.client", "qualname": "parse_s3_object", "kind": "function", "doc": "

Parse S3 object from string or S3Object format.

\n", "signature": "(s3_object: wmill.s3_types.S3Object | str) -> wmill.s3_types.S3Object:", "funcdef": "def"}, "wmill.client.parse_variable_syntax": {"fullname": "wmill.client.parse_variable_syntax", "modulename": "wmill.client", "qualname": "parse_variable_syntax", "kind": "function", "doc": "

Parse variable syntax from string.

\n", "signature": "(s: str) -> Optional[str]:", "funcdef": "def"}, "wmill.client.append_to_result_stream": {"fullname": "wmill.client.append_to_result_stream", "modulename": "wmill.client", "qualname": "append_to_result_stream", "kind": "function", "doc": "

Append a text to the result stream.

\n\n

Args:\n text: text to append to the result stream

\n", "signature": "(text: str) -> None:", "funcdef": "def"}, "wmill.client.stream_result": {"fullname": "wmill.client.stream_result", "modulename": "wmill.client", "qualname": "stream_result", "kind": "function", "doc": "

Stream to the result stream.

\n\n

Args:\n stream: stream to stream to the result stream

\n", "signature": "(stream) -> None:", "funcdef": "def"}, "wmill.s3_reader": {"fullname": "wmill.s3_reader", "modulename": "wmill.s3_reader", "kind": "module", "doc": "

\n"}, "wmill.s3_reader.S3BufferedReader": {"fullname": "wmill.s3_reader.S3BufferedReader", "modulename": "wmill.s3_reader", "qualname": "S3BufferedReader", "kind": "class", "doc": "

Create a new buffered reader using the given readable raw IO object.

\n", "bases": "_io.BufferedReader"}, "wmill.s3_reader.S3BufferedReader.__init__": {"fullname": "wmill.s3_reader.S3BufferedReader.__init__", "modulename": "wmill.s3_reader", "qualname": "S3BufferedReader.__init__", "kind": "function", "doc": "

\n", "signature": "(\tworkspace: str,\twindmill_client: httpx.Client,\tfile_key: str,\ts3_resource_path: Optional[str],\tstorage: Optional[str])"}, "wmill.s3_reader.S3BufferedReader.peek": {"fullname": "wmill.s3_reader.S3BufferedReader.peek", "modulename": "wmill.s3_reader", "qualname": "S3BufferedReader.peek", "kind": "function", "doc": "

\n", "signature": "(self, size=0):", "funcdef": "def"}, "wmill.s3_reader.S3BufferedReader.read": {"fullname": "wmill.s3_reader.S3BufferedReader.read", "modulename": "wmill.s3_reader", "qualname": "S3BufferedReader.read", "kind": "function", "doc": "

Read and return up to n bytes.

\n\n

If the argument is omitted, None, or negative, reads and\nreturns all data until EOF.

\n\n

If the argument is positive, and the underlying raw stream is\nnot 'interactive', multiple raw reads may be issued to satisfy\nthe byte count (unless EOF is reached first). But for\ninteractive raw streams (as well as sockets and pipes), at most\none raw read will be issued, and a short result does not imply\nthat EOF is imminent.

\n\n

Returns an empty bytes object on EOF.

\n\n

Returns None if the underlying raw stream was open in non-blocking\nmode and no data is available at the moment.

\n", "signature": "(self, size=-1):", "funcdef": "def"}, "wmill.s3_reader.S3BufferedReader.read1": {"fullname": "wmill.s3_reader.S3BufferedReader.read1", "modulename": "wmill.s3_reader", "qualname": "S3BufferedReader.read1", "kind": "function", "doc": "

Read and return up to n bytes, with at most one read() call\nto the underlying raw stream. A short result does not imply\nthat EOF is imminent.

\n\n

Returns an empty bytes object on EOF.

\n", "signature": "(self, size=-1):", "funcdef": "def"}, "wmill.s3_reader.bytes_generator": {"fullname": "wmill.s3_reader.bytes_generator", "modulename": "wmill.s3_reader", "qualname": "bytes_generator", "kind": "function", "doc": "

\n", "signature": "(buffered_reader: Union[_io.BufferedReader, _io.BytesIO]):", "funcdef": "def"}, "wmill.s3_types": {"fullname": "wmill.s3_types", "modulename": "wmill.s3_types", "kind": "module", "doc": "

\n"}, "wmill.s3_types.S3Object": {"fullname": "wmill.s3_types.S3Object", "modulename": "wmill.s3_types", "qualname": "S3Object", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.S3Object.s3": {"fullname": "wmill.s3_types.S3Object.s3", "modulename": "wmill.s3_types", "qualname": "S3Object.s3", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.S3Object.storage": {"fullname": "wmill.s3_types.S3Object.storage", "modulename": "wmill.s3_types", "qualname": "S3Object.storage", "kind": "variable", "doc": "

\n", "annotation": ": Optional[str]"}, "wmill.s3_types.S3Object.presigned": {"fullname": "wmill.s3_types.S3Object.presigned", "modulename": "wmill.s3_types", "qualname": "S3Object.presigned", "kind": "variable", "doc": "

\n", "annotation": ": Optional[str]"}, "wmill.s3_types.S3FsClientKwargs": {"fullname": "wmill.s3_types.S3FsClientKwargs", "modulename": "wmill.s3_types", "qualname": "S3FsClientKwargs", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.S3FsClientKwargs.region_name": {"fullname": "wmill.s3_types.S3FsClientKwargs.region_name", "modulename": "wmill.s3_types", "qualname": "S3FsClientKwargs.region_name", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.S3FsArgs": {"fullname": "wmill.s3_types.S3FsArgs", "modulename": "wmill.s3_types", "qualname": "S3FsArgs", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.S3FsArgs.endpoint_url": {"fullname": "wmill.s3_types.S3FsArgs.endpoint_url", "modulename": "wmill.s3_types", "qualname": "S3FsArgs.endpoint_url", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.S3FsArgs.key": {"fullname": "wmill.s3_types.S3FsArgs.key", "modulename": "wmill.s3_types", "qualname": "S3FsArgs.key", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.S3FsArgs.secret": {"fullname": "wmill.s3_types.S3FsArgs.secret", "modulename": "wmill.s3_types", "qualname": "S3FsArgs.secret", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.S3FsArgs.use_ssl": {"fullname": "wmill.s3_types.S3FsArgs.use_ssl", "modulename": "wmill.s3_types", "qualname": "S3FsArgs.use_ssl", "kind": "variable", "doc": "

\n", "annotation": ": bool"}, "wmill.s3_types.S3FsArgs.cache_regions": {"fullname": "wmill.s3_types.S3FsArgs.cache_regions", "modulename": "wmill.s3_types", "qualname": "S3FsArgs.cache_regions", "kind": "variable", "doc": "

\n", "annotation": ": bool"}, "wmill.s3_types.S3FsArgs.client_kwargs": {"fullname": "wmill.s3_types.S3FsArgs.client_kwargs", "modulename": "wmill.s3_types", "qualname": "S3FsArgs.client_kwargs", "kind": "variable", "doc": "

\n", "annotation": ": wmill.s3_types.S3FsClientKwargs"}, "wmill.s3_types.StorageOptions": {"fullname": "wmill.s3_types.StorageOptions", "modulename": "wmill.s3_types", "qualname": "StorageOptions", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"fullname": "wmill.s3_types.StorageOptions.aws_endpoint_url", "modulename": "wmill.s3_types", "qualname": "StorageOptions.aws_endpoint_url", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"fullname": "wmill.s3_types.StorageOptions.aws_access_key_id", "modulename": "wmill.s3_types", "qualname": "StorageOptions.aws_access_key_id", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"fullname": "wmill.s3_types.StorageOptions.aws_secret_access_key", "modulename": "wmill.s3_types", "qualname": "StorageOptions.aws_secret_access_key", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.StorageOptions.aws_region": {"fullname": "wmill.s3_types.StorageOptions.aws_region", "modulename": "wmill.s3_types", "qualname": "StorageOptions.aws_region", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.StorageOptions.aws_allow_http": {"fullname": "wmill.s3_types.StorageOptions.aws_allow_http", "modulename": "wmill.s3_types", "qualname": "StorageOptions.aws_allow_http", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.PolarsConnectionSettings": {"fullname": "wmill.s3_types.PolarsConnectionSettings", "modulename": "wmill.s3_types", "qualname": "PolarsConnectionSettings", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"fullname": "wmill.s3_types.PolarsConnectionSettings.s3fs_args", "modulename": "wmill.s3_types", "qualname": "PolarsConnectionSettings.s3fs_args", "kind": "variable", "doc": "

\n", "annotation": ": wmill.s3_types.S3FsArgs"}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"fullname": "wmill.s3_types.PolarsConnectionSettings.storage_options", "modulename": "wmill.s3_types", "qualname": "PolarsConnectionSettings.storage_options", "kind": "variable", "doc": "

\n", "annotation": ": wmill.s3_types.StorageOptions"}, "wmill.s3_types.Boto3ConnectionSettings": {"fullname": "wmill.s3_types.Boto3ConnectionSettings", "modulename": "wmill.s3_types", "qualname": "Boto3ConnectionSettings", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"fullname": "wmill.s3_types.Boto3ConnectionSettings.endpoint_url", "modulename": "wmill.s3_types", "qualname": "Boto3ConnectionSettings.endpoint_url", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"fullname": "wmill.s3_types.Boto3ConnectionSettings.region_name", "modulename": "wmill.s3_types", "qualname": "Boto3ConnectionSettings.region_name", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"fullname": "wmill.s3_types.Boto3ConnectionSettings.use_ssl", "modulename": "wmill.s3_types", "qualname": "Boto3ConnectionSettings.use_ssl", "kind": "variable", "doc": "

\n", "annotation": ": bool"}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"fullname": "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id", "modulename": "wmill.s3_types", "qualname": "Boto3ConnectionSettings.aws_access_key_id", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"fullname": "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key", "modulename": "wmill.s3_types", "qualname": "Boto3ConnectionSettings.aws_secret_access_key", "kind": "variable", "doc": "

\n", "annotation": ": str"}, "wmill.s3_types.DuckDbConnectionSettings": {"fullname": "wmill.s3_types.DuckDbConnectionSettings", "modulename": "wmill.s3_types", "qualname": "DuckDbConnectionSettings", "kind": "class", "doc": "

\n", "bases": "builtins.dict"}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"fullname": "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str", "modulename": "wmill.s3_types", "qualname": "DuckDbConnectionSettings.connection_settings_str", "kind": "variable", "doc": "

\n", "annotation": ": str"}}, "docInfo": {"wmill": {"qualname": 0, "fullname": 1, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.logger": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 8, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.JobStatus": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 12, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.__init__": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 45, "bases": 0, "doc": 3}, "wmill.client.Windmill.base_url": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.token": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.headers": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.verify": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.client": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.workspace": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.path": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.mocked_api": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_mocked_api": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 20, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_client": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 3}, "wmill.client.Windmill.get": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 43, "bases": 0, "doc": 3}, "wmill.client.Windmill.post": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 43, "bases": 0, "doc": 3}, "wmill.client.Windmill.create_token": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 42, "bases": 0, "doc": 3}, "wmill.client.Windmill.run_script_async": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 90, "bases": 0, "doc": 33}, "wmill.client.Windmill.run_script_by_path_async": {"qualname": 6, "fullname": 8, "annotation": 0, "default_value": 0, "signature": 60, "bases": 0, "doc": 14}, "wmill.client.Windmill.run_script_by_hash_async": {"qualname": 6, "fullname": 8, "annotation": 0, "default_value": 0, "signature": 65, "bases": 0, "doc": 14}, "wmill.client.Windmill.run_flow_async": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 86, "bases": 0, "doc": 12}, "wmill.client.Windmill.run_script": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 169, "bases": 0, "doc": 29}, "wmill.client.Windmill.run_script_by_path": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 143, "bases": 0, "doc": 12}, "wmill.client.Windmill.run_script_by_hash": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 144, "bases": 0, "doc": 12}, "wmill.client.Windmill.wait_job": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 118, "bases": 0, "doc": 3}, "wmill.client.Windmill.cancel_running": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 11}, "wmill.client.Windmill.get_job": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 25, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_root_job_id": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 38, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_id_token": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_job_status": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 55, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_result": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 46, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_variable": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 3}, "wmill.client.Windmill.set_variable": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 52, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_resource": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 49, "bases": 0, "doc": 3}, "wmill.client.Windmill.set_resource": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 42, "bases": 0, "doc": 3}, "wmill.client.Windmill.set_state": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 21, "bases": 0, "doc": 3}, "wmill.client.Windmill.set_progress": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 45, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_progress": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 38, "bases": 0, "doc": 3}, "wmill.client.Windmill.set_flow_user_state": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 34, "bases": 0, "doc": 13}, "wmill.client.Windmill.get_flow_user_state": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 13}, "wmill.client.Windmill.version": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.get_duckdb_connection_settings": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 55, "bases": 0, "doc": 23}, "wmill.client.Windmill.get_polars_connection_settings": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 49, "bases": 0, "doc": 23}, "wmill.client.Windmill.get_boto3_connection_settings": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 49, "bases": 0, "doc": 23}, "wmill.client.Windmill.load_s3_file": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 62, "bases": 0, "doc": 52}, "wmill.client.Windmill.load_s3_file_reader": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 68, "bases": 0, "doc": 51}, "wmill.client.Windmill.write_s3_file": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 153, "bases": 0, "doc": 77}, "wmill.client.Windmill.sign_s3_objects": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 67, "bases": 0, "doc": 3}, "wmill.client.Windmill.sign_s3_object": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 55, "bases": 0, "doc": 3}, "wmill.client.Windmill.whoami": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 3}, "wmill.client.Windmill.user": {"qualname": 2, "fullname": 4, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.state_path": {"qualname": 3, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.state": {"qualname": 2, "fullname": 4, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.client.Windmill.set_shared_state_pickle": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 41, "bases": 0, "doc": 11}, "wmill.client.Windmill.get_shared_state_pickle": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 31, "bases": 0, "doc": 11}, "wmill.client.Windmill.set_shared_state": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 41, "bases": 0, "doc": 11}, "wmill.client.Windmill.get_shared_state": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 31, "bases": 0, "doc": 11}, "wmill.client.Windmill.get_resume_urls": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 31, "bases": 0, "doc": 3}, "wmill.client.Windmill.request_interactive_slack_approval": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 116, "bases": 0, "doc": 344}, "wmill.client.Windmill.username_to_email": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 52}, "wmill.client.Windmill.send_teams_message": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 72, "bases": 0, "doc": 21}, "wmill.client.init_global_client": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 11, "bases": 0, "doc": 3}, "wmill.client.deprecate": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 18, "bases": 0, "doc": 3}, "wmill.client.get_workspace": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 10, "bases": 0, "doc": 3}, "wmill.client.get_root_job_id": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 33, "bases": 0, "doc": 3}, "wmill.client.get_version": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 10, "bases": 0, "doc": 3}, "wmill.client.run_script_async": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 72, "bases": 0, "doc": 3}, "wmill.client.run_flow_async": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 92, "bases": 0, "doc": 3}, "wmill.client.run_script_sync": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 131, "bases": 0, "doc": 3}, "wmill.client.run_script_by_path_async": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 76, "bases": 0, "doc": 3}, "wmill.client.run_script_by_hash_async": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 77, "bases": 0, "doc": 3}, "wmill.client.run_script_by_path_sync": {"qualname": 5, "fullname": 7, "annotation": 0, "default_value": 0, "signature": 131, "bases": 0, "doc": 3}, "wmill.client.get_id_token": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 24}, "wmill.client.get_job_status": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 50, "bases": 0, "doc": 3}, "wmill.client.get_result": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 46, "bases": 0, "doc": 3}, "wmill.client.duckdb_connection_settings": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 42, "bases": 0, "doc": 23}, "wmill.client.polars_connection_settings": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 42, "bases": 0, "doc": 23}, "wmill.client.boto3_connection_settings": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 42, "bases": 0, "doc": 23}, "wmill.client.load_s3_file": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 63, "bases": 0, "doc": 14}, "wmill.client.load_s3_file_reader": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 69, "bases": 0, "doc": 11}, "wmill.client.write_s3_file": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 154, "bases": 0, "doc": 45}, "wmill.client.sign_s3_objects": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 61, "bases": 0, "doc": 21}, "wmill.client.sign_s3_object": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 48, "bases": 0, "doc": 19}, "wmill.client.whoami": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 10, "bases": 0, "doc": 6}, "wmill.client.get_state": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 10, "bases": 0, "doc": 5}, "wmill.client.get_resource": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 44, "bases": 0, "doc": 6}, "wmill.client.set_resource": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 51, "bases": 0, "doc": 19}, "wmill.client.set_state": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 5}, "wmill.client.set_progress": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 43, "bases": 0, "doc": 5}, "wmill.client.get_progress": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 33, "bases": 0, "doc": 5}, "wmill.client.set_shared_state_pickle": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 34, "bases": 0, "doc": 11}, "wmill.client.get_shared_state_pickle": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 11}, "wmill.client.set_shared_state": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 34, "bases": 0, "doc": 11}, "wmill.client.get_shared_state": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 11}, "wmill.client.get_variable": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 12}, "wmill.client.set_variable": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 47, "bases": 0, "doc": 19}, "wmill.client.get_flow_user_state": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 13}, "wmill.client.set_flow_user_state": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 29, "bases": 0, "doc": 13}, "wmill.client.get_state_path": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 10, "bases": 0, "doc": 3}, "wmill.client.get_resume_urls": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 26, "bases": 0, "doc": 3}, "wmill.client.request_interactive_slack_approval": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 110, "bases": 0, "doc": 3}, "wmill.client.send_teams_message": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 59, "bases": 0, "doc": 3}, "wmill.client.cancel_running": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 10, "bases": 0, "doc": 11}, "wmill.client.run_script": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 157, "bases": 0, "doc": 29}, "wmill.client.run_script_by_path": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 131, "bases": 0, "doc": 12}, "wmill.client.run_script_by_hash": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 132, "bases": 0, "doc": 12}, "wmill.client.username_to_email": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 52}, "wmill.client.task": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 20, "bases": 0, "doc": 3}, "wmill.client.parse_resource_syntax": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 25, "bases": 0, "doc": 8}, "wmill.client.parse_s3_object": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 48, "bases": 0, "doc": 11}, "wmill.client.parse_variable_syntax": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 25, "bases": 0, "doc": 8}, "wmill.client.append_to_result_stream": {"qualname": 4, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 19, "bases": 0, "doc": 21}, "wmill.client.stream_result": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 19}, "wmill.s3_reader": {"qualname": 0, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_reader.S3BufferedReader": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 15}, "wmill.s3_reader.S3BufferedReader.__init__": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 80, "bases": 0, "doc": 3}, "wmill.s3_reader.S3BufferedReader.peek": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 21, "bases": 0, "doc": 3}, "wmill.s3_reader.S3BufferedReader.read": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 21, "bases": 0, "doc": 124}, "wmill.s3_reader.S3BufferedReader.read1": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 21, "bases": 0, "doc": 41}, "wmill.s3_reader.bytes_generator": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 41, "bases": 0, "doc": 3}, "wmill.s3_types": {"qualname": 0, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3Object": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.S3Object.s3": {"qualname": 2, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3Object.storage": {"qualname": 2, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3Object.presigned": {"qualname": 2, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsClientKwargs": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.S3FsClientKwargs.region_name": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsArgs": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.S3FsArgs.endpoint_url": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsArgs.key": {"qualname": 2, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsArgs.secret": {"qualname": 2, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsArgs.use_ssl": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsArgs.cache_regions": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.S3FsArgs.client_kwargs": {"qualname": 3, "fullname": 6, "annotation": 5, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.StorageOptions": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"qualname": 4, "fullname": 7, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"qualname": 5, "fullname": 8, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"qualname": 5, "fullname": 8, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.StorageOptions.aws_region": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.StorageOptions.aws_allow_http": {"qualname": 4, "fullname": 7, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.PolarsConnectionSettings": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"qualname": 3, "fullname": 6, "annotation": 5, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"qualname": 3, "fullname": 6, "annotation": 5, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.Boto3ConnectionSettings": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"qualname": 3, "fullname": 6, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"qualname": 5, "fullname": 8, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"qualname": 5, "fullname": 8, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "wmill.s3_types.DuckDbConnectionSettings": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 3}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"qualname": 4, "fullname": 7, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}}, "length": 153, "save": true}, "index": {"qualname": {"root": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 2, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}}, "df": 4}}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 6, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}}}}, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill": {"tf": 1}, "wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.Windmill.base_url": {"tf": 1}, "wmill.client.Windmill.token": {"tf": 1}, "wmill.client.Windmill.headers": {"tf": 1}, "wmill.client.Windmill.verify": {"tf": 1}, "wmill.client.Windmill.client": {"tf": 1}, "wmill.client.Windmill.workspace": {"tf": 1}, "wmill.client.Windmill.path": {"tf": 1}, "wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.version": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.Windmill.user": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 59}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.workspace": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}}, "df": 2}}}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.wait_job": {"tf": 1}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 2}}}}, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {"wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.whoami": {"tf": 1}}, "df": 2}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.init_global_client": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 3}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}}}}}}, "d": {"docs": {"wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}}, "df": 6}}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.base_url": {"tf": 1}}, "df": 1}}}, "y": {"docs": {"wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 9, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"3": {"docs": {"wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 6}}}}}}}}}}}}}}}}}}}, "docs": {}, "df": 0}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.base_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}}, "df": 4, "s": {"docs": {"wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}}, "df": 2}}}, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}}, "df": 2, "r": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.user": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 5, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}}, "df": 3, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.token": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}}, "df": 4}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.task": {"tf": 1}}, "df": 1}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.headers": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 4}}}, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {"wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}}, "df": 1}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.verify": {"tf": 1}}, "df": 1}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.version": {"tf": 1}, "wmill.client.get_version": {"tf": 1}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 5}}}}}}}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.client": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.init_global_client": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 4}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 7}}}}}}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.path": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}}, "df": 8}}, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 3}}}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.post": {"tf": 1}}, "df": 1}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 3}}}}}}}}}}}}}}}}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}}, "df": 4}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_types.S3Object.presigned": {"tf": 1}}, "df": 1}}}}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}}, "df": 4}}}}}, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "k": {"docs": {"wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}}, "df": 1}}}}, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}}}}, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {"wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}}, "df": 2}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.append_to_result_stream": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}}, "df": 8}}}}, "w": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 7}}, "c": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 4}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}}, "df": 1}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_version": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}}, "df": 33}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.init_global_client": {"tf": 1}}, "df": 1}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 16, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}}}, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}}, "df": 2}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}}, "df": 4}}, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}}, "df": 2}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}}, "df": 5}}}}}}, "a": {"docs": {}, "df": 0, "d": {"1": {"docs": {"wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 1}, "docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}}, "df": 2}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}}, "df": 3, "s": {"docs": {"wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}}, "df": 1}}}}}}}, "s": {"3": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}}, "df": 12, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 5}}}}}}}}}}}}}}, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}, "wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}}, "df": 4}}}}}}, "f": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}}, "df": 1, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}}, "df": 2}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 7}}}}}}}, "docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 14}}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 2}}, "e": {"docs": {"wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}}, "df": 18}}}, "r": {"docs": {"wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}}, "df": 2}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 2, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}}, "df": 6}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 14, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 7}}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 3}}}}}, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 4}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}}, "df": 8}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}}, "df": 2}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 2}}}}}, "s": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}}, "df": 2}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 6}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 6}}}}, "d": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.deprecate": {"tf": 1}}, "df": 1}}}}}}}}}, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}}, "df": 3, "s": {"docs": {"wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}}, "df": 2}}}}}}, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}}, "df": 3}}}}}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}}, "df": 2}}}}, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "y": {"docs": {"wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 5}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 1}}}}}}}}, "fullname": {"root": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 2, "w": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill": {"tf": 1}, "wmill.client": {"tf": 1}, "wmill.client.logger": {"tf": 1}, "wmill.client.JobStatus": {"tf": 1}, "wmill.client.Windmill": {"tf": 1}, "wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.Windmill.base_url": {"tf": 1}, "wmill.client.Windmill.token": {"tf": 1}, "wmill.client.Windmill.headers": {"tf": 1}, "wmill.client.Windmill.verify": {"tf": 1}, "wmill.client.Windmill.client": {"tf": 1}, "wmill.client.Windmill.workspace": {"tf": 1}, "wmill.client.Windmill.path": {"tf": 1}, "wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.version": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.Windmill.user": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.init_global_client": {"tf": 1}, "wmill.client.deprecate": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_version": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.whoami": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.client.task": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}, "wmill.s3_reader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}, "wmill.s3_reader.bytes_generator": {"tf": 1}, "wmill.s3_types": {"tf": 1}, "wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}, "wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 153}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill": {"tf": 1}, "wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.Windmill.base_url": {"tf": 1}, "wmill.client.Windmill.token": {"tf": 1}, "wmill.client.Windmill.headers": {"tf": 1}, "wmill.client.Windmill.verify": {"tf": 1}, "wmill.client.Windmill.client": {"tf": 1}, "wmill.client.Windmill.workspace": {"tf": 1}, "wmill.client.Windmill.path": {"tf": 1}, "wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.version": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.Windmill.user": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 59}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.workspace": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}}, "df": 2}}}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.wait_job": {"tf": 1}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 2}}}}, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {"wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.whoami": {"tf": 1}}, "df": 2}}}}}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client": {"tf": 1}, "wmill.client.logger": {"tf": 1}, "wmill.client.JobStatus": {"tf": 1}, "wmill.client.Windmill": {"tf": 1}, "wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.Windmill.base_url": {"tf": 1}, "wmill.client.Windmill.token": {"tf": 1}, "wmill.client.Windmill.headers": {"tf": 1}, "wmill.client.Windmill.verify": {"tf": 1}, "wmill.client.Windmill.client": {"tf": 1.4142135623730951}, "wmill.client.Windmill.workspace": {"tf": 1}, "wmill.client.Windmill.path": {"tf": 1}, "wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.version": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.Windmill.user": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.init_global_client": {"tf": 1.4142135623730951}, "wmill.client.deprecate": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_version": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.whoami": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.client.task": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 115}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 7}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}}, "df": 4}}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 6, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.init_global_client": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 3}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}}}}}}, "d": {"docs": {"wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}}, "df": 6}}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.base_url": {"tf": 1}}, "df": 1}}}, "y": {"docs": {"wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 9, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"3": {"docs": {"wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 6}}}}}}}}}}}}}}}}}}}, "docs": {}, "df": 0}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.base_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}}, "df": 4, "s": {"docs": {"wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}}, "df": 2}}}, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}}, "df": 2, "r": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.user": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 5, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}}, "df": 3, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.token": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}}, "df": 4}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.task": {"tf": 1}}, "df": 1}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types": {"tf": 1}, "wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}, "wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 31}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.headers": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 4}}}, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {"wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}}, "df": 1}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.verify": {"tf": 1}}, "df": 1}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.version": {"tf": 1}, "wmill.client.get_version": {"tf": 1}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 5}}}}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.path": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}}, "df": 8}}, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 3}}}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.post": {"tf": 1}}, "df": 1}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 3}}}}}}}}}}}}}}}}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}}, "df": 4}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_types.S3Object.presigned": {"tf": 1}}, "df": 1}}}}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}}, "df": 4}}}}}, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "k": {"docs": {"wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}}, "df": 1}}}}, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}}}}, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {"wmill.client.Windmill.mocked_api": {"tf": 1}, "wmill.client.Windmill.get_mocked_api": {"tf": 1}}, "df": 2}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.append_to_result_stream": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}}, "df": 8}}}}, "w": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 7}}, "c": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 4}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}}, "df": 1}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_version": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}}, "df": 33}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.init_global_client": {"tf": 1}}, "df": 1}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 16, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}}}, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}}, "df": 2}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}}, "df": 4}}, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}}, "df": 2}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}}, "df": 5}}}}}}, "a": {"docs": {}, "df": 0, "d": {"1": {"docs": {"wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 1}, "docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.s3_reader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}, "wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 9}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}}, "df": 3, "s": {"docs": {"wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}}, "df": 1}}}}}}}, "s": {"3": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.s3_reader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}, "wmill.s3_reader.bytes_generator": {"tf": 1}, "wmill.s3_types": {"tf": 1}, "wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1.4142135623730951}, "wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 49, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 5}}}}}}}}}}}}}}, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}, "wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}}, "df": 4}}}}}}, "f": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}}, "df": 1, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}}, "df": 2}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 7}}}}}}}, "docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 14}}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 2}}, "e": {"docs": {"wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}}, "df": 18}}}, "r": {"docs": {"wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}}, "df": 2}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 2, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}}, "df": 6}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 14, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 7}}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 3}}}}}, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 4}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}}, "df": 8}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}}, "df": 2}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 2}}}}}, "s": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}}, "df": 2}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 6}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 6}}}}, "d": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.deprecate": {"tf": 1}}, "df": 1}}}}}}}}}, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}}, "df": 3, "s": {"docs": {"wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}}, "df": 2}}}}}}, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}}, "df": 3}}}}}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}}, "df": 2}}}}, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "y": {"docs": {"wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}}, "df": 5}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 1}}}}}}}}, "annotation": {"root": {"docs": {"wmill.client.Windmill.user": {"tf": 1}, "wmill.client.Windmill.state_path": {"tf": 1}, "wmill.client.Windmill.state": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}, "wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 26, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.user": {"tf": 1}}, "df": 1}}}}, "s": {"3": {"docs": {"wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 3, "f": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}}, "df": 1}}}}}}}, "docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.state_path": {"tf": 1}, "wmill.s3_types.S3Object.s3": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1}, "wmill.s3_types.S3FsArgs.key": {"tf": 1}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1}}, "df": 16}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.state": {"tf": 1}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_types.S3Object.storage": {"tf": 1}, "wmill.s3_types.S3Object.presigned": {"tf": 1}}, "df": 2}}}}}}}}}}}}, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1}}, "df": 3}}}}, "w": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 3}}}}}, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1}}, "df": 3}}}}}}}, "default_value": {"root": {"docs": {"wmill.client.logger": {"tf": 1.4142135623730951}, "wmill.client.JobStatus": {"tf": 1}}, "df": 2, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}}, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}}}}, "g": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.logger": {"tf": 1}}, "df": 1}}, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}, "x": {"2": {"7": {"docs": {"wmill.client.JobStatus": {"tf": 2.449489742783178}}, "df": 1}, "docs": {}, "df": 0}, "docs": {}, "df": 0}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.JobStatus": {"tf": 1}}, "df": 1}}}}}}}}}, "signature": {"root": {"0": {"docs": {"wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}}, "df": 1}, "1": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 3}, "3": {"9": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 2.449489742783178}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_shared_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_shared_state": {"tf": 1.4142135623730951}, "wmill.client.get_job_status": {"tf": 2.449489742783178}, "wmill.client.duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.set_resource": {"tf": 1.4142135623730951}, "wmill.client.set_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.get_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.set_shared_state": {"tf": 1.4142135623730951}, "wmill.client.get_shared_state": {"tf": 1.4142135623730951}}, "df": 17}, "docs": {}, "df": 0}, "docs": {"wmill.client.Windmill.__init__": {"tf": 6}, "wmill.client.Windmill.get_mocked_api": {"tf": 4.123105625617661}, "wmill.client.Windmill.get_client": {"tf": 4}, "wmill.client.Windmill.get": {"tf": 5.830951894845301}, "wmill.client.Windmill.post": {"tf": 5.830951894845301}, "wmill.client.Windmill.create_token": {"tf": 5.916079783099616}, "wmill.client.Windmill.run_script_async": {"tf": 8.602325267042627}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 6.928203230275509}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 7.280109889280518}, "wmill.client.Windmill.run_flow_async": {"tf": 8.18535277187245}, "wmill.client.Windmill.run_script": {"tf": 11.74734012447073}, "wmill.client.Windmill.run_script_by_path": {"tf": 10.770329614269007}, "wmill.client.Windmill.run_script_by_hash": {"tf": 10.816653826391969}, "wmill.client.Windmill.wait_job": {"tf": 9.746794344808963}, "wmill.client.Windmill.cancel_running": {"tf": 3.4641016151377544}, "wmill.client.Windmill.get_job": {"tf": 4.47213595499958}, "wmill.client.Windmill.get_root_job_id": {"tf": 5.5677643628300215}, "wmill.client.Windmill.get_id_token": {"tf": 4.47213595499958}, "wmill.client.Windmill.get_job_status": {"tf": 6.4031242374328485}, "wmill.client.Windmill.get_result": {"tf": 5.830951894845301}, "wmill.client.Windmill.get_variable": {"tf": 4.47213595499958}, "wmill.client.Windmill.set_variable": {"tf": 6.48074069840786}, "wmill.client.Windmill.get_resource": {"tf": 6.244997998398398}, "wmill.client.Windmill.set_resource": {"tf": 5.830951894845301}, "wmill.client.Windmill.set_state": {"tf": 4.242640687119285}, "wmill.client.Windmill.set_progress": {"tf": 6.082762530298219}, "wmill.client.Windmill.get_progress": {"tf": 5.5677643628300215}, "wmill.client.Windmill.set_flow_user_state": {"tf": 5.291502622129181}, "wmill.client.Windmill.get_flow_user_state": {"tf": 4.47213595499958}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 6.557438524302}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 6.164414002968976}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 6.164414002968976}, "wmill.client.Windmill.load_s3_file": {"tf": 7}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 7.3484692283495345}, "wmill.client.Windmill.write_s3_file": {"tf": 11}, "wmill.client.Windmill.sign_s3_objects": {"tf": 7.280109889280518}, "wmill.client.Windmill.sign_s3_object": {"tf": 6.557438524302}, "wmill.client.Windmill.whoami": {"tf": 3.4641016151377544}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 5.656854249492381}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 4.898979485566356}, "wmill.client.Windmill.set_shared_state": {"tf": 5.656854249492381}, "wmill.client.Windmill.get_shared_state": {"tf": 4.898979485566356}, "wmill.client.Windmill.get_resume_urls": {"tf": 5.0990195135927845}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 9.539392014169456}, "wmill.client.Windmill.username_to_email": {"tf": 4.47213595499958}, "wmill.client.Windmill.send_teams_message": {"tf": 7.681145747868608}, "wmill.client.init_global_client": {"tf": 3.1622776601683795}, "wmill.client.deprecate": {"tf": 3.7416573867739413}, "wmill.client.get_workspace": {"tf": 3}, "wmill.client.get_root_job_id": {"tf": 5.196152422706632}, "wmill.client.get_version": {"tf": 3}, "wmill.client.run_script_async": {"tf": 7.54983443527075}, "wmill.client.run_flow_async": {"tf": 8.48528137423857}, "wmill.client.run_script_sync": {"tf": 10.295630140987}, "wmill.client.run_script_by_path_async": {"tf": 7.874007874011811}, "wmill.client.run_script_by_hash_async": {"tf": 7.937253933193772}, "wmill.client.run_script_by_path_sync": {"tf": 10.295630140987}, "wmill.client.get_id_token": {"tf": 4}, "wmill.client.get_job_status": {"tf": 6.082762530298219}, "wmill.client.get_result": {"tf": 5.830951894845301}, "wmill.client.duckdb_connection_settings": {"tf": 5.656854249492381}, "wmill.client.polars_connection_settings": {"tf": 5.656854249492381}, "wmill.client.boto3_connection_settings": {"tf": 5.656854249492381}, "wmill.client.load_s3_file": {"tf": 7.0710678118654755}, "wmill.client.load_s3_file_reader": {"tf": 7.416198487095663}, "wmill.client.write_s3_file": {"tf": 11.045361017187261}, "wmill.client.sign_s3_objects": {"tf": 6.928203230275509}, "wmill.client.sign_s3_object": {"tf": 6.082762530298219}, "wmill.client.whoami": {"tf": 3}, "wmill.client.get_state": {"tf": 3}, "wmill.client.get_resource": {"tf": 5.916079783099616}, "wmill.client.set_resource": {"tf": 6.324555320336759}, "wmill.client.set_state": {"tf": 4}, "wmill.client.set_progress": {"tf": 5.916079783099616}, "wmill.client.get_progress": {"tf": 5.196152422706632}, "wmill.client.set_shared_state_pickle": {"tf": 5.0990195135927845}, "wmill.client.get_shared_state_pickle": {"tf": 4.242640687119285}, "wmill.client.set_shared_state": {"tf": 5.0990195135927845}, "wmill.client.get_shared_state": {"tf": 4.242640687119285}, "wmill.client.get_variable": {"tf": 4}, "wmill.client.set_variable": {"tf": 6.164414002968976}, "wmill.client.get_flow_user_state": {"tf": 4}, "wmill.client.set_flow_user_state": {"tf": 4.898979485566356}, "wmill.client.get_state_path": {"tf": 3}, "wmill.client.get_resume_urls": {"tf": 4.69041575982343}, "wmill.client.request_interactive_slack_approval": {"tf": 9.273618495495704}, "wmill.client.send_teams_message": {"tf": 6.928203230275509}, "wmill.client.cancel_running": {"tf": 3}, "wmill.client.run_script": {"tf": 11.313708498984761}, "wmill.client.run_script_by_path": {"tf": 10.295630140987}, "wmill.client.run_script_by_hash": {"tf": 10.344080432788601}, "wmill.client.username_to_email": {"tf": 4}, "wmill.client.task": {"tf": 4.242640687119285}, "wmill.client.parse_resource_syntax": {"tf": 4.58257569495584}, "wmill.client.parse_s3_object": {"tf": 6.082762530298219}, "wmill.client.parse_variable_syntax": {"tf": 4.58257569495584}, "wmill.client.append_to_result_stream": {"tf": 4}, "wmill.client.stream_result": {"tf": 3.4641016151377544}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 7.937253933193772}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 4.242640687119285}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 4.242640687119285}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 4.242640687119285}, "wmill.s3_reader.bytes_generator": {"tf": 5.830951894845301}}, "df": 103, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_path": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1.7320508075688772}, "wmill.client.Windmill.wait_job": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path_sync": {"tf": 1.7320508075688772}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}, "wmill.client.run_script": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_hash": {"tf": 1.7320508075688772}}, "df": 18}}, "t": {"docs": {}, "df": 0, "o": {"3": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}}, "docs": {}, "df": 0}}}, "y": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 4, "i": {"docs": {}, "df": 0, "o": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}}}, "u": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 5}}}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}}, "df": 1}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}}, "df": 2}}}}}}}, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_async": {"tf": 2}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_flow_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script": {"tf": 2.449489742783178}, "wmill.client.Windmill.run_script_by_path": {"tf": 2}, "wmill.client.Windmill.run_script_by_hash": {"tf": 2}, "wmill.client.Windmill.wait_job": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_root_job_id": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 2.449489742783178}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.23606797749979}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1.4142135623730951}, "wmill.client.run_script_async": {"tf": 1.4142135623730951}, "wmill.client.run_flow_async": {"tf": 1.4142135623730951}, "wmill.client.run_script_sync": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path_async": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_hash_async": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path_sync": {"tf": 1.7320508075688772}, "wmill.client.get_result": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 2.6457513110645907}, "wmill.client.get_resource": {"tf": 1.4142135623730951}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1.4142135623730951}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 2.23606797749979}, "wmill.client.send_teams_message": {"tf": 1}, "wmill.client.run_script": {"tf": 2.23606797749979}, "wmill.client.run_script_by_path": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_hash": {"tf": 1.7320508075688772}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}}, "df": 55}}, "t": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 13}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path_sync": {"tf": 1.4142135623730951}, "wmill.client.get_result": {"tf": 1}, "wmill.client.run_script": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_hash": {"tf": 1.4142135623730951}}, "df": 17}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}}, "df": 2}}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 10}}}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 9}}}}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}}, "df": 4, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.Windmill.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1.4142135623730951}}, "df": 17}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1}}, "df": 3}}}}, "w": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 2}}}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 2}}}}}}, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.Windmill.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1.4142135623730951}}, "df": 17}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 1}}}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.__init__": {"tf": 1}}, "df": 1}}}, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 9}}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 14}}}}}, "s": {"3": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1.7320508075688772}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1.7320508075688772}, "wmill.client.Windmill.sign_s3_object": {"tf": 1.7320508075688772}, "wmill.client.duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 1.7320508075688772}, "wmill.client.sign_s3_objects": {"tf": 1.7320508075688772}, "wmill.client.sign_s3_object": {"tf": 1.7320508075688772}, "wmill.client.parse_s3_object": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 18, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1.7320508075688772}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.Windmill.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 1.7320508075688772}, "wmill.client.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1.4142135623730951}}, "df": 11}}}}}}}, "docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 2, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 44}}, "c": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}}, "df": 8}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}}, "df": 2}}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}}, "df": 2}}, "e": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}}, "df": 8}}}, "r": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_flow_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_id_token": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_variable": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 2}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}, "wmill.client.Windmill.username_to_email": {"tf": 1.4142135623730951}, "wmill.client.Windmill.send_teams_message": {"tf": 1.4142135623730951}, "wmill.client.deprecate": {"tf": 1}, "wmill.client.get_workspace": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1.4142135623730951}, "wmill.client.get_version": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1.7320508075688772}, "wmill.client.run_flow_async": {"tf": 1.7320508075688772}, "wmill.client.run_script_sync": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path_async": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_hash_async": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path_sync": {"tf": 1.4142135623730951}, "wmill.client.get_id_token": {"tf": 1.4142135623730951}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1.4142135623730951}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 2}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1.4142135623730951}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.get_variable": {"tf": 1.4142135623730951}, "wmill.client.set_variable": {"tf": 1.4142135623730951}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.get_state_path": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 2}, "wmill.client.send_teams_message": {"tf": 1.4142135623730951}, "wmill.client.run_script": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1.4142135623730951}, "wmill.client.parse_resource_syntax": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1.4142135623730951}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 2}}, "df": 79, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.stream_result": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}}, "df": 8}}}}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}}}, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.peek": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 3}}}}, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1.4142135623730951}}, "df": 10}}}}}}}, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.sign_s3_object": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}}, "df": 3, "s": {"docs": {"wmill.client.Windmill.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}}, "df": 2}}}}}}, "f": {"docs": {"wmill.client.deprecate": {"tf": 1}}, "df": 1}, "r": {"docs": {"wmill.client.run_script_async": {"tf": 1}}, "df": 1}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_mocked_api": {"tf": 1}, "wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.whoami": {"tf": 1}, "wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.whoami": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.send_teams_message": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 32}}, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 2}}}}}}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 10}}}}}}, "y": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.create_token": {"tf": 1}}, "df": 1}}}, "o": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}}, "df": 2}, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}}}, "h": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "x": {"docs": {"wmill.client.Windmill.get_client": {"tf": 1}, "wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 4}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 9}}}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_client": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1.4142135623730951}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "p": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 9}}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 2}}}}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1.7320508075688772}, "wmill.client.write_s3_file": {"tf": 1.7320508075688772}}, "df": 2}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}}}}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}}, "df": 2}}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}}, "df": 2}}}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 11}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 17}}}}}}, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_reader.bytes_generator": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 2}}}}}}}, "f": {"docs": {"wmill.client.init_global_client": {"tf": 1}}, "df": 1, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}}, "df": 2}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 7}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_path": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1.4142135623730951}, "wmill.client.Windmill.wait_job": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 13}}}, "v": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.deprecate": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 3}}}}, "k": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get": {"tf": 1}, "wmill.client.Windmill.post": {"tf": 1}, "wmill.client.task": {"tf": 1}}, "df": 3}}}}}, "e": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 5}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.get_variable": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1}}, "df": 42}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}}, "df": 2}}}}}, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}}, "df": 4}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.client.task": {"tf": 1}}, "df": 19}}}, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 11}}}}}, "n": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.set_resource": {"tf": 1}, "wmill.client.Windmill.set_state": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1.4142135623730951}, "wmill.client.get_result": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.set_resource": {"tf": 1.4142135623730951}, "wmill.client.set_state": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 31}}, "u": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_id_token": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}}, "df": 2}}}}}}}, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.get_resume_urls": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.get_resume_urls": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 4}}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1.4142135623730951}, "wmill.client.deprecate": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}}, "df": 9, "t": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.run_script_async": {"tf": 1}, "wmill.client.run_flow_async": {"tf": 1}, "wmill.client.run_script_by_path_async": {"tf": 1}, "wmill.client.run_script_by_hash_async": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 17}}, "s": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.set_variable": {"tf": 1}, "wmill.client.run_script_sync": {"tf": 1}, "wmill.client.run_script_by_path_sync": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 13}, "d": {"docs": {"wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}, "wmill.client.send_teams_message": {"tf": 1}}, "df": 16}, "f": {"docs": {"wmill.client.Windmill.get_resource": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}}, "df": 2}, "o": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.s3_reader.bytes_generator": {"tf": 1.4142135623730951}}, "df": 5}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.wait_job": {"tf": 1}, "wmill.client.Windmill.get_job": {"tf": 1}, "wmill.client.Windmill.get_root_job_id": {"tf": 1}, "wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.Windmill.get_result": {"tf": 1}, "wmill.client.Windmill.set_progress": {"tf": 1}, "wmill.client.Windmill.get_progress": {"tf": 1}, "wmill.client.get_root_job_id": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}, "wmill.client.get_result": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}}, "df": 12}}, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 6}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.get_job_status": {"tf": 1}, "wmill.client.get_job_status": {"tf": 1}}, "df": 2}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_objects": {"tf": 1.4142135623730951}}, "df": 2}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.request_interactive_slack_approval": {"tf": 1}}, "df": 2}}}}}}}}}, "bases": {"root": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "o": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1}}, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}}, "df": 7}}}}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_types.S3Object": {"tf": 1}, "wmill.s3_types.S3FsClientKwargs": {"tf": 1}, "wmill.s3_types.S3FsArgs": {"tf": 1}, "wmill.s3_types.StorageOptions": {"tf": 1}, "wmill.s3_types.PolarsConnectionSettings": {"tf": 1}, "wmill.s3_types.Boto3ConnectionSettings": {"tf": 1}, "wmill.s3_types.DuckDbConnectionSettings": {"tf": 1}}, "df": 7}}}}}}, "doc": {"root": {"4": {"2": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}, "8": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}}, "df": 1}, "docs": {"wmill": {"tf": 1.7320508075688772}, "wmill.client": {"tf": 1.7320508075688772}, "wmill.client.logger": {"tf": 1.7320508075688772}, "wmill.client.JobStatus": {"tf": 1.7320508075688772}, "wmill.client.Windmill": {"tf": 1.7320508075688772}, "wmill.client.Windmill.__init__": {"tf": 1.7320508075688772}, "wmill.client.Windmill.base_url": {"tf": 1.7320508075688772}, "wmill.client.Windmill.token": {"tf": 1.7320508075688772}, "wmill.client.Windmill.headers": {"tf": 1.7320508075688772}, "wmill.client.Windmill.verify": {"tf": 1.7320508075688772}, "wmill.client.Windmill.client": {"tf": 1.7320508075688772}, "wmill.client.Windmill.workspace": {"tf": 1.7320508075688772}, "wmill.client.Windmill.path": {"tf": 1.7320508075688772}, "wmill.client.Windmill.mocked_api": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_mocked_api": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_client": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get": {"tf": 1.7320508075688772}, "wmill.client.Windmill.post": {"tf": 1.7320508075688772}, "wmill.client.Windmill.create_token": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_async": {"tf": 2.8284271247461903}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_flow_async": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script": {"tf": 2.8284271247461903}, "wmill.client.Windmill.run_script_by_path": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1.7320508075688772}, "wmill.client.Windmill.wait_job": {"tf": 1.7320508075688772}, "wmill.client.Windmill.cancel_running": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_job": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_root_job_id": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_id_token": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_job_status": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_result": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_variable": {"tf": 1.7320508075688772}, "wmill.client.Windmill.set_variable": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_resource": {"tf": 1.7320508075688772}, "wmill.client.Windmill.set_resource": {"tf": 1.7320508075688772}, "wmill.client.Windmill.set_state": {"tf": 1.7320508075688772}, "wmill.client.Windmill.set_progress": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_progress": {"tf": 1.7320508075688772}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.version": {"tf": 1.7320508075688772}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file": {"tf": 3}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 3}, "wmill.client.Windmill.write_s3_file": {"tf": 4.242640687119285}, "wmill.client.Windmill.sign_s3_objects": {"tf": 1.7320508075688772}, "wmill.client.Windmill.sign_s3_object": {"tf": 1.7320508075688772}, "wmill.client.Windmill.whoami": {"tf": 1.7320508075688772}, "wmill.client.Windmill.user": {"tf": 1.7320508075688772}, "wmill.client.Windmill.state_path": {"tf": 1.7320508075688772}, "wmill.client.Windmill.state": {"tf": 1.7320508075688772}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_shared_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_shared_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_resume_urls": {"tf": 1.7320508075688772}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 10.862780491200215}, "wmill.client.Windmill.username_to_email": {"tf": 1.7320508075688772}, "wmill.client.Windmill.send_teams_message": {"tf": 1.4142135623730951}, "wmill.client.init_global_client": {"tf": 1.7320508075688772}, "wmill.client.deprecate": {"tf": 1.7320508075688772}, "wmill.client.get_workspace": {"tf": 1.7320508075688772}, "wmill.client.get_root_job_id": {"tf": 1.7320508075688772}, "wmill.client.get_version": {"tf": 1.7320508075688772}, "wmill.client.run_script_async": {"tf": 1.7320508075688772}, "wmill.client.run_flow_async": {"tf": 1.7320508075688772}, "wmill.client.run_script_sync": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path_async": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_hash_async": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path_sync": {"tf": 1.7320508075688772}, "wmill.client.get_id_token": {"tf": 1.7320508075688772}, "wmill.client.get_job_status": {"tf": 1.7320508075688772}, "wmill.client.get_result": {"tf": 1.7320508075688772}, "wmill.client.duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 3.1622776601683795}, "wmill.client.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.whoami": {"tf": 1.4142135623730951}, "wmill.client.get_state": {"tf": 1.4142135623730951}, "wmill.client.get_resource": {"tf": 1.4142135623730951}, "wmill.client.set_resource": {"tf": 1.4142135623730951}, "wmill.client.set_state": {"tf": 1.4142135623730951}, "wmill.client.set_progress": {"tf": 1.4142135623730951}, "wmill.client.get_progress": {"tf": 1.4142135623730951}, "wmill.client.set_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.get_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.set_shared_state": {"tf": 1.4142135623730951}, "wmill.client.get_shared_state": {"tf": 1.4142135623730951}, "wmill.client.get_variable": {"tf": 1.4142135623730951}, "wmill.client.set_variable": {"tf": 1.4142135623730951}, "wmill.client.get_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.set_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.get_state_path": {"tf": 1.7320508075688772}, "wmill.client.get_resume_urls": {"tf": 1.7320508075688772}, "wmill.client.request_interactive_slack_approval": {"tf": 1.7320508075688772}, "wmill.client.send_teams_message": {"tf": 1.7320508075688772}, "wmill.client.cancel_running": {"tf": 1.7320508075688772}, "wmill.client.run_script": {"tf": 2.8284271247461903}, "wmill.client.run_script_by_path": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_hash": {"tf": 1.7320508075688772}, "wmill.client.username_to_email": {"tf": 1.7320508075688772}, "wmill.client.task": {"tf": 1.7320508075688772}, "wmill.client.parse_resource_syntax": {"tf": 1.7320508075688772}, "wmill.client.parse_s3_object": {"tf": 1.7320508075688772}, "wmill.client.parse_variable_syntax": {"tf": 1.7320508075688772}, "wmill.client.append_to_result_stream": {"tf": 2.23606797749979}, "wmill.client.stream_result": {"tf": 2.23606797749979}, "wmill.s3_reader": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.__init__": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.peek": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 3.872983346207417}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 2.449489742783178}, "wmill.s3_reader.bytes_generator": {"tf": 1.7320508075688772}, "wmill.s3_types": {"tf": 1.7320508075688772}, "wmill.s3_types.S3Object": {"tf": 1.7320508075688772}, "wmill.s3_types.S3Object.s3": {"tf": 1.7320508075688772}, "wmill.s3_types.S3Object.storage": {"tf": 1.7320508075688772}, "wmill.s3_types.S3Object.presigned": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsClientKwargs": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsClientKwargs.region_name": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs.endpoint_url": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs.key": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs.secret": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs.use_ssl": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs.cache_regions": {"tf": 1.7320508075688772}, "wmill.s3_types.S3FsArgs.client_kwargs": {"tf": 1.7320508075688772}, "wmill.s3_types.StorageOptions": {"tf": 1.7320508075688772}, "wmill.s3_types.StorageOptions.aws_endpoint_url": {"tf": 1.7320508075688772}, "wmill.s3_types.StorageOptions.aws_access_key_id": {"tf": 1.7320508075688772}, "wmill.s3_types.StorageOptions.aws_secret_access_key": {"tf": 1.7320508075688772}, "wmill.s3_types.StorageOptions.aws_region": {"tf": 1.7320508075688772}, "wmill.s3_types.StorageOptions.aws_allow_http": {"tf": 1.7320508075688772}, "wmill.s3_types.PolarsConnectionSettings": {"tf": 1.7320508075688772}, "wmill.s3_types.PolarsConnectionSettings.s3fs_args": {"tf": 1.7320508075688772}, "wmill.s3_types.PolarsConnectionSettings.storage_options": {"tf": 1.7320508075688772}, "wmill.s3_types.Boto3ConnectionSettings": {"tf": 1.7320508075688772}, "wmill.s3_types.Boto3ConnectionSettings.endpoint_url": {"tf": 1.7320508075688772}, "wmill.s3_types.Boto3ConnectionSettings.region_name": {"tf": 1.7320508075688772}, "wmill.s3_types.Boto3ConnectionSettings.use_ssl": {"tf": 1.7320508075688772}, "wmill.s3_types.Boto3ConnectionSettings.aws_access_key_id": {"tf": 1.7320508075688772}, "wmill.s3_types.Boto3ConnectionSettings.aws_secret_access_key": {"tf": 1.7320508075688772}, "wmill.s3_types.DuckDbConnectionSettings": {"tf": 1.7320508075688772}, "wmill.s3_types.DuckDbConnectionSettings.connection_settings_str": {"tf": 1.7320508075688772}}, "df": 153, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 5}, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.set_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}}, "df": 2}}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.whoami": {"tf": 1}}, "df": 1, "l": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}}, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 2}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1.7320508075688772}}, "df": 5}}, "x": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 3}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 3}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"1": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "2": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}}}}, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.Windmill.send_teams_message": {"tf": 1.4142135623730951}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.set_resource": {"tf": 1.4142135623730951}, "wmill.client.get_variable": {"tf": 1.4142135623730951}, "wmill.client.set_variable": {"tf": 1.4142135623730951}, "wmill.client.get_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.set_flow_user_state": {"tf": 1.4142135623730951}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 26, "n": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 10, "d": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 2.449489742783178}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 22}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 2}}}}}}}}, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 14, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1.4142135623730951}}, "df": 1}}}}, "t": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 10}, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 1}}}, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.append_to_result_stream": {"tf": 1}, "wmill.client.stream_result": {"tf": 1}}, "df": 3}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 1, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "p": {"docs": {}, "df": 0, "p": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.23606797749979}}, "df": 1, "#": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1, "r": {"1": {"2": {"3": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}, "docs": {}, "df": 0}, "docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}}, "s": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 4}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.append_to_result_stream": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1, "o": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}, "d": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}, "d": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}}, "u": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "w": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}}}, "s": {"3": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file": {"tf": 2.23606797749979}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 2.23606797749979}, "wmill.client.Windmill.write_s3_file": {"tf": 2.6457513110645907}, "wmill.client.duckdb_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.polars_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.boto3_connection_settings": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1.4142135623730951}, "wmill.client.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1}}, "df": 15, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.7320508075688772}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1}}, "df": 4}}}}}}}, "docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}, "wmill.client.run_script": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 11}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.run_script": {"tf": 1}}, "df": 3}}}, "g": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 2, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 2}}}}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 6}}}}}}}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 2}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "y": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1.4142135623730951}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1.4142135623730951}}, "df": 13, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 1, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "e": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 14}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1.4142135623730951}, "wmill.client.stream_result": {"tf": 2.449489742783178}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 5, "s": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.set_resource": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 6}}}}, "e": {"docs": {}, "df": 0, "p": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}, "y": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}}, "df": 2}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}}, "df": 8}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 3.1622776601683795}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 1}}}}}}, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_flow_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 5}}, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}}, "df": 1}}}, "w": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 12, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.whoami": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 15}}}}}, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}, "wmill.client.append_to_result_stream": {"tf": 1.4142135623730951}, "wmill.client.stream_result": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 10}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}}, "df": 11}}}}}}, "a": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1.4142135623730951}}, "df": 3, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 2}}, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 1}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.6457513110645907}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2, "d": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script": {"tf": 1.7320508075688772}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.run_script": {"tf": 1.7320508075688772}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 8, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}}}, "b": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 1}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}, "w": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 2.23606797749979}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 3}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.set_resource": {"tf": 1.4142135623730951}, "wmill.client.set_variable": {"tf": 1.4142135623730951}}, "df": 3, "s": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 11}}, "d": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.449489742783178}, "wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 6}, "n": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 17, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.run_script": {"tf": 1}}, "df": 3}}}}}, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 2}}}}}}}}, "o": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 3}}}, "l": {"docs": {}, "df": 0, "y": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2}}}, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2}}}}}}}, "f": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.7320508075688772}}, "df": 5}, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.Windmill.username_to_email": {"tf": 1.7320508075688772}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 2.449489742783178}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 6, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "o": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.run_script": {"tf": 1}}, "df": 3}}}}}}}}, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}}, "df": 1}}}}, "v": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "b": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}}, "df": 2}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.write_s3_file": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "o": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.set_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 4}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 1}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.run_script": {"tf": 1}}, "df": 3}}}}}}, "i": {"docs": {}, "df": 0, "a": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1.7320508075688772}, "wmill.client.username_to_email": {"tf": 1.7320508075688772}}, "df": 2}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"1": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 3, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.run_script": {"tf": 1}}, "df": 4, "r": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.whoami": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 6, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1.7320508075688772}, "wmill.client.username_to_email": {"tf": 1.7320508075688772}}, "df": 2}}}}, "s": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 2}}, "f": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}, "d": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 3}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 11}}}, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}, "/": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.write_s3_file": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, "t": {"docs": {}, "df": 0, "f": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}}, "df": 1}}, "/": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}}, "p": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2}}}}}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}, "b": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 1, "y": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1.4142135623730951}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.run_script": {"tf": 1.4142135623730951}, "wmill.client.run_script_by_path": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 11, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1, "s": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1.4142135623730951}}, "df": 6}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"3": {"docs": {"wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 2}, "docs": {}, "df": 0}}}, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 3}}}}, "t": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 3}, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 5}, "a": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_path_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_path": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.7320508075688772}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_path": {"tf": 1}}, "df": 12, "/": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 3}}}}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 3}}}}, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}}, "df": 2}}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}, "y": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 3}}}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "w": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}}, "df": 2}}}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}}, "df": 8}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}}}}, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}}, "df": 2}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.23606797749979}, "wmill.client.run_script": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 6, "g": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.write_s3_file": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "f": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.7320508075688772}, "wmill.client.Windmill.username_to_email": {"tf": 2}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}, "wmill.client.username_to_email": {"tf": 2}}, "df": 12}, "b": {"docs": {}, "df": 0, "j": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 2}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1.7320508075688772}}, "df": 3, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.sign_s3_object": {"tf": 1.4142135623730951}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 5, "s": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}}, "df": 1}}}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 2}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.23606797749979}}, "df": 1}}, "b": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}, "n": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2, "l": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.run_script_async": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash_async": {"tf": 1}, "wmill.client.Windmill.run_script": {"tf": 1}, "wmill.client.Windmill.run_script_by_hash": {"tf": 1}, "wmill.client.run_script": {"tf": 1}, "wmill.client.run_script_by_hash": {"tf": 1}}, "df": 6}}}, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, ":": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "w": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.write_s3_file": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}}}}}}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"wmill.client.Windmill.run_flow_async": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.8284271247461903}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 6}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.client.parse_resource_syntax": {"tf": 1}, "wmill.client.parse_s3_object": {"tf": 1}, "wmill.client.parse_variable_syntax": {"tf": 1}}, "df": 14}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 2}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 2.23606797749979}, "wmill.client.Windmill.write_s3_file": {"tf": 3.1622776601683795}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}}, "df": 6}}, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.7320508075688772}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1.4142135623730951}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 7, "m": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2.23606797749979}}, "df": 1, "a": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.parse_s3_object": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"wmill.client.Windmill.set_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.set_shared_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.set_shared_state": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}}, "df": 8}}}}, "o": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.7320508075688772}}, "df": 1, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.set_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.load_s3_file": {"tf": 1}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 2}, "wmill.client.username_to_email": {"tf": 2}}, "df": 2}}}, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.write_s3_file": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 3}}}}, "t": {"docs": {}, "df": 0, "c": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}, "o": {"docs": {}, "df": 0, "f": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 2}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1.4142135623730951}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.cancel_running": {"tf": 1}, "wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.set_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.Windmill.set_shared_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.get_shared_state": {"tf": 1.4142135623730951}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 4}, "wmill.client.Windmill.username_to_email": {"tf": 2.8284271247461903}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}, "wmill.client.whoami": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_state": {"tf": 1}, "wmill.client.set_progress": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.set_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.get_shared_state_pickle": {"tf": 1.4142135623730951}, "wmill.client.set_shared_state": {"tf": 1.4142135623730951}, "wmill.client.get_shared_state": {"tf": 1.4142135623730951}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.client.cancel_running": {"tf": 1}, "wmill.client.username_to_email": {"tf": 2.8284271247461903}, "wmill.client.append_to_result_stream": {"tf": 1.4142135623730951}, "wmill.client.stream_result": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 2.449489742783178}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 43}, "a": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 10}}, "i": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 3}, "r": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}, "o": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}, "wmill.client.Windmill.username_to_email": {"tf": 1.4142135623730951}, "wmill.client.Windmill.send_teams_message": {"tf": 1.4142135623730951}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.client.sign_s3_objects": {"tf": 1}, "wmill.client.sign_s3_object": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1.4142135623730951}, "wmill.client.append_to_result_stream": {"tf": 1.7320508075688772}, "wmill.client.stream_result": {"tf": 1.7320508075688772}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1.4142135623730951}}, "df": 19, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1, "s": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}}, "df": 1}}}}}, "x": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1.4142135623730951}}, "df": 3}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 1}}}, "x": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.append_to_result_stream": {"tf": 1.7320508075688772}}, "df": 1}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.write_s3_file": {"tf": 1.7320508075688772}}, "df": 1}}}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.get_variable": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}, "wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 9}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_shared_state_pickle": {"tf": 1}, "wmill.client.Windmill.get_shared_state": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.get_id_token": {"tf": 1}, "wmill.client.get_state": {"tf": 1}, "wmill.client.get_resource": {"tf": 1}, "wmill.client.get_progress": {"tf": 1}, "wmill.client.get_shared_state_pickle": {"tf": 1}, "wmill.client.get_shared_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 12}}, "c": {"docs": {}, "df": 0, "p": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}, "u": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}}}}}}, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "y": {"1": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "2": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}, "docs": {"wmill.client.Windmill.set_flow_user_state": {"tf": 1}, "wmill.client.Windmill.get_flow_user_state": {"tf": 1}, "wmill.client.get_flow_user_state": {"tf": 1}, "wmill.client.set_flow_user_state": {"tf": 1}}, "df": 4}}}, "n": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.get_duckdb_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_polars_connection_settings": {"tf": 1}, "wmill.client.Windmill.get_boto3_connection_settings": {"tf": 1}, "wmill.client.duckdb_connection_settings": {"tf": 1}, "wmill.client.polars_connection_settings": {"tf": 1}, "wmill.client.boto3_connection_settings": {"tf": 1}}, "df": 6}}}}}}}, "w": {"docs": {"wmill.s3_reader.S3BufferedReader": {"tf": 1}}, "df": 1}, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}, "o": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.set_resource": {"tf": 1}, "wmill.client.set_variable": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 5, "e": {"docs": {}, "df": 0, "s": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}, "n": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1.4142135623730951}}, "df": 2}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.load_s3_file": {"tf": 1}, "wmill.client.load_s3_file_reader": {"tf": 1}}, "df": 4}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}}, "f": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.get_id_token": {"tf": 1}}, "df": 1}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.sign_s3_objects": {"tf": 1}}, "df": 1}}}}, "w": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 5}}}}}}}}, "m": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}, "wmill.client.Windmill.username_to_email": {"tf": 1.4142135623730951}, "wmill.client.username_to_email": {"tf": 1.4142135623730951}}, "df": 3, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1}, "wmill.client.Windmill.load_s3_file_reader": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 3}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"wmill.client.Windmill.load_s3_file_reader": {"tf": 1}, "wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 4, "i": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.4142135623730951}}, "df": 1}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}, "wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1.7320508075688772}, "wmill.client.get_resource": {"tf": 1}}, "df": 3}}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.write_s3_file": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 3}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1.7320508075688772}}, "df": 1}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}, "wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}, "m": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.load_s3_file": {"tf": 1.4142135623730951}, "wmill.client.Windmill.write_s3_file": {"tf": 1.7320508075688772}}, "df": 2}, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"wmill.client.Windmill.write_s3_file": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 2}, "wmill.client.Windmill.send_teams_message": {"tf": 1.4142135623730951}}, "df": 2}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"wmill.client.Windmill.username_to_email": {"tf": 1}, "wmill.client.username_to_email": {"tf": 1}}, "df": 2}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "z": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {"wmill.client.write_s3_file": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}, "wmill.s3_reader.S3BufferedReader.read1": {"tf": 1}}, "df": 2}}, "d": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.request_interactive_slack_approval": {"tf": 1}}, "df": 1}}, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "t": {"docs": {"wmill.client.Windmill.send_teams_message": {"tf": 1}}, "df": 1}}}}}}}}, "d": {"docs": {}, "df": 0, "n": {"docs": {"wmill.client.write_s3_file": {"tf": 1}}, "df": 1}}, "a": {"docs": {}, "df": 0, "y": {"docs": {"wmill.s3_reader.S3BufferedReader.read": {"tf": 1}}, "df": 1}}}}}}, "pipeline": ["trimmer"], "_isPrebuiltIndex": true}; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/python-client/docs/wmill.html b/python-client/docs/wmill.html new file mode 100644 index 0000000000..fafe32ad0b --- /dev/null +++ b/python-client/docs/wmill.html @@ -0,0 +1,240 @@ + + + + + + + wmill API documentation + + + + + + + + + +
+
+

+wmill

+ + + + + + +
1from .client import *
+2from .s3_types import *
+
+ + +
+
+ + \ No newline at end of file diff --git a/python-client/docs/wmill/client.html b/python-client/docs/wmill/client.html new file mode 100644 index 0000000000..aea20f915b --- /dev/null +++ b/python-client/docs/wmill/client.html @@ -0,0 +1,6459 @@ + + + + + + + wmill.client API documentation + + + + + + + + + +
+
+

+wmill.client

+ + + + + + +
   1from __future__ import annotations
+   2
+   3import atexit
+   4import datetime as dt
+   5import functools
+   6from io import BufferedReader, BytesIO
+   7import logging
+   8import os
+   9import random
+  10import time
+  11import warnings
+  12import json
+  13from json import JSONDecodeError
+  14from typing import Dict, Any, Union, Literal, Optional
+  15import re
+  16
+  17import httpx
+  18
+  19from .s3_reader import S3BufferedReader, bytes_generator
+  20from .s3_types import (
+  21    Boto3ConnectionSettings,
+  22    DuckDbConnectionSettings,
+  23    PolarsConnectionSettings,
+  24    S3Object,
+  25)
+  26
+  27_client: "Windmill | None" = None
+  28
+  29logger = logging.getLogger("windmill_client")
+  30
+  31JobStatus = Literal["RUNNING", "WAITING", "COMPLETED"]
+  32
+  33
+  34class Windmill:
+  35    def __init__(self, base_url=None, token=None, workspace=None, verify=True):
+  36        base = (
+  37            base_url
+  38            or os.environ.get("BASE_INTERNAL_URL")
+  39            or os.environ.get("WM_BASE_URL")
+  40        )
+  41
+  42        self.base_url = f"{base}/api"
+  43        self.token = token or os.environ.get("WM_TOKEN")
+  44        self.headers = {
+  45            "Content-Type": "application/json",
+  46            "Authorization": f"Bearer {self.token}",
+  47        }
+  48        self.verify = verify
+  49        self.client = self.get_client()
+  50        self.workspace = workspace or os.environ.get("WM_WORKSPACE")
+  51        self.path = os.environ.get("WM_JOB_PATH")
+  52
+  53        self.mocked_api = self.get_mocked_api()
+  54
+  55        assert self.workspace, (
+  56            f"workspace required as an argument or as WM_WORKSPACE environment variable"
+  57        )
+  58
+  59    def get_mocked_api(self) -> Optional[dict]:
+  60        mocked_path = os.environ.get("WM_MOCKED_API_FILE")
+  61        if not mocked_path:
+  62            return None
+  63        logger.info("Using mocked API from %s", mocked_path)
+  64        mocked_api = {"variables": {}, "resources": {}}
+  65        try:
+  66            with open(mocked_path, "r") as f:
+  67                incoming_mocked_api = json.load(f)
+  68            mocked_api = {**mocked_api, **incoming_mocked_api}
+  69        except Exception as e:
+  70            logger.warning(
+  71                "Error parsing mocked API file at path %s Using empty mocked API.",
+  72                mocked_path,
+  73            )
+  74            logger.debug(e)
+  75        return mocked_api
+  76
+  77    def get_client(self) -> httpx.Client:
+  78        return httpx.Client(
+  79            base_url=self.base_url,
+  80            headers=self.headers,
+  81            verify=self.verify,
+  82        )
+  83
+  84    def get(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:
+  85        endpoint = endpoint.lstrip("/")
+  86        resp = self.client.get(f"/{endpoint}", **kwargs)
+  87        if raise_for_status:
+  88            try:
+  89                resp.raise_for_status()
+  90            except httpx.HTTPStatusError as err:
+  91                error = f"{err.request.url}: {err.response.status_code}, {err.response.text}"
+  92                logger.error(error)
+  93                raise Exception(error)
+  94        return resp
+  95
+  96    def post(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:
+  97        endpoint = endpoint.lstrip("/")
+  98        resp = self.client.post(f"/{endpoint}", **kwargs)
+  99        if raise_for_status:
+ 100            try:
+ 101                resp.raise_for_status()
+ 102            except httpx.HTTPStatusError as err:
+ 103                error = f"{err.request.url}: {err.response.status_code}, {err.response.text}"
+ 104                logger.error(error)
+ 105                raise Exception(error)
+ 106        return resp
+ 107
+ 108    def create_token(self, duration=dt.timedelta(days=1)) -> str:
+ 109        endpoint = "/users/tokens/create"
+ 110        payload = {
+ 111            "label": f"refresh {time.time()}",
+ 112            "expiration": (dt.datetime.now() + duration).strftime("%Y-%m-%dT%H:%M:%SZ"),
+ 113        }
+ 114        return self.post(endpoint, json=payload).text
+ 115
+ 116    def run_script_async(
+ 117        self,
+ 118        path: str = None,
+ 119        hash_: str = None,
+ 120        args: dict = None,
+ 121        scheduled_in_secs: int = None,
+ 122    ) -> str:
+ 123        """Create a script job and return its job id.
+ 124        
+ 125        .. deprecated:: Use run_script_by_path_async or run_script_by_hash_async instead.
+ 126        """
+ 127        logging.warning(
+ 128            "run_script_async is deprecated. Use run_script_by_path_async or run_script_by_hash_async instead.",
+ 129        )
+ 130        assert not (path and hash_), "path and hash_ are mutually exclusive"
+ 131        return self._run_script_async_internal(path=path, hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs)
+ 132
+ 133    def _run_script_async_internal(
+ 134        self,
+ 135        path: str = None,
+ 136        hash_: str = None,
+ 137        args: dict = None,
+ 138        scheduled_in_secs: int = None,
+ 139    ) -> str:
+ 140        """Internal helper for running scripts asynchronously."""
+ 141        args = args or {}
+ 142        params = {"scheduled_in_secs": scheduled_in_secs} if scheduled_in_secs else {}
+ 143        if os.environ.get("WM_JOB_ID"):
+ 144            params["parent_job"] = os.environ.get("WM_JOB_ID")
+ 145        if os.environ.get("WM_ROOT_FLOW_JOB_ID"):
+ 146            params["root_job"] = os.environ.get("WM_ROOT_FLOW_JOB_ID")
+ 147        
+ 148        if path:
+ 149            endpoint = f"/w/{self.workspace}/jobs/run/p/{path}"
+ 150        elif hash_:
+ 151            endpoint = f"/w/{self.workspace}/jobs/run/h/{hash_}"
+ 152        else:
+ 153            raise Exception("path or hash_ must be provided")
+ 154        
+ 155        return self.post(endpoint, json=args, params=params).text
+ 156
+ 157    def run_script_by_path_async(
+ 158        self,
+ 159        path: str,
+ 160        args: dict = None,
+ 161        scheduled_in_secs: int = None,
+ 162    ) -> str:
+ 163        """Create a script job by path and return its job id."""
+ 164        return self._run_script_async_internal(path=path, args=args, scheduled_in_secs=scheduled_in_secs)
+ 165
+ 166    def run_script_by_hash_async(
+ 167        self,
+ 168        hash_: str,
+ 169        args: dict = None,
+ 170        scheduled_in_secs: int = None,
+ 171    ) -> str:
+ 172        """Create a script job by hash and return its job id."""
+ 173        return self._run_script_async_internal(hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs)
+ 174
+ 175    def run_flow_async(
+ 176        self,
+ 177        path: str,
+ 178        args: dict = None,
+ 179        scheduled_in_secs: int = None,
+ 180        # can only be set to false if this the job will be fully await and not concurrent with any other job
+ 181        # as otherwise the child flow and its own child will store their state in the parent job which will
+ 182        # lead to incorrectness and failures
+ 183        do_not_track_in_parent: bool = True,
+ 184    ) -> str:
+ 185        """Create a flow job and return its job id."""
+ 186        args = args or {}
+ 187        params = {"scheduled_in_secs": scheduled_in_secs} if scheduled_in_secs else {}
+ 188        if not do_not_track_in_parent:
+ 189            if os.environ.get("WM_JOB_ID"):
+ 190                params["parent_job"] = os.environ.get("WM_JOB_ID")
+ 191            if os.environ.get("WM_ROOT_FLOW_JOB_ID"):
+ 192                params["root_job"] = os.environ.get("WM_ROOT_FLOW_JOB_ID")
+ 193        if path:
+ 194            endpoint = f"/w/{self.workspace}/jobs/run/f/{path}"
+ 195        else:
+ 196            raise Exception("path must be provided")
+ 197        return self.post(endpoint, json=args, params=params).text
+ 198
+ 199    def run_script(
+ 200        self,
+ 201        path: str = None,
+ 202        hash_: str = None,
+ 203        args: dict = None,
+ 204        timeout: dt.timedelta | int | float | None = None,
+ 205        verbose: bool = False,
+ 206        cleanup: bool = True,
+ 207        assert_result_is_not_none: bool = False,
+ 208    ) -> Any:
+ 209        """Run script synchronously and return its result.
+ 210        
+ 211        .. deprecated:: Use run_script_by_path or run_script_by_hash instead.
+ 212        """
+ 213        logging.warning(
+ 214            "run_script is deprecated. Use run_script_by_path or run_script_by_hash instead.",
+ 215        )
+ 216        assert not (path and hash_), "path and hash_ are mutually exclusive"
+ 217        return self._run_script_internal(
+ 218            path=path, hash_=hash_, args=args, timeout=timeout, verbose=verbose,
+ 219            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+ 220        )
+ 221
+ 222    def _run_script_internal(
+ 223        self,
+ 224        path: str = None,
+ 225        hash_: str = None,
+ 226        args: dict = None,
+ 227        timeout: dt.timedelta | int | float | None = None,
+ 228        verbose: bool = False,
+ 229        cleanup: bool = True,
+ 230        assert_result_is_not_none: bool = False,
+ 231    ) -> Any:
+ 232        """Internal helper for running scripts synchronously."""
+ 233        args = args or {}
+ 234
+ 235        if verbose:
+ 236            if path:
+ 237                logger.info(f"running `{path}` synchronously with {args = }")
+ 238            elif hash_:
+ 239                logger.info(f"running script with hash `{hash_}` synchronously with {args = }")
+ 240
+ 241        if isinstance(timeout, dt.timedelta):
+ 242            timeout = timeout.total_seconds()
+ 243
+ 244        job_id = self._run_script_async_internal(path=path, hash_=hash_, args=args)
+ 245        return self.wait_job(
+ 246            job_id, timeout, verbose, cleanup, assert_result_is_not_none
+ 247        )
+ 248
+ 249    def run_script_by_path(
+ 250        self,
+ 251        path: str,
+ 252        args: dict = None,
+ 253        timeout: dt.timedelta | int | float | None = None,
+ 254        verbose: bool = False,
+ 255        cleanup: bool = True,
+ 256        assert_result_is_not_none: bool = False,
+ 257    ) -> Any:
+ 258        """Run script by path synchronously and return its result."""
+ 259        return self._run_script_internal(
+ 260            path=path, args=args, timeout=timeout, verbose=verbose,
+ 261            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+ 262        )
+ 263
+ 264    def run_script_by_hash(
+ 265        self,
+ 266        hash_: str,
+ 267        args: dict = None,
+ 268        timeout: dt.timedelta | int | float | None = None,
+ 269        verbose: bool = False,
+ 270        cleanup: bool = True,
+ 271        assert_result_is_not_none: bool = False,
+ 272    ) -> Any:
+ 273        """Run script by hash synchronously and return its result."""
+ 274        return self._run_script_internal(
+ 275            hash_=hash_, args=args, timeout=timeout, verbose=verbose,
+ 276            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+ 277        )
+ 278
+ 279    def wait_job(
+ 280        self,
+ 281        job_id,
+ 282        timeout: dt.timedelta | int | float | None = None,
+ 283        verbose: bool = False,
+ 284        cleanup: bool = True,
+ 285        assert_result_is_not_none: bool = False,
+ 286    ):
+ 287        def cancel_job():
+ 288            logger.warning(f"cancelling job: {job_id}")
+ 289            self.post(
+ 290                f"/w/{self.workspace}/jobs_u/queue/cancel/{job_id}",
+ 291                json={"reason": "parent script cancelled"},
+ 292            ).raise_for_status()
+ 293
+ 294        if cleanup:
+ 295            atexit.register(cancel_job)
+ 296
+ 297        start_time = time.time()
+ 298
+ 299        if isinstance(timeout, dt.timedelta):
+ 300            timeout = timeout.total_seconds()
+ 301
+ 302        while True:
+ 303            result_res = self.get(
+ 304                f"/w/{self.workspace}/jobs_u/completed/get_result_maybe/{job_id}", True
+ 305            ).json()
+ 306
+ 307            started = result_res["started"]
+ 308            completed = result_res["completed"]
+ 309            success = result_res["success"]
+ 310
+ 311            if not started and verbose:
+ 312                logger.info(f"job {job_id} has not started yet")
+ 313
+ 314            if cleanup and completed:
+ 315                atexit.unregister(cancel_job)
+ 316
+ 317            if completed:
+ 318                result = result_res["result"]
+ 319                if success:
+ 320                    if result is None and assert_result_is_not_none:
+ 321                        raise Exception("Result was none")
+ 322                    return result
+ 323                else:
+ 324                    error = result["error"]
+ 325                    raise Exception(f"Job {job_id} was not successful: {str(error)}")
+ 326
+ 327            if timeout and ((time.time() - start_time) > timeout):
+ 328                msg = "reached timeout"
+ 329                logger.warning(msg)
+ 330                self.post(
+ 331                    f"/w/{self.workspace}/jobs_u/queue/cancel/{job_id}",
+ 332                    json={"reason": msg},
+ 333                )
+ 334                raise TimeoutError(msg)
+ 335            if verbose:
+ 336                logger.info(f"sleeping 0.5 seconds for {job_id = }")
+ 337
+ 338            time.sleep(0.5)
+ 339
+ 340    def cancel_running(self) -> dict:
+ 341        """Cancel currently running executions of the same script."""
+ 342        logger.info("canceling running executions of this script")
+ 343
+ 344        jobs = self.get(
+ 345            f"/w/{self.workspace}/jobs/list",
+ 346            params={
+ 347                "running": "true",
+ 348                "script_path_exact": self.path,
+ 349            },
+ 350        ).json()
+ 351
+ 352        current_job_id = os.environ.get("WM_JOB_ID")
+ 353
+ 354        logger.debug(f"{current_job_id = }")
+ 355
+ 356        job_ids = [j["id"] for j in jobs if j["id"] != current_job_id]
+ 357
+ 358        if job_ids:
+ 359            logger.info(f"cancelling the following job ids: {job_ids}")
+ 360        else:
+ 361            logger.info("no previous executions to cancel")
+ 362
+ 363        result = {}
+ 364
+ 365        for id_ in job_ids:
+ 366            result[id_] = self.post(
+ 367                f"/w/{self.workspace}/jobs_u/queue/cancel/{id_}",
+ 368                json={"reason": "killed by `cancel_running` method"},
+ 369            )
+ 370
+ 371        return result
+ 372
+ 373    def get_job(self, job_id: str) -> dict:
+ 374        return self.get(f"/w/{self.workspace}/jobs_u/get/{job_id}").json()
+ 375
+ 376    def get_root_job_id(self, job_id: str | None = None) -> dict:
+ 377        job_id = job_id or os.environ.get("WM_JOB_ID")
+ 378        return self.get(f"/w/{self.workspace}/jobs_u/get_root_job_id/{job_id}").json()
+ 379
+ 380    def get_id_token(self, audience: str) -> str:
+ 381        return self.post(f"/w/{self.workspace}/oidc/token/{audience}").text
+ 382
+ 383    def get_job_status(self, job_id: str) -> JobStatus:
+ 384        job = self.get_job(job_id)
+ 385        job_type = job.get("type", "")
+ 386        assert job_type, f"{job} is not a valid job"
+ 387        if job_type.lower() == "completedjob":
+ 388            return "COMPLETED"
+ 389        if job.get("running"):
+ 390            return "RUNNING"
+ 391        return "WAITING"
+ 392
+ 393    def get_result(
+ 394        self,
+ 395        job_id: str,
+ 396        assert_result_is_not_none: bool = True,
+ 397    ) -> Any:
+ 398        result = self.get(f"/w/{self.workspace}/jobs_u/completed/get_result/{job_id}")
+ 399        result_text = result.text
+ 400        if assert_result_is_not_none and result_text is None:
+ 401            raise Exception(f"result is None for {job_id = }")
+ 402        try:
+ 403            return result.json()
+ 404        except JSONDecodeError:
+ 405            return result_text
+ 406
+ 407    def get_variable(self, path: str) -> str:
+ 408        path = parse_variable_syntax(path) or path
+ 409        if self.mocked_api is not None:
+ 410            variables = self.mocked_api["variables"]
+ 411            try:
+ 412                result = variables[path]
+ 413                return result
+ 414            except KeyError:
+ 415                logger.info(
+ 416                    f"MockedAPI present, but variable not found at {path}, falling back to real API"
+ 417                )
+ 418
+ 419        """Get variable from Windmill"""
+ 420        return self.get(f"/w/{self.workspace}/variables/get_value/{path}").json()
+ 421
+ 422    def set_variable(self, path: str, value: str, is_secret: bool = False) -> None:
+ 423        path = parse_variable_syntax(path) or path
+ 424        if self.mocked_api is not None:
+ 425            self.mocked_api["variables"][path] = value
+ 426            return
+ 427
+ 428        """Set variable from Windmill"""
+ 429        # check if variable exists
+ 430        r = self.get(
+ 431            f"/w/{self.workspace}/variables/get/{path}", raise_for_status=False
+ 432        )
+ 433        if r.status_code == 404:
+ 434            # create variable
+ 435            self.post(
+ 436                f"/w/{self.workspace}/variables/create",
+ 437                json={
+ 438                    "path": path,
+ 439                    "value": value,
+ 440                    "is_secret": is_secret,
+ 441                    "description": "",
+ 442                },
+ 443            )
+ 444        else:
+ 445            # update variable
+ 446            self.post(
+ 447                f"/w/{self.workspace}/variables/update/{path}",
+ 448                json={"value": value},
+ 449            )
+ 450
+ 451    def get_resource(
+ 452        self,
+ 453        path: str,
+ 454        none_if_undefined: bool = False,
+ 455    ) -> dict | None:
+ 456        path = parse_resource_syntax(path) or path
+ 457        if self.mocked_api is not None:
+ 458            resources = self.mocked_api["resources"]
+ 459            try:
+ 460                result = resources[path]
+ 461                return result
+ 462            except KeyError:
+ 463                # NOTE: should mocked_api respect `none_if_undefined`?
+ 464                if none_if_undefined:
+ 465                    logger.info(
+ 466                        f"resource not found at ${path}, but none_if_undefined is True, so returning None"
+ 467                    )
+ 468                    return None
+ 469                logger.info(
+ 470                    f"MockedAPI present, but resource not found at ${path}, falling back to real API"
+ 471                )
+ 472
+ 473        """Get resource from Windmill"""
+ 474        try:
+ 475            return self.get(
+ 476                f"/w/{self.workspace}/resources/get_value_interpolated/{path}"
+ 477            ).json()
+ 478        except Exception as e:
+ 479            if none_if_undefined:
+ 480                return None
+ 481            logger.error(e)
+ 482            raise e
+ 483
+ 484    def set_resource(
+ 485        self,
+ 486        value: Any,
+ 487        path: str,
+ 488        resource_type: str,
+ 489    ):
+ 490        path = parse_resource_syntax(path) or path
+ 491        if self.mocked_api is not None:
+ 492            self.mocked_api["resources"][path] = value
+ 493            return
+ 494
+ 495        # check if resource exists
+ 496        r = self.get(
+ 497            f"/w/{self.workspace}/resources/get/{path}", raise_for_status=False
+ 498        )
+ 499        if r.status_code == 404:
+ 500            # create resource
+ 501            self.post(
+ 502                f"/w/{self.workspace}/resources/create",
+ 503                json={
+ 504                    "path": path,
+ 505                    "value": value,
+ 506                    "resource_type": resource_type,
+ 507                },
+ 508            )
+ 509        else:
+ 510            # update resource
+ 511            self.post(
+ 512                f"/w/{self.workspace}/resources/update_value/{path}",
+ 513                json={"value": value},
+ 514            )
+ 515
+ 516    def set_state(self, value: Any):
+ 517        self.set_resource(value, path=self.state_path, resource_type="state")
+ 518
+ 519    def set_progress(self, value: int, job_id: Optional[str] = None):
+ 520        workspace = get_workspace()
+ 521        flow_id = os.environ.get("WM_FLOW_JOB_ID")
+ 522        job_id = job_id or os.environ.get("WM_JOB_ID")
+ 523
+ 524        if job_id != None:
+ 525            job = self.get_job(job_id)
+ 526            flow_id = job.get("parent_job")
+ 527
+ 528        self.post(
+ 529            f"/w/{workspace}/job_metrics/set_progress/{job_id}",
+ 530            json={
+ 531                "percent": value,
+ 532                "flow_job_id": flow_id or None,
+ 533            },
+ 534        )
+ 535
+ 536    def get_progress(self, job_id: Optional[str] = None) -> Any:
+ 537        workspace = get_workspace()
+ 538        job_id = job_id or os.environ.get("WM_JOB_ID")
+ 539
+ 540        r = self.get(
+ 541            f"/w/{workspace}/job_metrics/get_progress/{job_id}",
+ 542        )
+ 543        if r.status_code == 404:
+ 544            print(f"Job {job_id} does not exist")
+ 545            return None
+ 546        else:
+ 547            return r.json()
+ 548
+ 549    def set_flow_user_state(self, key: str, value: Any) -> None:
+ 550        """Set the user state of a flow at a given key"""
+ 551        flow_id = self.get_root_job_id()
+ 552        r = self.post(
+ 553            f"/w/{self.workspace}/jobs/flow/user_states/{flow_id}/{key}",
+ 554            json=value,
+ 555            raise_for_status=False,
+ 556        )
+ 557        if r.status_code == 404:
+ 558            print(f"Job {flow_id} does not exist or is not a flow")
+ 559
+ 560    def get_flow_user_state(self, key: str) -> Any:
+ 561        """Get the user state of a flow at a given key"""
+ 562        flow_id = self.get_root_job_id()
+ 563        r = self.get(
+ 564            f"/w/{self.workspace}/jobs/flow/user_states/{flow_id}/{key}",
+ 565            raise_for_status=False,
+ 566        )
+ 567        if r.status_code == 404:
+ 568            print(f"Job {flow_id} does not exist or is not a flow")
+ 569            return None
+ 570        else:
+ 571            return r.json()
+ 572
+ 573    @property
+ 574    def version(self):
+ 575        return self.get("version").text
+ 576
+ 577    def get_duckdb_connection_settings(
+ 578        self,
+ 579        s3_resource_path: str = "",
+ 580    ) -> DuckDbConnectionSettings | None:
+ 581        """
+ 582        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+ 583        initiate an S3 connection from DuckDB
+ 584        """
+ 585        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+ 586        try:
+ 587            raw_obj = self.post(
+ 588                f"/w/{self.workspace}/job_helpers/v2/duckdb_connection_settings",
+ 589                json={}
+ 590                if s3_resource_path == ""
+ 591                else {"s3_resource_path": s3_resource_path},
+ 592            ).json()
+ 593            return DuckDbConnectionSettings(raw_obj)
+ 594        except JSONDecodeError as e:
+ 595            raise Exception(
+ 596                "Could not generate DuckDB S3 connection settings from the provided resource"
+ 597            ) from e
+ 598
+ 599    def get_polars_connection_settings(
+ 600        self,
+ 601        s3_resource_path: str = "",
+ 602    ) -> PolarsConnectionSettings:
+ 603        """
+ 604        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+ 605        initiate an S3 connection from Polars
+ 606        """
+ 607        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+ 608        try:
+ 609            raw_obj = self.post(
+ 610                f"/w/{self.workspace}/job_helpers/v2/polars_connection_settings",
+ 611                json={}
+ 612                if s3_resource_path == ""
+ 613                else {"s3_resource_path": s3_resource_path},
+ 614            ).json()
+ 615            return PolarsConnectionSettings(raw_obj)
+ 616        except JSONDecodeError as e:
+ 617            raise Exception(
+ 618                "Could not generate Polars S3 connection settings from the provided resource"
+ 619            ) from e
+ 620
+ 621    def get_boto3_connection_settings(
+ 622        self,
+ 623        s3_resource_path: str = "",
+ 624    ) -> Boto3ConnectionSettings:
+ 625        """
+ 626        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+ 627        initiate an S3 connection using boto3
+ 628        """
+ 629        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+ 630        try:
+ 631            s3_resource = self.post(
+ 632                f"/w/{self.workspace}/job_helpers/v2/s3_resource_info",
+ 633                json={}
+ 634                if s3_resource_path == ""
+ 635                else {"s3_resource_path": s3_resource_path},
+ 636            ).json()
+ 637            return self.__boto3_connection_settings(s3_resource)
+ 638        except JSONDecodeError as e:
+ 639            raise Exception(
+ 640                "Could not generate Boto3 S3 connection settings from the provided resource"
+ 641            ) from e
+ 642
+ 643    def load_s3_file(self, s3object: S3Object | str, s3_resource_path: str | None) -> bytes:
+ 644        """
+ 645        Load a file from the workspace s3 bucket and returns its content as bytes.
+ 646
+ 647        '''python
+ 648        from wmill import S3Object
+ 649
+ 650        s3_obj = S3Object(s3="/path/to/my_file.txt")
+ 651        my_obj_content = client.load_s3_file(s3_obj)
+ 652        file_content = my_obj_content.decode("utf-8")
+ 653        '''
+ 654        """
+ 655        s3object = parse_s3_object(s3object)
+ 656        with self.load_s3_file_reader(s3object, s3_resource_path) as file_reader:
+ 657            return file_reader.read()
+ 658
+ 659    def load_s3_file_reader(
+ 660        self, s3object: S3Object | str, s3_resource_path: str | None
+ 661    ) -> BufferedReader:
+ 662        """
+ 663        Load a file from the workspace s3 bucket and returns the bytes stream.
+ 664
+ 665        '''python
+ 666        from wmill import S3Object
+ 667
+ 668        s3_obj = S3Object(s3="/path/to/my_file.txt")
+ 669        with wmill.load_s3_file_reader(s3object, s3_resource_path) as file_reader:
+ 670            print(file_reader.read())
+ 671        '''
+ 672        """
+ 673        s3object = parse_s3_object(s3object)
+ 674        reader = S3BufferedReader(
+ 675            f"{self.workspace}",
+ 676            self.client,
+ 677            s3object["s3"],
+ 678            s3_resource_path,
+ 679            s3object["storage"] if "storage" in s3object else None,
+ 680        )
+ 681        return reader
+ 682
+ 683    def write_s3_file(
+ 684        self,
+ 685        s3object: S3Object | str | None,
+ 686        file_content: BufferedReader | bytes,
+ 687        s3_resource_path: str | None,
+ 688        content_type: str | None = None,
+ 689        content_disposition: str | None = None,
+ 690    ) -> S3Object:
+ 691        """
+ 692        Write a file to the workspace S3 bucket
+ 693
+ 694        '''python
+ 695        from wmill import S3Object
+ 696
+ 697        s3_obj = S3Object(s3="/path/to/my_file.txt")
+ 698
+ 699        # for an in memory bytes array:
+ 700        file_content = b'Hello Windmill!'
+ 701        client.write_s3_file(s3_obj, file_content)
+ 702
+ 703        # for a file:
+ 704        with open("my_file.txt", "rb") as my_file:
+ 705            client.write_s3_file(s3_obj, my_file)
+ 706        '''
+ 707        """
+ 708        s3object = parse_s3_object(s3object)
+ 709        # httpx accepts either bytes or "a bytes generator" as content. If it's a BufferedReader, we need to convert it to a generator
+ 710        if isinstance(file_content, BufferedReader):
+ 711            content_payload = bytes_generator(file_content)
+ 712        elif isinstance(file_content, bytes):
+ 713            content_payload = file_content
+ 714        else:
+ 715            raise Exception("Type of file_content not supported")
+ 716
+ 717        query_params = {}
+ 718        if s3object is not None and s3object["s3"] != "":
+ 719            query_params["file_key"] = s3object["s3"]
+ 720        if s3_resource_path is not None and s3_resource_path != "":
+ 721            query_params["s3_resource_path"] = s3_resource_path
+ 722        if (
+ 723            s3object is not None
+ 724            and "storage" in s3object
+ 725            and s3object["storage"] is not None
+ 726        ):
+ 727            query_params["storage"] = s3object["storage"]
+ 728        if content_type is not None:
+ 729            query_params["content_type"] = content_type
+ 730        if content_disposition is not None:
+ 731            query_params["content_disposition"] = content_disposition
+ 732
+ 733        try:
+ 734            # need a vanilla client b/c content-type is not application/json here
+ 735            response = httpx.post(
+ 736                f"{self.base_url}/w/{self.workspace}/job_helpers/upload_s3_file",
+ 737                headers={
+ 738                    "Authorization": f"Bearer {self.token}",
+ 739                    "Content-Type": "application/octet-stream",
+ 740                },
+ 741                params=query_params,
+ 742                content=content_payload,
+ 743                verify=self.verify,
+ 744                timeout=None,
+ 745            ).json()
+ 746        except Exception as e:
+ 747            raise Exception("Could not write file to S3") from e
+ 748        return S3Object(s3=response["file_key"])
+ 749
+ 750    def sign_s3_objects(self, s3_objects: list[S3Object | str]) -> list[S3Object]:
+ 751        return self.post(
+ 752            f"/w/{self.workspace}/apps/sign_s3_objects", json={"s3_objects": list(map(parse_s3_object, s3_objects))}
+ 753        ).json()
+ 754
+ 755    def sign_s3_object(self, s3_object: S3Object | str) -> S3Object:
+ 756        return self.post(
+ 757            f"/w/{self.workspace}/apps/sign_s3_objects",
+ 758            json={"s3_objects": [s3_object]},
+ 759        ).json()[0]
+ 760
+ 761    def __boto3_connection_settings(self, s3_resource) -> Boto3ConnectionSettings:
+ 762        endpoint_url_prefix = "https://" if s3_resource["useSSL"] else "http://"
+ 763        return Boto3ConnectionSettings(
+ 764            {
+ 765                "endpoint_url": "{}{}".format(
+ 766                    endpoint_url_prefix, s3_resource["endPoint"]
+ 767                ),
+ 768                "region_name": s3_resource["region"],
+ 769                "use_ssl": s3_resource["useSSL"],
+ 770                "aws_access_key_id": s3_resource["accessKey"],
+ 771                "aws_secret_access_key": s3_resource["secretKey"],
+ 772                # no need for path_style here as boto3 is clever enough to determine which one to use
+ 773            }
+ 774        )
+ 775
+ 776    def whoami(self) -> dict:
+ 777        return self.get("/users/whoami").json()
+ 778
+ 779    @property
+ 780    def user(self) -> dict:
+ 781        return self.whoami()
+ 782
+ 783    @property
+ 784    def state_path(self) -> str:
+ 785        state_path = os.environ.get(
+ 786            "WM_STATE_PATH_NEW", os.environ.get("WM_STATE_PATH")
+ 787        )
+ 788        if state_path is None:
+ 789            raise Exception("State path not found")
+ 790        return state_path
+ 791
+ 792    @property
+ 793    def state(self) -> Any:
+ 794        return self.get_resource(path=self.state_path, none_if_undefined=True)
+ 795
+ 796    @state.setter
+ 797    def state(self, value: Any) -> None:
+ 798        self.set_state(value)
+ 799
+ 800    @staticmethod
+ 801    def set_shared_state_pickle(value: Any, path: str = "state.pickle") -> None:
+ 802        """
+ 803        Set the state in the shared folder using pickle
+ 804        """
+ 805        import pickle
+ 806
+ 807        with open(f"/shared/{path}", "wb") as handle:
+ 808            pickle.dump(value, handle, protocol=pickle.HIGHEST_PROTOCOL)
+ 809
+ 810    @staticmethod
+ 811    def get_shared_state_pickle(path: str = "state.pickle") -> Any:
+ 812        """
+ 813        Get the state in the shared folder using pickle
+ 814        """
+ 815        import pickle
+ 816
+ 817        with open(f"/shared/{path}", "rb") as handle:
+ 818            return pickle.load(handle)
+ 819
+ 820    @staticmethod
+ 821    def set_shared_state(value: Any, path: str = "state.json") -> None:
+ 822        """
+ 823        Set the state in the shared folder using pickle
+ 824        """
+ 825        import json
+ 826
+ 827        with open(f"/shared/{path}", "w", encoding="utf-8") as f:
+ 828            json.dump(value, f, ensure_ascii=False, indent=4)
+ 829
+ 830    @staticmethod
+ 831    def get_shared_state(path: str = "state.json") -> None:
+ 832        """
+ 833        Get the state in the shared folder using pickle
+ 834        """
+ 835        import json
+ 836
+ 837        with open(f"/shared/{path}", "r", encoding="utf-8") as f:
+ 838            return json.load(f)
+ 839
+ 840    def get_resume_urls(self, approver: str = None) -> dict:
+ 841        nonce = random.randint(0, 1000000000)
+ 842        job_id = os.environ.get("WM_JOB_ID") or "NO_ID"
+ 843        return self.get(
+ 844            f"/w/{self.workspace}/jobs/resume_urls/{job_id}/{nonce}",
+ 845            params={"approver": approver},
+ 846        ).json()
+ 847
+ 848    def request_interactive_slack_approval(
+ 849        self,
+ 850        slack_resource_path: str,
+ 851        channel_id: str,
+ 852        message: str = None,
+ 853        approver: str = None,
+ 854        default_args_json: dict = None,
+ 855        dynamic_enums_json: dict = None,
+ 856    ) -> None:
+ 857        """
+ 858        Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
+ 859
+ 860        **[Enterprise Edition Only]** To include form fields in the Slack approval request, use the "Advanced -> Suspend -> Form" functionality.
+ 861        Learn more at: https://www.windmill.dev/docs/flows/flow_approval#form
+ 862
+ 863        :param slack_resource_path: The path to the Slack resource in Windmill.
+ 864        :type slack_resource_path: str
+ 865        :param channel_id: The Slack channel ID where the approval request will be sent.
+ 866        :type channel_id: str
+ 867        :param message: Optional custom message to include in the Slack approval request.
+ 868        :type message: str, optional
+ 869        :param approver: Optional user ID or name of the approver for the request.
+ 870        :type approver: str, optional
+ 871        :param default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
+ 872        :type default_args_json: dict, optional
+ 873        :param dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
+ 874        :type dynamic_enums_json: dict, optional
+ 875
+ 876        :raises Exception: If the function is not called within a flow or flow preview.
+ 877        :raises Exception: If the required flow job or flow step environment variables are not set.
+ 878
+ 879        :return: None
+ 880
+ 881        **Usage Example:**
+ 882            >>> client.request_interactive_slack_approval(
+ 883            ...     slack_resource_path="/u/alex/my_slack_resource",
+ 884            ...     channel_id="admins-slack-channel",
+ 885            ...     message="Please approve this request",
+ 886            ...     approver="approver123",
+ 887            ...     default_args_json={"key1": "value1", "key2": 42},
+ 888            ...     dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]},
+ 889            ... )
+ 890
+ 891        **Notes:**
+ 892        - This function must be executed within a Windmill flow or flow preview.
+ 893        - The function checks for required environment variables (`WM_FLOW_JOB_ID`, `WM_FLOW_STEP_ID`) to ensure it is run in the appropriate context.
+ 894        """
+ 895        workspace = self.workspace
+ 896        flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
+ 897
+ 898        if not flow_job_id:
+ 899            raise Exception(
+ 900                "You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
+ 901            )
+ 902
+ 903        # Only include non-empty parameters
+ 904        params = {}
+ 905        if message:
+ 906            params["message"] = message
+ 907        if approver:
+ 908            params["approver"] = approver
+ 909        if slack_resource_path:
+ 910            params["slack_resource_path"] = slack_resource_path
+ 911        if channel_id:
+ 912            params["channel_id"] = channel_id
+ 913        if os.environ.get("WM_FLOW_STEP_ID"):
+ 914            params["flow_step_id"] = os.environ.get("WM_FLOW_STEP_ID")
+ 915        if default_args_json:
+ 916            params["default_args_json"] = json.dumps(default_args_json)
+ 917        if dynamic_enums_json:
+ 918            params["dynamic_enums_json"] = json.dumps(dynamic_enums_json)
+ 919
+ 920        self.get(
+ 921            f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}",
+ 922            params=params,
+ 923        )
+ 924
+ 925    def username_to_email(self, username: str) -> str:
+ 926        """
+ 927        Get email from workspace username
+ 928        This method is particularly useful for apps that require the email address of the viewer.
+ 929        Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.
+ 930        """
+ 931        return self.get(f"/w/{self.workspace}/users/username_to_email/{username}").text
+ 932
+ 933    def send_teams_message(
+ 934        self,
+ 935        conversation_id: str,
+ 936        text: str,
+ 937        success: bool = True,
+ 938        card_block: dict = None,
+ 939    ):
+ 940        """
+ 941        Send a message to a Microsoft Teams conversation with conversation_id, where success is used to style the message
+ 942        """
+ 943        return self.post(
+ 944            f"/teams/activities",
+ 945            json={
+ 946                "conversation_id": conversation_id,
+ 947                "text": text,
+ 948                "success": success,
+ 949                "card_block": card_block,
+ 950            },
+ 951        )
+ 952
+ 953
+ 954def init_global_client(f):
+ 955    @functools.wraps(f)
+ 956    def wrapper(*args, **kwargs):
+ 957        global _client
+ 958        if _client is None:
+ 959            _client = Windmill()
+ 960        return f(*args, **kwargs)
+ 961
+ 962    return wrapper
+ 963
+ 964
+ 965def deprecate(in_favor_of: str):
+ 966    def decorator(f):
+ 967        @functools.wraps(f)
+ 968        def wrapper(*args, **kwargs):
+ 969            warnings.warn(
+ 970                (
+ 971                    f"The '{f.__name__}' method is deprecated and may be removed in the future. "
+ 972                    f"Consider {in_favor_of}"
+ 973                ),
+ 974                DeprecationWarning,
+ 975            )
+ 976            return f(*args, **kwargs)
+ 977
+ 978        return wrapper
+ 979
+ 980    return decorator
+ 981
+ 982
+ 983@init_global_client
+ 984def get_workspace() -> str:
+ 985    return _client.workspace
+ 986
+ 987
+ 988@init_global_client
+ 989def get_root_job_id(job_id: str | None = None) -> str:
+ 990    return _client.get_root_job_id(job_id)
+ 991
+ 992
+ 993@init_global_client
+ 994@deprecate("Windmill().version")
+ 995def get_version() -> str:
+ 996    return _client.version
+ 997
+ 998
+ 999@init_global_client
+1000def run_script_async(
+1001    hash_or_path: str,
+1002    args: Dict[str, Any] = None,
+1003    scheduled_in_secs: int = None,
+1004) -> str:
+1005    is_path = "/" in hash_or_path
+1006    hash_ = None if is_path else hash_or_path
+1007    path = hash_or_path if is_path else None
+1008    return _client.run_script_async(
+1009        hash_=hash_,
+1010        path=path,
+1011        args=args,
+1012        scheduled_in_secs=scheduled_in_secs,
+1013    )
+1014
+1015
+1016@init_global_client
+1017def run_flow_async(
+1018    path: str,
+1019    args: Dict[str, Any] = None,
+1020    scheduled_in_secs: int = None,
+1021    # can only be set to false if this the job will be fully await and not concurrent with any other job
+1022    # as otherwise the child flow and its own child will store their state in the parent job which will
+1023    # lead to incorrectness and failures
+1024    do_not_track_in_parent: bool = True,
+1025) -> str:
+1026    return _client.run_flow_async(
+1027        path=path,
+1028        args=args,
+1029        scheduled_in_secs=scheduled_in_secs,
+1030        do_not_track_in_parent=do_not_track_in_parent,
+1031    )
+1032
+1033
+1034@init_global_client
+1035def run_script_sync(
+1036    hash: str,
+1037    args: Dict[str, Any] = None,
+1038    verbose: bool = False,
+1039    assert_result_is_not_none: bool = True,
+1040    cleanup: bool = True,
+1041    timeout: dt.timedelta = None,
+1042) -> Any:
+1043    return _client.run_script(
+1044        hash_=hash,
+1045        args=args,
+1046        verbose=verbose,
+1047        assert_result_is_not_none=assert_result_is_not_none,
+1048        cleanup=cleanup,
+1049        timeout=timeout,
+1050    )
+1051
+1052
+1053@init_global_client
+1054def run_script_by_path_async(
+1055    path: str,
+1056    args: Dict[str, Any] = None,
+1057    scheduled_in_secs: Union[None, int] = None,
+1058) -> str:
+1059    return _client.run_script_by_path_async(
+1060        path=path,
+1061        args=args,
+1062        scheduled_in_secs=scheduled_in_secs,
+1063    )
+1064
+1065
+1066@init_global_client
+1067def run_script_by_hash_async(
+1068    hash_: str,
+1069    args: Dict[str, Any] = None,
+1070    scheduled_in_secs: Union[None, int] = None,
+1071) -> str:
+1072    return _client.run_script_by_hash_async(
+1073        hash_=hash_,
+1074        args=args,
+1075        scheduled_in_secs=scheduled_in_secs,
+1076    )
+1077
+1078
+1079@init_global_client
+1080def run_script_by_path_sync(
+1081    path: str,
+1082    args: Dict[str, Any] = None,
+1083    verbose: bool = False,
+1084    assert_result_is_not_none: bool = True,
+1085    cleanup: bool = True,
+1086    timeout: dt.timedelta = None,
+1087) -> Any:
+1088    return _client.run_script(
+1089        path=path,
+1090        args=args,
+1091        verbose=verbose,
+1092        assert_result_is_not_none=assert_result_is_not_none,
+1093        cleanup=cleanup,
+1094        timeout=timeout,
+1095    )
+1096
+1097
+1098@init_global_client
+1099def get_id_token(audience: str) -> str:
+1100    """
+1101    Get a JWT token for the given audience for OIDC purposes to login into third parties like AWS, Vault, GCP, etc.
+1102    """
+1103    return _client.get_id_token(audience)
+1104
+1105
+1106@init_global_client
+1107def get_job_status(job_id: str) -> JobStatus:
+1108    return _client.get_job_status(job_id)
+1109
+1110
+1111@init_global_client
+1112def get_result(job_id: str, assert_result_is_not_none=True) -> Dict[str, Any]:
+1113    return _client.get_result(
+1114        job_id=job_id, assert_result_is_not_none=assert_result_is_not_none
+1115    )
+1116
+1117
+1118@init_global_client
+1119def duckdb_connection_settings(s3_resource_path: str = "") -> DuckDbConnectionSettings:
+1120    """
+1121    Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+1122    initiate an S3 connection from DuckDB
+1123    """
+1124    return _client.get_duckdb_connection_settings(s3_resource_path)
+1125
+1126
+1127@init_global_client
+1128def polars_connection_settings(s3_resource_path: str = "") -> PolarsConnectionSettings:
+1129    """
+1130    Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+1131    initiate an S3 connection from Polars
+1132    """
+1133    return _client.get_polars_connection_settings(s3_resource_path)
+1134
+1135
+1136@init_global_client
+1137def boto3_connection_settings(s3_resource_path: str = "") -> Boto3ConnectionSettings:
+1138    """
+1139    Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+1140    initiate an S3 connection using boto3
+1141    """
+1142    return _client.get_boto3_connection_settings(s3_resource_path)
+1143
+1144
+1145@init_global_client
+1146def load_s3_file(s3object: S3Object | str, s3_resource_path: str | None = None) -> bytes:
+1147    """
+1148    Load the entire content of a file stored in S3 as bytes
+1149    """
+1150    return _client.load_s3_file(
+1151        s3object, s3_resource_path if s3_resource_path != "" else None
+1152    )
+1153
+1154
+1155@init_global_client
+1156def load_s3_file_reader(
+1157    s3object: S3Object | str, s3_resource_path: str | None = None
+1158) -> BufferedReader:
+1159    """
+1160    Load the content of a file stored in S3
+1161    """
+1162    return _client.load_s3_file_reader(
+1163        s3object, s3_resource_path if s3_resource_path != "" else None
+1164    )
+1165
+1166
+1167@init_global_client
+1168def write_s3_file(
+1169    s3object: S3Object | str | None,
+1170    file_content: BufferedReader | bytes,
+1171    s3_resource_path: str | None = None,
+1172    content_type: str | None = None,
+1173    content_disposition: str | None = None,
+1174) -> S3Object:
+1175    """
+1176    Upload a file to S3
+1177
+1178    Content type will be automatically guessed from path extension if left empty
+1179
+1180    See MDN for content_disposition: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
+1181    and content_type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
+1182
+1183    """
+1184    return _client.write_s3_file(
+1185        s3object,
+1186        file_content,
+1187        s3_resource_path if s3_resource_path != "" else None,
+1188        content_type,
+1189        content_disposition,
+1190    )
+1191
+1192
+1193@init_global_client
+1194def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object]:
+1195    """
+1196    Sign S3 objects to be used by anonymous users in public apps
+1197    Returns a list of signed s3 tokens
+1198    """
+1199    return _client.sign_s3_objects(s3_objects)
+1200
+1201
+1202@init_global_client
+1203def sign_s3_object(s3_object: S3Object| str) -> S3Object:
+1204    """
+1205    Sign S3 object to be used by anonymous users in public apps
+1206    Returns a signed s3 object
+1207    """
+1208    return _client.sign_s3_object(s3_object)
+1209
+1210
+1211@init_global_client
+1212def whoami() -> dict:
+1213    """
+1214    Returns the current user
+1215    """
+1216    return _client.user
+1217
+1218
+1219@init_global_client
+1220@deprecate("Windmill().state")
+1221def get_state() -> Any:
+1222    """
+1223    Get the state
+1224    """
+1225    return _client.state
+1226
+1227
+1228@init_global_client
+1229def get_resource(
+1230    path: str,
+1231    none_if_undefined: bool = False,
+1232) -> dict | None:
+1233    """Get resource from Windmill"""
+1234    return _client.get_resource(path, none_if_undefined)
+1235
+1236
+1237@init_global_client
+1238def set_resource(path: str, value: Any, resource_type: str = "any") -> None:
+1239    """
+1240    Set the resource at a given path as a string, creating it if it does not exist
+1241    """
+1242    return _client.set_resource(value=value, path=path, resource_type=resource_type)
+1243
+1244
+1245@init_global_client
+1246def set_state(value: Any) -> None:
+1247    """
+1248    Set the state
+1249    """
+1250    return _client.set_state(value)
+1251
+1252
+1253@init_global_client
+1254def set_progress(value: int, job_id: Optional[str] = None) -> None:
+1255    """
+1256    Set the progress
+1257    """
+1258    return _client.set_progress(value, job_id)
+1259
+1260
+1261@init_global_client
+1262def get_progress(job_id: Optional[str] = None) -> Any:
+1263    """
+1264    Get the progress
+1265    """
+1266    return _client.get_progress(job_id)
+1267
+1268
+1269def set_shared_state_pickle(value: Any, path="state.pickle") -> None:
+1270    """
+1271    Set the state in the shared folder using pickle
+1272    """
+1273    return Windmill.set_shared_state_pickle(value=value, path=path)
+1274
+1275
+1276@deprecate("Windmill.get_shared_state_pickle(...)")
+1277def get_shared_state_pickle(path="state.pickle") -> Any:
+1278    """
+1279    Get the state in the shared folder using pickle
+1280    """
+1281    return Windmill.get_shared_state_pickle(path=path)
+1282
+1283
+1284def set_shared_state(value: Any, path="state.json") -> None:
+1285    """
+1286    Set the state in the shared folder using pickle
+1287    """
+1288    return Windmill.set_shared_state(value=value, path=path)
+1289
+1290
+1291def get_shared_state(path="state.json") -> None:
+1292    """
+1293    Get the state in the shared folder using pickle
+1294    """
+1295    return Windmill.get_shared_state(path=path)
+1296
+1297
+1298@init_global_client
+1299def get_variable(path: str) -> str:
+1300    """
+1301    Returns the variable at a given path as a string
+1302    """
+1303    return _client.get_variable(path)
+1304
+1305
+1306@init_global_client
+1307def set_variable(path: str, value: str, is_secret: bool = False) -> None:
+1308    """
+1309    Set the variable at a given path as a string, creating it if it does not exist
+1310    """
+1311    return _client.set_variable(path, value, is_secret)
+1312
+1313
+1314@init_global_client
+1315def get_flow_user_state(key: str) -> Any:
+1316    """
+1317    Get the user state of a flow at a given key
+1318    """
+1319    return _client.get_flow_user_state(key)
+1320
+1321
+1322@init_global_client
+1323def set_flow_user_state(key: str, value: Any) -> None:
+1324    """
+1325    Set the user state of a flow at a given key
+1326    """
+1327    return _client.set_flow_user_state(key, value)
+1328
+1329
+1330@init_global_client
+1331def get_state_path() -> str:
+1332    return _client.state_path
+1333
+1334
+1335@init_global_client
+1336def get_resume_urls(approver: str = None) -> dict:
+1337    return _client.get_resume_urls(approver)
+1338
+1339
+1340@init_global_client
+1341def request_interactive_slack_approval(
+1342    slack_resource_path: str,
+1343    channel_id: str,
+1344    message: str = None,
+1345    approver: str = None,
+1346    default_args_json: dict = None,
+1347    dynamic_enums_json: dict = None,
+1348) -> None:
+1349    return _client.request_interactive_slack_approval(
+1350        slack_resource_path=slack_resource_path,
+1351        channel_id=channel_id,
+1352        message=message,
+1353        approver=approver,
+1354        default_args_json=default_args_json,
+1355        dynamic_enums_json=dynamic_enums_json,
+1356    )
+1357
+1358
+1359@init_global_client
+1360def send_teams_message(
+1361    conversation_id: str, text: str, success: bool, card_block: dict = None
+1362):
+1363    return _client.send_teams_message(conversation_id, text, success, card_block)
+1364
+1365
+1366@init_global_client
+1367def cancel_running() -> dict:
+1368    """Cancel currently running executions of the same script."""
+1369    return _client.cancel_running()
+1370
+1371
+1372@init_global_client
+1373def run_script(
+1374    path: str = None,
+1375    hash_: str = None,
+1376    args: dict = None,
+1377    timeout: dt.timedelta | int | float = None,
+1378    verbose: bool = False,
+1379    cleanup: bool = True,
+1380    assert_result_is_not_none: bool = True,
+1381) -> Any:
+1382    """Run script synchronously and return its result.
+1383    
+1384    .. deprecated:: Use run_script_by_path or run_script_by_hash instead.
+1385    """
+1386    return _client.run_script(
+1387        path=path,
+1388        hash_=hash_,
+1389        args=args,
+1390        verbose=verbose,
+1391        assert_result_is_not_none=assert_result_is_not_none,
+1392        cleanup=cleanup,
+1393        timeout=timeout,
+1394    )
+1395
+1396
+1397@init_global_client
+1398def run_script_by_path(
+1399    path: str,
+1400    args: dict = None,
+1401    timeout: dt.timedelta | int | float = None,
+1402    verbose: bool = False,
+1403    cleanup: bool = True,
+1404    assert_result_is_not_none: bool = True,
+1405) -> Any:
+1406    """Run script by path synchronously and return its result."""
+1407    return _client.run_script_by_path(
+1408        path=path,
+1409        args=args,
+1410        verbose=verbose,
+1411        assert_result_is_not_none=assert_result_is_not_none,
+1412        cleanup=cleanup,
+1413        timeout=timeout,
+1414    )
+1415
+1416
+1417@init_global_client
+1418def run_script_by_hash(
+1419    hash_: str,
+1420    args: dict = None,
+1421    timeout: dt.timedelta | int | float = None,
+1422    verbose: bool = False,
+1423    cleanup: bool = True,
+1424    assert_result_is_not_none: bool = True,
+1425) -> Any:
+1426    """Run script by hash synchronously and return its result."""
+1427    return _client.run_script_by_hash(
+1428        hash_=hash_,
+1429        args=args,
+1430        verbose=verbose,
+1431        assert_result_is_not_none=assert_result_is_not_none,
+1432        cleanup=cleanup,
+1433        timeout=timeout,
+1434    )
+1435
+1436
+1437@init_global_client
+1438def username_to_email(username: str) -> str:
+1439    """
+1440    Get email from workspace username
+1441    This method is particularly useful for apps that require the email address of the viewer.
+1442    Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.
+1443    """
+1444    return _client.username_to_email(username)
+1445
+1446
+1447def task(*args, **kwargs):
+1448    from inspect import signature
+1449
+1450    def f(func, tag: str | None = None):
+1451        if (
+1452            os.environ.get("WM_JOB_ID") is None
+1453            or os.environ.get("MAIN_OVERRIDE") == func.__name__
+1454        ):
+1455
+1456            def inner(*args, **kwargs):
+1457                return func(*args, **kwargs)
+1458
+1459            return inner
+1460        else:
+1461
+1462            def inner(*args, **kwargs):
+1463                global _client
+1464                if _client is None:
+1465                    _client = Windmill()
+1466                w_id = os.environ.get("WM_WORKSPACE")
+1467                job_id = os.environ.get("WM_JOB_ID")
+1468                f_name = func.__name__
+1469                json = kwargs
+1470                params = list(signature(func).parameters)
+1471                for i, arg in enumerate(args):
+1472                    if i < len(params):
+1473                        p = params[i]
+1474                        key = p
+1475                        if key not in kwargs:
+1476                            json[key] = arg
+1477
+1478                params = {}
+1479                if tag is not None:
+1480                    params["tag"] = tag
+1481                w_as_code_response = _client.post(
+1482                    f"/w/{w_id}/jobs/run/workflow_as_code/{job_id}/{f_name}",
+1483                    json={"args": json},
+1484                    params=params,
+1485                )
+1486                job_id = w_as_code_response.text
+1487                print(f"Executing task {func.__name__} on job {job_id}")
+1488                job_result = _client.wait_job(job_id)
+1489                print(f"Task {func.__name__} ({job_id}) completed")
+1490                return job_result
+1491
+1492            return inner
+1493
+1494    if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
+1495        return f(args[0], None)
+1496    else:
+1497        return lambda x: f(x, kwargs.get("tag"))
+1498
+1499def parse_resource_syntax(s: str) -> Optional[str]:
+1500    """Parse resource syntax from string."""
+1501    if s is None:
+1502        return None
+1503    if s.startswith("$res:"):
+1504        return s[5:]
+1505    if s.startswith("res://"):
+1506        return s[6:]
+1507    return None
+1508
+1509def parse_s3_object(s3_object: S3Object | str) -> S3Object:
+1510    """Parse S3 object from string or S3Object format."""
+1511    if isinstance(s3_object, str):
+1512        match = re.match(r'^s3://([^/]*)/(.*)$', s3_object)
+1513        if match:
+1514            return S3Object(s3=match.group(2) or "", storage=match.group(1) or None)
+1515        return S3Object(s3="")
+1516    else:
+1517        return s3_object
+1518
+1519    
+1520
+1521def parse_variable_syntax(s: str) -> Optional[str]:
+1522    """Parse variable syntax from string."""
+1523    if s.startswith("var://"):
+1524        return s[6:]
+1525    return None
+1526
+1527
+1528def append_to_result_stream(text: str) -> None:
+1529    """Append a text to the result stream.
+1530    
+1531    Args:
+1532        text: text to append to the result stream
+1533    """
+1534    print("WM_STREAM: {}".format(text.replace(chr(10), '\\n')))
+1535
+1536def stream_result(stream) -> None:
+1537    """Stream to the result stream.
+1538    
+1539    Args:
+1540        stream: stream to stream to the result stream
+1541    """
+1542    for text in stream:
+1543        append_to_result_stream(text)
+
+ + +
+
+
+ logger = +<Logger windmill_client (WARNING)> + + +
+ + + + +
+
+
+ JobStatus = +typing.Literal['RUNNING', 'WAITING', 'COMPLETED'] + + +
+ + + + +
+
+ +
+ + class + Windmill: + + + +
+ +
 35class Windmill:
+ 36    def __init__(self, base_url=None, token=None, workspace=None, verify=True):
+ 37        base = (
+ 38            base_url
+ 39            or os.environ.get("BASE_INTERNAL_URL")
+ 40            or os.environ.get("WM_BASE_URL")
+ 41        )
+ 42
+ 43        self.base_url = f"{base}/api"
+ 44        self.token = token or os.environ.get("WM_TOKEN")
+ 45        self.headers = {
+ 46            "Content-Type": "application/json",
+ 47            "Authorization": f"Bearer {self.token}",
+ 48        }
+ 49        self.verify = verify
+ 50        self.client = self.get_client()
+ 51        self.workspace = workspace or os.environ.get("WM_WORKSPACE")
+ 52        self.path = os.environ.get("WM_JOB_PATH")
+ 53
+ 54        self.mocked_api = self.get_mocked_api()
+ 55
+ 56        assert self.workspace, (
+ 57            f"workspace required as an argument or as WM_WORKSPACE environment variable"
+ 58        )
+ 59
+ 60    def get_mocked_api(self) -> Optional[dict]:
+ 61        mocked_path = os.environ.get("WM_MOCKED_API_FILE")
+ 62        if not mocked_path:
+ 63            return None
+ 64        logger.info("Using mocked API from %s", mocked_path)
+ 65        mocked_api = {"variables": {}, "resources": {}}
+ 66        try:
+ 67            with open(mocked_path, "r") as f:
+ 68                incoming_mocked_api = json.load(f)
+ 69            mocked_api = {**mocked_api, **incoming_mocked_api}
+ 70        except Exception as e:
+ 71            logger.warning(
+ 72                "Error parsing mocked API file at path %s Using empty mocked API.",
+ 73                mocked_path,
+ 74            )
+ 75            logger.debug(e)
+ 76        return mocked_api
+ 77
+ 78    def get_client(self) -> httpx.Client:
+ 79        return httpx.Client(
+ 80            base_url=self.base_url,
+ 81            headers=self.headers,
+ 82            verify=self.verify,
+ 83        )
+ 84
+ 85    def get(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:
+ 86        endpoint = endpoint.lstrip("/")
+ 87        resp = self.client.get(f"/{endpoint}", **kwargs)
+ 88        if raise_for_status:
+ 89            try:
+ 90                resp.raise_for_status()
+ 91            except httpx.HTTPStatusError as err:
+ 92                error = f"{err.request.url}: {err.response.status_code}, {err.response.text}"
+ 93                logger.error(error)
+ 94                raise Exception(error)
+ 95        return resp
+ 96
+ 97    def post(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:
+ 98        endpoint = endpoint.lstrip("/")
+ 99        resp = self.client.post(f"/{endpoint}", **kwargs)
+100        if raise_for_status:
+101            try:
+102                resp.raise_for_status()
+103            except httpx.HTTPStatusError as err:
+104                error = f"{err.request.url}: {err.response.status_code}, {err.response.text}"
+105                logger.error(error)
+106                raise Exception(error)
+107        return resp
+108
+109    def create_token(self, duration=dt.timedelta(days=1)) -> str:
+110        endpoint = "/users/tokens/create"
+111        payload = {
+112            "label": f"refresh {time.time()}",
+113            "expiration": (dt.datetime.now() + duration).strftime("%Y-%m-%dT%H:%M:%SZ"),
+114        }
+115        return self.post(endpoint, json=payload).text
+116
+117    def run_script_async(
+118        self,
+119        path: str = None,
+120        hash_: str = None,
+121        args: dict = None,
+122        scheduled_in_secs: int = None,
+123    ) -> str:
+124        """Create a script job and return its job id.
+125        
+126        .. deprecated:: Use run_script_by_path_async or run_script_by_hash_async instead.
+127        """
+128        logging.warning(
+129            "run_script_async is deprecated. Use run_script_by_path_async or run_script_by_hash_async instead.",
+130        )
+131        assert not (path and hash_), "path and hash_ are mutually exclusive"
+132        return self._run_script_async_internal(path=path, hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs)
+133
+134    def _run_script_async_internal(
+135        self,
+136        path: str = None,
+137        hash_: str = None,
+138        args: dict = None,
+139        scheduled_in_secs: int = None,
+140    ) -> str:
+141        """Internal helper for running scripts asynchronously."""
+142        args = args or {}
+143        params = {"scheduled_in_secs": scheduled_in_secs} if scheduled_in_secs else {}
+144        if os.environ.get("WM_JOB_ID"):
+145            params["parent_job"] = os.environ.get("WM_JOB_ID")
+146        if os.environ.get("WM_ROOT_FLOW_JOB_ID"):
+147            params["root_job"] = os.environ.get("WM_ROOT_FLOW_JOB_ID")
+148        
+149        if path:
+150            endpoint = f"/w/{self.workspace}/jobs/run/p/{path}"
+151        elif hash_:
+152            endpoint = f"/w/{self.workspace}/jobs/run/h/{hash_}"
+153        else:
+154            raise Exception("path or hash_ must be provided")
+155        
+156        return self.post(endpoint, json=args, params=params).text
+157
+158    def run_script_by_path_async(
+159        self,
+160        path: str,
+161        args: dict = None,
+162        scheduled_in_secs: int = None,
+163    ) -> str:
+164        """Create a script job by path and return its job id."""
+165        return self._run_script_async_internal(path=path, args=args, scheduled_in_secs=scheduled_in_secs)
+166
+167    def run_script_by_hash_async(
+168        self,
+169        hash_: str,
+170        args: dict = None,
+171        scheduled_in_secs: int = None,
+172    ) -> str:
+173        """Create a script job by hash and return its job id."""
+174        return self._run_script_async_internal(hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs)
+175
+176    def run_flow_async(
+177        self,
+178        path: str,
+179        args: dict = None,
+180        scheduled_in_secs: int = None,
+181        # can only be set to false if this the job will be fully await and not concurrent with any other job
+182        # as otherwise the child flow and its own child will store their state in the parent job which will
+183        # lead to incorrectness and failures
+184        do_not_track_in_parent: bool = True,
+185    ) -> str:
+186        """Create a flow job and return its job id."""
+187        args = args or {}
+188        params = {"scheduled_in_secs": scheduled_in_secs} if scheduled_in_secs else {}
+189        if not do_not_track_in_parent:
+190            if os.environ.get("WM_JOB_ID"):
+191                params["parent_job"] = os.environ.get("WM_JOB_ID")
+192            if os.environ.get("WM_ROOT_FLOW_JOB_ID"):
+193                params["root_job"] = os.environ.get("WM_ROOT_FLOW_JOB_ID")
+194        if path:
+195            endpoint = f"/w/{self.workspace}/jobs/run/f/{path}"
+196        else:
+197            raise Exception("path must be provided")
+198        return self.post(endpoint, json=args, params=params).text
+199
+200    def run_script(
+201        self,
+202        path: str = None,
+203        hash_: str = None,
+204        args: dict = None,
+205        timeout: dt.timedelta | int | float | None = None,
+206        verbose: bool = False,
+207        cleanup: bool = True,
+208        assert_result_is_not_none: bool = False,
+209    ) -> Any:
+210        """Run script synchronously and return its result.
+211        
+212        .. deprecated:: Use run_script_by_path or run_script_by_hash instead.
+213        """
+214        logging.warning(
+215            "run_script is deprecated. Use run_script_by_path or run_script_by_hash instead.",
+216        )
+217        assert not (path and hash_), "path and hash_ are mutually exclusive"
+218        return self._run_script_internal(
+219            path=path, hash_=hash_, args=args, timeout=timeout, verbose=verbose,
+220            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+221        )
+222
+223    def _run_script_internal(
+224        self,
+225        path: str = None,
+226        hash_: str = None,
+227        args: dict = None,
+228        timeout: dt.timedelta | int | float | None = None,
+229        verbose: bool = False,
+230        cleanup: bool = True,
+231        assert_result_is_not_none: bool = False,
+232    ) -> Any:
+233        """Internal helper for running scripts synchronously."""
+234        args = args or {}
+235
+236        if verbose:
+237            if path:
+238                logger.info(f"running `{path}` synchronously with {args = }")
+239            elif hash_:
+240                logger.info(f"running script with hash `{hash_}` synchronously with {args = }")
+241
+242        if isinstance(timeout, dt.timedelta):
+243            timeout = timeout.total_seconds()
+244
+245        job_id = self._run_script_async_internal(path=path, hash_=hash_, args=args)
+246        return self.wait_job(
+247            job_id, timeout, verbose, cleanup, assert_result_is_not_none
+248        )
+249
+250    def run_script_by_path(
+251        self,
+252        path: str,
+253        args: dict = None,
+254        timeout: dt.timedelta | int | float | None = None,
+255        verbose: bool = False,
+256        cleanup: bool = True,
+257        assert_result_is_not_none: bool = False,
+258    ) -> Any:
+259        """Run script by path synchronously and return its result."""
+260        return self._run_script_internal(
+261            path=path, args=args, timeout=timeout, verbose=verbose,
+262            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+263        )
+264
+265    def run_script_by_hash(
+266        self,
+267        hash_: str,
+268        args: dict = None,
+269        timeout: dt.timedelta | int | float | None = None,
+270        verbose: bool = False,
+271        cleanup: bool = True,
+272        assert_result_is_not_none: bool = False,
+273    ) -> Any:
+274        """Run script by hash synchronously and return its result."""
+275        return self._run_script_internal(
+276            hash_=hash_, args=args, timeout=timeout, verbose=verbose,
+277            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+278        )
+279
+280    def wait_job(
+281        self,
+282        job_id,
+283        timeout: dt.timedelta | int | float | None = None,
+284        verbose: bool = False,
+285        cleanup: bool = True,
+286        assert_result_is_not_none: bool = False,
+287    ):
+288        def cancel_job():
+289            logger.warning(f"cancelling job: {job_id}")
+290            self.post(
+291                f"/w/{self.workspace}/jobs_u/queue/cancel/{job_id}",
+292                json={"reason": "parent script cancelled"},
+293            ).raise_for_status()
+294
+295        if cleanup:
+296            atexit.register(cancel_job)
+297
+298        start_time = time.time()
+299
+300        if isinstance(timeout, dt.timedelta):
+301            timeout = timeout.total_seconds()
+302
+303        while True:
+304            result_res = self.get(
+305                f"/w/{self.workspace}/jobs_u/completed/get_result_maybe/{job_id}", True
+306            ).json()
+307
+308            started = result_res["started"]
+309            completed = result_res["completed"]
+310            success = result_res["success"]
+311
+312            if not started and verbose:
+313                logger.info(f"job {job_id} has not started yet")
+314
+315            if cleanup and completed:
+316                atexit.unregister(cancel_job)
+317
+318            if completed:
+319                result = result_res["result"]
+320                if success:
+321                    if result is None and assert_result_is_not_none:
+322                        raise Exception("Result was none")
+323                    return result
+324                else:
+325                    error = result["error"]
+326                    raise Exception(f"Job {job_id} was not successful: {str(error)}")
+327
+328            if timeout and ((time.time() - start_time) > timeout):
+329                msg = "reached timeout"
+330                logger.warning(msg)
+331                self.post(
+332                    f"/w/{self.workspace}/jobs_u/queue/cancel/{job_id}",
+333                    json={"reason": msg},
+334                )
+335                raise TimeoutError(msg)
+336            if verbose:
+337                logger.info(f"sleeping 0.5 seconds for {job_id = }")
+338
+339            time.sleep(0.5)
+340
+341    def cancel_running(self) -> dict:
+342        """Cancel currently running executions of the same script."""
+343        logger.info("canceling running executions of this script")
+344
+345        jobs = self.get(
+346            f"/w/{self.workspace}/jobs/list",
+347            params={
+348                "running": "true",
+349                "script_path_exact": self.path,
+350            },
+351        ).json()
+352
+353        current_job_id = os.environ.get("WM_JOB_ID")
+354
+355        logger.debug(f"{current_job_id = }")
+356
+357        job_ids = [j["id"] for j in jobs if j["id"] != current_job_id]
+358
+359        if job_ids:
+360            logger.info(f"cancelling the following job ids: {job_ids}")
+361        else:
+362            logger.info("no previous executions to cancel")
+363
+364        result = {}
+365
+366        for id_ in job_ids:
+367            result[id_] = self.post(
+368                f"/w/{self.workspace}/jobs_u/queue/cancel/{id_}",
+369                json={"reason": "killed by `cancel_running` method"},
+370            )
+371
+372        return result
+373
+374    def get_job(self, job_id: str) -> dict:
+375        return self.get(f"/w/{self.workspace}/jobs_u/get/{job_id}").json()
+376
+377    def get_root_job_id(self, job_id: str | None = None) -> dict:
+378        job_id = job_id or os.environ.get("WM_JOB_ID")
+379        return self.get(f"/w/{self.workspace}/jobs_u/get_root_job_id/{job_id}").json()
+380
+381    def get_id_token(self, audience: str) -> str:
+382        return self.post(f"/w/{self.workspace}/oidc/token/{audience}").text
+383
+384    def get_job_status(self, job_id: str) -> JobStatus:
+385        job = self.get_job(job_id)
+386        job_type = job.get("type", "")
+387        assert job_type, f"{job} is not a valid job"
+388        if job_type.lower() == "completedjob":
+389            return "COMPLETED"
+390        if job.get("running"):
+391            return "RUNNING"
+392        return "WAITING"
+393
+394    def get_result(
+395        self,
+396        job_id: str,
+397        assert_result_is_not_none: bool = True,
+398    ) -> Any:
+399        result = self.get(f"/w/{self.workspace}/jobs_u/completed/get_result/{job_id}")
+400        result_text = result.text
+401        if assert_result_is_not_none and result_text is None:
+402            raise Exception(f"result is None for {job_id = }")
+403        try:
+404            return result.json()
+405        except JSONDecodeError:
+406            return result_text
+407
+408    def get_variable(self, path: str) -> str:
+409        path = parse_variable_syntax(path) or path
+410        if self.mocked_api is not None:
+411            variables = self.mocked_api["variables"]
+412            try:
+413                result = variables[path]
+414                return result
+415            except KeyError:
+416                logger.info(
+417                    f"MockedAPI present, but variable not found at {path}, falling back to real API"
+418                )
+419
+420        """Get variable from Windmill"""
+421        return self.get(f"/w/{self.workspace}/variables/get_value/{path}").json()
+422
+423    def set_variable(self, path: str, value: str, is_secret: bool = False) -> None:
+424        path = parse_variable_syntax(path) or path
+425        if self.mocked_api is not None:
+426            self.mocked_api["variables"][path] = value
+427            return
+428
+429        """Set variable from Windmill"""
+430        # check if variable exists
+431        r = self.get(
+432            f"/w/{self.workspace}/variables/get/{path}", raise_for_status=False
+433        )
+434        if r.status_code == 404:
+435            # create variable
+436            self.post(
+437                f"/w/{self.workspace}/variables/create",
+438                json={
+439                    "path": path,
+440                    "value": value,
+441                    "is_secret": is_secret,
+442                    "description": "",
+443                },
+444            )
+445        else:
+446            # update variable
+447            self.post(
+448                f"/w/{self.workspace}/variables/update/{path}",
+449                json={"value": value},
+450            )
+451
+452    def get_resource(
+453        self,
+454        path: str,
+455        none_if_undefined: bool = False,
+456    ) -> dict | None:
+457        path = parse_resource_syntax(path) or path
+458        if self.mocked_api is not None:
+459            resources = self.mocked_api["resources"]
+460            try:
+461                result = resources[path]
+462                return result
+463            except KeyError:
+464                # NOTE: should mocked_api respect `none_if_undefined`?
+465                if none_if_undefined:
+466                    logger.info(
+467                        f"resource not found at ${path}, but none_if_undefined is True, so returning None"
+468                    )
+469                    return None
+470                logger.info(
+471                    f"MockedAPI present, but resource not found at ${path}, falling back to real API"
+472                )
+473
+474        """Get resource from Windmill"""
+475        try:
+476            return self.get(
+477                f"/w/{self.workspace}/resources/get_value_interpolated/{path}"
+478            ).json()
+479        except Exception as e:
+480            if none_if_undefined:
+481                return None
+482            logger.error(e)
+483            raise e
+484
+485    def set_resource(
+486        self,
+487        value: Any,
+488        path: str,
+489        resource_type: str,
+490    ):
+491        path = parse_resource_syntax(path) or path
+492        if self.mocked_api is not None:
+493            self.mocked_api["resources"][path] = value
+494            return
+495
+496        # check if resource exists
+497        r = self.get(
+498            f"/w/{self.workspace}/resources/get/{path}", raise_for_status=False
+499        )
+500        if r.status_code == 404:
+501            # create resource
+502            self.post(
+503                f"/w/{self.workspace}/resources/create",
+504                json={
+505                    "path": path,
+506                    "value": value,
+507                    "resource_type": resource_type,
+508                },
+509            )
+510        else:
+511            # update resource
+512            self.post(
+513                f"/w/{self.workspace}/resources/update_value/{path}",
+514                json={"value": value},
+515            )
+516
+517    def set_state(self, value: Any):
+518        self.set_resource(value, path=self.state_path, resource_type="state")
+519
+520    def set_progress(self, value: int, job_id: Optional[str] = None):
+521        workspace = get_workspace()
+522        flow_id = os.environ.get("WM_FLOW_JOB_ID")
+523        job_id = job_id or os.environ.get("WM_JOB_ID")
+524
+525        if job_id != None:
+526            job = self.get_job(job_id)
+527            flow_id = job.get("parent_job")
+528
+529        self.post(
+530            f"/w/{workspace}/job_metrics/set_progress/{job_id}",
+531            json={
+532                "percent": value,
+533                "flow_job_id": flow_id or None,
+534            },
+535        )
+536
+537    def get_progress(self, job_id: Optional[str] = None) -> Any:
+538        workspace = get_workspace()
+539        job_id = job_id or os.environ.get("WM_JOB_ID")
+540
+541        r = self.get(
+542            f"/w/{workspace}/job_metrics/get_progress/{job_id}",
+543        )
+544        if r.status_code == 404:
+545            print(f"Job {job_id} does not exist")
+546            return None
+547        else:
+548            return r.json()
+549
+550    def set_flow_user_state(self, key: str, value: Any) -> None:
+551        """Set the user state of a flow at a given key"""
+552        flow_id = self.get_root_job_id()
+553        r = self.post(
+554            f"/w/{self.workspace}/jobs/flow/user_states/{flow_id}/{key}",
+555            json=value,
+556            raise_for_status=False,
+557        )
+558        if r.status_code == 404:
+559            print(f"Job {flow_id} does not exist or is not a flow")
+560
+561    def get_flow_user_state(self, key: str) -> Any:
+562        """Get the user state of a flow at a given key"""
+563        flow_id = self.get_root_job_id()
+564        r = self.get(
+565            f"/w/{self.workspace}/jobs/flow/user_states/{flow_id}/{key}",
+566            raise_for_status=False,
+567        )
+568        if r.status_code == 404:
+569            print(f"Job {flow_id} does not exist or is not a flow")
+570            return None
+571        else:
+572            return r.json()
+573
+574    @property
+575    def version(self):
+576        return self.get("version").text
+577
+578    def get_duckdb_connection_settings(
+579        self,
+580        s3_resource_path: str = "",
+581    ) -> DuckDbConnectionSettings | None:
+582        """
+583        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+584        initiate an S3 connection from DuckDB
+585        """
+586        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+587        try:
+588            raw_obj = self.post(
+589                f"/w/{self.workspace}/job_helpers/v2/duckdb_connection_settings",
+590                json={}
+591                if s3_resource_path == ""
+592                else {"s3_resource_path": s3_resource_path},
+593            ).json()
+594            return DuckDbConnectionSettings(raw_obj)
+595        except JSONDecodeError as e:
+596            raise Exception(
+597                "Could not generate DuckDB S3 connection settings from the provided resource"
+598            ) from e
+599
+600    def get_polars_connection_settings(
+601        self,
+602        s3_resource_path: str = "",
+603    ) -> PolarsConnectionSettings:
+604        """
+605        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+606        initiate an S3 connection from Polars
+607        """
+608        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+609        try:
+610            raw_obj = self.post(
+611                f"/w/{self.workspace}/job_helpers/v2/polars_connection_settings",
+612                json={}
+613                if s3_resource_path == ""
+614                else {"s3_resource_path": s3_resource_path},
+615            ).json()
+616            return PolarsConnectionSettings(raw_obj)
+617        except JSONDecodeError as e:
+618            raise Exception(
+619                "Could not generate Polars S3 connection settings from the provided resource"
+620            ) from e
+621
+622    def get_boto3_connection_settings(
+623        self,
+624        s3_resource_path: str = "",
+625    ) -> Boto3ConnectionSettings:
+626        """
+627        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+628        initiate an S3 connection using boto3
+629        """
+630        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+631        try:
+632            s3_resource = self.post(
+633                f"/w/{self.workspace}/job_helpers/v2/s3_resource_info",
+634                json={}
+635                if s3_resource_path == ""
+636                else {"s3_resource_path": s3_resource_path},
+637            ).json()
+638            return self.__boto3_connection_settings(s3_resource)
+639        except JSONDecodeError as e:
+640            raise Exception(
+641                "Could not generate Boto3 S3 connection settings from the provided resource"
+642            ) from e
+643
+644    def load_s3_file(self, s3object: S3Object | str, s3_resource_path: str | None) -> bytes:
+645        """
+646        Load a file from the workspace s3 bucket and returns its content as bytes.
+647
+648        '''python
+649        from wmill import S3Object
+650
+651        s3_obj = S3Object(s3="/path/to/my_file.txt")
+652        my_obj_content = client.load_s3_file(s3_obj)
+653        file_content = my_obj_content.decode("utf-8")
+654        '''
+655        """
+656        s3object = parse_s3_object(s3object)
+657        with self.load_s3_file_reader(s3object, s3_resource_path) as file_reader:
+658            return file_reader.read()
+659
+660    def load_s3_file_reader(
+661        self, s3object: S3Object | str, s3_resource_path: str | None
+662    ) -> BufferedReader:
+663        """
+664        Load a file from the workspace s3 bucket and returns the bytes stream.
+665
+666        '''python
+667        from wmill import S3Object
+668
+669        s3_obj = S3Object(s3="/path/to/my_file.txt")
+670        with wmill.load_s3_file_reader(s3object, s3_resource_path) as file_reader:
+671            print(file_reader.read())
+672        '''
+673        """
+674        s3object = parse_s3_object(s3object)
+675        reader = S3BufferedReader(
+676            f"{self.workspace}",
+677            self.client,
+678            s3object["s3"],
+679            s3_resource_path,
+680            s3object["storage"] if "storage" in s3object else None,
+681        )
+682        return reader
+683
+684    def write_s3_file(
+685        self,
+686        s3object: S3Object | str | None,
+687        file_content: BufferedReader | bytes,
+688        s3_resource_path: str | None,
+689        content_type: str | None = None,
+690        content_disposition: str | None = None,
+691    ) -> S3Object:
+692        """
+693        Write a file to the workspace S3 bucket
+694
+695        '''python
+696        from wmill import S3Object
+697
+698        s3_obj = S3Object(s3="/path/to/my_file.txt")
+699
+700        # for an in memory bytes array:
+701        file_content = b'Hello Windmill!'
+702        client.write_s3_file(s3_obj, file_content)
+703
+704        # for a file:
+705        with open("my_file.txt", "rb") as my_file:
+706            client.write_s3_file(s3_obj, my_file)
+707        '''
+708        """
+709        s3object = parse_s3_object(s3object)
+710        # httpx accepts either bytes or "a bytes generator" as content. If it's a BufferedReader, we need to convert it to a generator
+711        if isinstance(file_content, BufferedReader):
+712            content_payload = bytes_generator(file_content)
+713        elif isinstance(file_content, bytes):
+714            content_payload = file_content
+715        else:
+716            raise Exception("Type of file_content not supported")
+717
+718        query_params = {}
+719        if s3object is not None and s3object["s3"] != "":
+720            query_params["file_key"] = s3object["s3"]
+721        if s3_resource_path is not None and s3_resource_path != "":
+722            query_params["s3_resource_path"] = s3_resource_path
+723        if (
+724            s3object is not None
+725            and "storage" in s3object
+726            and s3object["storage"] is not None
+727        ):
+728            query_params["storage"] = s3object["storage"]
+729        if content_type is not None:
+730            query_params["content_type"] = content_type
+731        if content_disposition is not None:
+732            query_params["content_disposition"] = content_disposition
+733
+734        try:
+735            # need a vanilla client b/c content-type is not application/json here
+736            response = httpx.post(
+737                f"{self.base_url}/w/{self.workspace}/job_helpers/upload_s3_file",
+738                headers={
+739                    "Authorization": f"Bearer {self.token}",
+740                    "Content-Type": "application/octet-stream",
+741                },
+742                params=query_params,
+743                content=content_payload,
+744                verify=self.verify,
+745                timeout=None,
+746            ).json()
+747        except Exception as e:
+748            raise Exception("Could not write file to S3") from e
+749        return S3Object(s3=response["file_key"])
+750
+751    def sign_s3_objects(self, s3_objects: list[S3Object | str]) -> list[S3Object]:
+752        return self.post(
+753            f"/w/{self.workspace}/apps/sign_s3_objects", json={"s3_objects": list(map(parse_s3_object, s3_objects))}
+754        ).json()
+755
+756    def sign_s3_object(self, s3_object: S3Object | str) -> S3Object:
+757        return self.post(
+758            f"/w/{self.workspace}/apps/sign_s3_objects",
+759            json={"s3_objects": [s3_object]},
+760        ).json()[0]
+761
+762    def __boto3_connection_settings(self, s3_resource) -> Boto3ConnectionSettings:
+763        endpoint_url_prefix = "https://" if s3_resource["useSSL"] else "http://"
+764        return Boto3ConnectionSettings(
+765            {
+766                "endpoint_url": "{}{}".format(
+767                    endpoint_url_prefix, s3_resource["endPoint"]
+768                ),
+769                "region_name": s3_resource["region"],
+770                "use_ssl": s3_resource["useSSL"],
+771                "aws_access_key_id": s3_resource["accessKey"],
+772                "aws_secret_access_key": s3_resource["secretKey"],
+773                # no need for path_style here as boto3 is clever enough to determine which one to use
+774            }
+775        )
+776
+777    def whoami(self) -> dict:
+778        return self.get("/users/whoami").json()
+779
+780    @property
+781    def user(self) -> dict:
+782        return self.whoami()
+783
+784    @property
+785    def state_path(self) -> str:
+786        state_path = os.environ.get(
+787            "WM_STATE_PATH_NEW", os.environ.get("WM_STATE_PATH")
+788        )
+789        if state_path is None:
+790            raise Exception("State path not found")
+791        return state_path
+792
+793    @property
+794    def state(self) -> Any:
+795        return self.get_resource(path=self.state_path, none_if_undefined=True)
+796
+797    @state.setter
+798    def state(self, value: Any) -> None:
+799        self.set_state(value)
+800
+801    @staticmethod
+802    def set_shared_state_pickle(value: Any, path: str = "state.pickle") -> None:
+803        """
+804        Set the state in the shared folder using pickle
+805        """
+806        import pickle
+807
+808        with open(f"/shared/{path}", "wb") as handle:
+809            pickle.dump(value, handle, protocol=pickle.HIGHEST_PROTOCOL)
+810
+811    @staticmethod
+812    def get_shared_state_pickle(path: str = "state.pickle") -> Any:
+813        """
+814        Get the state in the shared folder using pickle
+815        """
+816        import pickle
+817
+818        with open(f"/shared/{path}", "rb") as handle:
+819            return pickle.load(handle)
+820
+821    @staticmethod
+822    def set_shared_state(value: Any, path: str = "state.json") -> None:
+823        """
+824        Set the state in the shared folder using pickle
+825        """
+826        import json
+827
+828        with open(f"/shared/{path}", "w", encoding="utf-8") as f:
+829            json.dump(value, f, ensure_ascii=False, indent=4)
+830
+831    @staticmethod
+832    def get_shared_state(path: str = "state.json") -> None:
+833        """
+834        Get the state in the shared folder using pickle
+835        """
+836        import json
+837
+838        with open(f"/shared/{path}", "r", encoding="utf-8") as f:
+839            return json.load(f)
+840
+841    def get_resume_urls(self, approver: str = None) -> dict:
+842        nonce = random.randint(0, 1000000000)
+843        job_id = os.environ.get("WM_JOB_ID") or "NO_ID"
+844        return self.get(
+845            f"/w/{self.workspace}/jobs/resume_urls/{job_id}/{nonce}",
+846            params={"approver": approver},
+847        ).json()
+848
+849    def request_interactive_slack_approval(
+850        self,
+851        slack_resource_path: str,
+852        channel_id: str,
+853        message: str = None,
+854        approver: str = None,
+855        default_args_json: dict = None,
+856        dynamic_enums_json: dict = None,
+857    ) -> None:
+858        """
+859        Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
+860
+861        **[Enterprise Edition Only]** To include form fields in the Slack approval request, use the "Advanced -> Suspend -> Form" functionality.
+862        Learn more at: https://www.windmill.dev/docs/flows/flow_approval#form
+863
+864        :param slack_resource_path: The path to the Slack resource in Windmill.
+865        :type slack_resource_path: str
+866        :param channel_id: The Slack channel ID where the approval request will be sent.
+867        :type channel_id: str
+868        :param message: Optional custom message to include in the Slack approval request.
+869        :type message: str, optional
+870        :param approver: Optional user ID or name of the approver for the request.
+871        :type approver: str, optional
+872        :param default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
+873        :type default_args_json: dict, optional
+874        :param dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
+875        :type dynamic_enums_json: dict, optional
+876
+877        :raises Exception: If the function is not called within a flow or flow preview.
+878        :raises Exception: If the required flow job or flow step environment variables are not set.
+879
+880        :return: None
+881
+882        **Usage Example:**
+883            >>> client.request_interactive_slack_approval(
+884            ...     slack_resource_path="/u/alex/my_slack_resource",
+885            ...     channel_id="admins-slack-channel",
+886            ...     message="Please approve this request",
+887            ...     approver="approver123",
+888            ...     default_args_json={"key1": "value1", "key2": 42},
+889            ...     dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]},
+890            ... )
+891
+892        **Notes:**
+893        - This function must be executed within a Windmill flow or flow preview.
+894        - The function checks for required environment variables (`WM_FLOW_JOB_ID`, `WM_FLOW_STEP_ID`) to ensure it is run in the appropriate context.
+895        """
+896        workspace = self.workspace
+897        flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
+898
+899        if not flow_job_id:
+900            raise Exception(
+901                "You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
+902            )
+903
+904        # Only include non-empty parameters
+905        params = {}
+906        if message:
+907            params["message"] = message
+908        if approver:
+909            params["approver"] = approver
+910        if slack_resource_path:
+911            params["slack_resource_path"] = slack_resource_path
+912        if channel_id:
+913            params["channel_id"] = channel_id
+914        if os.environ.get("WM_FLOW_STEP_ID"):
+915            params["flow_step_id"] = os.environ.get("WM_FLOW_STEP_ID")
+916        if default_args_json:
+917            params["default_args_json"] = json.dumps(default_args_json)
+918        if dynamic_enums_json:
+919            params["dynamic_enums_json"] = json.dumps(dynamic_enums_json)
+920
+921        self.get(
+922            f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}",
+923            params=params,
+924        )
+925
+926    def username_to_email(self, username: str) -> str:
+927        """
+928        Get email from workspace username
+929        This method is particularly useful for apps that require the email address of the viewer.
+930        Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.
+931        """
+932        return self.get(f"/w/{self.workspace}/users/username_to_email/{username}").text
+933
+934    def send_teams_message(
+935        self,
+936        conversation_id: str,
+937        text: str,
+938        success: bool = True,
+939        card_block: dict = None,
+940    ):
+941        """
+942        Send a message to a Microsoft Teams conversation with conversation_id, where success is used to style the message
+943        """
+944        return self.post(
+945            f"/teams/activities",
+946            json={
+947                "conversation_id": conversation_id,
+948                "text": text,
+949                "success": success,
+950                "card_block": card_block,
+951            },
+952        )
+
+ + + + +
+ +
+ + Windmill(base_url=None, token=None, workspace=None, verify=True) + + + +
+ +
36    def __init__(self, base_url=None, token=None, workspace=None, verify=True):
+37        base = (
+38            base_url
+39            or os.environ.get("BASE_INTERNAL_URL")
+40            or os.environ.get("WM_BASE_URL")
+41        )
+42
+43        self.base_url = f"{base}/api"
+44        self.token = token or os.environ.get("WM_TOKEN")
+45        self.headers = {
+46            "Content-Type": "application/json",
+47            "Authorization": f"Bearer {self.token}",
+48        }
+49        self.verify = verify
+50        self.client = self.get_client()
+51        self.workspace = workspace or os.environ.get("WM_WORKSPACE")
+52        self.path = os.environ.get("WM_JOB_PATH")
+53
+54        self.mocked_api = self.get_mocked_api()
+55
+56        assert self.workspace, (
+57            f"workspace required as an argument or as WM_WORKSPACE environment variable"
+58        )
+
+ + + + +
+
+
+ base_url + + +
+ + + + +
+
+
+ token + + +
+ + + + +
+
+
+ headers + + +
+ + + + +
+
+
+ verify + + +
+ + + + +
+
+
+ client + + +
+ + + + +
+
+
+ workspace + + +
+ + + + +
+
+
+ path + + +
+ + + + +
+
+
+ mocked_api + + +
+ + + + +
+
+ +
+ + def + get_mocked_api(self) -> Optional[dict]: + + + +
+ +
60    def get_mocked_api(self) -> Optional[dict]:
+61        mocked_path = os.environ.get("WM_MOCKED_API_FILE")
+62        if not mocked_path:
+63            return None
+64        logger.info("Using mocked API from %s", mocked_path)
+65        mocked_api = {"variables": {}, "resources": {}}
+66        try:
+67            with open(mocked_path, "r") as f:
+68                incoming_mocked_api = json.load(f)
+69            mocked_api = {**mocked_api, **incoming_mocked_api}
+70        except Exception as e:
+71            logger.warning(
+72                "Error parsing mocked API file at path %s Using empty mocked API.",
+73                mocked_path,
+74            )
+75            logger.debug(e)
+76        return mocked_api
+
+ + + + +
+
+ +
+ + def + get_client(self) -> httpx.Client: + + + +
+ +
78    def get_client(self) -> httpx.Client:
+79        return httpx.Client(
+80            base_url=self.base_url,
+81            headers=self.headers,
+82            verify=self.verify,
+83        )
+
+ + + + +
+
+ +
+ + def + get(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response: + + + +
+ +
85    def get(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:
+86        endpoint = endpoint.lstrip("/")
+87        resp = self.client.get(f"/{endpoint}", **kwargs)
+88        if raise_for_status:
+89            try:
+90                resp.raise_for_status()
+91            except httpx.HTTPStatusError as err:
+92                error = f"{err.request.url}: {err.response.status_code}, {err.response.text}"
+93                logger.error(error)
+94                raise Exception(error)
+95        return resp
+
+ + + + +
+
+ +
+ + def + post(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response: + + + +
+ +
 97    def post(self, endpoint, raise_for_status=True, **kwargs) -> httpx.Response:
+ 98        endpoint = endpoint.lstrip("/")
+ 99        resp = self.client.post(f"/{endpoint}", **kwargs)
+100        if raise_for_status:
+101            try:
+102                resp.raise_for_status()
+103            except httpx.HTTPStatusError as err:
+104                error = f"{err.request.url}: {err.response.status_code}, {err.response.text}"
+105                logger.error(error)
+106                raise Exception(error)
+107        return resp
+
+ + + + +
+
+ +
+ + def + create_token(self, duration=datetime.timedelta(days=1)) -> str: + + + +
+ +
109    def create_token(self, duration=dt.timedelta(days=1)) -> str:
+110        endpoint = "/users/tokens/create"
+111        payload = {
+112            "label": f"refresh {time.time()}",
+113            "expiration": (dt.datetime.now() + duration).strftime("%Y-%m-%dT%H:%M:%SZ"),
+114        }
+115        return self.post(endpoint, json=payload).text
+
+ + + + +
+
+ +
+ + def + run_script_async( self, path: str = None, hash_: str = None, args: dict = None, scheduled_in_secs: int = None) -> str: + + + +
+ +
117    def run_script_async(
+118        self,
+119        path: str = None,
+120        hash_: str = None,
+121        args: dict = None,
+122        scheduled_in_secs: int = None,
+123    ) -> str:
+124        """Create a script job and return its job id.
+125        
+126        .. deprecated:: Use run_script_by_path_async or run_script_by_hash_async instead.
+127        """
+128        logging.warning(
+129            "run_script_async is deprecated. Use run_script_by_path_async or run_script_by_hash_async instead.",
+130        )
+131        assert not (path and hash_), "path and hash_ are mutually exclusive"
+132        return self._run_script_async_internal(path=path, hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs)
+
+ + +

Create a script job and return its job id.

+ +

Deprecated since version Use run_script_by_path_async or run_script_by_hash_async instead..

+
+ + +
+
+ +
+ + def + run_script_by_path_async(self, path: str, args: dict = None, scheduled_in_secs: int = None) -> str: + + + +
+ +
158    def run_script_by_path_async(
+159        self,
+160        path: str,
+161        args: dict = None,
+162        scheduled_in_secs: int = None,
+163    ) -> str:
+164        """Create a script job by path and return its job id."""
+165        return self._run_script_async_internal(path=path, args=args, scheduled_in_secs=scheduled_in_secs)
+
+ + +

Create a script job by path and return its job id.

+
+ + +
+
+ +
+ + def + run_script_by_hash_async( self, hash_: str, args: dict = None, scheduled_in_secs: int = None) -> str: + + + +
+ +
167    def run_script_by_hash_async(
+168        self,
+169        hash_: str,
+170        args: dict = None,
+171        scheduled_in_secs: int = None,
+172    ) -> str:
+173        """Create a script job by hash and return its job id."""
+174        return self._run_script_async_internal(hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs)
+
+ + +

Create a script job by hash and return its job id.

+
+ + +
+
+ +
+ + def + run_flow_async( self, path: str, args: dict = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True) -> str: + + + +
+ +
176    def run_flow_async(
+177        self,
+178        path: str,
+179        args: dict = None,
+180        scheduled_in_secs: int = None,
+181        # can only be set to false if this the job will be fully await and not concurrent with any other job
+182        # as otherwise the child flow and its own child will store their state in the parent job which will
+183        # lead to incorrectness and failures
+184        do_not_track_in_parent: bool = True,
+185    ) -> str:
+186        """Create a flow job and return its job id."""
+187        args = args or {}
+188        params = {"scheduled_in_secs": scheduled_in_secs} if scheduled_in_secs else {}
+189        if not do_not_track_in_parent:
+190            if os.environ.get("WM_JOB_ID"):
+191                params["parent_job"] = os.environ.get("WM_JOB_ID")
+192            if os.environ.get("WM_ROOT_FLOW_JOB_ID"):
+193                params["root_job"] = os.environ.get("WM_ROOT_FLOW_JOB_ID")
+194        if path:
+195            endpoint = f"/w/{self.workspace}/jobs/run/f/{path}"
+196        else:
+197            raise Exception("path must be provided")
+198        return self.post(endpoint, json=args, params=params).text
+
+ + +

Create a flow job and return its job id.

+
+ + +
+
+ +
+ + def + run_script( self, path: str = None, hash_: str = None, args: dict = None, timeout: datetime.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any: + + + +
+ +
200    def run_script(
+201        self,
+202        path: str = None,
+203        hash_: str = None,
+204        args: dict = None,
+205        timeout: dt.timedelta | int | float | None = None,
+206        verbose: bool = False,
+207        cleanup: bool = True,
+208        assert_result_is_not_none: bool = False,
+209    ) -> Any:
+210        """Run script synchronously and return its result.
+211        
+212        .. deprecated:: Use run_script_by_path or run_script_by_hash instead.
+213        """
+214        logging.warning(
+215            "run_script is deprecated. Use run_script_by_path or run_script_by_hash instead.",
+216        )
+217        assert not (path and hash_), "path and hash_ are mutually exclusive"
+218        return self._run_script_internal(
+219            path=path, hash_=hash_, args=args, timeout=timeout, verbose=verbose,
+220            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+221        )
+
+ + +

Run script synchronously and return its result.

+ +

Deprecated since version Use run_script_by_path or run_script_by_hash instead..

+
+ + +
+
+ +
+ + def + run_script_by_path( self, path: str, args: dict = None, timeout: datetime.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any: + + + +
+ +
250    def run_script_by_path(
+251        self,
+252        path: str,
+253        args: dict = None,
+254        timeout: dt.timedelta | int | float | None = None,
+255        verbose: bool = False,
+256        cleanup: bool = True,
+257        assert_result_is_not_none: bool = False,
+258    ) -> Any:
+259        """Run script by path synchronously and return its result."""
+260        return self._run_script_internal(
+261            path=path, args=args, timeout=timeout, verbose=verbose,
+262            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+263        )
+
+ + +

Run script by path synchronously and return its result.

+
+ + +
+
+ +
+ + def + run_script_by_hash( self, hash_: str, args: dict = None, timeout: datetime.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False) -> Any: + + + +
+ +
265    def run_script_by_hash(
+266        self,
+267        hash_: str,
+268        args: dict = None,
+269        timeout: dt.timedelta | int | float | None = None,
+270        verbose: bool = False,
+271        cleanup: bool = True,
+272        assert_result_is_not_none: bool = False,
+273    ) -> Any:
+274        """Run script by hash synchronously and return its result."""
+275        return self._run_script_internal(
+276            hash_=hash_, args=args, timeout=timeout, verbose=verbose,
+277            cleanup=cleanup, assert_result_is_not_none=assert_result_is_not_none
+278        )
+
+ + +

Run script by hash synchronously and return its result.

+
+ + +
+
+ +
+ + def + wait_job( self, job_id, timeout: datetime.timedelta | int | float | None = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = False): + + + +
+ +
280    def wait_job(
+281        self,
+282        job_id,
+283        timeout: dt.timedelta | int | float | None = None,
+284        verbose: bool = False,
+285        cleanup: bool = True,
+286        assert_result_is_not_none: bool = False,
+287    ):
+288        def cancel_job():
+289            logger.warning(f"cancelling job: {job_id}")
+290            self.post(
+291                f"/w/{self.workspace}/jobs_u/queue/cancel/{job_id}",
+292                json={"reason": "parent script cancelled"},
+293            ).raise_for_status()
+294
+295        if cleanup:
+296            atexit.register(cancel_job)
+297
+298        start_time = time.time()
+299
+300        if isinstance(timeout, dt.timedelta):
+301            timeout = timeout.total_seconds()
+302
+303        while True:
+304            result_res = self.get(
+305                f"/w/{self.workspace}/jobs_u/completed/get_result_maybe/{job_id}", True
+306            ).json()
+307
+308            started = result_res["started"]
+309            completed = result_res["completed"]
+310            success = result_res["success"]
+311
+312            if not started and verbose:
+313                logger.info(f"job {job_id} has not started yet")
+314
+315            if cleanup and completed:
+316                atexit.unregister(cancel_job)
+317
+318            if completed:
+319                result = result_res["result"]
+320                if success:
+321                    if result is None and assert_result_is_not_none:
+322                        raise Exception("Result was none")
+323                    return result
+324                else:
+325                    error = result["error"]
+326                    raise Exception(f"Job {job_id} was not successful: {str(error)}")
+327
+328            if timeout and ((time.time() - start_time) > timeout):
+329                msg = "reached timeout"
+330                logger.warning(msg)
+331                self.post(
+332                    f"/w/{self.workspace}/jobs_u/queue/cancel/{job_id}",
+333                    json={"reason": msg},
+334                )
+335                raise TimeoutError(msg)
+336            if verbose:
+337                logger.info(f"sleeping 0.5 seconds for {job_id = }")
+338
+339            time.sleep(0.5)
+
+ + + + +
+
+ +
+ + def + cancel_running(self) -> dict: + + + +
+ +
341    def cancel_running(self) -> dict:
+342        """Cancel currently running executions of the same script."""
+343        logger.info("canceling running executions of this script")
+344
+345        jobs = self.get(
+346            f"/w/{self.workspace}/jobs/list",
+347            params={
+348                "running": "true",
+349                "script_path_exact": self.path,
+350            },
+351        ).json()
+352
+353        current_job_id = os.environ.get("WM_JOB_ID")
+354
+355        logger.debug(f"{current_job_id = }")
+356
+357        job_ids = [j["id"] for j in jobs if j["id"] != current_job_id]
+358
+359        if job_ids:
+360            logger.info(f"cancelling the following job ids: {job_ids}")
+361        else:
+362            logger.info("no previous executions to cancel")
+363
+364        result = {}
+365
+366        for id_ in job_ids:
+367            result[id_] = self.post(
+368                f"/w/{self.workspace}/jobs_u/queue/cancel/{id_}",
+369                json={"reason": "killed by `cancel_running` method"},
+370            )
+371
+372        return result
+
+ + +

Cancel currently running executions of the same script.

+
+ + +
+
+ +
+ + def + get_job(self, job_id: str) -> dict: + + + +
+ +
374    def get_job(self, job_id: str) -> dict:
+375        return self.get(f"/w/{self.workspace}/jobs_u/get/{job_id}").json()
+
+ + + + +
+
+ +
+ + def + get_root_job_id(self, job_id: str | None = None) -> dict: + + + +
+ +
377    def get_root_job_id(self, job_id: str | None = None) -> dict:
+378        job_id = job_id or os.environ.get("WM_JOB_ID")
+379        return self.get(f"/w/{self.workspace}/jobs_u/get_root_job_id/{job_id}").json()
+
+ + + + +
+
+ +
+ + def + get_id_token(self, audience: str) -> str: + + + +
+ +
381    def get_id_token(self, audience: str) -> str:
+382        return self.post(f"/w/{self.workspace}/oidc/token/{audience}").text
+
+ + + + +
+
+ +
+ + def + get_job_status(self, job_id: str) -> Literal['RUNNING', 'WAITING', 'COMPLETED']: + + + +
+ +
384    def get_job_status(self, job_id: str) -> JobStatus:
+385        job = self.get_job(job_id)
+386        job_type = job.get("type", "")
+387        assert job_type, f"{job} is not a valid job"
+388        if job_type.lower() == "completedjob":
+389            return "COMPLETED"
+390        if job.get("running"):
+391            return "RUNNING"
+392        return "WAITING"
+
+ + + + +
+
+ +
+ + def + get_result(self, job_id: str, assert_result_is_not_none: bool = True) -> Any: + + + +
+ +
394    def get_result(
+395        self,
+396        job_id: str,
+397        assert_result_is_not_none: bool = True,
+398    ) -> Any:
+399        result = self.get(f"/w/{self.workspace}/jobs_u/completed/get_result/{job_id}")
+400        result_text = result.text
+401        if assert_result_is_not_none and result_text is None:
+402            raise Exception(f"result is None for {job_id = }")
+403        try:
+404            return result.json()
+405        except JSONDecodeError:
+406            return result_text
+
+ + + + +
+
+ +
+ + def + get_variable(self, path: str) -> str: + + + +
+ +
408    def get_variable(self, path: str) -> str:
+409        path = parse_variable_syntax(path) or path
+410        if self.mocked_api is not None:
+411            variables = self.mocked_api["variables"]
+412            try:
+413                result = variables[path]
+414                return result
+415            except KeyError:
+416                logger.info(
+417                    f"MockedAPI present, but variable not found at {path}, falling back to real API"
+418                )
+419
+420        """Get variable from Windmill"""
+421        return self.get(f"/w/{self.workspace}/variables/get_value/{path}").json()
+
+ + + + +
+
+ +
+ + def + set_variable(self, path: str, value: str, is_secret: bool = False) -> None: + + + +
+ +
423    def set_variable(self, path: str, value: str, is_secret: bool = False) -> None:
+424        path = parse_variable_syntax(path) or path
+425        if self.mocked_api is not None:
+426            self.mocked_api["variables"][path] = value
+427            return
+428
+429        """Set variable from Windmill"""
+430        # check if variable exists
+431        r = self.get(
+432            f"/w/{self.workspace}/variables/get/{path}", raise_for_status=False
+433        )
+434        if r.status_code == 404:
+435            # create variable
+436            self.post(
+437                f"/w/{self.workspace}/variables/create",
+438                json={
+439                    "path": path,
+440                    "value": value,
+441                    "is_secret": is_secret,
+442                    "description": "",
+443                },
+444            )
+445        else:
+446            # update variable
+447            self.post(
+448                f"/w/{self.workspace}/variables/update/{path}",
+449                json={"value": value},
+450            )
+
+ + + + +
+
+ +
+ + def + get_resource(self, path: str, none_if_undefined: bool = False) -> dict | None: + + + +
+ +
452    def get_resource(
+453        self,
+454        path: str,
+455        none_if_undefined: bool = False,
+456    ) -> dict | None:
+457        path = parse_resource_syntax(path) or path
+458        if self.mocked_api is not None:
+459            resources = self.mocked_api["resources"]
+460            try:
+461                result = resources[path]
+462                return result
+463            except KeyError:
+464                # NOTE: should mocked_api respect `none_if_undefined`?
+465                if none_if_undefined:
+466                    logger.info(
+467                        f"resource not found at ${path}, but none_if_undefined is True, so returning None"
+468                    )
+469                    return None
+470                logger.info(
+471                    f"MockedAPI present, but resource not found at ${path}, falling back to real API"
+472                )
+473
+474        """Get resource from Windmill"""
+475        try:
+476            return self.get(
+477                f"/w/{self.workspace}/resources/get_value_interpolated/{path}"
+478            ).json()
+479        except Exception as e:
+480            if none_if_undefined:
+481                return None
+482            logger.error(e)
+483            raise e
+
+ + + + +
+
+ +
+ + def + set_resource(self, value: Any, path: str, resource_type: str): + + + +
+ +
485    def set_resource(
+486        self,
+487        value: Any,
+488        path: str,
+489        resource_type: str,
+490    ):
+491        path = parse_resource_syntax(path) or path
+492        if self.mocked_api is not None:
+493            self.mocked_api["resources"][path] = value
+494            return
+495
+496        # check if resource exists
+497        r = self.get(
+498            f"/w/{self.workspace}/resources/get/{path}", raise_for_status=False
+499        )
+500        if r.status_code == 404:
+501            # create resource
+502            self.post(
+503                f"/w/{self.workspace}/resources/create",
+504                json={
+505                    "path": path,
+506                    "value": value,
+507                    "resource_type": resource_type,
+508                },
+509            )
+510        else:
+511            # update resource
+512            self.post(
+513                f"/w/{self.workspace}/resources/update_value/{path}",
+514                json={"value": value},
+515            )
+
+ + + + +
+
+ +
+ + def + set_state(self, value: Any): + + + +
+ +
517    def set_state(self, value: Any):
+518        self.set_resource(value, path=self.state_path, resource_type="state")
+
+ + + + +
+
+ +
+ + def + set_progress(self, value: int, job_id: Optional[str] = None): + + + +
+ +
520    def set_progress(self, value: int, job_id: Optional[str] = None):
+521        workspace = get_workspace()
+522        flow_id = os.environ.get("WM_FLOW_JOB_ID")
+523        job_id = job_id or os.environ.get("WM_JOB_ID")
+524
+525        if job_id != None:
+526            job = self.get_job(job_id)
+527            flow_id = job.get("parent_job")
+528
+529        self.post(
+530            f"/w/{workspace}/job_metrics/set_progress/{job_id}",
+531            json={
+532                "percent": value,
+533                "flow_job_id": flow_id or None,
+534            },
+535        )
+
+ + + + +
+
+ +
+ + def + get_progress(self, job_id: Optional[str] = None) -> Any: + + + +
+ +
537    def get_progress(self, job_id: Optional[str] = None) -> Any:
+538        workspace = get_workspace()
+539        job_id = job_id or os.environ.get("WM_JOB_ID")
+540
+541        r = self.get(
+542            f"/w/{workspace}/job_metrics/get_progress/{job_id}",
+543        )
+544        if r.status_code == 404:
+545            print(f"Job {job_id} does not exist")
+546            return None
+547        else:
+548            return r.json()
+
+ + + + +
+
+ +
+ + def + set_flow_user_state(self, key: str, value: Any) -> None: + + + +
+ +
550    def set_flow_user_state(self, key: str, value: Any) -> None:
+551        """Set the user state of a flow at a given key"""
+552        flow_id = self.get_root_job_id()
+553        r = self.post(
+554            f"/w/{self.workspace}/jobs/flow/user_states/{flow_id}/{key}",
+555            json=value,
+556            raise_for_status=False,
+557        )
+558        if r.status_code == 404:
+559            print(f"Job {flow_id} does not exist or is not a flow")
+
+ + +

Set the user state of a flow at a given key

+
+ + +
+
+ +
+ + def + get_flow_user_state(self, key: str) -> Any: + + + +
+ +
561    def get_flow_user_state(self, key: str) -> Any:
+562        """Get the user state of a flow at a given key"""
+563        flow_id = self.get_root_job_id()
+564        r = self.get(
+565            f"/w/{self.workspace}/jobs/flow/user_states/{flow_id}/{key}",
+566            raise_for_status=False,
+567        )
+568        if r.status_code == 404:
+569            print(f"Job {flow_id} does not exist or is not a flow")
+570            return None
+571        else:
+572            return r.json()
+
+ + +

Get the user state of a flow at a given key

+
+ + +
+
+ +
+ version + + + +
+ +
574    @property
+575    def version(self):
+576        return self.get("version").text
+
+ + + + +
+
+ +
+ + def + get_duckdb_connection_settings( self, s3_resource_path: str = '') -> wmill.s3_types.DuckDbConnectionSettings | None: + + + +
+ +
578    def get_duckdb_connection_settings(
+579        self,
+580        s3_resource_path: str = "",
+581    ) -> DuckDbConnectionSettings | None:
+582        """
+583        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+584        initiate an S3 connection from DuckDB
+585        """
+586        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+587        try:
+588            raw_obj = self.post(
+589                f"/w/{self.workspace}/job_helpers/v2/duckdb_connection_settings",
+590                json={}
+591                if s3_resource_path == ""
+592                else {"s3_resource_path": s3_resource_path},
+593            ).json()
+594            return DuckDbConnectionSettings(raw_obj)
+595        except JSONDecodeError as e:
+596            raise Exception(
+597                "Could not generate DuckDB S3 connection settings from the provided resource"
+598            ) from e
+
+ + +

Convenient helpers that takes an S3 resource as input and returns the settings necessary to +initiate an S3 connection from DuckDB

+
+ + +
+
+ +
+ + def + get_polars_connection_settings( self, s3_resource_path: str = '') -> wmill.s3_types.PolarsConnectionSettings: + + + +
+ +
600    def get_polars_connection_settings(
+601        self,
+602        s3_resource_path: str = "",
+603    ) -> PolarsConnectionSettings:
+604        """
+605        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+606        initiate an S3 connection from Polars
+607        """
+608        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+609        try:
+610            raw_obj = self.post(
+611                f"/w/{self.workspace}/job_helpers/v2/polars_connection_settings",
+612                json={}
+613                if s3_resource_path == ""
+614                else {"s3_resource_path": s3_resource_path},
+615            ).json()
+616            return PolarsConnectionSettings(raw_obj)
+617        except JSONDecodeError as e:
+618            raise Exception(
+619                "Could not generate Polars S3 connection settings from the provided resource"
+620            ) from e
+
+ + +

Convenient helpers that takes an S3 resource as input and returns the settings necessary to +initiate an S3 connection from Polars

+
+ + +
+
+ +
+ + def + get_boto3_connection_settings( self, s3_resource_path: str = '') -> wmill.s3_types.Boto3ConnectionSettings: + + + +
+ +
622    def get_boto3_connection_settings(
+623        self,
+624        s3_resource_path: str = "",
+625    ) -> Boto3ConnectionSettings:
+626        """
+627        Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+628        initiate an S3 connection using boto3
+629        """
+630        s3_resource_path = parse_resource_syntax(s3_resource_path) or s3_resource_path
+631        try:
+632            s3_resource = self.post(
+633                f"/w/{self.workspace}/job_helpers/v2/s3_resource_info",
+634                json={}
+635                if s3_resource_path == ""
+636                else {"s3_resource_path": s3_resource_path},
+637            ).json()
+638            return self.__boto3_connection_settings(s3_resource)
+639        except JSONDecodeError as e:
+640            raise Exception(
+641                "Could not generate Boto3 S3 connection settings from the provided resource"
+642            ) from e
+
+ + +

Convenient helpers that takes an S3 resource as input and returns the settings necessary to +initiate an S3 connection using boto3

+
+ + +
+
+ +
+ + def + load_s3_file( self, s3object: wmill.s3_types.S3Object | str, s3_resource_path: str | None) -> bytes: + + + +
+ +
644    def load_s3_file(self, s3object: S3Object | str, s3_resource_path: str | None) -> bytes:
+645        """
+646        Load a file from the workspace s3 bucket and returns its content as bytes.
+647
+648        '''python
+649        from wmill import S3Object
+650
+651        s3_obj = S3Object(s3="/path/to/my_file.txt")
+652        my_obj_content = client.load_s3_file(s3_obj)
+653        file_content = my_obj_content.decode("utf-8")
+654        '''
+655        """
+656        s3object = parse_s3_object(s3object)
+657        with self.load_s3_file_reader(s3object, s3_resource_path) as file_reader:
+658            return file_reader.read()
+
+ + +

Load a file from the workspace s3 bucket and returns its content as bytes.

+ +

'''python +from wmill import S3Object

+ +

s3_obj = S3Object(s3="/path/to/my_file.txt") +my_obj_content = client.load_s3_file(s3_obj) +file_content = my_obj_content.decode("utf-8") +'''

+
+ + +
+
+ +
+ + def + load_s3_file_reader( self, s3object: wmill.s3_types.S3Object | str, s3_resource_path: str | None) -> _io.BufferedReader: + + + +
+ +
660    def load_s3_file_reader(
+661        self, s3object: S3Object | str, s3_resource_path: str | None
+662    ) -> BufferedReader:
+663        """
+664        Load a file from the workspace s3 bucket and returns the bytes stream.
+665
+666        '''python
+667        from wmill import S3Object
+668
+669        s3_obj = S3Object(s3="/path/to/my_file.txt")
+670        with wmill.load_s3_file_reader(s3object, s3_resource_path) as file_reader:
+671            print(file_reader.read())
+672        '''
+673        """
+674        s3object = parse_s3_object(s3object)
+675        reader = S3BufferedReader(
+676            f"{self.workspace}",
+677            self.client,
+678            s3object["s3"],
+679            s3_resource_path,
+680            s3object["storage"] if "storage" in s3object else None,
+681        )
+682        return reader
+
+ + +

Load a file from the workspace s3 bucket and returns the bytes stream.

+ +

'''python +from wmill import S3Object

+ +

s3_obj = S3Object(s3="/path/to/my_file.txt") +with wmill.load_s3_file_reader(s3object, s3_resource_path) as file_reader: + print(file_reader.read()) +'''

+
+ + +
+
+ +
+ + def + write_s3_file( self, s3object: wmill.s3_types.S3Object | str | None, file_content: _io.BufferedReader | bytes, s3_resource_path: str | None, content_type: str | None = None, content_disposition: str | None = None) -> wmill.s3_types.S3Object: + + + +
+ +
684    def write_s3_file(
+685        self,
+686        s3object: S3Object | str | None,
+687        file_content: BufferedReader | bytes,
+688        s3_resource_path: str | None,
+689        content_type: str | None = None,
+690        content_disposition: str | None = None,
+691    ) -> S3Object:
+692        """
+693        Write a file to the workspace S3 bucket
+694
+695        '''python
+696        from wmill import S3Object
+697
+698        s3_obj = S3Object(s3="/path/to/my_file.txt")
+699
+700        # for an in memory bytes array:
+701        file_content = b'Hello Windmill!'
+702        client.write_s3_file(s3_obj, file_content)
+703
+704        # for a file:
+705        with open("my_file.txt", "rb") as my_file:
+706            client.write_s3_file(s3_obj, my_file)
+707        '''
+708        """
+709        s3object = parse_s3_object(s3object)
+710        # httpx accepts either bytes or "a bytes generator" as content. If it's a BufferedReader, we need to convert it to a generator
+711        if isinstance(file_content, BufferedReader):
+712            content_payload = bytes_generator(file_content)
+713        elif isinstance(file_content, bytes):
+714            content_payload = file_content
+715        else:
+716            raise Exception("Type of file_content not supported")
+717
+718        query_params = {}
+719        if s3object is not None and s3object["s3"] != "":
+720            query_params["file_key"] = s3object["s3"]
+721        if s3_resource_path is not None and s3_resource_path != "":
+722            query_params["s3_resource_path"] = s3_resource_path
+723        if (
+724            s3object is not None
+725            and "storage" in s3object
+726            and s3object["storage"] is not None
+727        ):
+728            query_params["storage"] = s3object["storage"]
+729        if content_type is not None:
+730            query_params["content_type"] = content_type
+731        if content_disposition is not None:
+732            query_params["content_disposition"] = content_disposition
+733
+734        try:
+735            # need a vanilla client b/c content-type is not application/json here
+736            response = httpx.post(
+737                f"{self.base_url}/w/{self.workspace}/job_helpers/upload_s3_file",
+738                headers={
+739                    "Authorization": f"Bearer {self.token}",
+740                    "Content-Type": "application/octet-stream",
+741                },
+742                params=query_params,
+743                content=content_payload,
+744                verify=self.verify,
+745                timeout=None,
+746            ).json()
+747        except Exception as e:
+748            raise Exception("Could not write file to S3") from e
+749        return S3Object(s3=response["file_key"])
+
+ + +

Write a file to the workspace S3 bucket

+ +

'''python +from wmill import S3Object

+ +

s3_obj = S3Object(s3="/path/to/my_file.txt")

+ +

for an in memory bytes array:

+ +

file_content = b'Hello Windmill!' +client.write_s3_file(s3_obj, file_content)

+ +

for a file:

+ +

with open("my_file.txt", "rb") as my_file: + client.write_s3_file(s3_obj, my_file) +'''

+
+ + +
+
+ +
+ + def + sign_s3_objects( self, s3_objects: list[wmill.s3_types.S3Object | str]) -> list[wmill.s3_types.S3Object]: + + + +
+ +
751    def sign_s3_objects(self, s3_objects: list[S3Object | str]) -> list[S3Object]:
+752        return self.post(
+753            f"/w/{self.workspace}/apps/sign_s3_objects", json={"s3_objects": list(map(parse_s3_object, s3_objects))}
+754        ).json()
+
+ + + + +
+
+ +
+ + def + sign_s3_object( self, s3_object: wmill.s3_types.S3Object | str) -> wmill.s3_types.S3Object: + + + +
+ +
756    def sign_s3_object(self, s3_object: S3Object | str) -> S3Object:
+757        return self.post(
+758            f"/w/{self.workspace}/apps/sign_s3_objects",
+759            json={"s3_objects": [s3_object]},
+760        ).json()[0]
+
+ + + + +
+
+ +
+ + def + whoami(self) -> dict: + + + +
+ +
777    def whoami(self) -> dict:
+778        return self.get("/users/whoami").json()
+
+ + + + +
+
+ +
+ user: dict + + + +
+ +
780    @property
+781    def user(self) -> dict:
+782        return self.whoami()
+
+ + + + +
+
+ +
+ state_path: str + + + +
+ +
784    @property
+785    def state_path(self) -> str:
+786        state_path = os.environ.get(
+787            "WM_STATE_PATH_NEW", os.environ.get("WM_STATE_PATH")
+788        )
+789        if state_path is None:
+790            raise Exception("State path not found")
+791        return state_path
+
+ + + + +
+
+ +
+ state: Any + + + +
+ +
793    @property
+794    def state(self) -> Any:
+795        return self.get_resource(path=self.state_path, none_if_undefined=True)
+
+ + + + +
+
+ +
+
@staticmethod
+ + def + set_shared_state_pickle(value: Any, path: str = 'state.pickle') -> None: + + + +
+ +
801    @staticmethod
+802    def set_shared_state_pickle(value: Any, path: str = "state.pickle") -> None:
+803        """
+804        Set the state in the shared folder using pickle
+805        """
+806        import pickle
+807
+808        with open(f"/shared/{path}", "wb") as handle:
+809            pickle.dump(value, handle, protocol=pickle.HIGHEST_PROTOCOL)
+
+ + +

Set the state in the shared folder using pickle

+
+ + +
+
+ +
+
@staticmethod
+ + def + get_shared_state_pickle(path: str = 'state.pickle') -> Any: + + + +
+ +
811    @staticmethod
+812    def get_shared_state_pickle(path: str = "state.pickle") -> Any:
+813        """
+814        Get the state in the shared folder using pickle
+815        """
+816        import pickle
+817
+818        with open(f"/shared/{path}", "rb") as handle:
+819            return pickle.load(handle)
+
+ + +

Get the state in the shared folder using pickle

+
+ + +
+
+ +
+
@staticmethod
+ + def + set_shared_state(value: Any, path: str = 'state.json') -> None: + + + +
+ +
821    @staticmethod
+822    def set_shared_state(value: Any, path: str = "state.json") -> None:
+823        """
+824        Set the state in the shared folder using pickle
+825        """
+826        import json
+827
+828        with open(f"/shared/{path}", "w", encoding="utf-8") as f:
+829            json.dump(value, f, ensure_ascii=False, indent=4)
+
+ + +

Set the state in the shared folder using pickle

+
+ + +
+
+ +
+
@staticmethod
+ + def + get_shared_state(path: str = 'state.json') -> None: + + + +
+ +
831    @staticmethod
+832    def get_shared_state(path: str = "state.json") -> None:
+833        """
+834        Get the state in the shared folder using pickle
+835        """
+836        import json
+837
+838        with open(f"/shared/{path}", "r", encoding="utf-8") as f:
+839            return json.load(f)
+
+ + +

Get the state in the shared folder using pickle

+
+ + +
+
+ +
+ + def + get_resume_urls(self, approver: str = None) -> dict: + + + +
+ +
841    def get_resume_urls(self, approver: str = None) -> dict:
+842        nonce = random.randint(0, 1000000000)
+843        job_id = os.environ.get("WM_JOB_ID") or "NO_ID"
+844        return self.get(
+845            f"/w/{self.workspace}/jobs/resume_urls/{job_id}/{nonce}",
+846            params={"approver": approver},
+847        ).json()
+
+ + + + +
+
+ +
+ + def + request_interactive_slack_approval( self, slack_resource_path: str, channel_id: str, message: str = None, approver: str = None, default_args_json: dict = None, dynamic_enums_json: dict = None) -> None: + + + +
+ +
849    def request_interactive_slack_approval(
+850        self,
+851        slack_resource_path: str,
+852        channel_id: str,
+853        message: str = None,
+854        approver: str = None,
+855        default_args_json: dict = None,
+856        dynamic_enums_json: dict = None,
+857    ) -> None:
+858        """
+859        Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
+860
+861        **[Enterprise Edition Only]** To include form fields in the Slack approval request, use the "Advanced -> Suspend -> Form" functionality.
+862        Learn more at: https://www.windmill.dev/docs/flows/flow_approval#form
+863
+864        :param slack_resource_path: The path to the Slack resource in Windmill.
+865        :type slack_resource_path: str
+866        :param channel_id: The Slack channel ID where the approval request will be sent.
+867        :type channel_id: str
+868        :param message: Optional custom message to include in the Slack approval request.
+869        :type message: str, optional
+870        :param approver: Optional user ID or name of the approver for the request.
+871        :type approver: str, optional
+872        :param default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
+873        :type default_args_json: dict, optional
+874        :param dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
+875        :type dynamic_enums_json: dict, optional
+876
+877        :raises Exception: If the function is not called within a flow or flow preview.
+878        :raises Exception: If the required flow job or flow step environment variables are not set.
+879
+880        :return: None
+881
+882        **Usage Example:**
+883            >>> client.request_interactive_slack_approval(
+884            ...     slack_resource_path="/u/alex/my_slack_resource",
+885            ...     channel_id="admins-slack-channel",
+886            ...     message="Please approve this request",
+887            ...     approver="approver123",
+888            ...     default_args_json={"key1": "value1", "key2": 42},
+889            ...     dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]},
+890            ... )
+891
+892        **Notes:**
+893        - This function must be executed within a Windmill flow or flow preview.
+894        - The function checks for required environment variables (`WM_FLOW_JOB_ID`, `WM_FLOW_STEP_ID`) to ensure it is run in the appropriate context.
+895        """
+896        workspace = self.workspace
+897        flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
+898
+899        if not flow_job_id:
+900            raise Exception(
+901                "You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
+902            )
+903
+904        # Only include non-empty parameters
+905        params = {}
+906        if message:
+907            params["message"] = message
+908        if approver:
+909            params["approver"] = approver
+910        if slack_resource_path:
+911            params["slack_resource_path"] = slack_resource_path
+912        if channel_id:
+913            params["channel_id"] = channel_id
+914        if os.environ.get("WM_FLOW_STEP_ID"):
+915            params["flow_step_id"] = os.environ.get("WM_FLOW_STEP_ID")
+916        if default_args_json:
+917            params["default_args_json"] = json.dumps(default_args_json)
+918        if dynamic_enums_json:
+919            params["dynamic_enums_json"] = json.dumps(dynamic_enums_json)
+920
+921        self.get(
+922            f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}",
+923            params=params,
+924        )
+
+ + +

Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.

+ +

[Enterprise Edition Only] To include form fields in the Slack approval request, use the "Advanced -> Suspend -> Form" functionality. +Learn more at: https://www.windmill.dev/docs/flows/flow_approval#form

+ +
Parameters
+ +
    +
  • slack_resource_path: The path to the Slack resource in Windmill.
  • +
  • channel_id: The Slack channel ID where the approval request will be sent.
  • +
  • message: Optional custom message to include in the Slack approval request.
  • +
  • approver: Optional user ID or name of the approver for the request.
  • +
  • default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
  • +
  • dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
  • +
+ +
Raises
+ +
    +
  • Exception: If the function is not called within a flow or flow preview.
  • +
  • Exception: If the required flow job or flow step environment variables are not set.
  • +
+ +
Returns
+ +
+

None

+
+ +

Usage Example:

+ +
+
+
+

client.request_interactive_slack_approval( + ... slack_resource_path="/u/alex/my_slack_resource", + ... channel_id="admins-slack-channel", + ... message="Please approve this request", + ... approver="approver123", + ... default_args_json={"key1": "value1", "key2": 42}, + ... dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]}, + ... )

+
+
+
+ +

Notes:

+ +
    +
  • This function must be executed within a Windmill flow or flow preview.
  • +
  • The function checks for required environment variables (WM_FLOW_JOB_ID, WM_FLOW_STEP_ID) to ensure it is run in the appropriate context.
  • +
+
+ + +
+
+ +
+ + def + username_to_email(self, username: str) -> str: + + + +
+ +
926    def username_to_email(self, username: str) -> str:
+927        """
+928        Get email from workspace username
+929        This method is particularly useful for apps that require the email address of the viewer.
+930        Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.
+931        """
+932        return self.get(f"/w/{self.workspace}/users/username_to_email/{username}").text
+
+ + +

Get email from workspace username +This method is particularly useful for apps that require the email address of the viewer. +Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.

+
+ + +
+
+ +
+ + def + send_teams_message( self, conversation_id: str, text: str, success: bool = True, card_block: dict = None): + + + +
+ +
934    def send_teams_message(
+935        self,
+936        conversation_id: str,
+937        text: str,
+938        success: bool = True,
+939        card_block: dict = None,
+940    ):
+941        """
+942        Send a message to a Microsoft Teams conversation with conversation_id, where success is used to style the message
+943        """
+944        return self.post(
+945            f"/teams/activities",
+946            json={
+947                "conversation_id": conversation_id,
+948                "text": text,
+949                "success": success,
+950                "card_block": card_block,
+951            },
+952        )
+
+ + +

Send a message to a Microsoft Teams conversation with conversation_id, where success is used to style the message

+
+ + +
+
+
+ +
+ + def + init_global_client(f): + + + +
+ +
955def init_global_client(f):
+956    @functools.wraps(f)
+957    def wrapper(*args, **kwargs):
+958        global _client
+959        if _client is None:
+960            _client = Windmill()
+961        return f(*args, **kwargs)
+962
+963    return wrapper
+
+ + + + +
+
+ +
+ + def + deprecate(in_favor_of: str): + + + +
+ +
966def deprecate(in_favor_of: str):
+967    def decorator(f):
+968        @functools.wraps(f)
+969        def wrapper(*args, **kwargs):
+970            warnings.warn(
+971                (
+972                    f"The '{f.__name__}' method is deprecated and may be removed in the future. "
+973                    f"Consider {in_favor_of}"
+974                ),
+975                DeprecationWarning,
+976            )
+977            return f(*args, **kwargs)
+978
+979        return wrapper
+980
+981    return decorator
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + get_workspace() -> str: + + + +
+ +
984@init_global_client
+985def get_workspace() -> str:
+986    return _client.workspace
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + get_root_job_id(job_id: str | None = None) -> str: + + + +
+ +
989@init_global_client
+990def get_root_job_id(job_id: str | None = None) -> str:
+991    return _client.get_root_job_id(job_id)
+
+ + + + +
+
+ +
+
@init_global_client
+
@deprecate('Windmill().version')
+ + def + get_version() -> str: + + + +
+ +
994@init_global_client
+995@deprecate("Windmill().version")
+996def get_version() -> str:
+997    return _client.version
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + run_script_async( hash_or_path: str, args: Dict[str, Any] = None, scheduled_in_secs: int = None) -> str: + + + +
+ +
1000@init_global_client
+1001def run_script_async(
+1002    hash_or_path: str,
+1003    args: Dict[str, Any] = None,
+1004    scheduled_in_secs: int = None,
+1005) -> str:
+1006    is_path = "/" in hash_or_path
+1007    hash_ = None if is_path else hash_or_path
+1008    path = hash_or_path if is_path else None
+1009    return _client.run_script_async(
+1010        hash_=hash_,
+1011        path=path,
+1012        args=args,
+1013        scheduled_in_secs=scheduled_in_secs,
+1014    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + run_flow_async( path: str, args: Dict[str, Any] = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True) -> str: + + + +
+ +
1017@init_global_client
+1018def run_flow_async(
+1019    path: str,
+1020    args: Dict[str, Any] = None,
+1021    scheduled_in_secs: int = None,
+1022    # can only be set to false if this the job will be fully await and not concurrent with any other job
+1023    # as otherwise the child flow and its own child will store their state in the parent job which will
+1024    # lead to incorrectness and failures
+1025    do_not_track_in_parent: bool = True,
+1026) -> str:
+1027    return _client.run_flow_async(
+1028        path=path,
+1029        args=args,
+1030        scheduled_in_secs=scheduled_in_secs,
+1031        do_not_track_in_parent=do_not_track_in_parent,
+1032    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + run_script_sync( hash: str, args: Dict[str, Any] = None, verbose: bool = False, assert_result_is_not_none: bool = True, cleanup: bool = True, timeout: datetime.timedelta = None) -> Any: + + + +
+ +
1035@init_global_client
+1036def run_script_sync(
+1037    hash: str,
+1038    args: Dict[str, Any] = None,
+1039    verbose: bool = False,
+1040    assert_result_is_not_none: bool = True,
+1041    cleanup: bool = True,
+1042    timeout: dt.timedelta = None,
+1043) -> Any:
+1044    return _client.run_script(
+1045        hash_=hash,
+1046        args=args,
+1047        verbose=verbose,
+1048        assert_result_is_not_none=assert_result_is_not_none,
+1049        cleanup=cleanup,
+1050        timeout=timeout,
+1051    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + run_script_by_path_async( path: str, args: Dict[str, Any] = None, scheduled_in_secs: Optional[int] = None) -> str: + + + +
+ +
1054@init_global_client
+1055def run_script_by_path_async(
+1056    path: str,
+1057    args: Dict[str, Any] = None,
+1058    scheduled_in_secs: Union[None, int] = None,
+1059) -> str:
+1060    return _client.run_script_by_path_async(
+1061        path=path,
+1062        args=args,
+1063        scheduled_in_secs=scheduled_in_secs,
+1064    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + run_script_by_hash_async( hash_: str, args: Dict[str, Any] = None, scheduled_in_secs: Optional[int] = None) -> str: + + + +
+ +
1067@init_global_client
+1068def run_script_by_hash_async(
+1069    hash_: str,
+1070    args: Dict[str, Any] = None,
+1071    scheduled_in_secs: Union[None, int] = None,
+1072) -> str:
+1073    return _client.run_script_by_hash_async(
+1074        hash_=hash_,
+1075        args=args,
+1076        scheduled_in_secs=scheduled_in_secs,
+1077    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + run_script_by_path_sync( path: str, args: Dict[str, Any] = None, verbose: bool = False, assert_result_is_not_none: bool = True, cleanup: bool = True, timeout: datetime.timedelta = None) -> Any: + + + +
+ +
1080@init_global_client
+1081def run_script_by_path_sync(
+1082    path: str,
+1083    args: Dict[str, Any] = None,
+1084    verbose: bool = False,
+1085    assert_result_is_not_none: bool = True,
+1086    cleanup: bool = True,
+1087    timeout: dt.timedelta = None,
+1088) -> Any:
+1089    return _client.run_script(
+1090        path=path,
+1091        args=args,
+1092        verbose=verbose,
+1093        assert_result_is_not_none=assert_result_is_not_none,
+1094        cleanup=cleanup,
+1095        timeout=timeout,
+1096    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + get_id_token(audience: str) -> str: + + + +
+ +
1099@init_global_client
+1100def get_id_token(audience: str) -> str:
+1101    """
+1102    Get a JWT token for the given audience for OIDC purposes to login into third parties like AWS, Vault, GCP, etc.
+1103    """
+1104    return _client.get_id_token(audience)
+
+ + +

Get a JWT token for the given audience for OIDC purposes to login into third parties like AWS, Vault, GCP, etc.

+
+ + +
+
+ +
+
@init_global_client
+ + def + get_job_status(job_id: str) -> Literal['RUNNING', 'WAITING', 'COMPLETED']: + + + +
+ +
1107@init_global_client
+1108def get_job_status(job_id: str) -> JobStatus:
+1109    return _client.get_job_status(job_id)
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + get_result(job_id: str, assert_result_is_not_none=True) -> Dict[str, Any]: + + + +
+ +
1112@init_global_client
+1113def get_result(job_id: str, assert_result_is_not_none=True) -> Dict[str, Any]:
+1114    return _client.get_result(
+1115        job_id=job_id, assert_result_is_not_none=assert_result_is_not_none
+1116    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + duckdb_connection_settings(s3_resource_path: str = '') -> wmill.s3_types.DuckDbConnectionSettings: + + + +
+ +
1119@init_global_client
+1120def duckdb_connection_settings(s3_resource_path: str = "") -> DuckDbConnectionSettings:
+1121    """
+1122    Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+1123    initiate an S3 connection from DuckDB
+1124    """
+1125    return _client.get_duckdb_connection_settings(s3_resource_path)
+
+ + +

Convenient helpers that takes an S3 resource as input and returns the settings necessary to +initiate an S3 connection from DuckDB

+
+ + +
+
+ +
+
@init_global_client
+ + def + polars_connection_settings(s3_resource_path: str = '') -> wmill.s3_types.PolarsConnectionSettings: + + + +
+ +
1128@init_global_client
+1129def polars_connection_settings(s3_resource_path: str = "") -> PolarsConnectionSettings:
+1130    """
+1131    Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+1132    initiate an S3 connection from Polars
+1133    """
+1134    return _client.get_polars_connection_settings(s3_resource_path)
+
+ + +

Convenient helpers that takes an S3 resource as input and returns the settings necessary to +initiate an S3 connection from Polars

+
+ + +
+
+ +
+
@init_global_client
+ + def + boto3_connection_settings(s3_resource_path: str = '') -> wmill.s3_types.Boto3ConnectionSettings: + + + +
+ +
1137@init_global_client
+1138def boto3_connection_settings(s3_resource_path: str = "") -> Boto3ConnectionSettings:
+1139    """
+1140    Convenient helpers that takes an S3 resource as input and returns the settings necessary to
+1141    initiate an S3 connection using boto3
+1142    """
+1143    return _client.get_boto3_connection_settings(s3_resource_path)
+
+ + +

Convenient helpers that takes an S3 resource as input and returns the settings necessary to +initiate an S3 connection using boto3

+
+ + +
+
+ +
+
@init_global_client
+ + def + load_s3_file( s3object: wmill.s3_types.S3Object | str, s3_resource_path: str | None = None) -> bytes: + + + +
+ +
1146@init_global_client
+1147def load_s3_file(s3object: S3Object | str, s3_resource_path: str | None = None) -> bytes:
+1148    """
+1149    Load the entire content of a file stored in S3 as bytes
+1150    """
+1151    return _client.load_s3_file(
+1152        s3object, s3_resource_path if s3_resource_path != "" else None
+1153    )
+
+ + +

Load the entire content of a file stored in S3 as bytes

+
+ + +
+
+ +
+
@init_global_client
+ + def + load_s3_file_reader( s3object: wmill.s3_types.S3Object | str, s3_resource_path: str | None = None) -> _io.BufferedReader: + + + +
+ +
1156@init_global_client
+1157def load_s3_file_reader(
+1158    s3object: S3Object | str, s3_resource_path: str | None = None
+1159) -> BufferedReader:
+1160    """
+1161    Load the content of a file stored in S3
+1162    """
+1163    return _client.load_s3_file_reader(
+1164        s3object, s3_resource_path if s3_resource_path != "" else None
+1165    )
+
+ + +

Load the content of a file stored in S3

+
+ + +
+
+ +
+
@init_global_client
+ + def + write_s3_file( s3object: wmill.s3_types.S3Object | str | None, file_content: _io.BufferedReader | bytes, s3_resource_path: str | None = None, content_type: str | None = None, content_disposition: str | None = None) -> wmill.s3_types.S3Object: + + + +
+ +
1168@init_global_client
+1169def write_s3_file(
+1170    s3object: S3Object | str | None,
+1171    file_content: BufferedReader | bytes,
+1172    s3_resource_path: str | None = None,
+1173    content_type: str | None = None,
+1174    content_disposition: str | None = None,
+1175) -> S3Object:
+1176    """
+1177    Upload a file to S3
+1178
+1179    Content type will be automatically guessed from path extension if left empty
+1180
+1181    See MDN for content_disposition: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
+1182    and content_type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
+1183
+1184    """
+1185    return _client.write_s3_file(
+1186        s3object,
+1187        file_content,
+1188        s3_resource_path if s3_resource_path != "" else None,
+1189        content_type,
+1190        content_disposition,
+1191    )
+
+ + +

Upload a file to S3

+ +

Content type will be automatically guessed from path extension if left empty

+ +

See MDN for content_disposition: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition +and content_type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type

+
+ + +
+
+ +
+
@init_global_client
+ + def + sign_s3_objects( s3_objects: list[wmill.s3_types.S3Object | str]) -> list[wmill.s3_types.S3Object]: + + + +
+ +
1194@init_global_client
+1195def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object]:
+1196    """
+1197    Sign S3 objects to be used by anonymous users in public apps
+1198    Returns a list of signed s3 tokens
+1199    """
+1200    return _client.sign_s3_objects(s3_objects)
+
+ + +

Sign S3 objects to be used by anonymous users in public apps +Returns a list of signed s3 tokens

+
+ + +
+
+ +
+
@init_global_client
+ + def + sign_s3_object(s3_object: wmill.s3_types.S3Object | str) -> wmill.s3_types.S3Object: + + + +
+ +
1203@init_global_client
+1204def sign_s3_object(s3_object: S3Object| str) -> S3Object:
+1205    """
+1206    Sign S3 object to be used by anonymous users in public apps
+1207    Returns a signed s3 object
+1208    """
+1209    return _client.sign_s3_object(s3_object)
+
+ + +

Sign S3 object to be used by anonymous users in public apps +Returns a signed s3 object

+
+ + +
+
+ +
+
@init_global_client
+ + def + whoami() -> dict: + + + +
+ +
1212@init_global_client
+1213def whoami() -> dict:
+1214    """
+1215    Returns the current user
+1216    """
+1217    return _client.user
+
+ + +

Returns the current user

+
+ + +
+
+ +
+
@init_global_client
+
@deprecate('Windmill().state')
+ + def + get_state() -> Any: + + + +
+ +
1220@init_global_client
+1221@deprecate("Windmill().state")
+1222def get_state() -> Any:
+1223    """
+1224    Get the state
+1225    """
+1226    return _client.state
+
+ + +

Get the state

+
+ + +
+
+ +
+
@init_global_client
+ + def + get_resource(path: str, none_if_undefined: bool = False) -> dict | None: + + + +
+ +
1229@init_global_client
+1230def get_resource(
+1231    path: str,
+1232    none_if_undefined: bool = False,
+1233) -> dict | None:
+1234    """Get resource from Windmill"""
+1235    return _client.get_resource(path, none_if_undefined)
+
+ + +

Get resource from Windmill

+
+ + +
+
+ +
+
@init_global_client
+ + def + set_resource(path: str, value: Any, resource_type: str = 'any') -> None: + + + +
+ +
1238@init_global_client
+1239def set_resource(path: str, value: Any, resource_type: str = "any") -> None:
+1240    """
+1241    Set the resource at a given path as a string, creating it if it does not exist
+1242    """
+1243    return _client.set_resource(value=value, path=path, resource_type=resource_type)
+
+ + +

Set the resource at a given path as a string, creating it if it does not exist

+
+ + +
+
+ +
+
@init_global_client
+ + def + set_state(value: Any) -> None: + + + +
+ +
1246@init_global_client
+1247def set_state(value: Any) -> None:
+1248    """
+1249    Set the state
+1250    """
+1251    return _client.set_state(value)
+
+ + +

Set the state

+
+ + +
+
+ +
+
@init_global_client
+ + def + set_progress(value: int, job_id: Optional[str] = None) -> None: + + + +
+ +
1254@init_global_client
+1255def set_progress(value: int, job_id: Optional[str] = None) -> None:
+1256    """
+1257    Set the progress
+1258    """
+1259    return _client.set_progress(value, job_id)
+
+ + +

Set the progress

+
+ + +
+
+ +
+
@init_global_client
+ + def + get_progress(job_id: Optional[str] = None) -> Any: + + + +
+ +
1262@init_global_client
+1263def get_progress(job_id: Optional[str] = None) -> Any:
+1264    """
+1265    Get the progress
+1266    """
+1267    return _client.get_progress(job_id)
+
+ + +

Get the progress

+
+ + +
+
+ +
+ + def + set_shared_state_pickle(value: Any, path='state.pickle') -> None: + + + +
+ +
1270def set_shared_state_pickle(value: Any, path="state.pickle") -> None:
+1271    """
+1272    Set the state in the shared folder using pickle
+1273    """
+1274    return Windmill.set_shared_state_pickle(value=value, path=path)
+
+ + +

Set the state in the shared folder using pickle

+
+ + +
+
+ +
+
@deprecate('Windmill.get_shared_state_pickle(...)')
+ + def + get_shared_state_pickle(path='state.pickle') -> Any: + + + +
+ +
1277@deprecate("Windmill.get_shared_state_pickle(...)")
+1278def get_shared_state_pickle(path="state.pickle") -> Any:
+1279    """
+1280    Get the state in the shared folder using pickle
+1281    """
+1282    return Windmill.get_shared_state_pickle(path=path)
+
+ + +

Get the state in the shared folder using pickle

+
+ + +
+
+ +
+ + def + set_shared_state(value: Any, path='state.json') -> None: + + + +
+ +
1285def set_shared_state(value: Any, path="state.json") -> None:
+1286    """
+1287    Set the state in the shared folder using pickle
+1288    """
+1289    return Windmill.set_shared_state(value=value, path=path)
+
+ + +

Set the state in the shared folder using pickle

+
+ + +
+
+ +
+ + def + get_shared_state(path='state.json') -> None: + + + +
+ +
1292def get_shared_state(path="state.json") -> None:
+1293    """
+1294    Get the state in the shared folder using pickle
+1295    """
+1296    return Windmill.get_shared_state(path=path)
+
+ + +

Get the state in the shared folder using pickle

+
+ + +
+
+ +
+
@init_global_client
+ + def + get_variable(path: str) -> str: + + + +
+ +
1299@init_global_client
+1300def get_variable(path: str) -> str:
+1301    """
+1302    Returns the variable at a given path as a string
+1303    """
+1304    return _client.get_variable(path)
+
+ + +

Returns the variable at a given path as a string

+
+ + +
+
+ +
+
@init_global_client
+ + def + set_variable(path: str, value: str, is_secret: bool = False) -> None: + + + +
+ +
1307@init_global_client
+1308def set_variable(path: str, value: str, is_secret: bool = False) -> None:
+1309    """
+1310    Set the variable at a given path as a string, creating it if it does not exist
+1311    """
+1312    return _client.set_variable(path, value, is_secret)
+
+ + +

Set the variable at a given path as a string, creating it if it does not exist

+
+ + +
+
+ +
+
@init_global_client
+ + def + get_flow_user_state(key: str) -> Any: + + + +
+ +
1315@init_global_client
+1316def get_flow_user_state(key: str) -> Any:
+1317    """
+1318    Get the user state of a flow at a given key
+1319    """
+1320    return _client.get_flow_user_state(key)
+
+ + +

Get the user state of a flow at a given key

+
+ + +
+
+ +
+
@init_global_client
+ + def + set_flow_user_state(key: str, value: Any) -> None: + + + +
+ +
1323@init_global_client
+1324def set_flow_user_state(key: str, value: Any) -> None:
+1325    """
+1326    Set the user state of a flow at a given key
+1327    """
+1328    return _client.set_flow_user_state(key, value)
+
+ + +

Set the user state of a flow at a given key

+
+ + +
+
+ +
+
@init_global_client
+ + def + get_state_path() -> str: + + + +
+ +
1331@init_global_client
+1332def get_state_path() -> str:
+1333    return _client.state_path
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + get_resume_urls(approver: str = None) -> dict: + + + +
+ +
1336@init_global_client
+1337def get_resume_urls(approver: str = None) -> dict:
+1338    return _client.get_resume_urls(approver)
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + request_interactive_slack_approval( slack_resource_path: str, channel_id: str, message: str = None, approver: str = None, default_args_json: dict = None, dynamic_enums_json: dict = None) -> None: + + + +
+ +
1341@init_global_client
+1342def request_interactive_slack_approval(
+1343    slack_resource_path: str,
+1344    channel_id: str,
+1345    message: str = None,
+1346    approver: str = None,
+1347    default_args_json: dict = None,
+1348    dynamic_enums_json: dict = None,
+1349) -> None:
+1350    return _client.request_interactive_slack_approval(
+1351        slack_resource_path=slack_resource_path,
+1352        channel_id=channel_id,
+1353        message=message,
+1354        approver=approver,
+1355        default_args_json=default_args_json,
+1356        dynamic_enums_json=dynamic_enums_json,
+1357    )
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + send_teams_message( conversation_id: str, text: str, success: bool, card_block: dict = None): + + + +
+ +
1360@init_global_client
+1361def send_teams_message(
+1362    conversation_id: str, text: str, success: bool, card_block: dict = None
+1363):
+1364    return _client.send_teams_message(conversation_id, text, success, card_block)
+
+ + + + +
+
+ +
+
@init_global_client
+ + def + cancel_running() -> dict: + + + +
+ +
1367@init_global_client
+1368def cancel_running() -> dict:
+1369    """Cancel currently running executions of the same script."""
+1370    return _client.cancel_running()
+
+ + +

Cancel currently running executions of the same script.

+
+ + +
+
+ +
+
@init_global_client
+ + def + run_script( path: str = None, hash_: str = None, args: dict = None, timeout: datetime.timedelta | int | float = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = True) -> Any: + + + +
+ +
1373@init_global_client
+1374def run_script(
+1375    path: str = None,
+1376    hash_: str = None,
+1377    args: dict = None,
+1378    timeout: dt.timedelta | int | float = None,
+1379    verbose: bool = False,
+1380    cleanup: bool = True,
+1381    assert_result_is_not_none: bool = True,
+1382) -> Any:
+1383    """Run script synchronously and return its result.
+1384    
+1385    .. deprecated:: Use run_script_by_path or run_script_by_hash instead.
+1386    """
+1387    return _client.run_script(
+1388        path=path,
+1389        hash_=hash_,
+1390        args=args,
+1391        verbose=verbose,
+1392        assert_result_is_not_none=assert_result_is_not_none,
+1393        cleanup=cleanup,
+1394        timeout=timeout,
+1395    )
+
+ + +

Run script synchronously and return its result.

+ +

Deprecated since version Use run_script_by_path or run_script_by_hash instead..

+
+ + +
+
+ +
+
@init_global_client
+ + def + run_script_by_path( path: str, args: dict = None, timeout: datetime.timedelta | int | float = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = True) -> Any: + + + +
+ +
1398@init_global_client
+1399def run_script_by_path(
+1400    path: str,
+1401    args: dict = None,
+1402    timeout: dt.timedelta | int | float = None,
+1403    verbose: bool = False,
+1404    cleanup: bool = True,
+1405    assert_result_is_not_none: bool = True,
+1406) -> Any:
+1407    """Run script by path synchronously and return its result."""
+1408    return _client.run_script_by_path(
+1409        path=path,
+1410        args=args,
+1411        verbose=verbose,
+1412        assert_result_is_not_none=assert_result_is_not_none,
+1413        cleanup=cleanup,
+1414        timeout=timeout,
+1415    )
+
+ + +

Run script by path synchronously and return its result.

+
+ + +
+
+ +
+
@init_global_client
+ + def + run_script_by_hash( hash_: str, args: dict = None, timeout: datetime.timedelta | int | float = None, verbose: bool = False, cleanup: bool = True, assert_result_is_not_none: bool = True) -> Any: + + + +
+ +
1418@init_global_client
+1419def run_script_by_hash(
+1420    hash_: str,
+1421    args: dict = None,
+1422    timeout: dt.timedelta | int | float = None,
+1423    verbose: bool = False,
+1424    cleanup: bool = True,
+1425    assert_result_is_not_none: bool = True,
+1426) -> Any:
+1427    """Run script by hash synchronously and return its result."""
+1428    return _client.run_script_by_hash(
+1429        hash_=hash_,
+1430        args=args,
+1431        verbose=verbose,
+1432        assert_result_is_not_none=assert_result_is_not_none,
+1433        cleanup=cleanup,
+1434        timeout=timeout,
+1435    )
+
+ + +

Run script by hash synchronously and return its result.

+
+ + +
+
+ +
+
@init_global_client
+ + def + username_to_email(username: str) -> str: + + + +
+ +
1438@init_global_client
+1439def username_to_email(username: str) -> str:
+1440    """
+1441    Get email from workspace username
+1442    This method is particularly useful for apps that require the email address of the viewer.
+1443    Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.
+1444    """
+1445    return _client.username_to_email(username)
+
+ + +

Get email from workspace username +This method is particularly useful for apps that require the email address of the viewer. +Indeed, in the viewer context WM_USERNAME is set to the username of the viewer but WM_EMAIL is set to the email of the creator of the app.

+
+ + +
+
+ +
+ + def + task(*args, **kwargs): + + + +
+ +
1448def task(*args, **kwargs):
+1449    from inspect import signature
+1450
+1451    def f(func, tag: str | None = None):
+1452        if (
+1453            os.environ.get("WM_JOB_ID") is None
+1454            or os.environ.get("MAIN_OVERRIDE") == func.__name__
+1455        ):
+1456
+1457            def inner(*args, **kwargs):
+1458                return func(*args, **kwargs)
+1459
+1460            return inner
+1461        else:
+1462
+1463            def inner(*args, **kwargs):
+1464                global _client
+1465                if _client is None:
+1466                    _client = Windmill()
+1467                w_id = os.environ.get("WM_WORKSPACE")
+1468                job_id = os.environ.get("WM_JOB_ID")
+1469                f_name = func.__name__
+1470                json = kwargs
+1471                params = list(signature(func).parameters)
+1472                for i, arg in enumerate(args):
+1473                    if i < len(params):
+1474                        p = params[i]
+1475                        key = p
+1476                        if key not in kwargs:
+1477                            json[key] = arg
+1478
+1479                params = {}
+1480                if tag is not None:
+1481                    params["tag"] = tag
+1482                w_as_code_response = _client.post(
+1483                    f"/w/{w_id}/jobs/run/workflow_as_code/{job_id}/{f_name}",
+1484                    json={"args": json},
+1485                    params=params,
+1486                )
+1487                job_id = w_as_code_response.text
+1488                print(f"Executing task {func.__name__} on job {job_id}")
+1489                job_result = _client.wait_job(job_id)
+1490                print(f"Task {func.__name__} ({job_id}) completed")
+1491                return job_result
+1492
+1493            return inner
+1494
+1495    if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
+1496        return f(args[0], None)
+1497    else:
+1498        return lambda x: f(x, kwargs.get("tag"))
+
+ + + + +
+
+ +
+ + def + parse_resource_syntax(s: str) -> Optional[str]: + + + +
+ +
1500def parse_resource_syntax(s: str) -> Optional[str]:
+1501    """Parse resource syntax from string."""
+1502    if s is None:
+1503        return None
+1504    if s.startswith("$res:"):
+1505        return s[5:]
+1506    if s.startswith("res://"):
+1507        return s[6:]
+1508    return None
+
+ + +

Parse resource syntax from string.

+
+ + +
+
+ +
+ + def + parse_s3_object(s3_object: wmill.s3_types.S3Object | str) -> wmill.s3_types.S3Object: + + + +
+ +
1510def parse_s3_object(s3_object: S3Object | str) -> S3Object:
+1511    """Parse S3 object from string or S3Object format."""
+1512    if isinstance(s3_object, str):
+1513        match = re.match(r'^s3://([^/]*)/(.*)$', s3_object)
+1514        if match:
+1515            return S3Object(s3=match.group(2) or "", storage=match.group(1) or None)
+1516        return S3Object(s3="")
+1517    else:
+1518        return s3_object
+
+ + +

Parse S3 object from string or S3Object format.

+
+ + +
+
+ +
+ + def + parse_variable_syntax(s: str) -> Optional[str]: + + + +
+ +
1522def parse_variable_syntax(s: str) -> Optional[str]:
+1523    """Parse variable syntax from string."""
+1524    if s.startswith("var://"):
+1525        return s[6:]
+1526    return None
+
+ + +

Parse variable syntax from string.

+
+ + +
+
+ +
+ + def + append_to_result_stream(text: str) -> None: + + + +
+ +
1529def append_to_result_stream(text: str) -> None:
+1530    """Append a text to the result stream.
+1531    
+1532    Args:
+1533        text: text to append to the result stream
+1534    """
+1535    print("WM_STREAM: {}".format(text.replace(chr(10), '\\n')))
+
+ + +

Append a text to the result stream.

+ +

Args: + text: text to append to the result stream

+
+ + +
+
+ +
+ + def + stream_result(stream) -> None: + + + +
+ +
1537def stream_result(stream) -> None:
+1538    """Stream to the result stream.
+1539    
+1540    Args:
+1541        stream: stream to stream to the result stream
+1542    """
+1543    for text in stream:
+1544        append_to_result_stream(text)
+
+ + +

Stream to the result stream.

+ +

Args: + stream: stream to stream to the result stream

+
+ + +
+
+ + \ No newline at end of file diff --git a/python-client/docs/wmill/s3_reader.html b/python-client/docs/wmill/s3_reader.html new file mode 100644 index 0000000000..8bacbc95d3 --- /dev/null +++ b/python-client/docs/wmill/s3_reader.html @@ -0,0 +1,550 @@ + + + + + + + wmill.s3_reader API documentation + + + + + + + + + +
+
+

+wmill.s3_reader

+ + + + + + +
 1from io import BufferedReader, BytesIO
+ 2from typing import Optional, Union
+ 3
+ 4import httpx
+ 5
+ 6
+ 7class S3BufferedReader(BufferedReader):
+ 8    def __init__(self, workspace: str, windmill_client: httpx.Client, file_key: str, s3_resource_path: Optional[str], storage: Optional[str]):
+ 9        params = {
+10            "file_key": file_key,
+11        }
+12        if s3_resource_path is not None:
+13            params["s3_resource_path"] = s3_resource_path
+14        if storage is not None:
+15            params["storage"] = storage
+16        self._context_manager = windmill_client.stream(
+17            "GET",
+18            f"/w/{workspace}/job_helpers/download_s3_file",
+19            params=params,
+20            timeout=None,
+21        )
+22
+23    def __enter__(self):
+24        reader = self._context_manager.__enter__()
+25        if reader.status_code >= 400:
+26            error_bytes = reader.read()
+27            try:
+28                error_text = error_bytes.decode('utf-8')
+29            except UnicodeDecodeError:
+30                error_text = str(error_bytes)
+31            raise httpx.HTTPStatusError(
+32                f"Failed to load S3 file: {reader.status_code} {reader.reason_phrase} - {error_text}",
+33                request=reader.request,
+34                response=reader
+35            )
+36        self._iterator = reader.iter_bytes()
+37        return self
+38
+39    def peek(self, size=0):
+40        raise Exception("Not implemented, use read() instead")
+41
+42    def read(self, size=-1):
+43        read_result = []
+44        if size < 0:
+45            for b in self._iterator:
+46                read_result.append(b)
+47        else:
+48            for i in range(size):
+49                try:
+50                    b = self._iterator.__next__()
+51                except StopIteration:
+52                    break
+53                read_result.append(b)
+54
+55        return b"".join(read_result)
+56
+57    def read1(self, size=-1):
+58        return self.read(size)
+59
+60    def __exit__(self, *args):
+61        self._context_manager.__exit__(*args)
+62
+63
+64def bytes_generator(buffered_reader: Union[BufferedReader, BytesIO]):
+65    while True:
+66        byte = buffered_reader.read(50 * 1024)
+67        if not byte:
+68            break
+69        yield byte
+
+ + +
+
+ +
+ + class + S3BufferedReader(_io.BufferedReader): + + + +
+ +
 8class S3BufferedReader(BufferedReader):
+ 9    def __init__(self, workspace: str, windmill_client: httpx.Client, file_key: str, s3_resource_path: Optional[str], storage: Optional[str]):
+10        params = {
+11            "file_key": file_key,
+12        }
+13        if s3_resource_path is not None:
+14            params["s3_resource_path"] = s3_resource_path
+15        if storage is not None:
+16            params["storage"] = storage
+17        self._context_manager = windmill_client.stream(
+18            "GET",
+19            f"/w/{workspace}/job_helpers/download_s3_file",
+20            params=params,
+21            timeout=None,
+22        )
+23
+24    def __enter__(self):
+25        reader = self._context_manager.__enter__()
+26        if reader.status_code >= 400:
+27            error_bytes = reader.read()
+28            try:
+29                error_text = error_bytes.decode('utf-8')
+30            except UnicodeDecodeError:
+31                error_text = str(error_bytes)
+32            raise httpx.HTTPStatusError(
+33                f"Failed to load S3 file: {reader.status_code} {reader.reason_phrase} - {error_text}",
+34                request=reader.request,
+35                response=reader
+36            )
+37        self._iterator = reader.iter_bytes()
+38        return self
+39
+40    def peek(self, size=0):
+41        raise Exception("Not implemented, use read() instead")
+42
+43    def read(self, size=-1):
+44        read_result = []
+45        if size < 0:
+46            for b in self._iterator:
+47                read_result.append(b)
+48        else:
+49            for i in range(size):
+50                try:
+51                    b = self._iterator.__next__()
+52                except StopIteration:
+53                    break
+54                read_result.append(b)
+55
+56        return b"".join(read_result)
+57
+58    def read1(self, size=-1):
+59        return self.read(size)
+60
+61    def __exit__(self, *args):
+62        self._context_manager.__exit__(*args)
+
+ + +

Create a new buffered reader using the given readable raw IO object.

+
+ + +
+ +
+ + S3BufferedReader( workspace: str, windmill_client: httpx.Client, file_key: str, s3_resource_path: Optional[str], storage: Optional[str]) + + + +
+ +
 9    def __init__(self, workspace: str, windmill_client: httpx.Client, file_key: str, s3_resource_path: Optional[str], storage: Optional[str]):
+10        params = {
+11            "file_key": file_key,
+12        }
+13        if s3_resource_path is not None:
+14            params["s3_resource_path"] = s3_resource_path
+15        if storage is not None:
+16            params["storage"] = storage
+17        self._context_manager = windmill_client.stream(
+18            "GET",
+19            f"/w/{workspace}/job_helpers/download_s3_file",
+20            params=params,
+21            timeout=None,
+22        )
+
+ + + + +
+
+ +
+ + def + peek(self, size=0): + + + +
+ +
40    def peek(self, size=0):
+41        raise Exception("Not implemented, use read() instead")
+
+ + + + +
+
+ +
+ + def + read(self, size=-1): + + + +
+ +
43    def read(self, size=-1):
+44        read_result = []
+45        if size < 0:
+46            for b in self._iterator:
+47                read_result.append(b)
+48        else:
+49            for i in range(size):
+50                try:
+51                    b = self._iterator.__next__()
+52                except StopIteration:
+53                    break
+54                read_result.append(b)
+55
+56        return b"".join(read_result)
+
+ + +

Read and return up to n bytes.

+ +

If the argument is omitted, None, or negative, reads and +returns all data until EOF.

+ +

If the argument is positive, and the underlying raw stream is +not 'interactive', multiple raw reads may be issued to satisfy +the byte count (unless EOF is reached first). But for +interactive raw streams (as well as sockets and pipes), at most +one raw read will be issued, and a short result does not imply +that EOF is imminent.

+ +

Returns an empty bytes object on EOF.

+ +

Returns None if the underlying raw stream was open in non-blocking +mode and no data is available at the moment.

+
+ + +
+
+ +
+ + def + read1(self, size=-1): + + + +
+ +
58    def read1(self, size=-1):
+59        return self.read(size)
+
+ + +

Read and return up to n bytes, with at most one read() call +to the underlying raw stream. A short result does not imply +that EOF is imminent.

+ +

Returns an empty bytes object on EOF.

+
+ + +
+
+
+ +
+ + def + bytes_generator(buffered_reader: Union[_io.BufferedReader, _io.BytesIO]): + + + +
+ +
65def bytes_generator(buffered_reader: Union[BufferedReader, BytesIO]):
+66    while True:
+67        byte = buffered_reader.read(50 * 1024)
+68        if not byte:
+69            break
+70        yield byte
+
+ + + + +
+
+ + \ No newline at end of file diff --git a/python-client/docs/wmill/s3_types.html b/python-client/docs/wmill/s3_types.html new file mode 100644 index 0000000000..be8528bc05 --- /dev/null +++ b/python-client/docs/wmill/s3_types.html @@ -0,0 +1,841 @@ + + + + + + + wmill.s3_types API documentation + + + + + + + + + +
+
+

+wmill.s3_types

+ + + + + + +
 1from typing import Optional
+ 2
+ 3
+ 4class S3Object(dict):
+ 5    s3: str
+ 6    storage: Optional[str]
+ 7    presigned: Optional[str]
+ 8
+ 9    def __getattr__(self, attr):
+10        return self[attr]
+11
+12
+13class S3FsClientKwargs(dict):
+14    region_name: str
+15
+16    def __getattr__(self, attr):
+17        return self[attr]
+18
+19
+20class S3FsArgs(dict):
+21    endpoint_url: str
+22    key: str
+23    secret: str
+24    use_ssl: bool
+25    cache_regions: bool
+26    client_kwargs: S3FsClientKwargs
+27
+28    def __getattr__(self, attr):
+29        return self[attr]
+30
+31
+32class StorageOptions(dict):
+33    aws_endpoint_url: str
+34    aws_access_key_id: str
+35    aws_secret_access_key: str
+36    aws_region: str
+37    aws_allow_http: str
+38
+39    def __getattr__(self, attr):
+40        return self[attr]
+41
+42
+43class PolarsConnectionSettings(dict):
+44    s3fs_args: S3FsArgs
+45    storage_options: StorageOptions
+46
+47    def __getattr__(self, attr):
+48        return self[attr]
+49
+50
+51class Boto3ConnectionSettings(dict):
+52    endpoint_url: str
+53    region_name: str
+54    use_ssl: bool
+55    aws_access_key_id: str
+56    aws_secret_access_key: str
+57
+58    def __getattr__(self, attr):
+59        return self[attr]
+60
+61
+62class DuckDbConnectionSettings(dict):
+63    connection_settings_str: str
+64
+65    def __getattr__(self, attr):
+66        return self[attr]
+
+ + +
+
+ +
+ + class + S3Object(builtins.dict): + + + +
+ +
 5class S3Object(dict):
+ 6    s3: str
+ 7    storage: Optional[str]
+ 8    presigned: Optional[str]
+ 9
+10    def __getattr__(self, attr):
+11        return self[attr]
+
+ + + + +
+
+ s3: str + + +
+ + + + +
+
+
+ storage: Optional[str] + + +
+ + + + +
+
+
+ presigned: Optional[str] + + +
+ + + + +
+
+
+ +
+ + class + S3FsClientKwargs(builtins.dict): + + + +
+ +
14class S3FsClientKwargs(dict):
+15    region_name: str
+16
+17    def __getattr__(self, attr):
+18        return self[attr]
+
+ + + + +
+
+ region_name: str + + +
+ + + + +
+
+
+ +
+ + class + S3FsArgs(builtins.dict): + + + +
+ +
21class S3FsArgs(dict):
+22    endpoint_url: str
+23    key: str
+24    secret: str
+25    use_ssl: bool
+26    cache_regions: bool
+27    client_kwargs: S3FsClientKwargs
+28
+29    def __getattr__(self, attr):
+30        return self[attr]
+
+ + + + +
+
+ endpoint_url: str + + +
+ + + + +
+
+
+ key: str + + +
+ + + + +
+
+
+ secret: str + + +
+ + + + +
+
+
+ use_ssl: bool + + +
+ + + + +
+
+
+ cache_regions: bool + + +
+ + + + +
+
+
+ client_kwargs: S3FsClientKwargs + + +
+ + + + +
+
+
+ +
+ + class + StorageOptions(builtins.dict): + + + +
+ +
33class StorageOptions(dict):
+34    aws_endpoint_url: str
+35    aws_access_key_id: str
+36    aws_secret_access_key: str
+37    aws_region: str
+38    aws_allow_http: str
+39
+40    def __getattr__(self, attr):
+41        return self[attr]
+
+ + + + +
+
+ aws_endpoint_url: str + + +
+ + + + +
+
+
+ aws_access_key_id: str + + +
+ + + + +
+
+
+ aws_secret_access_key: str + + +
+ + + + +
+
+
+ aws_region: str + + +
+ + + + +
+
+
+ aws_allow_http: str + + +
+ + + + +
+
+
+ +
+ + class + PolarsConnectionSettings(builtins.dict): + + + +
+ +
44class PolarsConnectionSettings(dict):
+45    s3fs_args: S3FsArgs
+46    storage_options: StorageOptions
+47
+48    def __getattr__(self, attr):
+49        return self[attr]
+
+ + + + +
+
+ s3fs_args: S3FsArgs + + +
+ + + + +
+
+
+ storage_options: StorageOptions + + +
+ + + + +
+
+
+ +
+ + class + Boto3ConnectionSettings(builtins.dict): + + + +
+ +
52class Boto3ConnectionSettings(dict):
+53    endpoint_url: str
+54    region_name: str
+55    use_ssl: bool
+56    aws_access_key_id: str
+57    aws_secret_access_key: str
+58
+59    def __getattr__(self, attr):
+60        return self[attr]
+
+ + + + +
+
+ endpoint_url: str + + +
+ + + + +
+
+
+ region_name: str + + +
+ + + + +
+
+
+ use_ssl: bool + + +
+ + + + +
+
+
+ aws_access_key_id: str + + +
+ + + + +
+
+
+ aws_secret_access_key: str + + +
+ + + + +
+
+
+ +
+ + class + DuckDbConnectionSettings(builtins.dict): + + + +
+ +
63class DuckDbConnectionSettings(dict):
+64    connection_settings_str: str
+65
+66    def __getattr__(self, attr):
+67        return self[attr]
+
+ + + + +
+
+ connection_settings_str: str + + +
+ + + + +
+
+
+ + \ No newline at end of file From c4fff2165c2a2ff469309f6257533f14c07e9822 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 15 Oct 2025 22:35:15 +0000 Subject: [PATCH 07/33] nit badges suspended --- frontend/src/lib/components/runs/RunsQueue.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/runs/RunsQueue.svelte b/frontend/src/lib/components/runs/RunsQueue.svelte index 6967c92222..05e24f61c6 100644 --- a/frontend/src/lib/components/runs/RunsQueue.svelte +++ b/frontend/src/lib/components/runs/RunsQueue.svelte @@ -76,7 +76,7 @@ {/snippet}
0 - ? 'bg-yellow-500 text-white rounded-full w-6 h-6 flex center-center' + ? 'bg-yellow-500 text-white rounded-full min-w-6 h-6 flex center-center' : ''}>{queue_count ? ($queue_count ?? 0).toFixed(0) : '...'}
@@ -102,7 +102,7 @@ {/snippet}
0 - ? 'bg-surface-secondary-inverse text-primary-inverse rounded-full w-6 h-6 flex center-center' + ? 'bg-surface-secondary-inverse text-primary-inverse rounded-full min-w-6 h-6 flex center-center' : ''}>{suspended_count ? ($suspended_count ?? 0).toFixed(0) : '...'}
From 06c05200cd37e5d299768d7a1eda168e4d78b232 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 15 Oct 2025 22:39:10 +0000 Subject: [PATCH 08/33] chore(main): release 1.560.0 (#6823) * chore(main): release 1.560.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 20 +++ backend/Cargo.lock | 133 ++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 99 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ecd8fd76..bcac5dfdfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.560.0](https://github.com/windmill-labs/windmill/compare/v1.559.0...v1.560.0) (2025-10-15) + + +### Features + +* add support for zoho oauth ([#6809](https://github.com/windmill-labs/windmill/issues/6809)) ([9d9c29f](https://github.com/windmill-labs/windmill/commit/9d9c29fdfa15cc655854ec909dea944d10ce7374)) +* **backend:** use flow nodes opti for ai agent steps ([#6808](https://github.com/windmill-labs/windmill/issues/6808)) ([8d5acda](https://github.com/windmill-labs/windmill/commit/8d5acda340cd105c5b0dfc2bfe59b7e996bd2707)) +* build pydoc for wmill python client and mount in container image ([#6828](https://github.com/windmill-labs/windmill/issues/6828)) ([d75e9e3](https://github.com/windmill-labs/windmill/commit/d75e9e3d92d43f449a6296b367018f8fa3da6507)) +* **settings:** add unsaved changes warning for workspace settings ([#6813](https://github.com/windmill-labs/windmill/issues/6813)) ([cb88187](https://github.com/windmill-labs/windmill/commit/cb8818796ddd68d2b2ee1dea5f9b0a648f0c1ec9)) + + +### Bug Fixes + +* always create instance groups with uuid ([#6826](https://github.com/windmill-labs/windmill/issues/6826)) ([48acc57](https://github.com/windmill-labs/windmill/commit/48acc57823792c9e795f9735712e1b2ed6d2b4e2)) +* bug for loop flow inconsistent state ([#6815](https://github.com/windmill-labs/windmill/issues/6815)) ([2565222](https://github.com/windmill-labs/windmill/commit/256522273ee65b67075ac91408825b1c6e91ef06)) +* fix concurrency key filter ([892ce64](https://github.com/windmill-labs/windmill/commit/892ce64ea8550c22d65180c71f57c90a65583832)) +* gcp script picker ([#6837](https://github.com/windmill-labs/windmill/issues/6837)) ([d12c8f3](https://github.com/windmill-labs/windmill/commit/d12c8f34efe5ebbdbbf85ae41bb11307dc5d8ea3)) +* resource editor should not autoselect resources for optional fields ([#6821](https://github.com/windmill-labs/windmill/issues/6821)) ([85d1b8a](https://github.com/windmill-labs/windmill/commit/85d1b8a3e6af41bba93128ebcb88ada383ed2d65)) +* support dyn select for sub flow ([#6835](https://github.com/windmill-labs/windmill/issues/6835)) ([b211155](https://github.com/windmill-labs/windmill/commit/b211155784135b1377975a2759f2ddca1cffcea2)) + ## [1.559.0](https://github.com/windmill-labs/windmill/compare/v1.558.1...v1.559.0) (2025-10-14) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9f8e2fc17b..b7f7d1d42e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -122,7 +122,7 @@ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" dependencies = [ "aws-lc-sys", "zeroize", @@ -801,9 +801,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.31.0" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" dependencies = [ "bindgen 0.72.1", "cc", @@ -1023,7 +1023,7 @@ dependencies = [ "pin-project-lite", "rustls 0.21.12", "rustls 0.23.29", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.2", "rustls-pki-types", "tokio", "tower 0.5.2", @@ -1962,9 +1962,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -4831,9 +4831,9 @@ dependencies = [ [[package]] name = "dyn-stack-macros" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05dbec7076f432bb132db738df90d87a4f5789e99f59e7b1219a6b8ef61eaa68" +checksum = "00140340c29b813fdf6ff2237c4407405baefc72cacbe8f1e2277a75b90e5d30" [[package]] name = "dynasm" @@ -5870,21 +5870,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -6666,7 +6666,7 @@ dependencies = [ "hyper-util", "log", "rustls 0.23.29", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.2", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -7182,7 +7182,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] @@ -8180,7 +8180,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -8191,7 +8191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] @@ -8330,7 +8330,7 @@ dependencies = [ "bytes", "crc32fast", "flate2", - "getrandom 0.3.3", + "getrandom 0.3.4", "mysql-common-derive", "num-bigint", "num-traits", @@ -9058,9 +9058,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" dependencies = [ "bitflags 2.9.4", "cfg-if", @@ -9099,9 +9099,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -10179,9 +10179,9 @@ dependencies = [ [[package]] name = "pure-rust-locales" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" +checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d" [[package]] name = "pwd" @@ -10252,7 +10252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.0", "ring 0.17.14", @@ -10368,7 +10368,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -10700,7 +10700,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls 0.23.29", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.2", "rustls-pki-types", "serde", "serde_json", @@ -11161,9 +11161,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -13305,7 +13305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.2", "windows-sys 0.61.2", @@ -13832,7 +13832,7 @@ dependencies = [ "httparse", "rand 0.8.5", "ring 0.17.14", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.2", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -13925,7 +13925,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.2", "rustls-pemfile 2.2.0", "socket2 0.5.10", "tokio", @@ -14652,7 +14652,7 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "serde", "wasm-bindgen", @@ -14760,15 +14760,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -15131,7 +15122,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15191,7 +15182,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "argon2", @@ -15311,7 +15302,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.559.0" +version = "1.560.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15326,7 +15317,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.559.0" +version = "1.560.0" dependencies = [ "chrono", "serde", @@ -15339,7 +15330,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "axum", @@ -15358,7 +15349,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "async-recursion", @@ -15442,7 +15433,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.559.0" +version = "1.560.0" dependencies = [ "regex", "serde", @@ -15457,7 +15448,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "bytes", @@ -15481,7 +15472,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.559.0" +version = "1.560.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15493,7 +15484,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.559.0" +version = "1.560.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15502,7 +15493,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "lazy_static", @@ -15514,7 +15505,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "serde_json", @@ -15526,7 +15517,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "gosyn", @@ -15538,7 +15529,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "lazy_static", @@ -15550,7 +15541,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "serde_json", @@ -15562,7 +15553,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "nu-parser", @@ -15573,7 +15564,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15584,7 +15575,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15596,7 +15587,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "async-recursion", @@ -15619,7 +15610,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "lazy_static", @@ -15633,7 +15624,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15650,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "lazy_static", @@ -15664,7 +15655,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "lazy_static", @@ -15682,7 +15673,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15707,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "serde_json", @@ -15717,7 +15708,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "async-recursion", @@ -15750,7 +15741,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.559.0" +version = "1.560.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15760,7 +15751,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.559.0" +version = "1.560.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3df2bb6f25..04a3464307 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.559.0" +version = "1.560.0" authors.workspace = true edition.workspace = true @@ -34,7 +34,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.559.0" +version = "1.560.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 417b433d1d..f7b949f2d9 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.559.0 + version: 1.560.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index f0c2dbd0a6..71f1371085 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.559.0"; +export const VERSION = "v1.560.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 0e544c64bd..0aab1e0045 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.559.0"; +export const VERSION = "1.560.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6bccce08e3..ee1ce2687b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.559.0", + "version": "1.560.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.559.0", + "version": "1.560.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 32acaaa4d7..4d04a38246 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.559.0", + "version": "1.560.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 6ff3d03015..c970c8e1c6 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.559.0" -wmill_pg = ">=1.559.0" +wmill = ">=1.560.0" +wmill_pg = ">=1.560.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index e0bf944062..54d0a7d450 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.559.0 + version: 1.560.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 7ea75be2ec..ace7c69295 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.559.0' + ModuleVersion = '1.560.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 8e79fe355a..d867810f7d 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.559.0" +version = "1.560.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index c342ec65f5..1a905f263b 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.559.0" +version = "1.560.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index cc92ebd43a..70372d564c 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.559.0", + "version": "1.560.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index cd3c21925a..6e99cab791 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.559.0", + "version": "1.560.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index b38560d95d..3d75838ea8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.559.0 +1.560.0 From 32fae7a10c769473c708970e18c1f8268d62183f Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:08:18 +0200 Subject: [PATCH 09/33] feat: ansible playbook execution git repo mode (repo viewer + UI utils) (#6831) * Improve minio on flake.nix * Add first asset parsing logic for ansible * Correct html gt sign * Decouple s3 file picker from drawer * Factor duplicate code into snippet * Update S3FilePickerInner to be compatible * Fix pane shrinking issue * Git repo viewer * Change GitRepoViewer * Endpoints for git repo visualizer * Move git repo viewer to its own component * Add button to populate git repo viewer * Update parser yaml for new ansisble features (repo viewer) * Reflect parser changes for ansible * Add button to add the git repo mode of declaration for ansible * Factor function * Playbook + inventories into the drawer * Add button to add inventories from s3 * Move tests to lib.rs * Inventory loading from s3 * Move get github app token logic to be reused by ansible * Update parser and ansible executor * Use the correct path for inventories * Add nushell to flake for wasm builds * Add published parser * Update hubPaths with clone and upload to s3 * Update ee-repo to the branch ref * Fix npm run check * Update cargo.lock * Change labels on buttons * Remove debug log * Update ee-repo-ref * Fix ee issues * Update ee-repo ref * Fix typo * Fix ee * Update ee-repo-ref * Fix missing imports * Unused var * Fix typo * Layout improvents * Fix typos * Remove unused function and log --------- Co-authored-by: Ruben Fiszel --- backend/Cargo.lock | 1 + backend/ee-repo-ref.txt | 2 +- .../parsers/windmill-parser-wasm/src/lib.rs | 23 + .../parsers/windmill-parser-yaml/Cargo.toml | 1 + .../windmill-parser-yaml/src/asset_parser.rs | 41 + .../parsers/windmill-parser-yaml/src/lib.rs | 259 ++++- .../windmill-parser/src/asset_parser.rs | 7 + backend/windmill-api/openapi.yaml | 238 +++++ backend/windmill-api/src/ai.rs | 6 +- backend/windmill-api/src/resources.rs | 100 ++ backend/windmill-api/src/variables.rs | 33 - backend/windmill-common/src/git_sync_oss.rs | 31 + backend/windmill-common/src/lib.rs | 3 + backend/windmill-common/src/variables.rs | 36 +- .../windmill-worker/src/ansible_executor.rs | 139 ++- flake.nix | 23 +- frontend/package-lock.json | 8 +- frontend/package.json | 4 +- frontend/src/lib/ansibleUtils.ts | 384 ++++++++ frontend/src/lib/components/EditorBar.svelte | 35 + .../components/FlowStatusViewerInner.svelte | 2 +- .../components/GitRepoPopoverPicker.svelte | 99 ++ .../components/GitRepoResourcePicker.svelte | 275 ++++++ .../src/lib/components/GitRepoViewer.svelte | 183 ++++ .../src/lib/components/S3FilePicker.svelte | 906 +----------------- .../lib/components/S3FilePickerInner.svelte | 886 +++++++++++++++++ .../src/lib/components/ScriptBuilder.svelte | 2 +- .../src/lib/components/ScriptEditor.svelte | 350 ++++--- .../schema/EditableSchemaSdkWrapper.svelte | 4 +- .../components/sidebar/SidebarContent.svelte | 2 +- frontend/src/lib/hub.ts | 1 + frontend/src/lib/hubPaths.json | 3 +- frontend/src/lib/infer.ts | 16 +- 33 files changed, 3038 insertions(+), 1065 deletions(-) create mode 100644 backend/parsers/windmill-parser-yaml/src/asset_parser.rs create mode 100644 backend/windmill-common/src/git_sync_oss.rs create mode 100644 frontend/src/lib/ansibleUtils.ts create mode 100644 frontend/src/lib/components/GitRepoPopoverPicker.svelte create mode 100644 frontend/src/lib/components/GitRepoResourcePicker.svelte create mode 100644 frontend/src/lib/components/GitRepoViewer.svelte create mode 100644 frontend/src/lib/components/S3FilePickerInner.svelte diff --git a/backend/Cargo.lock b/backend/Cargo.lock index b7f7d1d42e..50b1a4a4f1 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15701,6 +15701,7 @@ name = "windmill-parser-yaml" version = "1.560.0" dependencies = [ "anyhow", + "serde", "serde_json", "windmill-parser", "yaml-rust", diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 12cf35dc1b..48777ef499 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -c7d34190819c83b4dfe62498a47ab9b439a321a9 +af5cfc6b0f0bd42f95a5842dff92cf598d3aa6d8 diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index 238b207dd2..34969f8e5f 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -150,6 +150,16 @@ pub fn parse_ansible(code: &str) -> String { wrap_sig(windmill_parser_yaml::parse_ansible_sig(code)) } +#[cfg(feature = "ansible-parser")] +#[wasm_bindgen] +pub fn parse_ansible_delegate(code: &str) -> String { + if let Ok(r) = windmill_parser_yaml::parse_delegate_to_git_repo(code) { + return serde_json::to_string(&r).unwrap(); + } else { + return "Invalid".to_string(); + } +} + #[cfg(feature = "csharp-parser")] #[wasm_bindgen] pub fn parse_csharp(code: &str) -> String { @@ -173,6 +183,7 @@ pub fn parse_java(code: &str) -> String { pub fn parse_ruby(code: &str) -> String { wrap_sig(windmill_parser_ruby::parse_ruby_signature(code)) } + #[cfg(feature = "sql-parser")] #[wasm_bindgen] pub fn parse_assets_sql(code: &str) -> String { @@ -203,4 +214,16 @@ pub fn parse_assets_py(code: &str) -> String { } } +#[cfg(feature = "ansible-parser")] +#[wasm_bindgen] +pub fn parse_assets_ansible(code: &str) -> String { + let o = windmill_parser_yaml::parse_assets(code); + if let Ok(r) = o { + return serde_json::to_string(&r).unwrap(); + } else { + return format!("err: {:?}", o.err().unwrap()); + return "Invalid".to_string(); + } +} + // for related places search: ADD_NEW_LANG diff --git a/backend/parsers/windmill-parser-yaml/Cargo.toml b/backend/parsers/windmill-parser-yaml/Cargo.toml index 9bcfbea7a7..eb01ba1936 100644 --- a/backend/parsers/windmill-parser-yaml/Cargo.toml +++ b/backend/parsers/windmill-parser-yaml/Cargo.toml @@ -13,3 +13,4 @@ yaml-rust.workspace = true windmill-parser.workspace = true anyhow.workspace = true serde_json.workspace = true +serde.workspace = true diff --git a/backend/parsers/windmill-parser-yaml/src/asset_parser.rs b/backend/parsers/windmill-parser-yaml/src/asset_parser.rs new file mode 100644 index 0000000000..0b9ba0d38a --- /dev/null +++ b/backend/parsers/windmill-parser-yaml/src/asset_parser.rs @@ -0,0 +1,41 @@ +use windmill_parser::asset_parser::{ + merge_assets, AssetKind, AssetUsageAccessType, ParseAssetsResult, + }; + +use crate::{parse_ansible_reqs, ResourceOrVariablePath}; + +pub fn parse_assets(input: &str) -> anyhow::Result>> { + let mut assets = vec![]; + if let (_, Some(ansible_reqs), _) = parse_ansible_reqs(input)? { + if let Some(delegate_to_git_repo_details) = ansible_reqs.delegate_to_git_repo { + assets.push(ParseAssetsResult { + kind: AssetKind::Resource, + path: delegate_to_git_repo_details.resource, + access_type: Some(AssetUsageAccessType::R), + }) + } + + for i in ansible_reqs.inventories { + if let Some(pinned_res) = i.pinned_resource { + assets.push(ParseAssetsResult { + kind: AssetKind::Resource, + path: pinned_res, + access_type: Some(AssetUsageAccessType::R), + }) + } + } + + for file in ansible_reqs.file_resources { + if let ResourceOrVariablePath::Resource(resource) = file.resource_path { + assets.push(ParseAssetsResult { + kind: AssetKind::Resource, + path: resource, + access_type: Some(AssetUsageAccessType::R), + }) + } + } + } + + Ok(merge_assets(assets)) +} + diff --git a/backend/parsers/windmill-parser-yaml/src/lib.rs b/backend/parsers/windmill-parser-yaml/src/lib.rs index 710d17c966..dee9da5745 100644 --- a/backend/parsers/windmill-parser-yaml/src/lib.rs +++ b/backend/parsers/windmill-parser-yaml/src/lib.rs @@ -1,15 +1,26 @@ use std::collections::HashMap; use anyhow::anyhow; +use serde::Serialize; use serde_json::json; use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ}; use yaml_rust::{Yaml, YamlEmitter, YamlLoader}; +pub mod asset_parser; +pub use asset_parser::parse_assets; + pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result { let docs = YamlLoader::load_from_str(inner_content) .map_err(|e| anyhow!("Failed to parse yaml: {}", e))?; - if docs.len() < 2 { + let mut delegating_to_git_repo = false; + if let Yaml::Hash(doc) = &docs[0] { + if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) { + delegating_to_git_repo = extract_delegate_to_git_repo_details(v).is_some(); + } + } + + if docs.len() < 2 && !delegating_to_git_repo { return Ok(MainArgSignature { star_args: false, star_kwargs: false, @@ -56,7 +67,21 @@ pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result { + for inv in parse_additional_inventories(value)? { + if let PreexistingAnsibleInventory::PassedInArgs(i) = inv { + args.push(Arg { + name: i.name, + otyp: None, + typ: Typ::List(Box::new(Typ::Str(i.options))), + has_default: false, + default: None, + oidx: None, + }); + } } } _ => (), @@ -211,6 +236,18 @@ pub struct AnsibleInventory { pub pinned_resource: Option, } +#[derive(Debug, Clone)] +pub enum PreexistingAnsibleInventory { + Static(String), + PassedInArgs(InventoryFilenameListDefinition), +} + +#[derive(Debug, Clone)] +pub struct InventoryFilenameListDefinition { + pub options: Option>, + pub name: String, +} + #[derive(Debug, Clone)] pub struct GitRepo { pub url: String, @@ -219,12 +256,22 @@ pub struct GitRepo { pub target_path: String, } +#[derive(Debug, Clone, Serialize)] +pub struct DelegateToGitRepoDetails { + pub resource: String, + pub playbook: Option, + pub commit: Option, + pub inventories_location: Option, + pub vars_location: Option, +} + #[derive(Debug, Clone)] pub struct AnsibleRequirements { pub python_reqs: Vec, pub roles_and_collections: Option, pub file_resources: Vec, pub inventories: Vec, + pub additional_inventories: Vec, pub vars: Vec<(String, String)>, pub resources: Vec<(String, String)>, pub options: AnsiblePlaybookOptions, @@ -232,6 +279,7 @@ pub struct AnsibleRequirements { pub vault_id: Vec, pub git_repos: Vec, pub git_ssh_identity: Vec, + pub delegate_to_git_repo: Option, } impl Default for AnsibleRequirements { @@ -241,6 +289,7 @@ impl Default for AnsibleRequirements { roles_and_collections: None, file_resources: vec![], inventories: vec![], + additional_inventories: vec![], vars: vec![], resources: vec![], options: AnsiblePlaybookOptions { @@ -254,10 +303,57 @@ impl Default for AnsibleRequirements { vault_id: vec![], git_repos: vec![], git_ssh_identity: vec![], + delegate_to_git_repo: None, } } } +fn parse_additional_inventories( + inventory_yaml: &Yaml, +) -> anyhow::Result> { + if let Yaml::Array(arr) = inventory_yaml { + let mut ret = vec![]; + let mut count = -1; + for inv in arr { + if let Yaml::String(inv_name) = inv { + ret.push(PreexistingAnsibleInventory::Static(inv_name.clone())); + } else if let Yaml::Hash(inv) = inv { + if let Some(options) = inv.get(&Yaml::String("options".to_string())) { + let options = match options { + Yaml::Null => None, + Yaml::Array(elements) => Some( + elements + .iter() + .filter_map(|s| s.as_str().map(|s| s.to_string())) + .collect(), + ), + _ => continue, + }; + + let name = inv + .get(&Yaml::String("name".to_string())) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| { + count += 1; + if count == 0 { + "Additional inventories".to_string() + } else { + format!("Additional inventories ({count})") + } + }); + + ret.push(PreexistingAnsibleInventory::PassedInArgs( + InventoryFilenameListDefinition { options, name }, + )) + } + } + } + return Ok(ret); + } + return Err(anyhow!("Invalid inventory definition")); +} + fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result> { if let Yaml::Array(arr) = inventory_yaml { let mut ret = vec![]; @@ -303,6 +399,20 @@ fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result anyhow::Result> { + let docs = YamlLoader::load_from_str(inner_content) + .map_err(|e| anyhow!("Failed to parse yaml: {}", e))?; + + if let Yaml::Hash(doc) = &docs[0] { + if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) { + return Ok(extract_delegate_to_git_repo_details(v)); + } + } + return Ok(None); +} + pub fn parse_ansible_reqs( inner_content: &str, ) -> anyhow::Result<(String, Option, String)> { @@ -310,12 +420,19 @@ pub fn parse_ansible_reqs( let docs = YamlLoader::load_from_str(inner_content) .map_err(|e| anyhow!("Failed to parse yaml: {}", e))?; - if docs.len() < 2 { - return Ok((logs, None, inner_content.to_string())); - } let mut ret = AnsibleRequirements::default(); + if let Yaml::Hash(doc) = &docs[0] { + if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) { + ret.delegate_to_git_repo = extract_delegate_to_git_repo_details(v); + } + } + + if ret.delegate_to_git_repo.is_none() && docs.len() < 2 { + return Ok((logs, None, inner_content.to_string())); + } + if let Yaml::Hash(doc) = &docs[0] { for (key, value) in doc { match key { @@ -367,7 +484,11 @@ pub fn parse_ansible_reqs( } } Yaml::String(key) if key == "inventory" => { - ret.inventories = parse_inventories(value)?; + ret.inventories.extend(parse_inventories(value)?); + } + Yaml::String(key) if key == "additional_inventories" => { + ret.additional_inventories + .extend(parse_additional_inventories(value)?); } Yaml::String(key) if key == "vault_password" => { let Yaml::String(filename) = value else { @@ -423,11 +544,13 @@ pub fn parse_ansible_reqs( ret.git_ssh_identity.push(file_name.clone()); } } + Yaml::String(key) if key == "delegate_to_git_repo" => {} Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)), _ => (), } } } + let mut out_str = String::new(); let mut emitter = YamlEmitter::new(&mut out_str); @@ -437,6 +560,42 @@ pub fn parse_ansible_reqs( Ok((logs, Some(ret), out_str)) } +fn extract_delegate_to_git_repo_details(value: &Yaml) -> Option { + if let Yaml::Hash(v) = value { + if let Some(resource) = v + .get(&Yaml::String("resource".to_string())) + .and_then(|s| s.as_str()) + .map(|s| s.to_string()) + { + let playbook = v + .get(&Yaml::String("playbook".to_string())) + .and_then(|s| s.as_str()) + .map(|s| s.to_string()); + let commit = v + .get(&Yaml::String("commit".to_string())) + .and_then(|s| s.as_str()) + .map(|s| s.to_string()); + let inventories_location = v + .get(&Yaml::String("inventories_location".to_string())) + .and_then(|s| s.as_str()) + .map(|s| s.to_string()); + let vars_location = v + .get(&Yaml::String("vars_location".to_string())) + .and_then(|s| s.as_str()) + .map(|s| s.to_string()); + + return Some(DelegateToGitRepoDetails { + resource, + playbook, + commit, + inventories_location, + vars_location, + }); + } + } + return None; +} + fn parse_git_repo(r: &Yaml) -> anyhow::Result { let Yaml::Hash(repo) = r else { return Err(anyhow!("Should be a Map")); @@ -679,7 +838,7 @@ pub fn add_versions_to_requirements_yaml( input: &str, role_versions: &HashMap, collection_versions: &HashMap, -) -> anyhow::Result<(String,String)> { +) -> anyhow::Result<(String, String)> { let mut docs = YamlLoader::load_from_str(input).map_err(|e| anyhow!("YAML parse error: {}", e))?; let doc = &mut docs[0]; @@ -709,3 +868,89 @@ pub fn add_versions_to_requirements_yaml( Ok((out_str, logs)) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_ansible_assets() { + let p = r#" +--- +inventory: + - resource_type: ansible_inventory + # You can pin an inventory to this script by hardcoding the resource path: + # resource: u/user/your_resource +# - name: hcloud.yml +# resource_type: dynamic_inventory + +additional_inventories: + - options: ["a", "b", "c"] + +options: + - verbosity: vvv + +delegate_to_git_repo: + resource: u/admin/git_reportino + playbook: ./playbooks/playbook.yml + commit: 7sh7dh73h7dhd299d91hd1hdh3d3hygh4372 + + +# File resources will be written in the relative `target` location before +# running the playbook +files: + - resource: u/user/fabulous_jinja_template + target: ./config_template.j2 + - variable: u/user/ssh_key + target: ./ssh_key + mode: '0600' + +# Define the arguments of the windmill script +extra_vars: + world_qualifier: + type: string + +# If using Ansible Vault: +# vault_password: u/user/ansible_vault_password + +dependencies: + galaxy: + collections: + - name: community.general + - name: community.vmware + roles: + python: + - jmespath +--- +- name: Echo + hosts: 127.0.0.1 + connection: local + vars: + my_result: + a: 2 + b: true + c: "Hello" + + tasks: + - name: Print debug message + debug: + msg: "Hello, {{world_qualifier}} world!" + - name: Write variable my_result to result.json + delegate_to: localhost + copy: + content: "{{ my_result | to_json }}" + dest: result.json +"#; + let a = parse_assets(p).unwrap(); + println!("The resulting assets are: {}", a.len()); + + let a = parse_ansible_reqs(p).unwrap(); + println!("The resulting reqs are: {:#?}", a); + + let a = parse_ansible_sig(p).unwrap(); + println!("The resulting sig is: {:#?}", a); + + let a = parse_delegate_to_git_repo(p).unwrap(); + println!("The resulting delegate_to_kit_repo is: {:#?}", a); + } +} diff --git a/backend/parsers/windmill-parser/src/asset_parser.rs b/backend/parsers/windmill-parser/src/asset_parser.rs index 82cf8b8499..a721590ae9 100644 --- a/backend/parsers/windmill-parser/src/asset_parser.rs +++ b/backend/parsers/windmill-parser/src/asset_parser.rs @@ -26,6 +26,13 @@ pub struct ParseAssetsResult> { pub access_type: Option, // None in case of ambiguity } +#[derive(Debug, Clone, Serialize)] +pub struct DelegateToGitRepoDetails { + pub resource: String, + pub playbook: Option, + pub commit: Option, +} + pub fn merge_assets>(assets: Vec>) -> Vec> { let mut arr: Vec> = vec![]; for asset in assets { diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index f7b949f2d9..37c46fff9a 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -4192,6 +4192,29 @@ paths: application/json: schema: {} + /w/{workspace}/resources/git_commit_hash/{path}: + get: + summary: get git repository latest commit hash + operationId: getGitCommitHash + tags: + - resource + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: git commit hash + content: + application/json: + schema: + type: object + properties: + commit_hash: + type: string + description: Latest commit hash from git ls-remote + required: + - commit_hash + /w/{workspace}/resources/exists/{path}: get: summary: does resource exists @@ -13441,6 +13464,160 @@ paths: schema: $ref: "#/components/schemas/WindmillFilePreview" + /w/{workspace}/job_helpers/list_git_repo_files: + get: + summary: List the file keys available in instance object storage with resource-based access control + operationId: listGitRepoFiles + tags: + - helpers + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: max_keys + in: query + required: true + schema: + type: integer + - name: marker + in: query + schema: + type: string + - name: prefix + in: query + required: false + schema: + type: string + description: Must follow format gitrepos/{workspace_id}/{resource_path}/... + - name: storage + in: query + schema: + type: string + responses: + "200": + description: List of file keys + content: + application/json: + schema: + type: object + properties: + next_marker: + type: string + windmill_large_files: + type: array + items: + $ref: "#/components/schemas/WindmillLargeFile" + restricted_access: + type: boolean + required: + - windmill_large_files + + /w/{workspace}/job_helpers/load_git_repo_file_preview: + get: + summary: Load a preview of a file from instance storage with resource-based access control + operationId: loadGitRepoFilePreview + tags: + - helpers + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: file_key + in: query + required: true + schema: + type: string + description: Must follow format gitrepos/{workspace_id}/{resource_path}/... + - name: file_size_in_bytes + in: query + schema: + type: integer + - name: file_mime_type + in: query + schema: + type: string + - name: csv_separator + in: query + schema: + type: string + - name: csv_has_header + in: query + schema: + type: boolean + - name: read_bytes_from + in: query + schema: + type: integer + - name: read_bytes_length + in: query + schema: + type: integer + - name: storage + in: query + schema: + type: string + responses: + "200": + description: FilePreview + content: + application/json: + schema: + $ref: "#/components/schemas/WindmillFilePreview" + + /w/{workspace}/job_helpers/load_git_repo_file_metadata: + get: + summary: Load file metadata from instance storage with resource-based access control + operationId: loadGitRepoFileMetadata + tags: + - helpers + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: file_key + in: query + required: true + schema: + type: string + description: Must follow format gitrepos/{workspace_id}/{resource_path}/... + - name: storage + in: query + schema: + type: string + responses: + "200": + description: FileMetadata + content: + application/json: + schema: + $ref: "#/components/schemas/WindmillFileMetadata" + + /w/{workspace}/job_helpers/check_s3_folder_exists: + get: + summary: Check if S3 path exists and is a folder + operationId: checkS3FolderExists + tags: + - helpers + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: file_key + description: S3 file key to check (e.g., gitrepos/{workspace_id}/u/user/resource/{commit_hash}) + in: query + required: true + schema: + type: string + responses: + "200": + description: S3 folder existence check result + content: + application/json: + schema: + type: object + properties: + exists: + type: boolean + description: Whether the path exists + is_folder: + type: boolean + description: Whether the path is a folder (true) or file (false) + required: + - exists + - is_folder + /w/{workspace}/job_helpers/load_parquet_preview/{path}: get: summary: Load a preview of a parquet file @@ -13677,6 +13854,67 @@ paths: required: - file_key + /w/{workspace}/job_helpers/upload_git_repo_file_to_instance_storage: + post: + summary: Upload a file to the instance storage gitrepos section for viewing + operationId: gitRepoViewerFileUpload + tags: + - helpers + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: file_key + in: query + required: false + schema: + type: string + - name: file_extension + in: query + required: false + schema: + type: string + - name: s3_resource_path + in: query + required: false + schema: + type: string + - name: resource_type + in: query + required: false + schema: + type: string + - name: storage + in: query + schema: + type: string + - name: content_type + in: query + schema: + type: string + - name: content_disposition + in: query + schema: + type: string + requestBody: + description: File content + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: File upload status + content: + application/json: + schema: + type: object + properties: + file_key: + type: string + required: + - file_key + /w/{workspace}/job_helpers/download_s3_file: get: summary: Download file from S3 bucket diff --git a/backend/windmill-api/src/ai.rs b/backend/windmill-api/src/ai.rs index 3c3ba77bb5..090732e025 100644 --- a/backend/windmill-api/src/ai.rs +++ b/backend/windmill-api/src/ai.rs @@ -1,7 +1,4 @@ -use crate::{ - db::{ApiAuthed, DB}, - variables::get_variable_or_self, -}; +use crate::db::{ApiAuthed, DB}; use axum::{body::Bytes, extract::Path, response::IntoResponse, routing::post, Extension, Router}; use http::{HeaderMap, Method}; @@ -9,6 +6,7 @@ use quick_cache::sync::Cache; use reqwest::{Client, RequestBuilder}; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; +use windmill_common::variables::get_variable_or_self; use std::collections::HashMap; use windmill_audit::{audit_oss::audit_log, ActionKind}; use windmill_common::ai_providers::{AIProvider, ProviderConfig, ProviderModel, AZURE_API_VERSION}; diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index c0162ae022..df31ed058e 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -28,6 +28,8 @@ use serde::{Deserialize, Serialize}; use serde_json::{value::RawValue, Value}; use sql_builder::{bind::Bind, quote, SqlBuilder}; use sqlx::{FromRow, Postgres, Transaction}; +use std::process::Stdio; +use tokio::process::Command; use uuid::Uuid; use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; @@ -58,6 +60,7 @@ pub fn workspaced_service() -> Router { .route("/delete/*path", delete(delete_resource)) .route("/delete_bulk", delete(delete_resources_bulk)) .route("/create", post(create_resource)) + .route("/git_commit_hash/*path", get(get_git_commit_hash)) .route("/type/list", get(list_resource_types)) .route("/type/listnames", get(list_resource_types_names)) .route("/type/get/:name", get(get_resource_type)) @@ -1387,3 +1390,100 @@ where Ok(resource) } + +#[derive(Deserialize, Serialize)] +struct GitRepositoryResource { + url: String, + #[serde(skip_serializing_if = "Option::is_none")] + branch: Option, +} + +#[derive(Serialize)] +struct GitCommitHashResponse { + commit_hash: String, +} + +async fn get_git_commit_hash( + authed: ApiAuthed, + Extension(user_db): Extension, + Extension(db): Extension, + Tokened { token }: Tokened, + Path((w_id, path)): Path<(String, StripPath)>, +) -> JsonResult { + let path = path.to_path(); + + check_scopes(&authed, || format!("resources:read:{}", path))?; + + let git_repo_resource_value = get_resource_value_interpolated_internal( + &authed, + Some(user_db), + &db, + &w_id, + path, + None, + &token, + false, + ) + .await + .map_err(|e| Error::NotAuthorized(format!("Access to resource {} denied: ({e})", path)))?; + + let git_resource: GitRepositoryResource = match git_repo_resource_value { + Some(value) => serde_json::from_value(value).map_err(|e| { + Error::BadRequest(format!("Invalid git repository resource format: {}", e)) + })?, + None => return Err(Error::NotFound(format!("Resource {} not found", path)).into()), + }; + + let commit_hash = get_repo_latest_commit_hash(&git_resource).await?; + + Ok(Json(GitCommitHashResponse { commit_hash })) +} + +async fn get_repo_latest_commit_hash(git_resource: &GitRepositoryResource) -> Result { + let mut git_cmd = Command::new("git"); + + let ref_spec = git_resource + .branch + .as_deref() + .filter(|s| !s.is_empty()) + .unwrap_or("HEAD"); + + git_cmd.args(["ls-remote", &git_resource.url, ref_spec]); + git_cmd.stderr(Stdio::piped()); + + let output = git_cmd + .output() + .await + .map_err(|e| Error::internal_err(format!("Failed to execute git command: {}", e)))?; + + if !output.status.success() { + let stderr = String::from_utf8(output.stderr) + .unwrap_or_else(|_| "Failed to decode stderr".to_string()); + return Err(Error::BadRequest(format!( + "Error getting git repo commit hash: {}", + stderr + ))); + } + + let stdout = String::from_utf8(output.stdout) + .map_err(|e| Error::internal_err(format!("Failed to decode git output: {}", e)))?; + + let lines: Vec<&str> = stdout.lines().collect(); + + if lines.is_empty() { + return Err(Error::BadRequest(format!( + "No commits found for reference '{}' in repository '{}'", + ref_spec, git_resource.url + ))); + } + + let commit_hash = lines + .first() + .and_then(|line| line.split_whitespace().next()) + .map(|s| s.to_string()) + .ok_or_else(|| { + Error::BadRequest("Unexpected output format for git ls-remote".to_string()) + })?; + + Ok(commit_hash) +} diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index 80eac1c9be..07d0572e6f 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -864,36 +864,3 @@ pub async fn get_value_internal<'a, 'e, A: sqlx::Acquire<'e, Database = Postgres Ok(r) } -pub async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result { - if !path.starts_with("$var:") { - return Ok(path); - } - let path = path.strip_prefix("$var:").unwrap().to_string(); - - let record = sqlx::query!( - "SELECT value, is_secret - FROM variable - WHERE path = $1 AND workspace_id = $2", - &path, - &w_id - ) - .fetch_optional(db) - .await?; - - if let Some(record) = record { - let mut value = record.value; - if record.is_secret { - let mc = build_crypt(db, w_id).await?; - value = decrypt(&mc, value).map_err(|e| { - Error::internal_err(format!("Error decrypting variable {}: {}", path, e)) - })?; - } - - Ok(value) - } else { - Err(Error::NotFound(format!( - "Variable not found when resolving `$var:{}`", - path - ))) - } -} diff --git a/backend/windmill-common/src/git_sync_oss.rs b/backend/windmill-common/src/git_sync_oss.rs new file mode 100644 index 0000000000..dcd2abf1f7 --- /dev/null +++ b/backend/windmill-common/src/git_sync_oss.rs @@ -0,0 +1,31 @@ +#[cfg(feature = "private")] +#[allow(unused)] +pub use crate::git_sync_ee::*; +use url::Url; +#[cfg(not(feature = "private"))] +use sqlx::{Pool, Postgres}; + +#[cfg(not(feature = "private"))] +pub async fn get_github_app_token_internal( + _db: &Pool, + _job_token: &str, +) -> crate::error::Result { + return Err(crate::error::Error::BadRequest("Github app authentication is not available on the open source build".to_string())) +} + +pub fn prepend_token_to_github_url( + github_url: &str, + installation_token: &str, +) -> crate::error::Result { + let url = Url::parse(github_url)?; + + if url.host_str() != Some("github.com") { + return Err(crate::error::Error::BadRequest("Invalid: not a github URL".to_string())); + } + + Ok(format!( + "https://x-access-token:{}@github.com{}", + installation_token, + url.path() + )) +} diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index c17ebc3503..4ff4e18723 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -87,6 +87,9 @@ pub mod variables; pub mod worker; pub mod worker_group_job_stats; pub mod workspaces; +#[cfg(feature = "private")] +pub mod git_sync_ee; +pub mod git_sync_oss; pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50; pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5; diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index a00ca01856..9dd9edc64d 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -6,7 +6,7 @@ * LICENSE-AGPL for a copy of the license. */ -use crate::error; +use crate::error::{self, Error}; use crate::scripts::ScriptHash; use crate::utils::WarnAfterExt; use crate::worker::Connection; @@ -438,3 +438,37 @@ async fn get_cached_workspace_envs(conn: &Connection, w_id: &str) -> Vec<(String }; custom_envs } + +pub async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> crate::error::Result { + if !path.starts_with("$var:") { + return Ok(path); + } + let path = path.strip_prefix("$var:").unwrap().to_string(); + + let record = sqlx::query!( + "SELECT value, is_secret + FROM variable + WHERE path = $1 AND workspace_id = $2", + &path, + &w_id + ) + .fetch_optional(db) + .await?; + + if let Some(record) = record { + let mut value = record.value; + if record.is_secret { + let mc = build_crypt(db, w_id).await?; + value = decrypt(&mc, value).map_err(|e| { + Error::internal_err(format!("Error decrypting variable {}: {}", path, e)) + })?; + } + + Ok(value) + } else { + Err(Error::NotFound(format!( + "Variable not found when resolving `$var:{}`", + path + ))) + } +} diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index 04567b6217..bcd058836e 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -13,6 +13,7 @@ use tokio::process::Command; use uuid::Uuid; use windmill_common::{ error, + git_sync_oss::{get_github_app_token_internal, prepend_token_to_github_url}, worker::{ is_allowed_file_location, to_raw_value, write_file, write_file_at_user_defined_location, Connection, WORKER_CONFIG, @@ -20,7 +21,9 @@ use windmill_common::{ }; use windmill_queue::MiniPulledJob; -use windmill_parser_yaml::{AnsibleRequirements, GitRepo, ResourceOrVariablePath}; +use windmill_parser_yaml::{ + AnsibleRequirements, GitRepo, PreexistingAnsibleInventory, ResourceOrVariablePath, +}; use windmill_queue::{append_logs, CanceledBy}; use crate::{ @@ -885,16 +888,41 @@ pub async fn handle_ansible_job( let inventories: Vec = reqs .as_ref() - .map(|x| { - x.inventories + .map(|x| -> Result, _> { + let mut ret: Vec = x + .inventories .clone() .iter() .flat_map(|i| vec!["-i".to_string(), i.name.clone()].into_iter()) - .collect() + .collect(); + + let additional: Vec = x + .additional_inventories + .iter() + .map(|i| match i { + PreexistingAnsibleInventory::Static(name) => Ok(Some(vec![name.clone()])), + PreexistingAnsibleInventory::PassedInArgs(inv_def) => interpolated_args + .as_ref() + .and_then(|args| args.get(&inv_def.name)) + .and_then(|v| serde_json::from_str(v.get()).transpose()) + .transpose(), + }) + .collect::, _>>()? + .into_iter() + .flatten() + .flatten() + .flat_map(|name| vec!["-i".to_string(), name]) + .collect(); + + ret.extend(additional); + Ok::<_, windmill_common::error::Error>(ret) }) + .transpose()? .unwrap_or_else(|| vec![]); let mut nsjail_extra_mounts = vec![]; + let mut playbook_override = None; + if let Some(r) = reqs.as_ref() { nsjail_extra_mounts = create_file_resources( &job.id, @@ -907,6 +935,104 @@ pub async fn handle_ansible_job( ) .await?; + if let Some(delegated_git_repo) = r.delegate_to_git_repo.as_ref() { + let serde_json::Value::Object(git_repo_resource) = client + .get_resource_value_interpolated::( + &delegated_git_repo.resource, + Some(job.id.to_string()), + ) + .await? + else { + return Err(windmill_common::error::Error::BadRequest( + "Git repository resource is not an object".to_string(), + )); + }; + + let mut secret_url = git_repo_resource.get("url").and_then(|s| s.as_str()).map(|s| s.to_string()) + .ok_or(anyhow!("Failed to get url from git repo resource, please check that the resource has the correct type (git_repository)"))?; + + let is_github_app = git_repo_resource.get("is_github_app").and_then(|s| s.as_bool()) + .ok_or(anyhow!("Failed to get `is_github_app` field from git repo resource, please check that the resource has the correct type (git_repository)"))?; + + if is_github_app { + if let Connection::Sql(db) = conn { + let token = get_github_app_token_internal(db, &client.token).await?; + secret_url = prepend_token_to_github_url(&secret_url, &token)?; + } else { + return Err(windmill_common::error::Error::BadRequest("Github App authentication is currently unavailable for agent workers. Contact the windmill team to request this feature".to_string())); + } + } + + let branch = Some(git_repo_resource.get("branch").and_then(|s| s.as_str()).map(|s| s.to_string()) + .ok_or(anyhow!("Failed to get branch from git repo resource, please check that the resource has the correct type (git_repository)"))?).filter(|s| !s.is_empty()); + + let target_path = "delegate_git_repository".to_string(); + + let repo = + GitRepo { url: secret_url, commit: delegated_git_repo.commit.clone(), branch, target_path }; + append_logs( + &job.id, + &job.workspace_id, + format!("\nCloning {}...\n", delegated_git_repo.resource), + conn, + ) + .await; + if let Some(commit) = delegated_git_repo.commit.as_ref() { + clone_repo_without_history( + &repo, + commit, + job_dir, + &job.id, + worker_name, + conn, + mem_peak, + canceled_by, + &job.workspace_id, + occupancy_metrics, + git_ssh_cmd, + ) + .await + .map_err(|e| anyhow!("Failed to clone git repo `{}`: {e}", repo.url))?; + } else { + clone_repo( + &repo, + job_dir, + &job.id, + worker_name, + conn, + mem_peak, + canceled_by, + &job.workspace_id, + occupancy_metrics, + git_ssh_cmd, + ) + .await + .map_err(|e| anyhow!("Failed to clone git repo `{}`: {e}", repo.url))?; + } + + append_logs( + &job.id, + &job.workspace_id, + format!( + "Cloned {} into {}\n", + delegated_git_repo.resource, &repo.target_path + ), + conn, + ) + .await; + + playbook_override = Some( + delegated_git_repo + .playbook + .as_ref() + .map(|p| format!("{}/{}", &repo.target_path, p)), + ); + } + + if playbook_override.clone().flatten().is_none() && playbook.is_empty() { + return Err(windmill_common::error::Error::BadRequest("No playbook was specified. Append a playbook to your script or specify one in the delegate_to_git_repo -> playbook section.".to_string())); + } + for repo in &r.git_repos { append_logs( &job.id, @@ -1060,7 +1186,10 @@ mount {{ reserved_variables.insert("PYTHONPATH".to_string(), additional_python_paths_folders); } - let mut cmd_args = vec!["main.yml", "--extra-vars", "@args.json"]; + let playbook = playbook_override + .flatten() + .unwrap_or("main.yml".to_string()); + let mut cmd_args = vec![playbook.as_str(), "--extra-vars", "@args.json"]; cmd_args.extend(inventories.iter().map(|s| s.as_str())); cmd_args.extend(cmd_options.iter().map(|s| s.as_str())); diff --git a/flake.nix b/flake.nix index c6f09a193d..102c79e8dd 100644 --- a/flake.nix +++ b/flake.nix @@ -103,6 +103,7 @@ wasm-pack deno emscripten + nushell # Needed for extra dependencies glibc_multi ]); @@ -231,17 +232,31 @@ set -e cd ./backend mkdir -p .minio-data/wmill - ${pkgs.minio}/bin/minio server ./.minio-data + ${pkgs.minio}/bin/minio server ./.minio-data --console-address ":9001" '') # Generate keys # TODO: Do not set new keys if ran multiple times (pkgs.writeScriptBin "wm-minio-keys" '' set -e cd ./backend + + # Set up MinIO alias ${pkgs.minio-client}/bin/mc alias set 'wmill-minio-dev' 'http://localhost:9000' 'minioadmin' 'minioadmin' - ${pkgs.minio-client}/bin/mc admin accesskey create 'wmill-minio-dev' | tee .minio-data/secrets.txt - echo "" - echo 'Saving to: ./backend/.minio-data/secrets.txt' + + # Check if secrets file exists and contains valid keys + if [[ -f .minio-data/secrets.txt ]] && [[ -s .minio-data/secrets.txt ]]; then + echo "Access keys already exist:" + cat .minio-data/secrets.txt + echo "" + echo "Keys loaded from: ./backend/.minio-data/secrets.txt" + else + echo "Creating new access keys..." + mkdir -p .minio-data + ${pkgs.minio-client}/bin/mc admin accesskey create 'wmill-minio-dev' | tee .minio-data/secrets.txt + echo "" + echo 'New keys saved to: ./backend/.minio-data/secrets.txt' + fi + echo "bucket: wmill" echo "endpoint: http://localhost:9000" '') diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ee1ce2687b..3910920c3f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -83,7 +83,7 @@ "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", "windmill-parser-wasm-ts": "1.538.0", - "windmill-parser-wasm-yaml": "1.510.1", + "windmill-parser-wasm-yaml": "1.558.1", "windmill-sql-datatype-parser-wasm": "1.512.0", "windmill-utils-internal": "^1.3.1", "xterm": "^5.3.0", @@ -13779,9 +13779,9 @@ "integrity": "sha512-hHhMIVIPhmsHx0lsNCGMoIa7cDBFlVWhhd9j/5yOIq2sxwqg5sl5juQIIJGvuwg5umdsD0ChlSm2/uES78DLYg==" }, "node_modules/windmill-parser-wasm-yaml": { - "version": "1.510.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-yaml/-/windmill-parser-wasm-yaml-1.510.1.tgz", - "integrity": "sha512-zQ1imcKrhP3iccJ01BK0+tptguo3Xc+J5ku2lgrZ+YQdDcC2wjGb6gH+kvcsMXDE3WT4aRwV3nL+ecHa7WHSrw==" + "version": "1.558.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-yaml/-/windmill-parser-wasm-yaml-1.558.1.tgz", + "integrity": "sha512-KBaSekkFiLJP5GpeArctupHStfp9/aWpNeT9my8Cp+QJB2TziJSf3FQ4k9mxJcKbjUQ65vO0jBtdNDvrnYufqQ==" }, "node_modules/windmill-sql-datatype-parser-wasm": { "version": "1.512.0", diff --git a/frontend/package.json b/frontend/package.json index 4d04a38246..e90f3672c2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -148,7 +148,7 @@ "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", "windmill-parser-wasm-ts": "1.538.0", - "windmill-parser-wasm-yaml": "1.510.1", + "windmill-parser-wasm-yaml": "1.558.1", "windmill-sql-datatype-parser-wasm": "1.512.0", "windmill-utils-internal": "^1.3.1", "xterm": "^5.3.0", @@ -541,4 +541,4 @@ "@rollup/rollup-linux-x64-gnu": "^4.35.0", "fsevents": "^2.3.3" } -} \ No newline at end of file +} diff --git a/frontend/src/lib/ansibleUtils.ts b/frontend/src/lib/ansibleUtils.ts new file mode 100644 index 0000000000..762c1793a2 --- /dev/null +++ b/frontend/src/lib/ansibleUtils.ts @@ -0,0 +1,384 @@ +interface DelegateToGitRepoConfig { + resource?: string + playbook?: string + inventories_location?: string +} + +/** + * Updates a specific field in the delegate_to_git_repo section + * @param code - The current YAML script content + * @param fieldName - The field name to update (resource, playbook, inventories_location) + * @param value - The value to set (or undefined to remove the field) + * @returns The modified YAML script content + */ +export function updateDelegateToGitRepoField(code: string, fieldName: string, value: string | undefined): string { + const lines = code.split('\n') + + // Find delegate_to_git_repo section + const delegateLineIndex = lines.findIndex(line => line.trim().startsWith('delegate_to_git_repo:')) + + if (delegateLineIndex === -1) { + // If no delegate section exists and we're setting a value, create the whole section + if (value !== undefined) { + return insertDelegateToGitRepoSection(code, { [fieldName]: value }) + } + return code + } + + // Find the specific field line + const fieldLineIndex = lines.findIndex((line, index) => + index > delegateLineIndex && line.trim().startsWith(`${fieldName}:`) + ) + + if (fieldLineIndex !== -1) { + if (value !== undefined) { + // Update existing field + lines[fieldLineIndex] = ` ${fieldName}: ${value}` + } else { + // Remove field + lines.splice(fieldLineIndex, 1) + } + } else if (value !== undefined) { + // Add new field after delegate_to_git_repo line + lines.splice(delegateLineIndex + 1, 0, ` ${fieldName}: ${value}`) + } + + return lines.join('\n') +} + +/** + * Inserts or updates multiple fields in a delegate_to_git_repo section + * @param code - The current YAML script content + * @param config - Configuration object with fields to update + * @returns The modified YAML script content + */ +export function updateDelegateToGitRepoConfig(code: string, config: DelegateToGitRepoConfig): string { + let updatedCode = code + + // Update each field that's provided + for (const [fieldName, value] of Object.entries(config)) { + if (value !== undefined) { + updatedCode = updateDelegateToGitRepoField(updatedCode, fieldName, value) + } + } + + return updatedCode +} + +/** + * Legacy function for backward compatibility + * Inserts or updates a delegate_to_git_repo section in an Ansible YAML script + * @param code - The current YAML script content + * @param resourcePath - The git repository resource path to delegate to + * @returns The modified YAML script content + */ +export function insertDelegateToGitRepoInCode(code: string, resourcePath: string): string { + return updateDelegateToGitRepoField(code, 'resource', resourcePath) +} + +/** + * Inserts a new delegate_to_git_repo section with the given configuration + * @param code - The current YAML script content + * @param config - Configuration object with fields to set + * @returns The modified YAML script content + */ +function insertDelegateToGitRepoSection(code: string, config: DelegateToGitRepoConfig): string { + const lines = code.split('\n') + + // Build the delegate section with all provided fields + const delegateSection = ['delegate_to_git_repo:'] + + // Add fields in a consistent order + if (config.resource) { + delegateSection.push(` resource: ${config.resource}`) + } + if (config.playbook) { + delegateSection.push(` playbook: ${config.playbook}`) + } + if (config.inventories_location) { + delegateSection.push(` inventories_location: ${config.inventories_location}`) + } + + // Find a good insertion point (after ---, then after inventories if they exist, otherwise at the top) + let insertionIndex = 0 + + // First, skip whitespace and find document start marker --- + for (let i = 0; i < lines.length; i++) { + const trimmedLine = lines[i].trim() + if (trimmedLine === '---') { + insertionIndex = i + 1 // Start after the document marker + break + } else if (trimmedLine && !trimmedLine.startsWith('#')) { + // Hit non-comment, non-whitespace content without finding ---, stop looking + break + } + } + + // Look for the end of inventories section + for (let i = insertionIndex; i < lines.length; i++) { + const line = lines[i].trim() + if (line.startsWith('inventories:')) { + // Find the end of inventories section + for (let j = i + 1; j < lines.length; j++) { + const nextLine = lines[j].trim() + if (nextLine && !nextLine.startsWith('-') && !nextLine.startsWith(' ') && !nextLine.startsWith('#')) { + insertionIndex = j + break + } + } + break + } else if (line && !line.startsWith('#') && insertionIndex <= 1) { + // First non-comment line after ---, insert before it + insertionIndex = i + break + } + } + + // Insert the delegate section + lines.splice(insertionIndex, 0, ...delegateSection, '') + + return lines.join('\n') +} + +/** + * Generic function to extract a specific field from delegate_to_git_repo section + * @param code - The YAML script content + * @param fieldName - The field name to extract (resource, playbook, inventories_location) + * @returns The field value if found, undefined otherwise + */ +function extractDelegateToGitRepoField(code: string, fieldName: string): string | undefined { + const lines = code.split('\n') + + // Find delegate_to_git_repo section + const delegateLineIndex = lines.findIndex(line => line.trim().startsWith('delegate_to_git_repo:')) + + if (delegateLineIndex === -1) { + return undefined + } + + // Look for the field line after delegate_to_git_repo + for (let i = delegateLineIndex + 1; i < lines.length; i++) { + const line = lines[i].trim() + if (line.startsWith(`${fieldName}:`)) { + // Extract the field value (everything after "fieldName:") + const fieldMatch = line.match(new RegExp(`^${fieldName}:\\s*(.+)$`)) + return fieldMatch?.[1]?.trim() + } else if (line && !line.startsWith(' ') && !line.startsWith('\t')) { + // Hit a new top-level section, stop looking + break + } + } + + return undefined +} + +/** + * Extracts the current git repository resource from delegate_to_git_repo section + * @param code - The YAML script content + * @returns The resource path if found, undefined otherwise + */ +export function extractCurrentGitRepoResource(code: string): string | undefined { + return extractDelegateToGitRepoField(code, 'resource') +} + +/** + * Extracts the current playbook path from delegate_to_git_repo section + * @param code - The YAML script content + * @returns The playbook path if found, undefined otherwise + */ +export function extractDelegateToGitRepoPlaybook(code: string): string | undefined { + return extractDelegateToGitRepoField(code, 'playbook') +} + +/** + * Extracts the current inventories location from delegate_to_git_repo section + * @param code - The YAML script content + * @returns The inventories location if found, undefined otherwise + */ +export function extractDelegateToGitRepoInventoriesLocation(code: string): string | undefined { + return extractDelegateToGitRepoField(code, 'inventories_location') +} + +/** + * Extracts all delegate_to_git_repo configuration from the code + * @param code - The YAML script content + * @returns Configuration object with all extracted fields + */ +export function extractDelegateToGitRepoConfig(code: string): DelegateToGitRepoConfig { + return { + resource: extractCurrentGitRepoResource(code), + playbook: extractDelegateToGitRepoPlaybook(code), + inventories_location: extractDelegateToGitRepoInventoriesLocation(code) + } +} + +/** + * Inserts or updates additional_inventories section in an Ansible YAML script + * @param code - The current YAML script content + * @param inventoryPaths - Array of inventory file paths + * @returns The modified YAML script content + */ +export function insertAdditionalInventories(code: string, inventoryPaths: string[]): string { + const lines = code.split('\n') + + // Find and update existing additional_inventories section if it exists + const additionalInventoriesIndex = lines.findIndex(line => line.trim().startsWith('additional_inventories:')) + if (additionalInventoriesIndex !== -1) { + // Determine the indentation level of the additional_inventories line + const sectionLine = lines[additionalInventoriesIndex] + const sectionIndentation = sectionLine.length - sectionLine.trimStart().length + + // Find the options: field within the section + let optionsIndex = -1 + let optionsEndIndex = -1 + + for (let i = additionalInventoriesIndex + 1; i < lines.length; i++) { + const line = lines[i] + const trimmedLine = line.trim() + + // Skip empty lines + if (!trimmedLine) { + continue + } + + // Calculate indentation of current line + const currentIndentation = line.length - line.trimStart().length + + // If we find a line with same or lesser indentation than the section header, + // we've reached the end of the additional_inventories section + if (currentIndentation <= sectionIndentation) { + break + } + + // Look for options: field (should be directly under additional_inventories) + if (trimmedLine.startsWith('- options:') && currentIndentation > sectionIndentation) { + optionsIndex = i + + // Check if it's inline format: options: [...] + if (trimmedLine.includes('[') && trimmedLine.includes(']')) { + // Inline format - just this line + optionsEndIndex = i + 1 + break + } else { + // Dash format - find all the dash items + optionsEndIndex = i + 1 + for (let j = i + 1; j < lines.length; j++) { + const nextLine = lines[j] + const nextTrimmed = nextLine.trim() + const nextIndentation = nextLine.length - nextLine.trimStart().length + + // Skip empty lines + if (!nextTrimmed) { + continue + } + + // If we hit a line that's not more indented than options:, we're done + if (nextIndentation <= currentIndentation) { + break + } + + // If it's a dash item, include it + if (nextTrimmed.startsWith('-')) { + optionsEndIndex = j + 1 + } else { + // Hit a non-dash line that's indented - stop here + break + } + } + break + } + } + } + + // Format the new options content + const optionsIndentation = ' ' // Standard 2-space indentation under additional_inventories + const formattedPaths = inventoryPaths.map(path => `"delegated_git_repository/${path}"`) + const inlineFormat = `${optionsIndentation}- options: [${formattedPaths.join(', ')}]` + + let newOptionsContent: string[] + if (inlineFormat.length <= 100) { + // Use inline format + newOptionsContent = [inlineFormat] + } else { + // Use dash format + newOptionsContent = [`${optionsIndentation}- options:`] + inventoryPaths.forEach(path => { + newOptionsContent.push(`${optionsIndentation} - "delegated_git_repository/${path}"`) + }) + } + + if (optionsIndex !== -1) { + // Replace existing options: field + lines.splice(optionsIndex, optionsEndIndex - optionsIndex, ...newOptionsContent) + } else { + // Add options: field to existing section (right after the section header) + lines.splice(additionalInventoriesIndex + 1, 0, ...newOptionsContent) + } + + return lines.join('\n') + } + + // Format the inventory paths based on length + const formattedPaths = inventoryPaths.map(path => `"delegated_git_repository/${path}"`) + const inlineFormat = `options: [${formattedPaths.join(', ')}]` + + let inventorySection: string[] + if (inlineFormat.length <= 100) { + // Use inline format + inventorySection = [ + 'additional_inventories:', + ` ${inlineFormat}` + ] + } else { + // Use dash format with each item on new line + inventorySection = [ + 'additional_inventories:', + ' - options:' + ] + inventoryPaths.forEach(path => { + inventorySection.push(` - "delegated_git_repository/${path}"`) + }) + } + + // Find insertion point (after the complete delegate_to_git_repo section) + const delegateLineIndex = lines.findIndex(line => line.trim().startsWith('delegate_to_git_repo:')) + if (delegateLineIndex === -1) { + // If no delegate_to_git_repo section, insert at the beginning (after document marker if exists) + let insertionIndex = 0 + for (let i = 0; i < lines.length; i++) { + const trimmedLine = lines[i].trim() + if (trimmedLine === '---') { + insertionIndex = i + 1 + break + } else if (trimmedLine && !trimmedLine.startsWith('#')) { + break + } + } + lines.splice(insertionIndex, 0, ...inventorySection, '') + } else { + // Find the last actual content line of the delegate_to_git_repo section + let lastContentIndex = delegateLineIndex + for (let i = delegateLineIndex + 1; i < lines.length; i++) { + const line = lines[i] + const trimmedLine = line.trim() + + // If we hit a non-empty line that doesn't start with whitespace (not indented), + // we've reached the end of the delegate_to_git_repo section + if (trimmedLine && !line.startsWith(' ') && !line.startsWith('\t')) { + break + } + + // If this is an indented non-empty line, it's part of delegate_to_git_repo + if (trimmedLine && (line.startsWith(' ') || line.startsWith('\t'))) { + lastContentIndex = i + } + } + + // Insert right after the last content line of delegate_to_git_repo + const insertionIndex = lastContentIndex + 1 + lines.splice(insertionIndex, 0, ...inventorySection, '') + } + + return lines.join('\n') +} + diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index f81cb5ce78..b66834a49a 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -31,6 +31,7 @@ DiffIcon, DollarSign, File, + GitBranch, History, Library, Link, @@ -54,6 +55,8 @@ import DucklakeIcon from './icons/DucklakeIcon.svelte' import FlowInlineScriptAiButton from './copilot/FlowInlineScriptAIButton.svelte' import ScriptGen from './copilot/ScriptGen.svelte' + import GitRepoPopoverPicker from './GitRepoPopoverPicker.svelte' + import { insertDelegateToGitRepoInCode } from '$lib/ansibleUtils' interface Props { lang: SupportedLanguage | 'bunnative' | undefined @@ -120,6 +123,7 @@ let s3FilePicker: S3FilePicker | undefined = $state() let ducklakePicker: ItemPicker | undefined = $state() let databasePicker: ItemPicker | undefined = $state() + let gitRepoPickerOpen = $state(false) let showContextVarPicker = $derived( [ @@ -194,6 +198,7 @@ ) let showDucklakePicker = $derived(['duckdb'].includes(lang ?? '')) let showDatabasePicker = $derived(['duckdb'].includes(lang ?? '')) + let showGitRepoPicker = $derived(lang === 'ansible') let showResourceTypePicker = $derived( ['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) || @@ -205,6 +210,14 @@ let codeViewer: Drawer | undefined = $state() let codeObj: { language: SupportedLanguage; content: string } | undefined = $state(undefined) + function insertDelegateToGitRepo(resourcePath: string) { + if (!editor) return + + const currentCode = editor.getCode() + const newCode = insertDelegateToGitRepoInCode(currentCode, resourcePath) + editor.setCode(newCode) + } + function addEditorActions() { editor?.addAction('insert-variable', 'Windmill: Insert variable', () => { variablePicker?.openDrawer() @@ -845,6 +858,28 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS {/if} + {#if showGitRepoPicker && customUi?.resource != false} + insertDelegateToGitRepo(e.detail.resourcePath)} + > + + + {/if} + {#if showResourceTypePicker && customUi?.type != false} + {/each} +
+ {/if} +
+ {/snippet} + \ No newline at end of file diff --git a/frontend/src/lib/components/GitRepoResourcePicker.svelte b/frontend/src/lib/components/GitRepoResourcePicker.svelte new file mode 100644 index 0000000000..bc5676370f --- /dev/null +++ b/frontend/src/lib/components/GitRepoResourcePicker.svelte @@ -0,0 +1,275 @@ + + + + +
+
+
Git Repository Resource
+ + {#if currentResource} +
+
+ +
+
+ Currently delegating to: +
+
+ {currentResource} +
+
+
+
+ {/if} + + {#if loading} +
+ + Loading git repository resources... +
+ {:else if gitRepoResources.length === 0} +
+ +

No git repository resources found

+

+ Create a git repository resource first to use this feature +

+
+ {:else} + +

+ Specify the path to your main playbook file relative to the git repository root +

+
+ + + +
+ + +
+ {#if selectedResource} +
+
+ Inventories Location +
+ +

+ Specify the directory containing your inventory files relative to the git repository + root +

+ +
+ +
+
+ {/if} +
+
+
diff --git a/frontend/src/lib/components/GitRepoViewer.svelte b/frontend/src/lib/components/GitRepoViewer.svelte new file mode 100644 index 0000000000..35ada1ee61 --- /dev/null +++ b/frontend/src/lib/components/GitRepoViewer.svelte @@ -0,0 +1,183 @@ + + +{#if error} + +

{error}

+
+{:else if isLoadingCommitHash} +
+ + Fetching latest commit hash... +
+{:else if isLoadingRepoClone} +
+ + Cloning repository... +
+{:else if commitHash && isCheckingPathExists} +
+ + Checking repository availability... +
+{:else if commitHash && pathExists === false} +
+ +

The git repository content is not yet available in storage.

+
+ +
+{:else if commitHash && pathExists === true} + { + const bucketConfig: any = await SettingService.getGlobal({ key: 'object_store_cache_config' }) + return SettingService.testObjectStorageConfig({ + requestBody: bucketConfig + }) + }) as any} + loadFileMetadataRequest={HelpersService.loadGitRepoFileMetadata} + > + {#snippet replaceUnauthorizedWarning()} +
+ +

+ The git repo resource you are trying to access either doesn't exist or you don't have + access to it. Make sure the resource path is correct and that you have visibility over + the resource. +

+
+
+ {/snippet} +
+{/if} diff --git a/frontend/src/lib/components/S3FilePicker.svelte b/frontend/src/lib/components/S3FilePicker.svelte index c98725c287..338a08ea02 100644 --- a/frontend/src/lib/components/S3FilePicker.svelte +++ b/frontend/src/lib/components/S3FilePicker.svelte @@ -1,55 +1,15 @@ @@ -508,413 +43,32 @@ { - dispatch( - 'close', - selectedFileKey?.s3 - ? { - s3: selectedFileKey.s3, - storage: storage - } - : undefined - ) + dispatch('close') + s3FilePickerInner?.close?.() }} size="1200px" > { + s3FilePickerInner?.exit?.() + drawer?.closeDrawer?.() + }} tooltip="Files present in the Workspace S3 bucket. You can set the workspace S3 bucket in the settings." documentationLink="https://www.windmill.dev/docs/integrations/s3" > - {#if workspaceSettingsInitialized === false} - {#if fromWorkspaceSettings} - -
-

- Double check the S3 resource fields and try again. -

-
-
- {:else} - -
-

- The workspace needs to be connected to an S3 storage to use this feature. You can configure it here. -

-
-
- {/if} - {:else} - {#if fileListUnavailable == true} -
- -

- You don't have access to the S3 bucket resource and your administrator has restricted - the access to it. You are not authorized to browse the bucket content. If you think - this is incorrect, please contact your workspace administrator. -

-

- More info in Windmill's documentation

-
- {/if} -
- {#if !fileListUnavailable} -
-
- -
- {#if fileListLoading === false && displayedFileKeys.length === 0} -
- No files in the workspace S3 bucket at that prefix -
- {:else} -
- - {#snippet header()}{/snippet} - {#snippet footer()}{/snippet} - {#snippet item({ index, style })} - {@const file_info = allFilesByKey[displayedFileKeys[index]]} - -
- {#if file_info} -
selectItem(index)} - class={twMerge( - 'flex flex-row h-full font-semibold text-xs items-center justify-start', - selectedFileKey !== undefined && - selectedFileKey.s3 === file_info.full_key - ? 'bg-surface-hover' - : '' - )} - > -
- {#if file_info.type === 'folder'} - {#if file_info.collapsed}{:else}{/if} -
- {file_info.display_name} ({file_info.count}{count % 1000 === 0 && - lastKeyFolders[file_info.nestingLevel / 2] === - file_info.display_name - ? '+' - : ''} item{file_info.count === 1 ? '' : 's'}) -
- {:else} - -
- {file_info.display_name} -
- {/if} -
-
- {/if} -
- {/snippet} -
-
-
- {#if fileListLoading === true} -
- Loading content -
- {:else} -
- {displayedCount}{count % maxKeys === 0 ? '+' : ''} - {displayedCount !== count ? 'filtered ' : ''}items (including inside folders) -
- - {#if count % maxKeys === 0} - - {/if} - {/if} -
- {/if} -
- {/if} -
- {#if fileMetadata === undefined} -
- {#if fileInfoLoading} -
- {:else if fileListUnavailable} -
- {:else} -
- {/if} -
- {:else} -
-
- {#snippet action()} -
- {#if filePreview !== undefined} -
- {/snippet} -
- -
- {/if} - -
- {#if filePreviewLoading || fileMetadata} - {#if fileMetadata?.fileKey.endsWith('.png') || fileMetadata?.fileKey.endsWith('.jpg') || fileMetadata?.fileKey.endsWith('.jpeg') || fileMetadata?.fileKey.endsWith('.webp')} -
- S3 preview -
- {:else if fileMetadata?.fileKey.endsWith('.pdf')} -
- {#await import('$lib/components/display/PdfViewer.svelte')} - - {:then Module} - - {/await} -
- {:else if filePreviewLoading} -
- File preview loading -
- {:else if fileMetadata !== undefined && filePreview !== undefined} -
- {#if filePreview.contentType === 'Unknown'} - Type of file not supported for preview. - {:else if filePreview.contentType === 'Csv'} - Previewing a {filePreview.contentType?.toLowerCase()} file. Separator character: -
- -
- Header row: -
- - loadFilePreview( - fileMetadata?.fileKey ?? '', - fileMetadata?.size, - fileMetadata?.mimeType - ) - )} - /> -
- {:else} - Previewing a {filePreview.contentType?.toLowerCase()} file. - {/if} -
-
{#if !emptyString(filePreview.contentPreview)}{filePreview.contentPreview}{:else if filePreview.contentType !== undefined}Preview impossible.{/if}
-							
- {/if} - {/if} -
-
-
- {/if} - - {#snippet actions()} -
- {#if secondaryStorageNames.value?.length} - -
- Are you sure you want to permanently move {fileMetadata?.fileKey}? - - - - { - uploadModalOpen = false - if (evt.detail !== undefined && evt.detail !== null) { - selectedFileKey = { s3: evt.detail, storage } - await clearAndLoadFiles() - loadFileMetadataPlusPreviewAsync(evt.detail) - } - }} -/> diff --git a/frontend/src/lib/components/S3FilePickerInner.svelte b/frontend/src/lib/components/S3FilePickerInner.svelte new file mode 100644 index 0000000000..99c107308e --- /dev/null +++ b/frontend/src/lib/components/S3FilePickerInner.svelte @@ -0,0 +1,886 @@ + + +{#if workspaceSettingsInitialized === false} + {#if fromWorkspaceSettings} + +
+

Double check the S3 resource fields and try again.

+
+
+ {:else} + +
+

+ The workspace needs to be connected to an S3 storage to use this feature. You can configure it here. +

+
+
+ {/if} +{:else} + {#if fileListUnavailable == true} + {#if replaceUnauthorizedWarning} + {@render replaceUnauthorizedWarning()} + {:else} +
+ +

+ You don't have access to the S3 bucket resource and your administrator has restricted the + access to it. You are not authorized to browse the bucket content. If you think this is + incorrect, please contact your workspace administrator. +

+

+ More info in Windmill's documentation

+
+ {/if} + {/if} +
+ {#if !fileListUnavailable} +
+ {#if !rootPath} +
+ +
+ {/if} + {#if fileListLoading === false && displayedFileKeys.length === 0} +
+ No files in the workspace S3 bucket at that prefix +
+ {:else} +
+ + {#snippet header()}{/snippet} + {#snippet footer()}{/snippet} + {#snippet item({ index, style })} + {@const file_info = allFilesByKey[displayedFileKeys[index]]} + +
+ {#if file_info} + {@const nestingLevel = file_info.nestingLevel - 2 * rootPathNestingLevel} + + +
selectItem(index)} + class={twMerge( + 'flex flex-row h-full font-semibold text-xs items-center justify-start', + selectedFileKey !== undefined && selectedFileKey.s3 === file_info.full_key + ? 'bg-surface-hover' + : '' + )} + > +
+ {#if file_info.type === 'folder'} + {#if file_info.collapsed}{:else}{/if} +
+ {file_info.display_name} ({file_info.count}{count % 1000 === 0 && + lastKeyFolders[file_info.nestingLevel / 2] === file_info.display_name + ? '+' + : ''} item{file_info.count === 1 ? '' : 's'}) +
+ {:else} + +
+ {file_info.display_name} +
+ {/if} +
+
+ {/if} +
+ {/snippet} +
+
+
+ {#if fileListLoading === true} +
+ Loading content +
+ {:else} +
+ {displayedCount}{count % maxKeys === 0 ? '+' : ''} + {displayedCount !== count ? 'filtered ' : ''}items (including inside folders) +
+ + {#if count % maxKeys === 0} + + {/if} + {/if} +
+ {/if} +
+ {/if} +
+ {#if fileMetadata === undefined} +
+ {#if fileInfoLoading} +
+ {:else if fileListUnavailable} +
+ {:else} +
+ {/if} +
+ {:else} +
+
p.startsWith(rootPath) ? p.slice(rootPath.length) : p)(fileMetadata.fileKey)} breakAll> + {#snippet action()} +
+ {#if filePreview !== undefined} + {#if !hideS3SpecificDetails} +
+ {/snippet} +
+ {#if !hideS3SpecificDetails} + + {/if} +
+ {/if} + +
+ {#if filePreviewLoading || fileMetadata} + {#if fileMetadata?.fileKey.endsWith('.png') || fileMetadata?.fileKey.endsWith('.jpg') || fileMetadata?.fileKey.endsWith('.jpeg') || fileMetadata?.fileKey.endsWith('.webp')} +
+ S3 preview +
+ {:else if fileMetadata?.fileKey.endsWith('.pdf')} +
+ {#await import('$lib/components/display/PdfViewer.svelte')} + + {:then Module} + + {/await} +
+ {:else if filePreviewLoading} +
+ File preview loading +
+ {:else if fileMetadata !== undefined && filePreview !== undefined} +
+ {#if filePreview.contentType === 'Unknown'} + Type of file not supported for preview. + {:else if filePreview.contentType === 'Csv'} + Previewing a {filePreview.contentType?.toLowerCase()} file. Separator character: +
+ +
+ Header row: +
+ + loadFilePreview( + fileMetadata?.fileKey ?? '', + fileMetadata?.size, + fileMetadata?.mimeType + ) + )} + /> +
+ {:else if !hideS3SpecificDetails} + Previewing a {filePreview.contentType?.toLowerCase()} file. + {/if} +
+
{#if !emptyString(filePreview.contentPreview)}{filePreview.contentPreview}{:else if filePreview.contentType !== undefined}Preview impossible.{/if}
+					
+ {/if} + {/if} +
+
+
+{/if} + + + { + deletionModalOpen = false + }} + on:confirmed={() => { + deleteFileFromS3(fileMetadata?.fileKey) + }} + keyListen={false} + loading={fileDeletionInProgress} +> +
+ Are you sure you want to permanently delete {fileMetadata?.fileKey} from the S3 bucket? +
+
+ + { + moveModalOpen = false + }} + on:confirmed={() => { + moveS3File(fileMetadata?.fileKey, moveDestKey) + }} + keyListen={false} + loading={fileMoveInProgress} +> +
+
+ New key: + +
+ Are you sure you want to permanently move {fileMetadata?.fileKey}? +
+
+ + { + uploadModalOpen = false + if (evt.detail !== undefined && evt.detail !== null) { + selectedFileKey = { s3: evt.detail, storage } + await clearAndLoadFiles() + loadFileMetadataPlusPreviewAsync(evt.detail) + } + }} +/> diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 2394ffcbe2..c0957d606e 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -1377,7 +1377,7 @@ documentationLink="https://www.windmill.dev/docs/core_concepts/dedicated_workers" > In this mode, the script is meant to be run on dedicated workers that run - the script at native speed. Can reach >1500rps per dedicated worker. Only + the script at native speed. Can reach >1500rps per dedicated worker. Only available on enterprise edition and for Python3, Deno and Bun. For other languages, the efficiency is already on par with deidcated workers since they do not spawn a full runtime { + if ( + v !== undefined && + (v === null || + v.resource !== ansibleAlternativeExecutionMode?.resource || + v.playbook !== ansibleAlternativeExecutionMode?.playbook || + v.inventories_location !== ansibleAlternativeExecutionMode?.inventories_location || + v.commit !== ansibleAlternativeExecutionMode?.commit) + ) { + ansibleAlternativeExecutionMode = v + } + }) + } }) }) @@ -176,6 +195,12 @@ let peers: { name: string }[] = $state([]) let showCollabPopup = $state(false) + let ansibleAlternativeExecutionMode = $state< + | { resource?: string; commit?: string; inventories_location?: string; playbook?: string } + | null + | undefined + >() + const url = new URL(window.location.toString()) let initialCollab = /true|1/i.test(url.searchParams.get('collab') ?? '0') @@ -271,6 +296,40 @@ } } + let gitRepoResourcePickerOpen = $state(false) + let commitHashForGitRepo = $derived(ansibleAlternativeExecutionMode?.commit) + + // Check if delegate_to_git_repo exists in the code + let hasDelegateToGitRepo = $derived(code && code.includes('delegate_to_git_repo:')) + + function handleDelegateConfigUpdate(event: { + detail: { resourcePath: string; playbook?: string; inventoriesLocation?: string } + }) { + if (!editor) return + + const currentCode = editor.getCode() + const newCode = updateDelegateToGitRepoConfig(currentCode, { + resource: event.detail.resourcePath, + playbook: event.detail.playbook, + inventories_location: event.detail.inventoriesLocation + }) + editor.setCode(newCode) + + // Trigger schema inference to update assets + inferSchema(newCode) + } + + function handleAddInventories(event: { detail: { inventoryPaths: string[] } }) { + if (!editor) return + + const currentCode = editor.getCode() + const newCode = insertAdditionalInventories(currentCode, event.detail.inventoryPaths) + editor.setCode(newCode) + + // Trigger schema inference to update assets + inferSchema(newCode) + } + onMount(() => { inferSchema(code) loadPastTests() @@ -538,126 +597,30 @@ -
-
- {#if assets?.length} - - {/if} - {#if testPanelSize === 0} -
- - {#key lang} - { - inferSchema(e.detail) - }} - on:saveDraft - on:toggleTestPanel={toggleTestPanel} - cmdEnterAction={async () => { - await inferSchema(code) - runTest() - }} - formatAction={async () => { - await inferSchema(code) - try { - localStorage.setItem(path ?? 'last_save', code) - } catch (e) { - console.error('Could not save last_save to local storage', e) - } - dispatch('format') - }} - class="flex flex-1 h-full !overflow-visible" - scriptLang={lang} - automaticLayout={true} - {fixedOverflowWidgets} - {args} - {enablePreprocessorSnippet} - /> - { - showHistoryDrawer = true - } - }, - { - text: 'Quit diff mode', - onClick: () => { - hideDiffMode() - }, - color: 'red' - } - ] - : []} - /> - {/key} -
+ {#if lang === 'ansible' && ansibleAlternativeExecutionMode != null} + + + + {@render editorContent()} + + +
+
+

File Browser

+
+ +
+
+
+ {:else} + + {@render editorContent()} + {/if}
@@ -812,3 +775,148 @@ + +{#snippet editorContent()} +
+
+ {#if assets?.length} + + {/if} + {#if lang === 'ansible' && hasDelegateToGitRepo} + + {/if} + {#if testPanelSize === 0} +
+ + {#key lang} + { + inferSchema(e.detail) + }} + on:saveDraft + on:toggleTestPanel={toggleTestPanel} + cmdEnterAction={async () => { + await inferSchema(code) + runTest() + }} + formatAction={async () => { + await inferSchema(code) + try { + localStorage.setItem(path ?? 'last_save', code) + } catch (e) { + console.error('Could not save last_save to local storage', e) + } + dispatch('format') + }} + class="flex flex-1 h-full !overflow-visible" + scriptLang={lang} + automaticLayout={true} + {fixedOverflowWidgets} + {args} + {enablePreprocessorSnippet} + /> + { + showHistoryDrawer = true + } + }, + { + text: 'Quit diff mode', + onClick: () => { + hideDiffMode() + }, + color: 'red' + } + ] + : []} + /> + {/key} +
+{/snippet} + + diff --git a/frontend/src/lib/components/schema/EditableSchemaSdkWrapper.svelte b/frontend/src/lib/components/schema/EditableSchemaSdkWrapper.svelte index c7f6dcc614..124779635e 100644 --- a/frontend/src/lib/components/schema/EditableSchemaSdkWrapper.svelte +++ b/frontend/src/lib/components/schema/EditableSchemaSdkWrapper.svelte @@ -12,9 +12,9 @@ let schema = $state(oldSchema) - let lastSchema = $state.snapshot(schema) + let lastSchema = $state(undefined) $effect(() => { - if (onSchemaChange) { + if (onSchemaChange && schema) { readFieldsRecursively(schema) let newSchema = $state.snapshot(schema) if (!deepEqual(lastSchema, newSchema)) { diff --git a/frontend/src/lib/components/sidebar/SidebarContent.svelte b/frontend/src/lib/components/sidebar/SidebarContent.svelte index f0bba47a7b..26b2bf8a74 100644 --- a/frontend/src/lib/components/sidebar/SidebarContent.svelte +++ b/frontend/src/lib/components/sidebar/SidebarContent.svelte @@ -73,7 +73,7 @@ } async function deleteFork() { - await WorkspaceService.deleteWorkspace({ workspace: $workspaceStore ?? '', onlyDeleteForks: true }) + await WorkspaceService.deleteWorkspace({ workspace: $workspaceStore ?? '' }) sendUserToast('You deleted the workspace') clearStores() goto('/user/workspaces') diff --git a/frontend/src/lib/hub.ts b/frontend/src/lib/hub.ts index 1ecf45c14b..5f49ff59b9 100644 --- a/frontend/src/lib/hub.ts +++ b/frontend/src/lib/hub.ts @@ -94,6 +94,7 @@ type HubPaths = { teamsRecoveryHandler: string teamsSuccessHandler: string emailErrorHandler: string + cloneRepoToS3forGitRepoViewer: string } export const hubPaths = JSON.parse(rawHubPaths) as HubPaths diff --git a/frontend/src/lib/hubPaths.json b/frontend/src/lib/hubPaths.json index 3207b3fbd7..3a82035564 100644 --- a/frontend/src/lib/hubPaths.json +++ b/frontend/src/lib/hubPaths.json @@ -36,5 +36,6 @@ "teamsSuccessHandler": "hub/11596/schedule-success-handler-teams", "slackReport": "hub/9084/slack", "discordReport": "hub/9085/discord", - "smtpReport": "hub/9086/smtp" + "smtpReport": "hub/9086/smtp", + "cloneRepoToS3forGitRepoViewer": "hub/19825/clone_repo_and_upload_to_instance_storage" } diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index 7685cd9a66..3510d84bcc 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -23,7 +23,7 @@ import initPythonParser, { parse_assets_py, parse_python } from 'windmill-parser import initGoParser, { parse_go } from 'windmill-parser-wasm-go' import initPhpParser, { parse_php } from 'windmill-parser-wasm-php' import initRustParser, { parse_rust } from 'windmill-parser-wasm-rust' -import initYamlParser, { parse_ansible } from 'windmill-parser-wasm-yaml' +import initYamlParser, { parse_assets_ansible, parse_ansible, parse_ansible_delegate } from 'windmill-parser-wasm-yaml' import initCSharpParser, { parse_csharp } from 'windmill-parser-wasm-csharp' import initNuParser, { parse_nu } from 'windmill-parser-wasm-nu' import initJavaParser, { parse_java } from 'windmill-parser-wasm-java' @@ -103,6 +103,10 @@ export async function inferAssets( await initWasmPython() return JSON.parse(parse_assets_py(code)) } + if (language === 'ansible') { + await initWasmYaml() + return JSON.parse(parse_assets_ansible(code)) + } } catch (e) { console.error('error parsing assets', e) return [] @@ -110,6 +114,16 @@ export async function inferAssets( return [] } +export async function inferAnsibleExecutionMode(code: string) { + try { + await initWasmYaml() + return JSON.parse(parse_ansible_delegate(code)) + } catch (e) { + console.error('error parsing git repo for ansible', e) + return undefined + } +} + const SQL_LANGUAGES = [ 'postgresql', 'mysql', From aeb6829011c029ebb8c8f727ae6d26657d3f3f69 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 09:19:44 +0000 Subject: [PATCH 10/33] use excluded where relevant --- ...8fe7fd014adbf40cc4bc8c041f5864423367.json} | 4 +- ...861f3483fa70b5d58dd2838a5cb6dabe9cc1.json} | 4 +- ...3df5f38fb8892226f04cf700f60eb45199ef.json} | 4 +- ...b3e03bd44048af4d69fab854f97fa821649b.json} | 4 +- ...ab38dfe15284a6a7f54366ba25d7eb75a74a.json} | 4 +- ...b1af55045c1b8c2a138ca995882dde955971.json} | 4 +- ...72e7b051adb368b3685181047f1f4522a473.json} | 4 +- ...cfba31cfdd64a0f68001e123694ed5cde5ed.json} | 4 +- ...48910d900b9575d986143823781e1d005d57.json} | 4 +- ...a7d07ee06d417a5bd9f02d64fcb467cf362a.json} | 4 +- ...f6cb506c75e84c65438c9026831eb10d340b.json} | 4 +- ...fa6325fcae4dc3bf559f2ecc683fb40537e8.json} | 4 +- ...b7cbc891e55d29d30d1ca7b13f25ed1fdc87.json} | 4 +- ...a2eb517e82943bb8c078c7bb2e60def3cbf1.json} | 4 +- ...0d622c9d2d844a2ab3938e6ec23c6150fb40.json} | 4 +- ...5bc14039aff6f023d79139b20519a9cdbe7d.json} | 4 +- ...270e5e8a52540da1726aa875fcbe2517f16a.json} | 4 +- ...dacb12266f606b5ce7e8ed6110f3f381f945.json} | 4 +- ...0af413027dbd39aa2c4182e3d219640d397e.json} | 4 +- ...4af00edc86a9bc498f319c1e5bee55d77a8a.json} | 4 +- ...db2b1061c22698393c2e42d94da11e697b8b.json} | 4 +- ...e059481091ac173414418b8678f6beadf2ac.json} | 4 +- ...66512801c20ad685e8ed544fe4b86601aaa8.json} | 4 +- ...ef762fecf782424c5b61b89f32918b8d6971.json} | 4 +- ...f3db1fb30c9564ed039ad4c952333ec29b39.json} | 4 +- ...13481db39eea0b52cc513b3ee6571cf44ed4.json} | 4 +- ...63ce8ce9fbe89abe83989f52ff3657690fef.json} | 4 +- ...474d2f8513202dfb64767b30e7b50833b857.json} | 4 +- ...243b590b2dd0cfe41aa5100a7e829afad9d3.json} | 4 +- ...16448202c1181ca0b36936a9c8840c14a95d.json} | 4 +- ...cd6b45dc161423abab22cddb2b13f6fb9833.json} | 4 +- ...b0692a02dec4c8415fd7d364b0cc088905c2.json} | 4 +- ...676d0c8ae31982865cb2c8103ed735c42c69.json} | 4 +- backend/ee-repo-ref.txt | 2 +- backend/src/monitor.rs | 2 +- backend/windmill-api/src/apps.rs | 2 +- backend/windmill-api/src/configs.rs | 2 +- backend/windmill-api/src/drafts.rs | 2 +- backend/windmill-api/src/flows.rs | 2 +- backend/windmill-api/src/resources.rs | 2 +- backend/windmill-api/src/scripts.rs | 3 +- backend/windmill-api/src/settings.rs | 2 +- backend/windmill-api/src/users.rs | 44 +++++++++++++------ backend/windmill-api/src/workspaces.rs | 4 +- backend/windmill-common/src/result_stream.rs | 2 +- backend/windmill-common/src/worker.rs | 3 +- backend/windmill-queue/src/jobs.rs | 8 ++-- backend/windmill-worker/src/common.rs | 2 +- backend/windmill-worker/src/go_executor.rs | 7 ++- backend/windmill-worker/src/java_executor.rs | 2 +- backend/windmill-worker/src/job_logger.rs | 2 +- .../windmill-worker/src/python_executor.rs | 2 +- backend/windmill-worker/src/ruby_executor.rs | 2 +- 53 files changed, 125 insertions(+), 104 deletions(-) rename backend/.sqlx/{query-db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0.json => query-07335b75233811352fb898cf3d6c8fe7fd014adbf40cc4bc8c041f5864423367.json} (62%) rename backend/.sqlx/{query-a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681.json => query-08c1121171b98889f188ea6b33b1861f3483fa70b5d58dd2838a5cb6dabe9cc1.json} (59%) rename backend/.sqlx/{query-8bd028c8b5f8a4d566f89eebc2e63fd04beaf2b0b49e07c7df42ecddd70737f3.json => query-19e4625de06b8bab10039280a6213df5f38fb8892226f04cf700f60eb45199ef.json} (75%) rename backend/.sqlx/{query-9f1f388924176dbe3dea882e0c62728a82ba256029096812dd705ccb1a552cfe.json => query-1e426c8a06d7bbed7af67a105f74b3e03bd44048af4d69fab854f97fa821649b.json} (78%) rename backend/.sqlx/{query-a00e61e770e20157bbd9e4cdedf7fb5f9de7c8c9e50282e3ecf2e3ce917ec37a.json => query-24f38f0642b49626c8c8417e1846ab38dfe15284a6a7f54366ba25d7eb75a74a.json} (62%) rename backend/.sqlx/{query-af00c212f509076e37538be52f582ba09e47db50ba93af322649ccddbb05cc49.json => query-25bf02e605e9e8e708a5dcfbb898b1af55045c1b8c2a138ca995882dde955971.json} (64%) rename backend/.sqlx/{query-55a2f170823f1d1abce76287d8817a6cf34de92b9b4079c00b75423a9ff835b9.json => query-2b34ae324f90924dba6c4562024f72e7b051adb368b3685181047f1f4522a473.json} (76%) rename backend/.sqlx/{query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json => query-2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed.json} (75%) rename backend/.sqlx/{query-652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a.json => query-384753e7ceb602790646b0f269df48910d900b9575d986143823781e1d005d57.json} (74%) rename backend/.sqlx/{query-42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4.json => query-3904d59575e05df6e414be03e84ca7d07ee06d417a5bd9f02d64fcb467cf362a.json} (73%) rename backend/.sqlx/{query-c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e.json => query-39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b.json} (64%) rename backend/.sqlx/{query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json => query-4d3b8726656ebf6eb78d0c4c23fdfa6325fcae4dc3bf559f2ecc683fb40537e8.json} (69%) rename backend/.sqlx/{query-c7dd35561e9b1cfd86238d410139f50e4d87c762e4867d8b32c11bd8c74846eb.json => query-5585bc46fe2b9d5aebddad300f43b7cbc891e55d29d30d1ca7b13f25ed1fdc87.json} (74%) rename backend/.sqlx/{query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json => query-5701ee0b862dbdb44990702af270a2eb517e82943bb8c078c7bb2e60def3cbf1.json} (72%) rename backend/.sqlx/{query-14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d.json => query-6d7a4185063dbcca0dbea1b002330d622c9d2d844a2ab3938e6ec23c6150fb40.json} (80%) rename backend/.sqlx/{query-d970a0b07a2b5840d1feb1baacb834dbaf91c633d3e7e1e29c8eb7eedc53e888.json => query-7dc75cb67922e31ffe0f88b03a8a5bc14039aff6f023d79139b20519a9cdbe7d.json} (74%) rename backend/.sqlx/{query-43b376a2eff086a32cd76e54361ce3631feee1565935d2a6ddbecc17950758d1.json => query-7dcc77eb6da5863f7a25ab6ad83d270e5e8a52540da1726aa875fcbe2517f16a.json} (67%) rename backend/.sqlx/{query-b8c66d905a6c7ffa6441c84b14ea897040069dac7367895813cc2d64a9867193.json => query-7dcf840fc5b329f4a591a51e24c9dacb12266f606b5ce7e8ed6110f3f381f945.json} (75%) rename backend/.sqlx/{query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json => query-8fda0400ec5ba04a2a1469672bbb0af413027dbd39aa2c4182e3d219640d397e.json} (69%) rename backend/.sqlx/{query-e40f7e0b61567f948bfea0b6f50518564634885ccc2c0d30ccca79fc13bdcf07.json => query-99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a.json} (60%) rename backend/.sqlx/{query-f9e1334ecb17b313924587d96fc8b0310b14d75e85a558c8cb70f2761923d175.json => query-9aebf706529889dc044e0ef41da8db2b1061c22698393c2e42d94da11e697b8b.json} (78%) rename backend/.sqlx/{query-20fcdcd2674a52ee9bd8d1de518d6bce075f20bcd5d2328f183d8a59331f6bb1.json => query-a5bf005e0f7c9a86a136e049445de059481091ac173414418b8678f6beadf2ac.json} (75%) rename backend/.sqlx/{query-366609f7e7fbd73ea807128b931eff2f1ab763fa630c8531f590fed2110c03d9.json => query-b474ae4401b3d4c95add2d3353eb66512801c20ad685e8ed544fe4b86601aaa8.json} (86%) rename backend/.sqlx/{query-8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce.json => query-bf2163c542fb8c4e173167a8f333ef762fecf782424c5b61b89f32918b8d6971.json} (87%) rename backend/.sqlx/{query-afc7c23c057748f6d4a61dbef17e433b8875c6588b91e38e8141d12189118412.json => query-cc8e10a4f39e118b145cc8f6fbd5f3db1fb30c9564ed039ad4c952333ec29b39.json} (74%) rename backend/.sqlx/{query-7b48820a08fbb3bee0fb2dc802a0b4f28ed5507797165e8c577ce6a95d11694c.json => query-ceb10ca124d9425b24d81d8a279a13481db39eea0b52cc513b3ee6571cf44ed4.json} (77%) rename backend/.sqlx/{query-9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde.json => query-d142ff8b56f4b69c20815230b5b763ce8ce9fbe89abe83989f52ff3657690fef.json} (73%) rename backend/.sqlx/{query-fb581eb6f883ebb5909a00c65a6e1217f088290fce0e052171230bcd88f945b9.json => query-da7e23a32f284c9760614735d459474d2f8513202dfb64767b30e7b50833b857.json} (57%) rename backend/.sqlx/{query-c4e0f3eab227a798d9cd7478db50a7c1a588e69c2c8d9a1def9276ba326453d2.json => query-db201730803047bfabccf5f10456243b590b2dd0cfe41aa5100a7e829afad9d3.json} (63%) rename backend/.sqlx/{query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json => query-e16f464c7e302e80f1017ff6550716448202c1181ca0b36936a9c8840c14a95d.json} (76%) rename backend/.sqlx/{query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json => query-e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833.json} (74%) rename backend/.sqlx/{query-6afc5c7cbb3abe11ade0cedf1f7328005ce4de3165cdd998e5a0d27e044c7153.json => query-e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2.json} (70%) rename backend/.sqlx/{query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json => query-e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69.json} (74%) diff --git a/backend/.sqlx/query-db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0.json b/backend/.sqlx/query-07335b75233811352fb898cf3d6c8fe7fd014adbf40cc4bc8c041f5864423367.json similarity index 62% rename from backend/.sqlx/query-db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0.json rename to backend/.sqlx/query-07335b75233811352fb898cf3d6c8fe7fd014adbf40cc4bc8c041f5864423367.json index 1e36101464..9c85043e7c 100644 --- a/backend/.sqlx/query-db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0.json +++ b/backend/.sqlx/query-07335b75233811352fb898cf3d6c8fe7fd014adbf40cc4bc8c041f5864423367.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4", + "query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL\n DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "db558b5ecdc4c3b1af0def511f1bcd91a548f00376f644c8ba38f73812b462d0" + "hash": "07335b75233811352fb898cf3d6c8fe7fd014adbf40cc4bc8c041f5864423367" } diff --git a/backend/.sqlx/query-a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681.json b/backend/.sqlx/query-08c1121171b98889f188ea6b33b1861f3483fa70b5d58dd2838a5cb6dabe9cc1.json similarity index 59% rename from backend/.sqlx/query-a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681.json rename to backend/.sqlx/query-08c1121171b98889f188ea6b33b1861f3483fa70b5d58dd2838a5cb6dabe9cc1.json index da8842f9a7..16a6101127 100644 --- a/backend/.sqlx/query-a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681.json +++ b/backend/.sqlx/query-08c1121171b98889f188ea6b33b1861f3483fa70b5d58dd2838a5cb6dabe9cc1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()", + "query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "a59b70164dc87224d09a04d5469ca217eb19a15a250c3b83ca63f606f89b9681" + "hash": "08c1121171b98889f188ea6b33b1861f3483fa70b5d58dd2838a5cb6dabe9cc1" } diff --git a/backend/.sqlx/query-8bd028c8b5f8a4d566f89eebc2e63fd04beaf2b0b49e07c7df42ecddd70737f3.json b/backend/.sqlx/query-19e4625de06b8bab10039280a6213df5f38fb8892226f04cf700f60eb45199ef.json similarity index 75% rename from backend/.sqlx/query-8bd028c8b5f8a4d566f89eebc2e63fd04beaf2b0b49e07c7df42ecddd70737f3.json rename to backend/.sqlx/query-19e4625de06b8bab10039280a6213df5f38fb8892226f04cf700f60eb45199ef.json index f1f7a0b56c..bec2b14783 100644 --- a/backend/.sqlx/query-8bd028c8b5f8a4d566f89eebc2e63fd04beaf2b0b49e07c7df42ecddd70737f3.json +++ b/backend/.sqlx/query-19e4625de06b8bab10039280a6213df5f38fb8892226f04cf700f60eb45199ef.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, edited_at = now()", + "query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = EXCLUDED.value, edited_at = now()", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "8bd028c8b5f8a4d566f89eebc2e63fd04beaf2b0b49e07c7df42ecddd70737f3" + "hash": "19e4625de06b8bab10039280a6213df5f38fb8892226f04cf700f60eb45199ef" } diff --git a/backend/.sqlx/query-9f1f388924176dbe3dea882e0c62728a82ba256029096812dd705ccb1a552cfe.json b/backend/.sqlx/query-1e426c8a06d7bbed7af67a105f74b3e03bd44048af4d69fab854f97fa821649b.json similarity index 78% rename from backend/.sqlx/query-9f1f388924176dbe3dea882e0c62728a82ba256029096812dd705ccb1a552cfe.json rename to backend/.sqlx/query-1e426c8a06d7bbed7af67a105f74b3e03bd44048af4d69fab854f97fa821649b.json index 51ddd8af63..0f4f6a0a90 100644 --- a/backend/.sqlx/query-9f1f388924176dbe3dea882e0c62728a82ba256029096812dd705ccb1a552cfe.json +++ b/backend/.sqlx/query-1e426c8a06d7bbed7af67a105f74b3e03bd44048af4d69fab854f97fa821649b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3", + "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value", "describe": { "columns": [], "parameters": { @@ -15,5 +15,5 @@ }, "nullable": [] }, - "hash": "9f1f388924176dbe3dea882e0c62728a82ba256029096812dd705ccb1a552cfe" + "hash": "1e426c8a06d7bbed7af67a105f74b3e03bd44048af4d69fab854f97fa821649b" } diff --git a/backend/.sqlx/query-a00e61e770e20157bbd9e4cdedf7fb5f9de7c8c9e50282e3ecf2e3ce917ec37a.json b/backend/.sqlx/query-24f38f0642b49626c8c8417e1846ab38dfe15284a6a7f54366ba25d7eb75a74a.json similarity index 62% rename from backend/.sqlx/query-a00e61e770e20157bbd9e4cdedf7fb5f9de7c8c9e50282e3ecf2e3ce917ec37a.json rename to backend/.sqlx/query-24f38f0642b49626c8c8417e1846ab38dfe15284a6a7f54366ba25d7eb75a74a.json index 9f95781136..265ac4fba1 100644 --- a/backend/.sqlx/query-a00e61e770e20157bbd9e4cdedf7fb5f9de7c8c9e50282e3ecf2e3ce917ec37a.json +++ b/backend/.sqlx/query-24f38f0642b49626c8c8417e1846ab38dfe15284a6a7f54366ba25d7eb75a74a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2", + "query": "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "a00e61e770e20157bbd9e4cdedf7fb5f9de7c8c9e50282e3ecf2e3ce917ec37a" + "hash": "24f38f0642b49626c8c8417e1846ab38dfe15284a6a7f54366ba25d7eb75a74a" } diff --git a/backend/.sqlx/query-af00c212f509076e37538be52f582ba09e47db50ba93af322649ccddbb05cc49.json b/backend/.sqlx/query-25bf02e605e9e8e708a5dcfbb898b1af55045c1b8c2a138ca995882dde955971.json similarity index 64% rename from backend/.sqlx/query-af00c212f509076e37538be52f582ba09e47db50ba93af322649ccddbb05cc49.json rename to backend/.sqlx/query-25bf02e605e9e8e708a5dcfbb898b1af55045c1b8c2a138ca995882dde955971.json index 2c3c76ca31..660044efb2 100644 --- a/backend/.sqlx/query-af00c212f509076e37538be52f582ba09e47db50ba93af322649ccddbb05cc49.json +++ b/backend/.sqlx/query-25bf02e605e9e8e708a5dcfbb898b1af55045c1b8c2a138ca995882dde955971.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = $2", + "query": "INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = EXCLUDED.config", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "af00c212f509076e37538be52f582ba09e47db50ba93af322649ccddbb05cc49" + "hash": "25bf02e605e9e8e708a5dcfbb898b1af55045c1b8c2a138ca995882dde955971" } diff --git a/backend/.sqlx/query-55a2f170823f1d1abce76287d8817a6cf34de92b9b4079c00b75423a9ff835b9.json b/backend/.sqlx/query-2b34ae324f90924dba6c4562024f72e7b051adb368b3685181047f1f4522a473.json similarity index 76% rename from backend/.sqlx/query-55a2f170823f1d1abce76287d8817a6cf34de92b9b4079c00b75423a9ff835b9.json rename to backend/.sqlx/query-2b34ae324f90924dba6c4562024f72e7b051adb368b3685181047f1f4522a473.json index 7b7dd7fe29..302c0b461d 100644 --- a/backend/.sqlx/query-55a2f170823f1d1abce76287d8817a6cf34de92b9b4079c00b75423a9ff835b9.json +++ b/backend/.sqlx/query-2b34ae324f90924dba6c4562024f72e7b051adb368b3685181047f1f4522a473.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3", + "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value", "describe": { "columns": [], "parameters": { @@ -16,5 +16,5 @@ }, "nullable": [] }, - "hash": "55a2f170823f1d1abce76287d8817a6cf34de92b9b4079c00b75423a9ff835b9" + "hash": "2b34ae324f90924dba6c4562024f72e7b051adb368b3685181047f1f4522a473" } diff --git a/backend/.sqlx/query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json b/backend/.sqlx/query-2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed.json similarity index 75% rename from backend/.sqlx/query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json rename to backend/.sqlx/query-2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed.json index 1c2f9a9f33..347ad02c31 100644 --- a/backend/.sqlx/query-8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29.json +++ b/backend/.sqlx/query-2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", + "query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29" + "hash": "2e131a019051bdac7c9c65f7c504cfba31cfdd64a0f68001e123694ed5cde5ed" } diff --git a/backend/.sqlx/query-652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a.json b/backend/.sqlx/query-384753e7ceb602790646b0f269df48910d900b9575d986143823781e1d005d57.json similarity index 74% rename from backend/.sqlx/query-652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a.json rename to backend/.sqlx/query-384753e7ceb602790646b0f269df48910d900b9575d986143823781e1d005d57.json index 91e268889d..d90cb38e55 100644 --- a/backend/.sqlx/query-652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a.json +++ b/backend/.sqlx/query-384753e7ceb602790646b0f269df48910d900b9575d986143823781e1d005d57.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4", + "query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "652835b2b7f801532a591988ac76d385188991c6654d529f6d65f6f03794844a" + "hash": "384753e7ceb602790646b0f269df48910d900b9575d986143823781e1d005d57" } diff --git a/backend/.sqlx/query-42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4.json b/backend/.sqlx/query-3904d59575e05df6e414be03e84ca7d07ee06d417a5bd9f02d64fcb467cf362a.json similarity index 73% rename from backend/.sqlx/query-42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4.json rename to backend/.sqlx/query-3904d59575e05df6e414be03e84ca7d07ee06d417a5bd9f02d64fcb467cf362a.json index 695b4d2969..f6284b98a7 100644 --- a/backend/.sqlx/query-42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4.json +++ b/backend/.sqlx/query-3904d59575e05df6e414be03e84ca7d07ee06d417a5bd9f02d64fcb467cf362a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, edited_at = now()", + "query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value, edited_at = now()", "describe": { "columns": [], "parameters": { @@ -15,5 +15,5 @@ }, "nullable": [] }, - "hash": "42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4" + "hash": "3904d59575e05df6e414be03e84ca7d07ee06d417a5bd9f02d64fcb467cf362a" } diff --git a/backend/.sqlx/query-c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e.json b/backend/.sqlx/query-39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b.json similarity index 64% rename from backend/.sqlx/query-c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e.json rename to backend/.sqlx/query-39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b.json index d3634c6e96..69064f7d45 100644 --- a/backend/.sqlx/query-c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e.json +++ b/backend/.sqlx/query-39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()", + "query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now()", "describe": { "columns": [], "parameters": { @@ -15,5 +15,5 @@ }, "nullable": [] }, - "hash": "c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e" + "hash": "39426bd3018b390ea2073419884cf6cb506c75e84c65438c9026831eb10d340b" } diff --git a/backend/.sqlx/query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json b/backend/.sqlx/query-4d3b8726656ebf6eb78d0c4c23fdfa6325fcae4dc3bf559f2ecc683fb40537e8.json similarity index 69% rename from backend/.sqlx/query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json rename to backend/.sqlx/query-4d3b8726656ebf6eb78d0c4c23fdfa6325fcae4dc3bf559f2ecc683fb40537e8.json index 4a85852957..83c02ba868 100644 --- a/backend/.sqlx/query-9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1.json +++ b/backend/.sqlx/query-4d3b8726656ebf6eb78d0c4c23fdfa6325fcae4dc3bf559f2ecc683fb40537e8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "9a9e4a8779b0bf8a275d029221dfa1465e5d44cd8a7be5879219ffc8cd7ae6b1" + "hash": "4d3b8726656ebf6eb78d0c4c23fdfa6325fcae4dc3bf559f2ecc683fb40537e8" } diff --git a/backend/.sqlx/query-c7dd35561e9b1cfd86238d410139f50e4d87c762e4867d8b32c11bd8c74846eb.json b/backend/.sqlx/query-5585bc46fe2b9d5aebddad300f43b7cbc891e55d29d30d1ca7b13f25ed1fdc87.json similarity index 74% rename from backend/.sqlx/query-c7dd35561e9b1cfd86238d410139f50e4d87c762e4867d8b32c11bd8c74846eb.json rename to backend/.sqlx/query-5585bc46fe2b9d5aebddad300f43b7cbc891e55d29d30d1ca7b13f25ed1fdc87.json index 73a1911081..0df8109669 100644 --- a/backend/.sqlx/query-c7dd35561e9b1cfd86238d410139f50e4d87c762e4867d8b32c11bd8c74846eb.json +++ b/backend/.sqlx/query-5585bc46fe2b9d5aebddad300f43b7cbc891e55d29d30d1ca7b13f25ed1fdc87.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text)", + "query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs)", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "c7dd35561e9b1cfd86238d410139f50e4d87c762e4867d8b32c11bd8c74846eb" + "hash": "5585bc46fe2b9d5aebddad300f43b7cbc891e55d29d30d1ca7b13f25ed1fdc87" } diff --git a/backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json b/backend/.sqlx/query-5701ee0b862dbdb44990702af270a2eb517e82943bb8c078c7bb2e60def3cbf1.json similarity index 72% rename from backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json rename to backend/.sqlx/query-5701ee0b862dbdb44990702af270a2eb517e82943bb8c078c7bb2e60def3cbf1.json index b56784fd64..e1384a4e70 100644 --- a/backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json +++ b/backend/.sqlx/query-5701ee0b862dbdb44990702af270a2eb517e82943bb8c078c7bb2e60def3cbf1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, expires_at = $7", + "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at", "describe": { "columns": [], "parameters": { @@ -16,5 +16,5 @@ }, "nullable": [] }, - "hash": "08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324" + "hash": "5701ee0b862dbdb44990702af270a2eb517e82943bb8c078c7bb2e60def3cbf1" } diff --git a/backend/.sqlx/query-14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d.json b/backend/.sqlx/query-6d7a4185063dbcca0dbea1b002330d622c9d2d844a2ab3938e6ec23c6150fb40.json similarity index 80% rename from backend/.sqlx/query-14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d.json rename to backend/.sqlx/query-6d7a4185063dbcca0dbea1b002330d622c9d2d844a2ab3938e6ec23c6150fb40.json index 29aaf47e88..0161985a7f 100644 --- a/backend/.sqlx/query-14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d.json +++ b/backend/.sqlx/query-6d7a4185063dbcca0dbea1b002330d622c9d2d844a2ab3938e6ec23c6150fb40.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO concurrency_locks (id, last_locked_at, owner)\n VALUES ($1, now(), $2)\n ON CONFLICT (id)\n DO UPDATE SET\n last_locked_at = now(),\n owner = $2", + "query": "INSERT INTO concurrency_locks (id, last_locked_at, owner)\n VALUES ($1, now(), $2)\n ON CONFLICT (id)\n DO UPDATE SET\n last_locked_at = now(),\n owner = EXCLUDED.owner", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "14abf759dae7ba5c38017ba6001927c6df0653a02b87bcea939066e39ebcf24d" + "hash": "6d7a4185063dbcca0dbea1b002330d622c9d2d844a2ab3938e6ec23c6150fb40" } diff --git a/backend/.sqlx/query-d970a0b07a2b5840d1feb1baacb834dbaf91c633d3e7e1e29c8eb7eedc53e888.json b/backend/.sqlx/query-7dc75cb67922e31ffe0f88b03a8a5bc14039aff6f023d79139b20519a9cdbe7d.json similarity index 74% rename from backend/.sqlx/query-d970a0b07a2b5840d1feb1baacb834dbaf91c633d3e7e1e29c8eb7eedc53e888.json rename to backend/.sqlx/query-7dc75cb67922e31ffe0f88b03a8a5bc14039aff6f023d79139b20519a9cdbe7d.json index c6324a22d5..33e330c061 100644 --- a/backend/.sqlx/query-d970a0b07a2b5840d1feb1baacb834dbaf91c633d3e7e1e29c8eb7eedc53e888.json +++ b/backend/.sqlx/query-7dc75cb67922e31ffe0f88b03a8a5bc14039aff6f023d79139b20519a9cdbe7d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin, operator)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, email)\n DO UPDATE SET is_admin = $3, operator = $4", + "query": "INSERT INTO workspace_invite\n (workspace_id, email, is_admin, operator)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, email)\n DO UPDATE SET is_admin = EXCLUDED.is_admin, operator = EXCLUDED.operator", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "d970a0b07a2b5840d1feb1baacb834dbaf91c633d3e7e1e29c8eb7eedc53e888" + "hash": "7dc75cb67922e31ffe0f88b03a8a5bc14039aff6f023d79139b20519a9cdbe7d" } diff --git a/backend/.sqlx/query-43b376a2eff086a32cd76e54361ce3631feee1565935d2a6ddbecc17950758d1.json b/backend/.sqlx/query-7dcc77eb6da5863f7a25ab6ad83d270e5e8a52540da1726aa875fcbe2517f16a.json similarity index 67% rename from backend/.sqlx/query-43b376a2eff086a32cd76e54361ce3631feee1565935d2a6ddbecc17950758d1.json rename to backend/.sqlx/query-7dcc77eb6da5863f7a25ab6ad83d270e5e8a52540da1726aa875fcbe2517f16a.json index 5b9eeaa790..d7b8988edf 100644 --- a/backend/.sqlx/query-43b376a2eff086a32cd76e54361ce3631feee1565935d2a6ddbecc17950758d1.json +++ b/backend/.sqlx/query-7dcc77eb6da5863f7a25ab6ad83d270e5e8a52540da1726aa875fcbe2517f16a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, slack_name = $3, slack_email = $4", + "query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = EXCLUDED.slack_team_id, slack_name = EXCLUDED.slack_name, slack_email = EXCLUDED.slack_email", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "43b376a2eff086a32cd76e54361ce3631feee1565935d2a6ddbecc17950758d1" + "hash": "7dcc77eb6da5863f7a25ab6ad83d270e5e8a52540da1726aa875fcbe2517f16a" } diff --git a/backend/.sqlx/query-b8c66d905a6c7ffa6441c84b14ea897040069dac7367895813cc2d64a9867193.json b/backend/.sqlx/query-7dcf840fc5b329f4a591a51e24c9dacb12266f606b5ce7e8ed6110f3f381f945.json similarity index 75% rename from backend/.sqlx/query-b8c66d905a6c7ffa6441c84b14ea897040069dac7367895813cc2d64a9867193.json rename to backend/.sqlx/query-7dcf840fc5b329f4a591a51e24c9dacb12266f606b5ce7e8ed6110f3f381f945.json index 265a3c96a4..5084c21af8 100644 --- a/backend/.sqlx/query-b8c66d905a6c7ffa6441c84b14ea897040069dac7367895813cc2d64a9867193.json +++ b/backend/.sqlx/query-7dcf840fc5b329f4a591a51e24c9dacb12266f606b5ce7e8ed6110f3f381f945.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO workspace_env (workspace_id, name, value) VALUES ($1, $2, $3) ON CONFLICT (workspace_id, name) DO UPDATE SET value = $3", + "query": "INSERT INTO workspace_env (workspace_id, name, value) VALUES ($1, $2, $3) ON CONFLICT (workspace_id, name) DO UPDATE SET value = EXCLUDED.value", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "b8c66d905a6c7ffa6441c84b14ea897040069dac7367895813cc2d64a9867193" + "hash": "7dcf840fc5b329f4a591a51e24c9dacb12266f606b5ce7e8ed6110f3f381f945" } diff --git a/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json b/backend/.sqlx/query-8fda0400ec5ba04a2a1469672bbb0af413027dbd39aa2c4182e3d219640d397e.json similarity index 69% rename from backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json rename to backend/.sqlx/query-8fda0400ec5ba04a2a1469672bbb0af413027dbd39aa2c4182e3d219640d397e.json index 9709a354cf..226bcb9083 100644 --- a/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json +++ b/backend/.sqlx/query-8fda0400ec5ba04a2a1469672bbb0af413027dbd39aa2c4182e3d219640d397e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c" + "hash": "8fda0400ec5ba04a2a1469672bbb0af413027dbd39aa2c4182e3d219640d397e" } diff --git a/backend/.sqlx/query-e40f7e0b61567f948bfea0b6f50518564634885ccc2c0d30ccca79fc13bdcf07.json b/backend/.sqlx/query-99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a.json similarity index 60% rename from backend/.sqlx/query-e40f7e0b61567f948bfea0b6f50518564634885ccc2c0d30ccca79fc13bdcf07.json rename to backend/.sqlx/query-99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a.json index be7ab6923c..c5a256e0d7 100644 --- a/backend/.sqlx/query-e40f7e0b61567f948bfea0b6f50518564634885ccc2c0d30ccca79fc13bdcf07.json +++ b/backend/.sqlx/query-99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = $1::bigint::bit(64)", + "query": "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = EXCLUDED.progress", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "e40f7e0b61567f948bfea0b6f50518564634885ccc2c0d30ccca79fc13bdcf07" + "hash": "99e6bffe177e69448b09e82b30d24af00edc86a9bc498f319c1e5bee55d77a8a" } diff --git a/backend/.sqlx/query-f9e1334ecb17b313924587d96fc8b0310b14d75e85a558c8cb70f2761923d175.json b/backend/.sqlx/query-9aebf706529889dc044e0ef41da8db2b1061c22698393c2e42d94da11e697b8b.json similarity index 78% rename from backend/.sqlx/query-f9e1334ecb17b313924587d96fc8b0310b14d75e85a558c8cb70f2761923d175.json rename to backend/.sqlx/query-9aebf706529889dc044e0ef41da8db2b1061c22698393c2e42d94da11e697b8b.json index f21a486c83..1cdf7354e2 100644 --- a/backend/.sqlx/query-f9e1334ecb17b313924587d96fc8b0310b14d75e85a558c8cb70f2761923d175.json +++ b/backend/.sqlx/query-9aebf706529889dc044e0ef41da8db2b1061c22698393c2e42d94da11e697b8b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + EXCLUDED.usage", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "f9e1334ecb17b313924587d96fc8b0310b14d75e85a558c8cb70f2761923d175" + "hash": "9aebf706529889dc044e0ef41da8db2b1061c22698393c2e42d94da11e697b8b" } diff --git a/backend/.sqlx/query-20fcdcd2674a52ee9bd8d1de518d6bce075f20bcd5d2328f183d8a59331f6bb1.json b/backend/.sqlx/query-a5bf005e0f7c9a86a136e049445de059481091ac173414418b8678f6beadf2ac.json similarity index 75% rename from backend/.sqlx/query-20fcdcd2674a52ee9bd8d1de518d6bce075f20bcd5d2328f183d8a59331f6bb1.json rename to backend/.sqlx/query-a5bf005e0f7c9a86a136e049445de059481091ac173414418b8678f6beadf2ac.json index 824335990f..6317641989 100644 --- a/backend/.sqlx/query-20fcdcd2674a52ee9bd8d1de518d6bce075f20bcd5d2328f183d8a59331f6bb1.json +++ b/backend/.sqlx/query-a5bf005e0f7c9a86a136e049445de059481091ac173414418b8678f6beadf2ac.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, edited_at = now()", + "query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value, edited_at = now()", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "20fcdcd2674a52ee9bd8d1de518d6bce075f20bcd5d2328f183d8a59331f6bb1" + "hash": "a5bf005e0f7c9a86a136e049445de059481091ac173414418b8678f6beadf2ac" } diff --git a/backend/.sqlx/query-366609f7e7fbd73ea807128b931eff2f1ab763fa630c8531f590fed2110c03d9.json b/backend/.sqlx/query-b474ae4401b3d4c95add2d3353eb66512801c20ad685e8ed544fe4b86601aaa8.json similarity index 86% rename from backend/.sqlx/query-366609f7e7fbd73ea807128b931eff2f1ab763fa630c8531f590fed2110c03d9.json rename to backend/.sqlx/query-b474ae4401b3d4c95add2d3353eb66512801c20ad685e8ed544fe4b86601aaa8.json index e9ec98136b..1341337cc5 100644 --- a/backend/.sqlx/query-366609f7e7fbd73ea807128b931eff2f1ab763fa630c8531f590fed2110c03d9.json +++ b/backend/.sqlx/query-b474ae4401b3d4c95add2d3353eb66512801c20ad685e8ed544fe4b86601aaa8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO draft\n (workspace_id, path, value, typ)\n VALUES ($1, $2, $3::text::json, $4)\n ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = $3::text::json", + "query": "INSERT INTO draft\n (workspace_id, path, value, typ)\n VALUES ($1, $2, $3::text::json, $4)\n ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = EXCLUDED.value", "describe": { "columns": [], "parameters": { @@ -24,5 +24,5 @@ }, "nullable": [] }, - "hash": "366609f7e7fbd73ea807128b931eff2f1ab763fa630c8531f590fed2110c03d9" + "hash": "b474ae4401b3d4c95add2d3353eb66512801c20ad685e8ed544fe4b86601aaa8" } diff --git a/backend/.sqlx/query-8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce.json b/backend/.sqlx/query-bf2163c542fb8c4e173167a8f333ef762fecf782424c5b61b89f32918b8d6971.json similarity index 87% rename from backend/.sqlx/query-8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce.json rename to backend/.sqlx/query-bf2163c542fb8c4e173167a8f333ef762fecf782424c5b61b89f32918b8d6971.json index 2c70bdd6b6..46db07e1e0 100644 --- a/backend/.sqlx/query-8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce.json +++ b/backend/.sqlx/query-bf2163c542fb8c4e173167a8f333ef762fecf782424c5b61b89f32918b8d6971.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)", + "query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) \n values ($1, $32, $33, $34, $35, $36, $37, $2, $41) \n ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 OR $40 THEN now() END, $30, $31)", "describe": { "columns": [], "parameters": { @@ -129,5 +129,5 @@ }, "nullable": [] }, - "hash": "8d67ed8e1271a27b072a8e8ae9973e372949f5ec7d71b80d1ba3eaddf851adce" + "hash": "bf2163c542fb8c4e173167a8f333ef762fecf782424c5b61b89f32918b8d6971" } diff --git a/backend/.sqlx/query-afc7c23c057748f6d4a61dbef17e433b8875c6588b91e38e8141d12189118412.json b/backend/.sqlx/query-cc8e10a4f39e118b145cc8f6fbd5f3db1fb30c9564ed039ad4c952333ec29b39.json similarity index 74% rename from backend/.sqlx/query-afc7c23c057748f6d4a61dbef17e433b8875c6588b91e38e8141d12189118412.json rename to backend/.sqlx/query-cc8e10a4f39e118b145cc8f6fbd5f3db1fb30c9564ed039ad4c952333ec29b39.json index 08ea345138..49df64b555 100644 --- a/backend/.sqlx/query-afc7c23c057748f6d4a61dbef17e433b8875c6588b91e38e8141d12189118412.json +++ b/backend/.sqlx/query-cc8e10a4f39e118b145cc8f6fbd5f3db1fb30c9564ed039ad4c952333ec29b39.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = $4", + "query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "afc7c23c057748f6d4a61dbef17e433b8875c6588b91e38e8141d12189118412" + "hash": "cc8e10a4f39e118b145cc8f6fbd5f3db1fb30c9564ed039ad4c952333ec29b39" } diff --git a/backend/.sqlx/query-7b48820a08fbb3bee0fb2dc802a0b4f28ed5507797165e8c577ce6a95d11694c.json b/backend/.sqlx/query-ceb10ca124d9425b24d81d8a279a13481db39eea0b52cc513b3ee6571cf44ed4.json similarity index 77% rename from backend/.sqlx/query-7b48820a08fbb3bee0fb2dc802a0b4f28ed5507797165e8c577ce6a95d11694c.json rename to backend/.sqlx/query-ceb10ca124d9425b24d81d8a279a13481db39eea0b52cc513b3ee6571cf44ed4.json index 56481d8eb0..0b5137067a 100644 --- a/backend/.sqlx/query-7b48820a08fbb3bee0fb2dc802a0b4f28ed5507797165e8c577ce6a95d11694c.json +++ b/backend/.sqlx/query-ceb10ca124d9425b24d81d8a279a13481db39eea0b52cc513b3ee6571cf44ed4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + "query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + EXCLUDED.usage", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "7b48820a08fbb3bee0fb2dc802a0b4f28ed5507797165e8c577ce6a95d11694c" + "hash": "ceb10ca124d9425b24d81d8a279a13481db39eea0b52cc513b3ee6571cf44ed4" } diff --git a/backend/.sqlx/query-9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde.json b/backend/.sqlx/query-d142ff8b56f4b69c20815230b5b763ce8ce9fbe89abe83989f52ff3657690fef.json similarity index 73% rename from backend/.sqlx/query-9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde.json rename to backend/.sqlx/query-d142ff8b56f4b69c20815230b5b763ce8ce9fbe89abe83989f52ff3657690fef.json index de16cf3146..8d92b42219 100644 --- a/backend/.sqlx/query-9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde.json +++ b/backend/.sqlx/query-d142ff8b56f4b69c20815230b5b763ce8ce9fbe89abe83989f52ff3657690fef.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO job_result_stream_v2 (workspace_id, job_id, stream, idx)\n VALUES (\n $1, \n $2,\n $3, \n $4\n )\n ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || $3\n ", + "query": "\n INSERT INTO job_result_stream_v2 (workspace_id, job_id, stream, idx)\n VALUES (\n $1, \n $2,\n $3, \n $4\n )\n ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || EXCLUDED.stream\n ", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "9bfc2a821b25641af48b0e3954163078922340294f1c6515400fb2c896666fde" + "hash": "d142ff8b56f4b69c20815230b5b763ce8ce9fbe89abe83989f52ff3657690fef" } diff --git a/backend/.sqlx/query-fb581eb6f883ebb5909a00c65a6e1217f088290fce0e052171230bcd88f945b9.json b/backend/.sqlx/query-da7e23a32f284c9760614735d459474d2f8513202dfb64767b30e7b50833b857.json similarity index 57% rename from backend/.sqlx/query-fb581eb6f883ebb5909a00c65a6e1217f088290fce0e052171230bcd88f945b9.json rename to backend/.sqlx/query-da7e23a32f284c9760614735d459474d2f8513202dfb64767b30e7b50833b857.json index b3f640dd76..e54180983e 100644 --- a/backend/.sqlx/query-fb581eb6f883ebb5909a00c65a6e1217f088290fce0e052171230bcd88f945b9.json +++ b/backend/.sqlx/query-da7e23a32f284c9760614735d459474d2f8513202dfb64767b30e7b50833b857.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO global_settings (name, value) VALUES ('slack', $1) ON CONFLICT (name) DO UPDATE SET value = $1, updated_at = now()", + "query": "INSERT INTO global_settings (name, value) VALUES ('slack', $1) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()", "describe": { "columns": [], "parameters": { @@ -10,5 +10,5 @@ }, "nullable": [] }, - "hash": "fb581eb6f883ebb5909a00c65a6e1217f088290fce0e052171230bcd88f945b9" + "hash": "da7e23a32f284c9760614735d459474d2f8513202dfb64767b30e7b50833b857" } diff --git a/backend/.sqlx/query-c4e0f3eab227a798d9cd7478db50a7c1a588e69c2c8d9a1def9276ba326453d2.json b/backend/.sqlx/query-db201730803047bfabccf5f10456243b590b2dd0cfe41aa5100a7e829afad9d3.json similarity index 63% rename from backend/.sqlx/query-c4e0f3eab227a798d9cd7478db50a7c1a588e69c2c8d9a1def9276ba326453d2.json rename to backend/.sqlx/query-db201730803047bfabccf5f10456243b590b2dd0cfe41aa5100a7e829afad9d3.json index bd16dab3bf..033d77862e 100644 --- a/backend/.sqlx/query-c4e0f3eab227a798d9cd7478db50a7c1a588e69c2c8d9a1def9276ba326453d2.json +++ b/backend/.sqlx/query-db201730803047bfabccf5f10456243b590b2dd0cfe41aa5100a7e829afad9d3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO cloud_workspace_settings (workspace_id, threshold_alert_amount) VALUES ($1, $2) ON CONFLICT (workspace_id) DO UPDATE SET threshold_alert_amount = $2, last_alert_sent = NULL", + "query": "INSERT INTO cloud_workspace_settings (workspace_id, threshold_alert_amount) VALUES ($1, $2) ON CONFLICT (workspace_id) DO UPDATE SET threshold_alert_amount = EXCLUDED.threshold_alert_amount, last_alert_sent = NULL", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "c4e0f3eab227a798d9cd7478db50a7c1a588e69c2c8d9a1def9276ba326453d2" + "hash": "db201730803047bfabccf5f10456243b590b2dd0cfe41aa5100a7e829afad9d3" } diff --git a/backend/.sqlx/query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json b/backend/.sqlx/query-e16f464c7e302e80f1017ff6550716448202c1181ca0b36936a9c8840c14a95d.json similarity index 76% rename from backend/.sqlx/query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json rename to backend/.sqlx/query-e16f464c7e302e80f1017ff6550716448202c1181ca0b36936a9c8840c14a95d.json index 4e0d53b0f3..3dc6dd5339 100644 --- a/backend/.sqlx/query-a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c.json +++ b/backend/.sqlx/query-e16f464c7e302e80f1017ff6550716448202c1181ca0b36936a9c8840c14a95d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text) RETURNING length(logs)", + "query": "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs) RETURNING length(logs)", "describe": { "columns": [ { @@ -20,5 +20,5 @@ null ] }, - "hash": "a2e86f169ffbf8acee5f7c7b71db5859ac94ffbad267c9cd6c652e8ce8fc5d3c" + "hash": "e16f464c7e302e80f1017ff6550716448202c1181ca0b36936a9c8840c14a95d" } diff --git a/backend/.sqlx/query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json b/backend/.sqlx/query-e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833.json similarity index 74% rename from backend/.sqlx/query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json rename to backend/.sqlx/query-e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833.json index ace3edec94..2ee8c422fd 100644 --- a/backend/.sqlx/query-f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981.json +++ b/backend/.sqlx/query-e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", + "query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981" + "hash": "e572fa64eec9188368d7c271ac7ecd6b45dc161423abab22cddb2b13f6fb9833" } diff --git a/backend/.sqlx/query-6afc5c7cbb3abe11ade0cedf1f7328005ce4de3165cdd998e5a0d27e044c7153.json b/backend/.sqlx/query-e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2.json similarity index 70% rename from backend/.sqlx/query-6afc5c7cbb3abe11ade0cedf1f7328005ce4de3165cdd998e5a0d27e044c7153.json rename to backend/.sqlx/query-e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2.json index 0bbe880941..3867319977 100644 --- a/backend/.sqlx/query-6afc5c7cbb3abe11ade0cedf1f7328005ce4de3165cdd998e5a0d27e044c7153.json +++ b/backend/.sqlx/query-e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) DO UPDATE set ip = $3, custom_tags = $4, worker_group = $5", + "query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) \n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "6afc5c7cbb3abe11ade0cedf1f7328005ce4de3165cdd998e5a0d27e044c7153" + "hash": "e5f8830450e90f678494ae2b0f86b0692a02dec4c8415fd7d364b0cc088905c2" } diff --git a/backend/.sqlx/query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json b/backend/.sqlx/query-e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69.json similarity index 74% rename from backend/.sqlx/query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json rename to backend/.sqlx/query-e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69.json index d643e4b8f8..84645b71b0 100644 --- a/backend/.sqlx/query-2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d.json +++ b/backend/.sqlx/query-e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5", + "query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = EXCLUDED.callback_job_ids, deployment_msg = EXCLUDED.deployment_msg", "describe": { "columns": [], "parameters": { @@ -14,5 +14,5 @@ }, "nullable": [] }, - "hash": "2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d" + "hash": "e8e33f599eae064011232f9f715e676d0c8ae31982865cb2c8103ed735c42c69" } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 48777ef499..253d116503 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -af5cfc6b0f0bd42f95a5842dff92cf598d3aa6d8 +0f18d7155059dccebbe690162b52133d526e6fb5 \ No newline at end of file diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 6d66d2ad66..e4d3c380af 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -2724,7 +2724,7 @@ pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<( async fn generate_and_save_jwt_secret(db: &DB) -> error::Result { let secret = rd_string(32); sqlx::query!( - "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2", + "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value", JWT_SECRET_SETTING, serde_json::to_value(&secret).unwrap() ).execute(db).await?; diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 3043be10ce..15a89d762f 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -699,7 +699,7 @@ async fn update_app_history( check_scopes(&authed, || format!("apps:write:{}", &app_path))?; sqlx::query!( - "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = $4", + "INSERT INTO deployment_metadata (workspace_id, path, app_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg", w_id, app_path, app_version, diff --git a/backend/windmill-api/src/configs.rs b/backend/windmill-api/src/configs.rs index 5a1e597592..719437b29c 100644 --- a/backend/windmill-api/src/configs.rs +++ b/backend/windmill-api/src/configs.rs @@ -173,7 +173,7 @@ async fn update_config( let mut tx = db.begin().await?; sqlx::query!( - "INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = $2", + "INSERT INTO config (name, config) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET config = EXCLUDED.config", &name, config ) diff --git a/backend/windmill-api/src/drafts.rs b/backend/windmill-api/src/drafts.rs index 02d3b3c4fb..41e8d4709d 100644 --- a/backend/windmill-api/src/drafts.rs +++ b/backend/windmill-api/src/drafts.rs @@ -79,7 +79,7 @@ async fn create_draft( "INSERT INTO draft (workspace_id, path, value, typ) VALUES ($1, $2, $3::text::json, $4) - ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = $3::text::json", + ON CONFLICT (workspace_id, path, typ) DO UPDATE SET value = EXCLUDED.value", &w_id, draft.path, //to preserve key orders diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index cd21e5b3c8..c983fb8d1f 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -722,7 +722,7 @@ async fn update_flow_history( } sqlx::query!( - "INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = $4", + "INSERT INTO deployment_metadata (workspace_id, path, flow_version, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg", w_id, path_o.unwrap(), version, diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index df31ed058e..9decd976f8 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -740,7 +740,7 @@ async fn create_resource( "INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) - DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()", + DO UPDATE SET value = EXCLUDED.value, description = EXCLUDED.description, resource_type = EXCLUDED.resource_type, edited_at = now()", w_id, resource.path, raw_json as sqlx::types::Json<&RawValue>, diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 6aaf77366b..56107f055e 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -1249,7 +1249,8 @@ async fn update_script_history( let mut tx = user_db.begin(&authed).await?; sqlx::query!( - "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET deployment_msg = $4", + "INSERT INTO deployment_metadata (workspace_id, path, script_hash, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL + DO UPDATE SET deployment_msg = EXCLUDED.deployment_msg", w_id, script_path, script_hash.0, diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index c7c3aba0d3..b1c9df8536 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -334,7 +334,7 @@ pub async fn set_global_setting_internal( } v => { sqlx::query!( - "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = $2, updated_at = now()", + "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()", key, v ) diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 722c05ebce..67a946f16b 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -583,7 +583,7 @@ async fn update_tutorial_progress( Json(progress): Json, ) -> Result { sqlx::query_scalar!( - "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = $1::bigint::bit(64)", + "INSERT INTO tutorial_progress VALUES ($2, $1::bigint::bit(64)) ON CONFLICT (email) DO UPDATE SET progress = EXCLUDED.progress", progress.progress as i64, authed.email ) @@ -1385,7 +1385,9 @@ async fn convert_user_to_group( // Check if user is already a group user if let Some(added_via) = &user_info.added_via { if added_via.get("source").and_then(|v| v.as_str()) == Some("instance_group") { - return Err(Error::BadRequest("User is already a group user".to_string())); + return Err(Error::BadRequest( + "User is already a group user".to_string(), + )); } } @@ -1408,16 +1410,18 @@ async fn convert_user_to_group( if eligible_groups.is_empty() { return Err(Error::BadRequest( - "User is not a member of any instance groups configured for auto-add in this workspace".to_string() + "User is not a member of any instance groups configured for auto-add in this workspace" + .to_string(), )); } // Determine the group with highest precedence (same logic as process_instance_group_auto_adds) - let roles: std::collections::HashMap = if let Some(roles_json) = &eligible_groups[0].auto_add_instance_groups_roles { - serde_json::from_value(roles_json.clone()).unwrap_or_default() - } else { - std::collections::HashMap::new() - }; + let roles: std::collections::HashMap = + if let Some(roles_json) = &eligible_groups[0].auto_add_instance_groups_roles { + serde_json::from_value(roles_json.clone()).unwrap_or_default() + } else { + std::collections::HashMap::new() + }; let mut best_group = &eligible_groups[0].group_name; let mut best_precedence = 0u8; @@ -1443,7 +1447,10 @@ async fn convert_user_to_group( // Determine role from group configuration using the selected primary group let default_role = "developer".to_string(); - let role = roles.get(primary_group_name).unwrap_or(&default_role).as_str(); + let role = roles + .get(primary_group_name) + .unwrap_or(&default_role) + .as_str(); let (is_admin, is_operator) = match role { "admin" => (true, false), @@ -1487,12 +1494,18 @@ async fn convert_user_to_group( &db, &w_id, windmill_git_sync::DeployedObject::User { email: user_info.email.clone() }, - Some(format!("Converted user '{}' to group user (group: {}, role: {})", &user_info.email, primary_group_name, role)), + Some(format!( + "Converted user '{}' to group user (group: {}, role: {})", + &user_info.email, primary_group_name, role + )), true, ) .await?; - Ok(format!("User {} converted to group user (group: {}, role: {})", username_to_convert, primary_group_name, role)) + Ok(format!( + "User {} converted to group user (group: {}, role: {})", + username_to_convert, primary_group_name, role + )) } async fn update_user( @@ -1594,9 +1607,12 @@ async fn delete_user( } // Remove user from all instance groups email_to_igroup - sqlx::query!("DELETE FROM email_to_igroup WHERE email = $1", &email_to_delete) - .execute(&mut *tx) - .await?; + sqlx::query!( + "DELETE FROM email_to_igroup WHERE email = $1", + &email_to_delete + ) + .execute(&mut *tx) + .await?; audit_log( &mut *tx, diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index b5a166bf96..ff399ee7c6 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -1901,7 +1901,7 @@ async fn set_environment_variable( match value { Some(value) => { sqlx::query!( - "INSERT INTO workspace_env (workspace_id, name, value) VALUES ($1, $2, $3) ON CONFLICT (workspace_id, name) DO UPDATE SET value = $3", + "INSERT INTO workspace_env (workspace_id, name, value) VALUES ($1, $2, $3) ON CONFLICT (workspace_id, name) DO UPDATE SET value = EXCLUDED.value", &w_id, name, value @@ -3155,7 +3155,7 @@ async fn invite_user( "INSERT INTO workspace_invite (workspace_id, email, is_admin, operator) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, email) - DO UPDATE SET is_admin = $3, operator = $4", + DO UPDATE SET is_admin = EXCLUDED.is_admin, operator = EXCLUDED.operator", &w_id, nu.email, nu.is_admin, diff --git a/backend/windmill-common/src/result_stream.rs b/backend/windmill-common/src/result_stream.rs index 02bfa75b92..c44c2d556e 100644 --- a/backend/windmill-common/src/result_stream.rs +++ b/backend/windmill-common/src/result_stream.rs @@ -31,7 +31,7 @@ pub async fn append_result_stream_db( $3, $4 ) - ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || $3 + ON CONFLICT (job_id, idx) DO UPDATE SET stream = job_result_stream_v2.stream || EXCLUDED.stream "#, workspace_id, job_id, diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 704a31e6e0..4568e910e4 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -1277,7 +1277,8 @@ pub async fn insert_ping_query( db: &DB, ) -> anyhow::Result<()> { sqlx::query!( - "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) DO UPDATE set ip = $3, custom_tags = $4, worker_group = $5", + "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, wm_version, vcpus, memory) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (worker) + DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group", worker_instance, worker_name, ip, diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 326c5a964c..000e15ff00 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -374,7 +374,7 @@ pub async fn append_logs( match conn { Connection::Sql(pool) => { if let Err(err) = sqlx::query!( - "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text)", + "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs)", logs.as_ref(), job_id, workspace.as_ref(), @@ -1429,7 +1429,7 @@ fn apply_completed_job_cloud_usage( let _ = sqlx::query!( "INSERT INTO usage (id, is_workspace, month_, usage) VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + EXCLUDED.usage", w_id, additional_usage as i32 ) @@ -1443,7 +1443,7 @@ fn apply_completed_job_cloud_usage( let _ = sqlx::query!( "INSERT INTO usage (id, is_workspace, month_, usage) VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) - ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2", + ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + EXCLUDED.usage", email, additional_usage as i32 ) @@ -4328,7 +4328,7 @@ pub async fn push<'c, 'd>( inserted_job_perms AS ( INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id, end_user_email) values ($1, $32, $33, $34, $35, $36, $37, $2, $41) - ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2 + ON CONFLICT (job_id) DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, is_admin = EXCLUDED.is_admin, is_operator = EXCLUDED.is_operator, folders = EXCLUDED.folders, groups = EXCLUDED.groups, workspace_id = EXCLUDED.workspace_id, end_user_email = EXCLUDED.end_user_email ) INSERT INTO v2_job_queue (workspace_id, id, running, scheduled_for, started_at, tag, priority) diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 38e7b366f6..eea0a8fbb9 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -997,7 +997,7 @@ pub async fn save_in_cache( "INSERT INTO resource (workspace_id, path, value, resource_type, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path) - DO UPDATE SET value = $3, edited_at = now()", + DO UPDATE SET value = EXCLUDED.value, edited_at = now()", job.workspace_id, &cached_path, raw_json as Json<&CachedResource>, diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index 7a758051ab..0409ceba95 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -406,7 +406,10 @@ func Run(req Req) (interface{{}}, error){{ #[cfg(windows)] set_windows_env_vars(&mut run_go); - run_go.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()); + run_go + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); start_child_process(run_go, &compiled_executable_name, false).await? }; let handle_result = handle_child( @@ -647,7 +650,7 @@ pub async fn install_go_dependencies( if non_dep_job { if let Some(db) = conn.as_sql() { sqlx::query!( - "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile", hash, req_content ) diff --git a/backend/windmill-worker/src/java_executor.rs b/backend/windmill-worker/src/java_executor.rs index d133308489..92ef69da16 100644 --- a/backend/windmill-worker/src/java_executor.rs +++ b/backend/windmill-worker/src/java_executor.rs @@ -247,7 +247,7 @@ pub async fn resolve<'a>( if let Connection::Sql(db) = conn { sqlx::query!( - "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile", req_hash, lock.clone(), ) diff --git a/backend/windmill-worker/src/job_logger.rs b/backend/windmill-worker/src/job_logger.rs index 11e7785ee5..364c352a21 100644 --- a/backend/windmill-worker/src/job_logger.rs +++ b/backend/windmill-worker/src/job_logger.rs @@ -108,7 +108,7 @@ pub async fn append_logs_with_compaction( worker_name: &str, ) { let log_length = sqlx::query_scalar!( - "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, $1::text) RETURNING length(logs)", + "INSERT INTO job_logs (logs, job_id, workspace_id) VALUES ($1, $2, $3) ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs) RETURNING length(logs)", logs, job_id, &w_id, diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index c4e88e0853..7266ead92d 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -413,7 +413,7 @@ pub async fn uv_pip_compile( ); if let Some(db) = conn.as_sql() { sqlx::query!( - "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('5 mins')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile", req_hash, lockfile ).fetch_optional(db).await?; diff --git a/backend/windmill-worker/src/ruby_executor.rs b/backend/windmill-worker/src/ruby_executor.rs index b635a6bade..4dbc5bcf8a 100644 --- a/backend/windmill-worker/src/ruby_executor.rs +++ b/backend/windmill-worker/src/ruby_executor.rs @@ -433,7 +433,7 @@ Your Gemfile syntax will continue to work as-is." if let Some(db) = conn.as_sql() { sqlx::query!( - "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = EXCLUDED.lockfile", req_hash, lock.clone(), ).fetch_optional(db).await?; From 3b5c96247350b70fe947d204e9bff61f81be219c Mon Sep 17 00:00:00 2001 From: hugocasa Date: Thu, 16 Oct 2025 16:05:06 +0200 Subject: [PATCH 11/33] fix(backend): revert flow node opti for ai agents (#6840) * Revert "feat(backend): use flow nodes opti for ai agent steps (#6808)" This reverts commit 8d5acda340cd105c5b0dfc2bfe59b7e996bd2707. * fix(backend): revert flow node opti for ai agents * keep standard base64 --- backend/windmill-common/src/cache.rs | 38 +++-- backend/windmill-common/src/flows.rs | 13 -- backend/windmill-common/src/jobs.rs | 1 - backend/windmill-queue/src/jobs.rs | 9 +- backend/windmill-worker/src/ai_executor.rs | 135 +++++------------- backend/windmill-worker/src/worker_flow.rs | 4 +- .../windmill-worker/src/worker_lockfiles.rs | 48 ++----- 7 files changed, 76 insertions(+), 172 deletions(-) diff --git a/backend/windmill-common/src/cache.rs b/backend/windmill-common/src/cache.rs index f46259f457..3d1524916a 100644 --- a/backend/windmill-common/src/cache.rs +++ b/backend/windmill-common/src/cache.rs @@ -15,6 +15,7 @@ use crate::{ scripts::{ScriptHash, ScriptLang}, }; use anyhow::anyhow; +use serde_json::value::to_raw_value; #[cfg(feature = "scoped_cache")] use std::thread::ThreadId; @@ -282,25 +283,32 @@ pub mod future { pub struct FlowData { pub raw_flow: Box, pub flow: FlowValue, - pub summary: Option, +} + +/// !!!Shouldn't be used. Reverted optimization for ai agent steps.!!! +#[derive(Deserialize)] +struct RevertedFlowNodeFlow { + value: FlowValue, } impl FlowData { pub fn from_raw(raw_flow: Box) -> error::Result { - let (flow, summary) = if let Ok(parsed) = - serde_json::from_str::(raw_flow.get()) - { - (parsed.value, parsed.summary) - } else { - // fallback to plain FlowValue - ( - serde_json::from_str::(raw_flow.get()).map_err(|e| { - error::Error::internal_err(format!("Failed to parse as FlowValue: {}", e)) - })?, - None, - ) - }; - Ok(Self { raw_flow, flow, summary }) + match serde_json::from_str::(raw_flow.get()) { + Ok(flow) => Ok(FlowData { raw_flow, flow }), + _ => { + // fallback for compatibility with bad version 1.560.0 + // TODO: remove this in a future version. Reverted optimization for ai agent steps. + let flow_node_flow = serde_json::from_str::(raw_flow.get()) + .map_err(|e| { + error::Error::internal_err(format!( + "Failed to parse as RevertedFlowNodeFlow: {}", + e + )) + })?; + let raw_flow = to_raw_value(&flow_node_flow.value)?; + Ok(FlowData { raw_flow, flow: flow_node_flow.value }) + } + } } pub fn value(&self) -> &FlowValue { diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 99093cc107..810368f901 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -141,13 +141,6 @@ pub struct FlowValue { pub chat_input_enabled: Option, } -#[derive(Serialize, Deserialize)] -pub struct FlowNodeFlow { - pub value: FlowValue, - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, -} - impl FlowValue { pub fn get_flow_module_at_step(&self, step: Step) -> anyhow::Result<&FlowModule> { let flow_module = match step { @@ -733,8 +726,6 @@ pub enum FlowModuleValue { AIAgent { input_transforms: HashMap, tools: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - modules_node: Option, }, } @@ -872,7 +863,6 @@ impl<'de> Deserialize<'de> for FlowModuleValue { tools: untagged .tools .ok_or_else(|| serde::de::Error::missing_field("tools"))?, - modules_node: untagged.modules_node, }), other => Err(serde::de::Error::unknown_variant( other, @@ -1064,9 +1054,6 @@ pub async fn resolve_module( .await?; } } - AIAgent { tools, modules_node, .. } => { - resolve_modules(db, workspace_id, tools, modules_node.take(), with_code).await?; - } _ => {} } *value = to_raw_value(&val); diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index b4d4f0acc7..4608fe72b3 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -425,7 +425,6 @@ pub enum JobPayload { Noop, AIAgent { path: String, - flow_node_id: Option, }, } diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 000e15ff00..5cec1ac6f2 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -3737,8 +3737,7 @@ pub async fn push<'c, 'd>( if let Some(skip_handler) = skip_handler { let mut skip_input_transforms = HashMap::::new(); for (arg_name, arg_value) in skip_handler.args { - skip_input_transforms - .insert(arg_name, InputTransform::Static { value: arg_value }); + skip_input_transforms.insert(arg_name, InputTransform::Static { value: arg_value }); } modules.push(FlowModule { @@ -3873,7 +3872,7 @@ pub async fn push<'c, 'd>( // this is a new flow being pushed, flow_status is set to flow_value: let flow_status: FlowStatus = FlowStatus::new(&flow_value); ( - None, // No version needed - flow is stored in raw_flow like FlowPreview + None, // No version needed - flow is stored in raw_flow like FlowPreview Some(path), None, JobKind::SingleStepFlow, @@ -4072,8 +4071,8 @@ pub async fn push<'c, 'd>( None, None, ), - JobPayload::AIAgent { path, flow_node_id } => ( - flow_node_id.map(|id| id.0), + JobPayload::AIAgent { path } => ( + None, Some(path), None, JobKind::AIAgent, diff --git a/backend/windmill-worker/src/ai_executor.rs b/backend/windmill-worker/src/ai_executor.rs index 8fda760a83..01a4b36b71 100644 --- a/backend/windmill-worker/src/ai_executor.rs +++ b/backend/windmill-worker/src/ai_executor.rs @@ -1,7 +1,4 @@ -use crate::{ - memory_oss::{read_from_memory, write_to_memory}, - worker_flow::JobPayloadWithTag, -}; +use crate::memory_oss::{read_from_memory, write_to_memory}; use anyhow::Context; use async_recursion::async_recursion; use regex::Regex; @@ -17,9 +14,9 @@ use windmill_common::{ error::{self, to_anyhow, Error}, flow_conversations::{add_message_to_conversation_tx, MessageType}, flow_status::AgentAction, - flows::{FlowModuleValue, FlowNodeId, Step}, + flows::{FlowModuleValue, FlowValue, Step}, get_latest_hash_for_path, - jobs::{JobKind, JobPayload}, + jobs::JobKind, scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, utils::{StripPath, HTTP_CLIENT}, worker::{to_raw_value, Connection}, @@ -209,51 +206,36 @@ pub async fn handle_ai_agent_job( )); }; - let (tools, summary) = if let Some(ScriptHash(flow_node_id)) = job.runnable_id { - tracing::debug!( - "Fetching AI Agent flow data using flow node id {}", - flow_node_id - ); - let flow_data = cache::flow::fetch_flow(db, FlowNodeId(flow_node_id)).await?; + let flow_job = get_flow_job_runnable_and_raw_flow(db, &parent_job).await?; - let value = flow_data.value(); - - (value.modules.clone(), flow_data.summary.clone()) - } else { - tracing::debug!("Fetching flow data for parent job of AI Agent job"); - let flow_job = get_flow_job_runnable_and_raw_flow(db, &parent_job).await?; - - let flow_data = match flow_job.kind { - JobKind::Flow | JobKind::FlowNode => { - cache::job::fetch_flow(db, &flow_job.kind, flow_job.runnable_id).await? - } - JobKind::FlowPreview => { - cache::job::fetch_preview_flow(db, &parent_job, flow_job.raw_flow).await? - } - _ => { - return Err(Error::internal_err( - "expected parent flow, flow preview or flow node for ai agent job".to_string(), - )); - } - }; - - let value = flow_data.value(); - - let module = value.modules.iter().find(|m| m.id == *flow_step_id); - - let Some(module) = module else { + let flow_data = match flow_job.kind { + JobKind::Flow | JobKind::FlowNode => { + cache::job::fetch_flow(db, &flow_job.kind, flow_job.runnable_id).await? + } + JobKind::FlowPreview => { + cache::job::fetch_preview_flow(db, &parent_job, flow_job.raw_flow).await? + } + _ => { return Err(Error::internal_err( - "AI agent module not found in flow".to_string(), + "expected parent flow, flow preview or flow node for ai agent job".to_string(), )); - }; + } + }; - let FlowModuleValue::AIAgent { tools, .. } = module.get_value()? else { - return Err(Error::internal_err( - "AI agent module is not an AI agent".to_string(), - )); - }; + let value = flow_data.value(); - (tools, module.summary.clone()) + let module = value.modules.iter().find(|m| m.id == *flow_step_id); + + let Some(module) = module else { + return Err(Error::internal_err( + "AI agent module not found in flow".to_string(), + )); + }; + + let FlowModuleValue::AIAgent { tools, .. } = module.get_value()? else { + return Err(Error::internal_err( + "AI agent module is not an AI agent".to_string(), + )); }; let tools = futures::future::try_join_all(tools.into_iter().map(|mut t| { @@ -321,10 +303,6 @@ pub async fn handle_ai_agent_job( Ok(FlowModuleValue::RawScript { content, language, .. }) => { Ok(Some(parse_raw_script_schema(&content, &language)?)) } - Ok(FlowModuleValue::FlowScript { id, language, .. }) => { - let script_data = cache::flow::fetch_script(conn, id.clone()).await?; - Ok(Some(parse_raw_script_schema(&script_data.code, &language)?)) - } Err(e) => { return Err(Error::internal_err(format!( "Invalid tool {}: {}", @@ -376,7 +354,7 @@ pub async fn handle_ai_agent_job( parent_job, &args, &tools, - summary.as_deref(), + value, client, &mut inner_occupancy_metrics, job_completed_tx, @@ -491,12 +469,14 @@ async fn update_flow_status_module_with_actions_success( } /// Get step name from the flow module (summary if exists, else id) -fn get_step_name_from_flow(summary: Option<&str>, flow_step_id: Option<&str>) -> Option { +fn get_step_name_from_flow(flow_value: &FlowValue, flow_step_id: Option<&str>) -> Option { let flow_step_id = flow_step_id?; + let module = flow_value.modules.iter().find(|m| m.id == flow_step_id)?; Some( - summary - .map(|s| s.to_string()) - .unwrap_or_else(|| format!("AI Agent Step {}", flow_step_id)), + module + .summary + .clone() + .unwrap_or_else(|| format!("AI Agent Step {}", module.id)), ) } @@ -519,7 +499,7 @@ pub async fn run_agent( parent_job: &Uuid, args: &AIAgentArgs, tools: &[Tool], - summary: Option<&str>, + flow_value: &FlowValue, // job execution context client: &AuthedClient, @@ -771,7 +751,7 @@ pub async fn run_agent( let db_clone = db.clone(); let message_content = response_content.clone(); let step_name = get_step_name_from_flow( - summary, + flow_value, job.flow_step_id.as_deref(), ); @@ -934,43 +914,6 @@ pub async fn run_agent( ); payload } - FlowModuleValue::FlowScript { - id, - language, - custom_concurrency_key, - concurrent_limit, - concurrency_time_window_s, - tag, - .. - } => { - let path = format!( - "{}/tools/{}", - job.runnable_path(), - tool.module.id - ); - - let payload = JobPayloadWithTag { - payload: JobPayload::FlowScript { - id, - language, - custom_concurrency_key: custom_concurrency_key - .clone(), - concurrent_limit, - concurrency_time_window_s, - cache_ttl: tool.module.cache_ttl.map(|x| x as i32), - dedicated_worker: None, - path, - }, - tag: tag.clone(), - delete_after_use: tool - .module - .delete_after_use - .unwrap_or(false), - timeout: None, - on_behalf_of: None, - }; - payload - } _ => { return Err(Error::internal_err(format!( "Unsupported tool: {}", @@ -1178,7 +1121,7 @@ pub async fn run_agent( let tool_job_id = job_id; let db_clone = db.clone(); let step_name = get_step_name_from_flow( - summary, + flow_value, job.flow_step_id.as_deref(), ); @@ -1285,7 +1228,7 @@ pub async fn run_agent( let db_clone = db.clone(); let tool_name = tool_call.function.name.clone(); let step_name = get_step_name_from_flow( - summary, + flow_value, job.flow_step_id.as_deref(), ); let content = if success { diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 7a7f6cb52a..ad5b40ee4a 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -3701,9 +3701,9 @@ async fn compute_next_flow_transform( NextStatus::NextStep, )) } - FlowModuleValue::AIAgent { modules_node, .. } => { + FlowModuleValue::AIAgent { .. } => { let path = get_path(flow_job, status, module); - let payload = JobPayload::AIAgent { path, flow_node_id: modules_node }; + let payload = JobPayload::AIAgent { path }; Ok(NextFlowTransform::Continue( ContinuePayload::SingleJob(JobPayloadWithTag { payload, diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 621012aa9a..59f0f87552 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -16,7 +16,7 @@ use uuid::Uuid; use windmill_common::assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind}; use windmill_common::error::Error; use windmill_common::error::Result; -use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeFlow, FlowNodeId}; +use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId}; use windmill_common::get_latest_deployed_hash_for_path; use windmill_common::jobs::JobPayload; use windmill_common::scripts::{hash_script, NewScript, ScriptHash}; @@ -1635,10 +1635,8 @@ async fn insert_flow_modules<'c>( workspace_id: &str, failure_module: Option<&Box>, same_worker: bool, - summary: Option, modules: &mut Vec, modules_node: &mut Option, - force_insert: bool, ) -> Result> { tx = Box::pin(reduce_flow( tx, @@ -1649,22 +1647,9 @@ async fn insert_flow_modules<'c>( same_worker, )) .await?; - if !force_insert - && (modules.is_empty() || crate::worker_flow::is_simple_modules(modules, failure_module)) - { + if modules.is_empty() || crate::worker_flow::is_simple_modules(modules, failure_module) { return Ok(tx); } - - let flow_node_flow = FlowNodeFlow { - value: FlowValue { - modules: std::mem::take(modules), - failure_module: failure_module.cloned(), - same_worker, - ..Default::default() - }, - summary, - }; - let id; (tx, id) = insert_flow_node( tx, @@ -1672,7 +1657,12 @@ async fn insert_flow_modules<'c>( workspace_id, None, None, - Some(&Json(to_raw_value(&flow_node_flow))), + Some(&Json(to_raw_value(&FlowValue { + modules: std::mem::take(modules), + failure_module: failure_module.cloned(), + same_worker, + ..Default::default() + }))), None, ) .await?; @@ -1749,10 +1739,8 @@ async fn reduce_flow<'c>( workspace_id, failure_module, same_worker, - None, modules, modules_node, - false, ) .await?; } @@ -1764,10 +1752,8 @@ async fn reduce_flow<'c>( workspace_id, failure_module, same_worker, - None, &mut branch.modules, &mut branch.modules_node, - false, ) .await?; } @@ -1777,10 +1763,8 @@ async fn reduce_flow<'c>( workspace_id, failure_module, same_worker, - None, default, default_node, - false, ) .await?; } @@ -1792,28 +1776,12 @@ async fn reduce_flow<'c>( workspace_id, failure_module, same_worker, - None, &mut branch.modules, &mut branch.modules_node, - false, ) .await?; } } - AIAgent { tools, modules_node, .. } => { - tx = insert_flow_modules( - tx, - path, - workspace_id, - failure_module, - same_worker, - module.summary.clone(), // we only include summary for ai agents modules - tools, - modules_node, - true, - ) - .await?; - } _ => {} } module.value = to_raw_value(&val); From a2387505544a04675a7c9fddf2ed8c042f8bfa42 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 14:17:42 +0000 Subject: [PATCH 12/33] fix: fix job loader in public apps with jwt token --- .../src/routes/public/[workspace]/[...secret]/+page.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/public/[workspace]/[...secret]/+page.svelte b/frontend/src/routes/public/[workspace]/[...secret]/+page.svelte index bdad54f27a..df72207624 100644 --- a/frontend/src/routes/public/[workspace]/[...secret]/+page.svelte +++ b/frontend/src/routes/public/[workspace]/[...secret]/+page.svelte @@ -66,7 +66,9 @@ async function loadUser() { if (parsedSecret.jwt) { - OpenAPI.TOKEN = 'jwt_ext_' + parsedSecret.jwt + const token = 'jwt_ext_' + parsedSecret.jwt + OpenAPI.TOKEN = token + setContext<{ token?: string }>('AuthToken', { token }) jwtError = false } try { From 25e7a2ea544ab69a2f184704c6ef42fcb0623891 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 14:41:12 +0000 Subject: [PATCH 13/33] Is Password nit --- frontend/src/lib/components/StringTypeNarrowing.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/StringTypeNarrowing.svelte b/frontend/src/lib/components/StringTypeNarrowing.svelte index c2cc77dc41..a4936dcf97 100644 --- a/frontend/src/lib/components/StringTypeNarrowing.svelte +++ b/frontend/src/lib/components/StringTypeNarrowing.svelte @@ -375,10 +375,14 @@ /> {/if} - {#if kind == 'none' || kind == 'pattern'} + {#if kind == 'none' || kind == 'pattern' || kind == 'format'} { if (e.detail) { From 0fe81f5b98904e9400c35646750df5702d6d8ebe Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 15:07:59 +0000 Subject: [PATCH 14/33] chore(main): release 1.561.0 (#6838) * chore(main): release 1.561.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 13 ++++ backend/Cargo.lock | 68 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 64 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcac5dfdfa..30c724f781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.561.0](https://github.com/windmill-labs/windmill/compare/v1.560.0...v1.561.0) (2025-10-16) + + +### Features + +* ansible playbook execution git repo mode (repo viewer + UI utils) ([#6831](https://github.com/windmill-labs/windmill/issues/6831)) ([32fae7a](https://github.com/windmill-labs/windmill/commit/32fae7a10c769473c708970e18c1f8268d62183f)) + + +### Bug Fixes + +* **backend:** revert flow node opti for ai agents ([#6840](https://github.com/windmill-labs/windmill/issues/6840)) ([3b5c962](https://github.com/windmill-labs/windmill/commit/3b5c96247350b70fe947d204e9bff61f81be219c)) +* fix job loader in public apps with jwt token ([a238750](https://github.com/windmill-labs/windmill/commit/a2387505544a04675a7c9fddf2ed8c042f8bfa42)) + ## [1.560.0](https://github.com/windmill-labs/windmill/compare/v1.559.0...v1.560.0) (2025-10-15) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 50b1a4a4f1..730e4d390f 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -4831,9 +4831,9 @@ dependencies = [ [[package]] name = "dyn-stack-macros" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00140340c29b813fdf6ff2237c4407405baefc72cacbe8f1e2277a75b90e5d30" +checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" [[package]] name = "dynasm" @@ -5943,9 +5943,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305" dependencies = [ "aho-corasick", "bstr", @@ -6911,9 +6911,9 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403" dependencies = [ "crossbeam-deque", "globset", @@ -15122,7 +15122,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15182,7 +15182,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "argon2", @@ -15302,7 +15302,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.560.0" +version = "1.561.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15317,7 +15317,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.560.0" +version = "1.561.0" dependencies = [ "chrono", "serde", @@ -15330,7 +15330,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "axum", @@ -15349,7 +15349,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "async-recursion", @@ -15433,7 +15433,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.560.0" +version = "1.561.0" dependencies = [ "regex", "serde", @@ -15448,7 +15448,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "bytes", @@ -15472,7 +15472,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.560.0" +version = "1.561.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15484,7 +15484,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.560.0" +version = "1.561.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15493,7 +15493,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "lazy_static", @@ -15505,7 +15505,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "serde_json", @@ -15517,7 +15517,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "gosyn", @@ -15529,7 +15529,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "lazy_static", @@ -15541,7 +15541,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "serde_json", @@ -15553,7 +15553,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "nu-parser", @@ -15564,7 +15564,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15575,7 +15575,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15587,7 +15587,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "async-recursion", @@ -15610,7 +15610,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "lazy_static", @@ -15624,7 +15624,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15641,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "lazy_static", @@ -15655,7 +15655,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "lazy_static", @@ -15673,7 +15673,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "serde", @@ -15709,7 +15709,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "async-recursion", @@ -15742,7 +15742,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.560.0" +version = "1.561.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15752,7 +15752,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.560.0" +version = "1.561.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 04a3464307..855287e63c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.560.0" +version = "1.561.0" authors.workspace = true edition.workspace = true @@ -34,7 +34,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.560.0" +version = "1.561.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 37c46fff9a..ea799547a1 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.560.0 + version: 1.561.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 71f1371085..1ed1ed65e4 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.560.0"; +export const VERSION = "v1.561.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 0aab1e0045..5b0f7493eb 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.560.0"; +export const VERSION = "1.561.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3910920c3f..4f95518603 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.560.0", + "version": "1.561.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.560.0", + "version": "1.561.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index e90f3672c2..3fda798c10 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.560.0", + "version": "1.561.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index c970c8e1c6..c736768087 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.560.0" -wmill_pg = ">=1.560.0" +wmill = ">=1.561.0" +wmill_pg = ">=1.561.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 54d0a7d450..ff3fca7d07 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.560.0 + version: 1.561.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index ace7c69295..b698f03b85 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.560.0' + ModuleVersion = '1.561.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index d867810f7d..b02878072d 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.560.0" +version = "1.561.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 1a905f263b..807e5d7ab9 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.560.0" +version = "1.561.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 70372d564c..8aec478d53 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.560.0", + "version": "1.561.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 6e99cab791..76fd97760f 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.560.0", + "version": "1.561.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 3d75838ea8..cb4c46e580 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.560.0 +1.561.0 From f723a1fb7227ae45661fea5cf2e6f9928a39672b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 15:29:49 +0000 Subject: [PATCH 15/33] fix: add configurable timeout sse stream --- backend/windmill-api/src/jobs.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 52e3b23f06..47c3260fc1 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -6889,6 +6889,12 @@ enum JobUpdateSSEStream { Ping, } +lazy_static::lazy_static! { + pub static ref TIMEOUT_SSE_STREAM: u64 = + std::env::var("TIMEOUT_SSE_STREAM").unwrap_or("60".to_string()).parse::().unwrap_or(60); +} + + fn start_job_update_sse_stream( opt_authed: Option, opt_tokened: OptTokened, @@ -7021,7 +7027,7 @@ fn start_job_update_sse_stream( last_ping = Instant::now(); } - if start.elapsed().as_secs() > 30 { + if start.elapsed().as_secs() > *TIMEOUT_SSE_STREAM { if tx.send(JobUpdateSSEStream::Timeout).await.is_err() { tracing::warn!("Failed to send job timeout for job {job_id}"); } From d16cc56f58819af2aa4304fa8a7c29e82ad1399e Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 16:24:36 +0000 Subject: [PATCH 16/33] nit compile --- backend/windmill-worker/src/ansible_executor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index bcd058836e..395341d698 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -951,9 +951,11 @@ pub async fn handle_ansible_job( let mut secret_url = git_repo_resource.get("url").and_then(|s| s.as_str()).map(|s| s.to_string()) .ok_or(anyhow!("Failed to get url from git repo resource, please check that the resource has the correct type (git_repository)"))?; + #[cfg(feature = "enterprise")] let is_github_app = git_repo_resource.get("is_github_app").and_then(|s| s.as_bool()) .ok_or(anyhow!("Failed to get `is_github_app` field from git repo resource, please check that the resource has the correct type (git_repository)"))?; + #[cfg(feature = "enterprise")] if is_github_app { if let Connection::Sql(db) = conn { let token = get_github_app_token_internal(db, &client.token).await?; From 0c1fbc6f0d1f9ef21c11ad271edb7a16b200c1ae Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 16:28:04 +0000 Subject: [PATCH 17/33] fix compile --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 253d116503..85cd80d864 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -0f18d7155059dccebbe690162b52133d526e6fb5 \ No newline at end of file +656c0418f9959154738e3583ade5e2a80c4a4daa \ No newline at end of file From defb6c9694ac294dbf19ba5cd42ce7399ad1b9ac Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:49:35 +0200 Subject: [PATCH 18/33] feat: dependency job debouncing (#6769) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * v0 Signed-off-by: pyranota * optimize relocks * make it work with relative relative imports Signed-off-by: pyranota * use fallback Signed-off-by: pyranota * remove dbg and todos Signed-off-by: pyranota * future proof a bit Signed-off-by: pyranota * cleanup Signed-off-by: pyranota * more cleanup Signed-off-by: pyranota * remove final TODO Signed-off-by: pyranota * do not use bytemuck Signed-off-by: pyranota * optimize hashing Signed-off-by: pyranota * implementation 1 Signed-off-by: pyranota * almost v0 Signed-off-by: pyranota * v0 Signed-off-by: pyranota * add comments and use fallback Signed-off-by: pyranota * call dissolve for apps Signed-off-by: pyranota * add comms Signed-off-by: pyranota * refactor v0 (partially tested + dirty) Signed-off-by: pyranota * finishing Signed-off-by: pyranota * remove TODO Signed-off-by: pyranota * Update SQLx metadata * silence unused argument Signed-off-by: pyranota * cleanup Signed-off-by: pyranota * implement rebuild_map endpoint Signed-off-by: pyranota * update windmill api client Signed-off-by: pyranota * almost finish with tests Signed-off-by: pyranota * add proper testing Signed-off-by: pyranota * remove unused fixtures Signed-off-by: pyranota * Update SQLx metadata * partial cleanup Signed-off-by: pyranota * Update backend/windmill-worker/src/scoped_dependency_map.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update backend/windmill-common/src/scripts.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * evil doings Signed-off-by: pyranota * more cleanup * Update SQLx metadata * more cleanup Signed-off-by: pyranota * fixing CI Signed-off-by: pyranota * remove python from default features Signed-off-by: pyranota * feat: dependency job debouncing * checkpoint Signed-off-by: pyranota * more improvements Signed-off-by: pyranota * refactor: clean up dependency job debouncing implementation - Add comprehensive comments explaining the debouncing mechanism - Replace debug statements (dbg!) with proper tracing calls - Extract helper functions to reduce code duplication: - extract_to_relock_from_args() for extracting nodes/components - accumulate_debounce_stale_data() for updating stale data - Improve code readability and maintainability Co-authored-by: Pyra * cleanup Signed-off-by: pyranota * Update SQLx metadata * Update backend/windmill-common/src/jobs.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update backend/windmill-common/src/jobs.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update backend/windmill-queue/src/jobs.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * better error handling for helper Signed-off-by: pyranota * update ee-ref Signed-off-by: pyranota * test: add race condition test for dependency job debouncing - Implement test_2 for scripts to test the race condition edge case - Add comprehensive documentation comments to all test functions - Remove empty test_2 stubs for flows (not needed) - Keep test_2 stub for apps with TODO comment The race condition test simulates the scenario where a job is marked as running but debounce_key hasn't been cleaned up yet, forcing the system to create a new job while reusing the existing debounce_key. This edge case can occur due to the lack of transactions in the pull function for performance reasons (see jobs.rs:4415-4425). Co-authored-by: Pyra * test: implement race condition test for dependency job debouncing - Add comprehensive test_2 for script module that tests the race condition edge case - Remove empty test_2 stubs from flows and apps modules - Fix unused variable warning in worker_lockfiles.rs - Add detailed comments explaining the race condition scenario and test logic Co-authored-by: Pyra * implement fallback Signed-off-by: pyranota * make it mostly work * all tests are almost working Signed-off-by: pyranota * add comments a bit Signed-off-by: pyranota * Update backend/windmill-common/src/scripts.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update backend/windmill-worker/src/worker_lockfiles.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update backend/windmill-worker/src/worker_lockfiles.rs Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * feat: improve debouncing documentation, tracing, and error handling - Add comprehensive 3-phase debouncing documentation explaining push/accumulation/pull - Enhance all tracing with structured logging (job_id, workspace_id, node_count, etc.) - Add proper error handling with .map_err() and contextual messages - Replace dbg!() with proper tracing::debug!() - Replace todo!() with proper error handling - Fix typos: 'and edge case' → 'an edge case', 'bc' → 'because' - Fix debug variable name: 'debounce_job_id_0' → 'debounce_job_id_o' - Add documentation for debounce cleanup and stale data retrieval - Add trace-level logging for non-error paths to reduce noise Co-authored-by: Pyra * do some work for future improvements Signed-off-by: pyranota * fix tests Signed-off-by: pyranota * clippy Signed-off-by: pyranota * update sqlx Signed-off-by: pyranota * clippy Signed-off-by: pyranota * update ee ref Signed-off-by: pyranota * flag tests behind the feature, add timeout Signed-off-by: pyranota * fix timeout + cleanup Signed-off-by: pyranota * cleanup Signed-off-by: pyranota * row lock debounce_key Signed-off-by: pyranota * addressing TODOs Signed-off-by: pyranota * fix test Signed-off-by: pyranota * ee ref Signed-off-by: pyranota * ee repo Signed-off-by: pyranota --------- Signed-off-by: pyranota Co-authored-by: Pyra <92104930+pyranye@users.noreply.github.com> Co-authored-by: GitHub Action Co-authored-by: windmill-internal-app[bot] Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Pyra Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel --- ...d7d1a2e10342bbbc7f8486df0b73f5657a493.json | 20 + ...71dcc58cb037a59afe08cd1372b51791b4165.json | 20 + ...54334eb7375b483e8ffd80364c60c3aad04b4.json | 20 + ...dc18a4db630616b3aa80c27b54e4bb4e20f30.json | 20 + ...9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json | 20 + ...3b465b4bbf83bc86c0efacd6133bb432ee13d.json | 20 + ...2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json | 20 + ...df8567579218d5c4332b01e169732c836ebdc.json | 20 + ...dafc044d1861048857ec2fc70f929ab358373.json | 20 + ...583902596c4faed6d83e668be85aba8eb644a.json | 20 + ...8f8ca36db091c67cf9ec5e7cec40486532ab9.json | 20 + ...a626245abe58c306392a76710252d16d0bd44.json | 20 + ...46fed29a76e579e72d6c8539cd0de73b424db.json | 20 + ...73fc77421710992b10f526aa36bc64ff0930.json} | 5 +- ...a0fd360da8a9f06e0ab59d9e724851ced4247.json | 20 + ...88f9d694bc37caacef7269e3738de8b5f6013.json | 22 + ...dc17b17372b7292e39d3888a93ff4fe49e4f5.json | 14 + ...476226c66fe42c9f992bd4c6c232a68ade2bd.json | 20 + ...f5d7df9acf74de6dcc566924de02f1807af2.json} | 8 +- ...bed6cae29f361167deee879b8b683ad1bf684.json | 15 + ...4f9e3e416e1baf20bdb25c9a8ac9efc288931.json | 15 + ...a4fc58818c109deda6678c79474bf45428966.json | 22 + ...23c85a6e6490c2d9e1dc6ac39112d763f0f75.json | 23 + ...467dcabc2c3367fb33592e5608bd985c9e436.json | 26 + ...423b891b57e9a65a02b52966377c1960ab89e.json | 20 + ...0b45df00a1ae022b3c29730af2347bd76deef.json | 20 + ...3e0822cb91bde2324327f1828236112d278b1.json | 20 + ...98a5e50bc98a8360387bd4158f9bc1289319f.json | 24 - ...f231912db86c05c881aff1ffb5460f9711390.json | 20 + ...0f6929f5dcbf29f3938e40c7f93d98aa7f49c.json | 20 + ...f178ba8a1b8c1e01e0fec1dfd6a54ea7a894.json} | 4 +- ...7694378b2be15f3a40e7e6690f31157bdb5af.json | 20 + ...3eff389e0d44b7a58b69fd5c79629599902fc.json | 22 + ...4d21d38edb827d2c08b0ca7d72311b78d574.json} | 8 +- ...24d3b06170d3a02a06a4c209e49e5ef175916.json | 20 + ...4d0ab538043fff345a0438a40708c4066771b.json | 20 + ...928e1be4696120c5d78c1649d5e430a5dae4c.json | 20 + ...a0854975ad4c3f6fe24557b87a197485dff39.json | 20 + ...f97f2301599a39668fcd20e1642fd828e3eec.json | 23 + ...2680159db11c8aeef82056ec30b498f8129da.json | 20 + ...768ab79733e33bf8f9110a4f4d75a3c07da67.json | 20 + ...39ae72faf0c8fe097e6ad6d309aee9a8aede2.json | 24 + ...5fe3964efb8551ad6d74f141fc457034cc5b9.json | 22 + backend/Cargo.toml | 1 + backend/ee-repo-ref.txt | 2 +- .../20250925142554_job_debouncing.down.sql | 3 + .../20250925142554_job_debouncing.up.sql | 19 + backend/src/monitor.rs | 2 + backend/tests/common/mod.rs | 60 +- backend/tests/fixtures/djob_debouncing.sql | 242 ++ backend/tests/job_payload.rs | 1 + backend/tests/relative_imports.rs | 1981 ++++++++++++++++- backend/tests/worker.rs | 21 +- backend/windmill-api/src/apps.rs | 13 +- backend/windmill-api/src/flows.rs | 15 +- backend/windmill-api/src/jobs.rs | 15 + backend/windmill-api/src/scripts.rs | 10 + .../src/triggers/trigger_helpers.rs | 1 + backend/windmill-common/src/error.rs | 6 + backend/windmill-common/src/jobs.rs | 48 +- backend/windmill-common/src/scripts.rs | 103 +- backend/windmill-queue/src/jobs.rs | 595 ++++- backend/windmill-queue/src/schedule.rs | 57 +- backend/windmill-worker/src/ai_executor.rs | 1 + backend/windmill-worker/src/worker.rs | 60 +- backend/windmill-worker/src/worker_flow.rs | 1 + .../windmill-worker/src/worker_lockfiles.rs | 526 ++--- 67 files changed, 4061 insertions(+), 539 deletions(-) create mode 100644 backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json create mode 100644 backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json create mode 100644 backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json create mode 100644 backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json create mode 100644 backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json create mode 100644 backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json create mode 100644 backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json create mode 100644 backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json create mode 100644 backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json create mode 100644 backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json create mode 100644 backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json create mode 100644 backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json create mode 100644 backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json rename backend/.sqlx/{query-b5fbd7893950610f1285662df24f438c9855ba860e23befd88c2544ef86e9133.json => query-69550451b86f221a3d2ef626be7073fc77421710992b10f526aa36bc64ff0930.json} (60%) create mode 100644 backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json create mode 100644 backend/.sqlx/query-73fcf81d272c1613e094d60c0a088f9d694bc37caacef7269e3738de8b5f6013.json create mode 100644 backend/.sqlx/query-76774e6f72c8c8b7473487e4176dc17b17372b7292e39d3888a93ff4fe49e4f5.json create mode 100644 backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json rename backend/.sqlx/{query-526bfaccaafbe2e6f70dd5e6cd21c0c60d4ec155f79d067a8b74cf24eebad88c.json => query-7bfb3b210d23f2c00a1d6a653e9df5d7df9acf74de6dcc566924de02f1807af2.json} (52%) create mode 100644 backend/.sqlx/query-7ec724b84479c2f737637e91b8cbed6cae29f361167deee879b8b683ad1bf684.json create mode 100644 backend/.sqlx/query-7ed404f3a8b23f98fb7c15a26b14f9e3e416e1baf20bdb25c9a8ac9efc288931.json create mode 100644 backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json create mode 100644 backend/.sqlx/query-99e11c04bcc436ec7a75f46365423c85a6e6490c2d9e1dc6ac39112d763f0f75.json create mode 100644 backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json create mode 100644 backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json create mode 100644 backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json create mode 100644 backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json delete mode 100644 backend/.sqlx/query-bf252ea52aeb57664ad5054b39998a5e50bc98a8360387bd4158f9bc1289319f.json create mode 100644 backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json create mode 100644 backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json rename backend/.sqlx/{query-9f4811fe735d401b62f4b7bf3db2b5cd13eb3364a8a8546007dec7ab528b1f9d.json => query-c6ef0acdf20bd71dd26de981fb49f178ba8a1b8c1e01e0fec1dfd6a54ea7a894.json} (50%) create mode 100644 backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json create mode 100644 backend/.sqlx/query-cfe06702916362aaf5122bb95593eff389e0d44b7a58b69fd5c79629599902fc.json rename backend/.sqlx/{query-ca15fe5d43f0e94f50408efe5c9e359770b759e8661687b4503c4b692ecd245e.json => query-d5661c7557cf3a8dee7cf799cd364d21d38edb827d2c08b0ca7d72311b78d574.json} (50%) create mode 100644 backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json create mode 100644 backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json create mode 100644 backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json create mode 100644 backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json create mode 100644 backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json create mode 100644 backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json create mode 100644 backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json create mode 100644 backend/.sqlx/query-f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2.json create mode 100644 backend/.sqlx/query-fe1539db7384c8edc6d8ec672495fe3964efb8551ad6d74f141fc457034cc5b9.json create mode 100644 backend/migrations/20250925142554_job_debouncing.down.sql create mode 100644 backend/migrations/20250925142554_job_debouncing.up.sql create mode 100644 backend/tests/fixtures/djob_debouncing.sql diff --git a/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json b/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json new file mode 100644 index 0000000000..2878e54920 --- /dev/null +++ b/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493" +} diff --git a/backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json b/backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json new file mode 100644 index 0000000000..2fdaa1a63d --- /dev/null +++ b/backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job_completed", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165" +} diff --git a/backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json b/backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json new file mode 100644 index 0000000000..7162b07bef --- /dev/null +++ b/backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM app_version WHERE app_id = '2'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4" +} diff --git a/backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json b/backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json new file mode 100644 index 0000000000..3d444ae09b --- /dev/null +++ b/backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT runnable_id FROM v2_job ORDER BY created_at DESC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "runnable_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true + ] + }, + "hash": "1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30" +} diff --git a/backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json b/backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json new file mode 100644 index 0000000000..fbeff75f56 --- /dev/null +++ b/backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT versions FROM flow WHERE path = 'f/dre/flow'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "versions", + "type_info": "Int8Array" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209" +} diff --git a/backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json b/backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json new file mode 100644 index 0000000000..623fab4416 --- /dev/null +++ b/backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT (scheduled_for - created_at) FROM v2_job_queue WHERE running = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Interval" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d" +} diff --git a/backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json b/backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json new file mode 100644 index 0000000000..8d828b265a --- /dev/null +++ b/backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM flow_version WHERE path = 'f/dre/flow'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc" +} diff --git a/backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json b/backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json new file mode 100644 index 0000000000..41c08565b0 --- /dev/null +++ b/backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT hash FROM script WHERE path = 'f/dre_script/script' AND archived = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hash", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc" +} diff --git a/backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json b/backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json new file mode 100644 index 0000000000..7876a6f7f0 --- /dev/null +++ b/backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM v2_job_queue WHERE running = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373" +} diff --git a/backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json b/backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json new file mode 100644 index 0000000000..d567e90159 --- /dev/null +++ b/backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) from debounce_key", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a" +} diff --git a/backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json b/backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json new file mode 100644 index 0000000000..ae9532cdee --- /dev/null +++ b/backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job_queue WHERE running = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9" +} diff --git a/backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json b/backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json new file mode 100644 index 0000000000..a7733f5403 --- /dev/null +++ b/backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT versions FROM app WHERE path = 'f/dre_app/app'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "versions", + "type_info": "Int8Array" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44" +} diff --git a/backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json b/backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json new file mode 100644 index 0000000000..a193aa5b98 --- /dev/null +++ b/backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM debounce_stale_data", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db" +} diff --git a/backend/.sqlx/query-b5fbd7893950610f1285662df24f438c9855ba860e23befd88c2544ef86e9133.json b/backend/.sqlx/query-69550451b86f221a3d2ef626be7073fc77421710992b10f526aa36bc64ff0930.json similarity index 60% rename from backend/.sqlx/query-b5fbd7893950610f1285662df24f438c9855ba860e23befd88c2544ef86e9133.json rename to backend/.sqlx/query-69550451b86f221a3d2ef626be7073fc77421710992b10f526aa36bc64ff0930.json index 766748fa60..caf6213fa2 100644 --- a/backend/.sqlx/query-b5fbd7893950610f1285662df24f438c9855ba860e23befd88c2544ef86e9133.json +++ b/backend/.sqlx/query-69550451b86f221a3d2ef626be7073fc77421710992b10f526aa36bc64ff0930.json @@ -1,17 +1,16 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, $4, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ", + "query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets)\n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets\n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ", "describe": { "columns": [], "parameters": { "Left": [ "Int8", "Int8", - "Text", "Text" ] }, "nullable": [] }, - "hash": "b5fbd7893950610f1285662df24f438c9855ba860e23befd88c2544ef86e9133" + "hash": "69550451b86f221a3d2ef626be7073fc77421710992b10f526aa36bc64ff0930" } diff --git a/backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json b/backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json new file mode 100644 index 0000000000..8239f32a08 --- /dev/null +++ b/backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) from debounce_stale_data", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247" +} diff --git a/backend/.sqlx/query-73fcf81d272c1613e094d60c0a088f9d694bc37caacef7269e3738de8b5f6013.json b/backend/.sqlx/query-73fcf81d272c1613e094d60c0a088f9d694bc37caacef7269e3738de8b5f6013.json new file mode 100644 index 0000000000..00f7ec50d2 --- /dev/null +++ b/backend/.sqlx/query-73fcf81d272c1613e094d60c0a088f9d694bc37caacef7269e3738de8b5f6013.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT key FROM debounce_key WHERE job_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "key", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false + ] + }, + "hash": "73fcf81d272c1613e094d60c0a088f9d694bc37caacef7269e3738de8b5f6013" +} diff --git a/backend/.sqlx/query-76774e6f72c8c8b7473487e4176dc17b17372b7292e39d3888a93ff4fe49e4f5.json b/backend/.sqlx/query-76774e6f72c8c8b7473487e4176dc17b17372b7292e39d3888a93ff4fe49e4f5.json new file mode 100644 index 0000000000..694ed1887f --- /dev/null +++ b/backend/.sqlx/query-76774e6f72c8c8b7473487e4176dc17b17372b7292e39d3888a93ff4fe49e4f5.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM debounce_key WHERE key = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "76774e6f72c8c8b7473487e4176dc17b17372b7292e39d3888a93ff4fe49e4f5" +} diff --git a/backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json b/backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json new file mode 100644 index 0000000000..6d3b24293f --- /dev/null +++ b/backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT hash FROM script WHERE path = 'f/dre_script/script' AND archived = true", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hash", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd" +} diff --git a/backend/.sqlx/query-526bfaccaafbe2e6f70dd5e6cd21c0c60d4ec155f79d067a8b74cf24eebad88c.json b/backend/.sqlx/query-7bfb3b210d23f2c00a1d6a653e9df5d7df9acf74de6dcc566924de02f1807af2.json similarity index 52% rename from backend/.sqlx/query-526bfaccaafbe2e6f70dd5e6cd21c0c60d4ec155f79d067a8b74cf24eebad88c.json rename to backend/.sqlx/query-7bfb3b210d23f2c00a1d6a653e9df5d7df9acf74de6dcc566924de02f1807af2.json index 08e38953ab..19ec28bbe3 100644 --- a/backend/.sqlx/query-526bfaccaafbe2e6f70dd5e6cd21c0c60d4ec155f79d067a8b74cf24eebad88c.json +++ b/backend/.sqlx/query-7bfb3b210d23f2c00a1d6a653e9df5d7df9acf74de6dcc566924de02f1807af2.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT versions[array_upper(versions, 1)] FROM flow WHERE path = $1 AND workspace_id = $2", + "query": "SELECT id FROM flow_version WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1", "describe": { "columns": [ { "ordinal": 0, - "name": "versions", + "name": "id", "type_info": "Int8" } ], @@ -16,8 +16,8 @@ ] }, "nullable": [ - null + false ] }, - "hash": "526bfaccaafbe2e6f70dd5e6cd21c0c60d4ec155f79d067a8b74cf24eebad88c" + "hash": "7bfb3b210d23f2c00a1d6a653e9df5d7df9acf74de6dcc566924de02f1807af2" } diff --git a/backend/.sqlx/query-7ec724b84479c2f737637e91b8cbed6cae29f361167deee879b8b683ad1bf684.json b/backend/.sqlx/query-7ec724b84479c2f737637e91b8cbed6cae29f361167deee879b8b683ad1bf684.json new file mode 100644 index 0000000000..a979af6114 --- /dev/null +++ b/backend/.sqlx/query-7ec724b84479c2f737637e91b8cbed6cae29f361167deee879b8b683ad1bf684.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO debounce_stale_data (job_id, to_relock)\n VALUES ($1, $2)\n ON CONFLICT (job_id)\n DO UPDATE SET to_relock = (\n SELECT array_agg(DISTINCT x)\n FROM unnest(\n -- Combine existing array with new values, removing duplicates\n array_cat(debounce_stale_data.to_relock, EXCLUDED.to_relock)\n ) AS x\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "TextArray" + ] + }, + "nullable": [] + }, + "hash": "7ec724b84479c2f737637e91b8cbed6cae29f361167deee879b8b683ad1bf684" +} diff --git a/backend/.sqlx/query-7ed404f3a8b23f98fb7c15a26b14f9e3e416e1baf20bdb25c9a8ac9efc288931.json b/backend/.sqlx/query-7ed404f3a8b23f98fb7c15a26b14f9e3e416e1baf20bdb25c9a8ac9efc288931.json new file mode 100644 index 0000000000..9216f73a7e --- /dev/null +++ b/backend/.sqlx/query-7ed404f3a8b23f98fb7c15a26b14f9e3e416e1baf20bdb25c9a8ac9efc288931.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO debounce_key (key, job_id) VALUES ($1, $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "7ed404f3a8b23f98fb7c15a26b14f9e3e416e1baf20bdb25c9a8ac9efc288931" +} diff --git a/backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json b/backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json new file mode 100644 index 0000000000..d41b7811ee --- /dev/null +++ b/backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT running FROM v2_job_queue WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "running", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false + ] + }, + "hash": "91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966" +} diff --git a/backend/.sqlx/query-99e11c04bcc436ec7a75f46365423c85a6e6490c2d9e1dc6ac39112d763f0f75.json b/backend/.sqlx/query-99e11c04bcc436ec7a75f46365423c85a6e6490c2d9e1dc6ac39112d763f0f75.json new file mode 100644 index 0000000000..6fb4205e32 --- /dev/null +++ b/backend/.sqlx/query-99e11c04bcc436ec7a75f46365423c85a6e6490c2d9e1dc6ac39112d763f0f75.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM app_version WHERE app_id = (SELECT id FROM app WHERE path = $1 AND workspace_id = $2) ORDER BY created_at DESC LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "99e11c04bcc436ec7a75f46365423c85a6e6490c2d9e1dc6ac39112d763f0f75" +} diff --git a/backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json b/backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json new file mode 100644 index 0000000000..751c25d839 --- /dev/null +++ b/backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n dsd.to_relock,\n dk.key\n FROM debounce_key dk\n JOIN debounce_stale_data dsd ON dk.job_id = dsd.job_id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "to_relock", + "type_info": "TextArray" + }, + { + "ordinal": 1, + "name": "key", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true, + false + ] + }, + "hash": "9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436" +} diff --git a/backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json b/backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json new file mode 100644 index 0000000000..5b0998c259 --- /dev/null +++ b/backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT versions[1] FROM flow WHERE path = 'f/dre/flow'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "versions", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e" +} diff --git a/backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json b/backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json new file mode 100644 index 0000000000..3df9d93ef0 --- /dev/null +++ b/backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM v2_job_completed", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef" +} diff --git a/backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json b/backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json new file mode 100644 index 0000000000..cdcab1c509 --- /dev/null +++ b/backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT (scheduled_for - created_at) FROM v2_job_queue", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Interval" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1" +} diff --git a/backend/.sqlx/query-bf252ea52aeb57664ad5054b39998a5e50bc98a8360387bd4158f9bc1289319f.json b/backend/.sqlx/query-bf252ea52aeb57664ad5054b39998a5e50bc98a8360387bd4158f9bc1289319f.json deleted file mode 100644 index 97929c71e3..0000000000 --- a/backend/.sqlx/query-bf252ea52aeb57664ad5054b39998a5e50bc98a8360387bd4158f9bc1289319f.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Int8" - ] - }, - "nullable": [ - false - ] - }, - "hash": "bf252ea52aeb57664ad5054b39998a5e50bc98a8360387bd4158f9bc1289319f" -} diff --git a/backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json b/backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json new file mode 100644 index 0000000000..bfd7cd16bd --- /dev/null +++ b/backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT parent_hashes FROM script WHERE path = 'f/dre_script/script' AND archived = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "parent_hashes", + "type_info": "Int8Array" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true + ] + }, + "hash": "bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390" +} diff --git a/backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json b/backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json new file mode 100644 index 0000000000..ddc6263aff --- /dev/null +++ b/backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT runnable_path FROM v2_job", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "runnable_path", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true + ] + }, + "hash": "c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c" +} diff --git a/backend/.sqlx/query-9f4811fe735d401b62f4b7bf3db2b5cd13eb3364a8a8546007dec7ab528b1f9d.json b/backend/.sqlx/query-c6ef0acdf20bd71dd26de981fb49f178ba8a1b8c1e01e0fec1dfd6a54ea7a894.json similarity index 50% rename from backend/.sqlx/query-9f4811fe735d401b62f4b7bf3db2b5cd13eb3364a8a8546007dec7ab528b1f9d.json rename to backend/.sqlx/query-c6ef0acdf20bd71dd26de981fb49f178ba8a1b8c1e01e0fec1dfd6a54ea7a894.json index 4c9693ce27..0bd82635da 100644 --- a/backend/.sqlx/query-9f4811fe735d401b62f4b7bf3db2b5cd13eb3364a8a8546007dec7ab528b1f9d.json +++ b/backend/.sqlx/query-c6ef0acdf20bd71dd26de981fb49f178ba8a1b8c1e01e0fec1dfd6a54ea7a894.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\nWITH rows_to_delete AS (\n SELECT concurrency_id\n FROM concurrency_counter\n WHERE job_uuids = '{}'::jsonb\n FOR UPDATE SKIP LOCKED\n)\nDELETE FROM concurrency_counter\nWHERE concurrency_id IN (SELECT concurrency_id FROM rows_to_delete) RETURNING concurrency_id", + "query": "\nWITH rows_to_delete AS (\n SELECT concurrency_id\n FROM concurrency_counter\n \n WHERE job_uuids = '{}'::jsonb\n FOR UPDATE SKIP LOCKED\n)\nDELETE FROM concurrency_counter\nWHERE concurrency_id IN (SELECT concurrency_id FROM rows_to_delete) RETURNING concurrency_id", "describe": { "columns": [ { @@ -16,5 +16,5 @@ false ] }, - "hash": "9f4811fe735d401b62f4b7bf3db2b5cd13eb3364a8a8546007dec7ab528b1f9d" + "hash": "c6ef0acdf20bd71dd26de981fb49f178ba8a1b8c1e01e0fec1dfd6a54ea7a894" } diff --git a/backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json b/backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json new file mode 100644 index 0000000000..4c4921e2a0 --- /dev/null +++ b/backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT key FROM debounce_key", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "key", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af" +} diff --git a/backend/.sqlx/query-cfe06702916362aaf5122bb95593eff389e0d44b7a58b69fd5c79629599902fc.json b/backend/.sqlx/query-cfe06702916362aaf5122bb95593eff389e0d44b7a58b69fd5c79629599902fc.json new file mode 100644 index 0000000000..e9cedd467d --- /dev/null +++ b/backend/.sqlx/query-cfe06702916362aaf5122bb95593eff389e0d44b7a58b69fd5c79629599902fc.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "to_relock", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + true + ] + }, + "hash": "cfe06702916362aaf5122bb95593eff389e0d44b7a58b69fd5c79629599902fc" +} diff --git a/backend/.sqlx/query-ca15fe5d43f0e94f50408efe5c9e359770b759e8661687b4503c4b692ecd245e.json b/backend/.sqlx/query-d5661c7557cf3a8dee7cf799cd364d21d38edb827d2c08b0ca7d72311b78d574.json similarity index 50% rename from backend/.sqlx/query-ca15fe5d43f0e94f50408efe5c9e359770b759e8661687b4503c4b692ecd245e.json rename to backend/.sqlx/query-d5661c7557cf3a8dee7cf799cd364d21d38edb827d2c08b0ca7d72311b78d574.json index 6812bfdfc7..907b140fdd 100644 --- a/backend/.sqlx/query-ca15fe5d43f0e94f50408efe5c9e359770b759e8661687b4503c4b692ecd245e.json +++ b/backend/.sqlx/query-d5661c7557cf3a8dee7cf799cd364d21d38edb827d2c08b0ca7d72311b78d574.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT versions[array_upper(versions, 1)] FROM app WHERE path = $1 AND workspace_id = $2", + "query": "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2 AND deleted = false ORDER BY created_at DESC LIMIT 1", "describe": { "columns": [ { "ordinal": 0, - "name": "versions", + "name": "hash", "type_info": "Int8" } ], @@ -16,8 +16,8 @@ ] }, "nullable": [ - null + false ] }, - "hash": "ca15fe5d43f0e94f50408efe5c9e359770b759e8661687b4503c4b692ecd245e" + "hash": "d5661c7557cf3a8dee7cf799cd364d21d38edb827d2c08b0ca7d72311b78d574" } diff --git a/backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json b/backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json new file mode 100644 index 0000000000..b69e04115f --- /dev/null +++ b/backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT lock FROM script WHERE path = 'f/dre_script/script'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "lock", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true + ] + }, + "hash": "d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916" +} diff --git a/backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json b/backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json new file mode 100644 index 0000000000..99c0aa49ad --- /dev/null +++ b/backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT versions[2] FROM flow WHERE path = 'f/dre/flow'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "versions", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b" +} diff --git a/backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json b/backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json new file mode 100644 index 0000000000..cd4130b542 --- /dev/null +++ b/backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT jsonb_array_elements(value->'modules')->'value'->>'lock' AS lock FROM flow", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "lock", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c" +} diff --git a/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json b/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json new file mode 100644 index 0000000000..8e8a5c98e9 --- /dev/null +++ b/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job_queue", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39" +} diff --git a/backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json b/backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json new file mode 100644 index 0000000000..e866bd4001 --- /dev/null +++ b/backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT\n j1.completed_at < j2.started_at\nFROM\n v2_job_completed j1,\n v2_job_completed j2\nWHERE\n j1.id = $1 \n AND j2.id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "?column?", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Uuid" + ] + }, + "nullable": [ + null + ] + }, + "hash": "eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec" +} diff --git a/backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json b/backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json new file mode 100644 index 0000000000..0e1f9620e0 --- /dev/null +++ b/backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) from v2_job_queue", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da" +} diff --git a/backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json b/backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json new file mode 100644 index 0000000000..6a48df6e2b --- /dev/null +++ b/backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM debounce_key", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67" +} diff --git a/backend/.sqlx/query-f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2.json b/backend/.sqlx/query-f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2.json new file mode 100644 index 0000000000..32616f298f --- /dev/null +++ b/backend/.sqlx/query-f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO flow_version\n (workspace_id, path, value, schema, created_by)\n\n SELECT workspace_id, path, value, schema, created_by\n FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3\n\n RETURNING id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Int8" + ] + }, + "nullable": [ + false + ] + }, + "hash": "f0efa383f2025158de160577ad839ae72faf0c8fe097e6ad6d309aee9a8aede2" +} diff --git a/backend/.sqlx/query-fe1539db7384c8edc6d8ec672495fe3964efb8551ad6d74f141fc457034cc5b9.json b/backend/.sqlx/query-fe1539db7384c8edc6d8ec672495fe3964efb8551ad6d74f141fc457034cc5b9.json new file mode 100644 index 0000000000..7b7e250ef7 --- /dev/null +++ b/backend/.sqlx/query-fe1539db7384c8edc6d8ec672495fe3964efb8551ad6d74f141fc457034cc5b9.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT job_id FROM debounce_key WHERE key = $1 FOR UPDATE", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "job_id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "fe1539db7384c8edc6d8ec672495fe3964efb8551ad6d74f141fc457034cc5b9" +} diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 855287e63c..58ecd617dd 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -86,6 +86,7 @@ oauth2 = ["windmill-api/oauth2"] zip = ["windmill-api/zip"] static_frontend = ["windmill-api/static_frontend"] scoped_cache = ["windmill-common/scoped_cache"] +test_job_debouncing = [] # Languages python = ["windmill-worker/python", "windmill-api/python"] rust = ["windmill-worker/rust"] diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 85cd80d864..bf77fc7312 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -656c0418f9959154738e3583ade5e2a80c4a4daa \ No newline at end of file +365a3c9dd7a3fddc3280bbda86ba93ef149d1b64 diff --git a/backend/migrations/20250925142554_job_debouncing.down.sql b/backend/migrations/20250925142554_job_debouncing.down.sql new file mode 100644 index 0000000000..fd1c155f43 --- /dev/null +++ b/backend/migrations/20250925142554_job_debouncing.down.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS debounce_key; +DROP TABLE IF EXISTS debounce_stale_data; +DROP TABLE IF EXISTS debounce_obj_latest_version; diff --git a/backend/migrations/20250925142554_job_debouncing.up.sql b/backend/migrations/20250925142554_job_debouncing.up.sql new file mode 100644 index 0000000000..0c946aaaf5 --- /dev/null +++ b/backend/migrations/20250925142554_job_debouncing.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE debounce_key ( + key VARCHAR(255) NOT NULL, + job_id uuid NOT NULL, + PRIMARY KEY (key) +); + +CREATE TABLE debounce_stale_data ( + job_id uuid NOT NULL, + to_relock TEXT[], + PRIMARY KEY (job_id) +); + +-- TODO: Prune on move/deletion +-- But normally this will persist across runs. +-- CREATE TABLE unlocked_script_latest_version ( +-- key VARCHAR(255) NOT NULL, +-- version BIGINT NOT NULL, +-- PRIMARY KEY (key) +-- ); diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index e4d3c380af..3e6ddec903 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -1588,6 +1588,7 @@ pub async fn monitor_db( } } }; + // run every hour (60 minutes / 30 seconds = 120) let cleanup_worker_group_stats_f = async { if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(120) { @@ -2415,6 +2416,7 @@ async fn cleanup_concurrency_counters_empty_keys(db: &DB) -> error::Result<()> { WITH rows_to_delete AS ( SELECT concurrency_id FROM concurrency_counter + WHERE job_uuids = '{}'::jsonb FOR UPDATE SKIP LOCKED ) diff --git a/backend/tests/common/mod.rs b/backend/tests/common/mod.rs index 026eee12f7..73db6d1f62 100644 --- a/backend/tests/common/mod.rs +++ b/backend/tests/common/mod.rs @@ -20,6 +20,17 @@ use windmill_common::{ }; use windmill_queue::PushIsolationLevel; +pub async fn init_client(db: Pool) -> (windmill_api_client::Client, u16, ApiServer) { + initialize_tracing().await; + let server = ApiServer::start(db).await.unwrap(); + let port = server.addr.port(); + let client = windmill_api_client::create_client( + &format!("http://localhost:{port}"), + "SECRET_TOKEN".to_string(), + ); + (client, port, server) +} + /// it's important this is unique between tests as there is one prometheus registry and /// run_worker shouldn't register the same metric with the same worker name more than once. /// @@ -131,7 +142,7 @@ impl RunJob { let tx = PushIsolationLevel::IsolatedRoot(db.clone()); let (uuid, tx) = windmill_queue::push( - &db, + db, tx, "test-workspace", payload, @@ -157,6 +168,7 @@ impl RunJob { None, false, None, + None, ) .await .expect("push has to succeed"); @@ -170,8 +182,8 @@ impl RunJob { let uuid = self.push(db).await; let listener = listen_for_completed_jobs(db).await; in_test_worker(db, listener.find(&uuid), port).await; - let r = completed_job(uuid, db).await; - r + + completed_job(uuid, db).await } /// push the job, spawn a worker, wait until the job is in completed_job @@ -185,8 +197,8 @@ impl RunJob { let listener = listen_for_completed_jobs(db).await; test(uuid).await; in_test_worker(db, listener.find(&uuid), port).await; - let r = completed_job(uuid, db).await; - r + + completed_job(uuid, db).await } } @@ -251,10 +263,10 @@ pub fn spawn_test_worker( let base_internal_url = format!("http://localhost:{}", port); { let mut wc = WORKER_CONFIG.write().await; - (*wc).worker_tags = windmill_common::worker::DEFAULT_TAGS.clone(); - (*wc).priority_tags_sorted = vec![windmill_common::worker::PriorityTags { + wc.worker_tags = windmill_common::worker::DEFAULT_TAGS.clone(); + wc.priority_tags_sorted = vec![windmill_common::worker::PriorityTags { priority: 0, - tags: (*wc).worker_tags.clone(), + tags: wc.worker_tags.clone(), }]; windmill_common::worker::store_suspended_pull_query(&wc).await; windmill_common::worker::store_pull_query(&wc).await; @@ -349,7 +361,7 @@ fn find_module_in_vec(modules: Vec, id: &str) -> Option () { +pub async fn set_jwt_secret() { let secret = "mytestsecret".to_string(); let mut l = JWT_SECRET.write().await; *l = secret; @@ -475,10 +487,10 @@ pub async fn assert_lockfile( .await .unwrap(); - let mut completed = listen_for_completed_jobs(&db).await; + let mut completed = listen_for_completed_jobs(db).await; let db2 = db.clone(); in_test_worker( - &db, + db, async move { completed.next().await; // deployed script @@ -571,10 +583,10 @@ pub async fn run_deployed_relative_imports( .await .unwrap(); - let mut completed = listen_for_completed_jobs(&db).await; + let mut completed = listen_for_completed_jobs(db).await; let db2 = db.clone(); in_test_worker( - &db, + db, async move { completed.next().await; // deployed script @@ -631,10 +643,10 @@ pub async fn run_preview_relative_imports( let server = ApiServer::start(db.clone()).await?; let port = server.addr.port(); - let mut completed = listen_for_completed_jobs(&db).await; + let mut completed = listen_for_completed_jobs(db).await; let db2 = db.clone(); in_test_worker( - &db, + db, async move { let job = RunJob::from(JobPayload::Code(RawCode { hash: None, @@ -671,3 +683,21 @@ pub async fn run_preview_relative_imports( Ok(()) } + +/// IMPORTANT!: +/// Do not run parallel in tests! +/// +/// No tests can run this at the same time, will result into conflicts!!! +pub async fn rebuild_dmap(client: &windmill_api_client::Client) -> bool { + client + .client() + .post(format!( + "{}/w/test-workspace/workspaces/rebuild_dependency_map", + client.baseurl() + )) + .send() + .await + .unwrap() + .status() + .is_success() +} diff --git a/backend/tests/fixtures/djob_debouncing.sql b/backend/tests/fixtures/djob_debouncing.sql new file mode 100644 index 0000000000..067d46c398 --- /dev/null +++ b/backend/tests/fixtures/djob_debouncing.sql @@ -0,0 +1,242 @@ +-- FLOWS -- +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +'#requirements: +#bottle==0.13.2 +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre/leaf_left', 333400, 'python3', ''); +-- Padded Hex: 0000000000051658 + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +'#requirements: +#tiny==0.1.3 +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre/leaf_right', 333403, 'python3', ''); +-- Padded Hex: 000000000005165B + + +INSERT INTO public.flow(workspace_id, summary, description, path, versions, schema, value, edited_by) VALUES ( +'test-workspace', +'', +'', +'f/dre/flow', +'{1443253234253454}', +'{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {}, + "required": [], + "type": "object" +}', +$tag$ +{ + "modules": [ + { + "id": "a", + "value": { + "lock": "# py: 3.11\n", + "type": "rawscript", + "assets": [], + "content": "import f.dre.leaf_left\n\ndef main():\n pass", + "language": "python3", + "input_transforms": {} + }, + "summary": "leaf Left" + }, + { + "id": "b", + "value": { + "lock": "# py: 3.11\n", + "type": "rawscript", + "assets": [], + "content": "import f.dre.leaf_right\nimport f.dre.leaf_left\n\ndef main():\n pass", + "language": "python3", + "input_transforms": {} + }, + "summary": "leaf Left and Right" + }, + { + "id": "c", + "value": { + "lock": "# py: 3.11\n", + "type": "rawscript", + "assets": [], + "content": "import f.dre.leaf_right\n\ndef main():\n pass", + "language": "python3", + "input_transforms": {} + }, + "summary": "leaf RIght" + } + ] +}$tag$, +'system' +); + +INSERT INTO public.flow_version(id, workspace_id, path, schema, value, created_by) VALUES ( +1443253234253454, +'test-workspace', +'f/dre/flow', +'{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {}, + "required": [], + "type": "object" +}', +$tag$ +{ + "modules": [ + { + "id": "a", + "value": { + "lock": "# py: 3.11\n", + "type": "rawscript", + "assets": [], + "content": "import f.dre.leaf_left\n\ndef main():\n pass", + "language": "python3", + "input_transforms": {} + }, + "summary": "leaf Left" + }, + { + "id": "b", + "value": { + "lock": "# py: 3.11\n", + "type": "rawscript", + "assets": [], + "content": "import f.dre.leaf_right\nimport f.dre.leaf_left\n\ndef main():\n pass", + "language": "python3", + "input_transforms": {} + }, + "summary": "leaf Left and Right" + }, + { + "id": "c", + "value": { + "lock": "# py: 3.11\n", + "type": "rawscript", + "assets": [], + "content": "import f.dre.leaf_right\n\ndef main():\n pass", + "language": "python3", + "input_transforms": {} + }, + "summary": "leaf RIght" + } + ] +}$tag$, +'system' +); + +-- APPS -- +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +'#requirements: +#bottle==0.13.2 +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre_app/leaf_left', 433400, 'python3', ''); +-- Padded Hex: 0000000000069CF8 + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +'#requirements: +#tiny==0.1.3 +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre_app/leaf_right', 433403, 'python3', ''); +-- Padded Hex: 0000000000069CFB + +INSERT INTO public.app(id, workspace_id, path, versions, policy) VALUES ( +2, +'test-workspace', +'f/dre_app/app', +'{0}', +'{}' +); + +INSERT INTO public.app_version(id, app_id, value, created_by) VALUES ( +0, +2, +$tag${"grid":[{"3":{"fixed":true,"x":0,"y":0,"fullHeight":false,"w":6,"h":2},"12":{"fixed":true,"x":0,"y":0,"fullHeight":false,"w":12,"h":2},"data":{"type":"containercomponent","configuration":{},"customCss":{"container":{"class":"!p-0","style":""}},"numberOfSubgrids":1,"id":"topbar"},"id":"topbar"},{"3":{"fixed":false,"x":0,"y":2,"fullHeight":false,"w":1,"h":1},"12":{"fixed":false,"x":0,"y":2,"fullHeight":false,"w":2,"h":1},"data":{"type":"buttoncomponent","configuration":{"label":{"type":"static","value":"A"},"color":{"type":"static","value":"blue"},"size":{"type":"static","value":"xs"},"fillContainer":{"type":"static","value":false},"disabled":{"type":"static","value":false},"beforeIcon":{"type":"static"},"afterIcon":{"type":"static"},"tooltip":{"type":"static","value":""},"triggerOnAppLoad":{"type":"static","value":false},"runInBackground":{"type":"static","value":false},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"setTab":{"setTab":{"type":"static","value":[]}},"sendToast":{"message":{"type":"static","value":""}},"openModal":{"modalId":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}}}},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"errorOverlay":{},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"setTab":{"setTab":{"type":"static","value":[]}},"sendErrorToast":{"message":{"type":"static","value":"An error occurred"},"appendError":{"type":"static","value":true}},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}}}},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fieldType":"any","fields":{},"runnable":{"type":"runnableByName","name":"Inline Script","inlineScript":{"content":"import f.dre_app.leaf_left\n\ndef main():\n pass\n","language":"python3","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"},"path":"f/dre_app/app/Inline_Script"}},"autoRefresh":false,"recomputeOnInputChanged":false},"customCss":{"button":{"style":"","class":""},"container":{"style":"","class":""}},"recomputeIds":[],"horizontalAlignment":"center","verticalAlignment":"center","id":"a"},"id":"a"},{"3":{"fixed":false,"x":1,"y":2,"fullHeight":false,"w":1,"h":1},"12":{"fixed":false,"x":2,"y":2,"fullHeight":false,"w":2,"h":1},"data":{"type":"buttoncomponent","configuration":{"label":{"type":"static","value":"B"},"color":{"type":"static","value":"blue"},"size":{"type":"static","value":"xs"},"fillContainer":{"type":"static","value":false},"disabled":{"type":"static","value":false},"beforeIcon":{"type":"static"},"afterIcon":{"type":"static"},"tooltip":{"type":"static","value":""},"triggerOnAppLoad":{"type":"static","value":false},"runInBackground":{"type":"static","value":false},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"setTab":{"setTab":{"type":"static","value":[]}},"sendToast":{"message":{"type":"static","value":""}},"openModal":{"modalId":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}}}},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"errorOverlay":{},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"setTab":{"setTab":{"type":"static","value":[]}},"sendErrorToast":{"message":{"type":"static","value":"An error occurred"},"appendError":{"type":"static","value":true}},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}}}},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fieldType":"any","fields":{},"runnable":{"type":"runnableByName","name":"Inline Script","inlineScript":{"content":"import f.dre_app.leaf_left\nimport f.dre_app.leaf_right\n\ndef main():\n pass\n","language":"python3","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"},"path":"f/dre_app/app/Inline_Script"}},"autoRefresh":false,"recomputeOnInputChanged":false},"customCss":{"button":{"style":"","class":""},"container":{"style":"","class":""}},"recomputeIds":[],"horizontalAlignment":"center","verticalAlignment":"center","id":"b"},"id":"b"},{"3":{"fixed":false,"x":2,"y":2,"fullHeight":false,"w":1,"h":1},"12":{"fixed":false,"x":4,"y":2,"fullHeight":false,"w":2,"h":1},"data":{"type":"buttoncomponent","configuration":{"label":{"type":"static","value":"C"},"color":{"type":"static","value":"blue"},"size":{"type":"static","value":"xs"},"fillContainer":{"type":"static","value":false},"disabled":{"type":"static","value":false},"beforeIcon":{"type":"static"},"afterIcon":{"type":"static"},"tooltip":{"type":"static","value":""},"triggerOnAppLoad":{"type":"static","value":false},"runInBackground":{"type":"static","value":false},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"setTab":{"setTab":{"type":"static","value":[]}},"sendToast":{"message":{"type":"static","value":""}},"openModal":{"modalId":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}}}},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"errorOverlay":{},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"setTab":{"setTab":{"type":"static","value":[]}},"sendErrorToast":{"message":{"type":"static","value":"An error occurred"},"appendError":{"type":"static","value":true}},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}}}},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fieldType":"any","fields":{},"runnable":{"type":"runnableByName","name":"Inline Script","inlineScript":{"content":"import f.dre_app.leaf_right\n\ndef main():\n pass\n","language":"python3","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"},"path":"f/dre_app/app/Inline_Script"}},"autoRefresh":false,"recomputeOnInputChanged":false},"customCss":{"button":{"style":"","class":""},"container":{"style":"","class":""}},"recomputeIds":[],"horizontalAlignment":"center","verticalAlignment":"center","id":"c"},"id":"c"}],"fullscreen":false,"unusedInlineScripts":[],"hiddenInlineScripts":[],"theme":{"type":"path","path":"f/app_themes/theme_0"},"subgrids":{"topbar-0":[{"3":{"fixed":false,"x":0,"y":0,"fullHeight":false,"w":6,"h":1},"12":{"fixed":false,"x":0,"y":0,"fullHeight":false,"w":6,"h":1},"data":{"type":"textcomponent","configuration":{"style":{"type":"static","value":"Body"},"copyButton":{"type":"static","value":false},"tooltip":{"type":"evalv2","value":"","fieldType":"text","expr":"`Author: ${ctx.author}`","connections":[{"componentId":"ctx","id":"author"}]},"disableNoText":{"type":"static","value":true,"fieldType":"boolean"}},"componentInput":{"type":"templatev2","fieldType":"template","eval":"${ctx.summary}","connections":[{"id":"summary","componentId":"ctx"}]},"customCss":{"text":{"class":"text-xl font-semibold whitespace-nowrap truncate","style":""},"container":{"class":"","style":""}},"horizontalAlignment":"left","verticalAlignment":"center","id":"title"},"id":"title"},{"3":{"fixed":false,"x":0,"y":1,"fullHeight":false,"w":3,"h":1},"12":{"fixed":false,"x":6,"y":0,"fullHeight":false,"w":6,"h":1},"data":{"type":"recomputeallcomponent","configuration":{"defaultRefreshInterval":{"type":"static","value":"0"}},"customCss":{"container":{"style":"","class":""}},"menuItems":[],"horizontalAlignment":"right","verticalAlignment":"center","id":"recomputeall"},"id":"recomputeall"}]},"hideLegacyTopBar":true,"mobileViewOnSmallerScreens":false}$tag$, +'system' +); + +-- SCRIPTS -- +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +'#requirements: +#bottle==0.13.2 +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre_script/leaf_left', 533400, 'python3', ''); +-- Padded Hex: 0000000000082398 + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +'#requirements: +#tiny==0.1.3 +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre_script/leaf_right', 533403, 'python3', ''); +-- Padded Hex: 000000000008239B + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +import f.dre_script.leaf_left +import f.dre_script.leaf_right + +def main(): + pass +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/dre_script/script', 533404, 'python3', ''); +-- Padded Hex: 000000000008239C + +-- Create dependency map +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre/leaf_left', 'flow', 'f/dre/flow', 'a'); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre/leaf_left', 'flow', 'f/dre/flow', 'b'); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre/leaf_right', 'flow', 'f/dre/flow', 'b'); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre/leaf_right', 'flow', 'f/dre/flow', 'c'); + +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre_app/leaf_left', 'app', 'f/dre_app/app', 'a'); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre_app/leaf_left', 'app', 'f/dre_app/app', 'b'); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre_app/leaf_right', 'app', 'f/dre_app/app', 'b'); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre_app/leaf_right', 'app', 'f/dre_app/app', 'c'); + +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre_script/leaf_left', 'script', 'f/dre_script/script', ''); +INSERT INTO dependency_map (workspace_id, imported_path, importer_kind, importer_path, importer_node_id) VALUES ('test-workspace', 'f/dre_script/leaf_right', 'script', 'f/dre_script/script', ''); diff --git a/backend/tests/job_payload.rs b/backend/tests/job_payload.rs index d01064d4bc..ace3e61ef0 100644 --- a/backend/tests/job_payload.rs +++ b/backend/tests/job_payload.rs @@ -10,6 +10,7 @@ mod job_payload { use windmill_common::jobs::JobPayload; use windmill_common::flows::{FlowValue, FlowModule, FlowModuleValue}; use windmill_common::flow_status::RestartedFrom; + use windmill_common::worker::{ MIN_VERSION_IS_AT_LEAST_1_427, MIN_VERSION_IS_AT_LEAST_1_432, MIN_VERSION_IS_AT_LEAST_1_440, }; diff --git a/backend/tests/relative_imports.rs b/backend/tests/relative_imports.rs index 91af80918b..b088241f5a 100644 --- a/backend/tests/relative_imports.rs +++ b/backend/tests/relative_imports.rs @@ -1,48 +1,57 @@ // TODO: move all related logic here (if anything left anywhere in codebase) mod common; +use windmill_api_client::types::NewScript; + +fn quick_ns( + content: &str, + language: windmill_api_client::types::ScriptLang, + path: &str, + lock: Option, + parent_hash: Option, +) -> NewScript { + NewScript { + content: content.into(), + language, + lock, + parent_hash, + path: path.into(), + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + description: "".to_string(), + draft_only: None, + envs: vec![], + is_template: None, + kind: None, + summary: "".to_string(), + tag: None, + schema: std::collections::HashMap::new(), + ws_error_handler_muted: Some(false), + priority: None, + delete_after_use: None, + timeout: None, + restart_unless_cancelled: None, + deployment_message: None, + concurrency_key: None, + visible_to_runner_only: None, + no_main_func: None, + codebase: None, + has_preprocessor: None, + on_behalf_of_email: None, + assets: vec![], + } +} + mod dependency_map { + use super::quick_ns; use sqlx::{Pool, Postgres}; use tokio_stream::StreamExt; - use windmill_api_client::types::NewScript; - use crate::common::{in_test_worker, listen_for_completed_jobs, ApiServer}; - - pub async fn initialize_tracing() { - use std::sync::Once; - - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - let _ = windmill_common::tracing_init::initialize_tracing( - "test", - &windmill_common::utils::Mode::Standalone, - "test", - ); - }); - } - - async fn rebuild_dmap(client: &windmill_api_client::Client) -> bool { - client - .client() - .post(format!( - "{}/w/test-workspace/workspaces/rebuild_dependency_map", - client.baseurl() - )) - .send() - .await - .unwrap() - .status() - .is_success() - } + use crate::common::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer}; async fn init(db: Pool) -> (windmill_api_client::Client, u16, ApiServer) { - initialize_tracing().await; - let server = ApiServer::start(db).await.unwrap(); - let port = server.addr.port(); - let client = windmill_api_client::create_client( - &format!("http://localhost:{port}"), - "SECRET_TOKEN".to_string(), - ); - (client, port, server) + init_client(db).await } async fn _clear_dmap(db: &Pool) { @@ -108,47 +117,6 @@ mod dependency_map { ); } - fn quick_ns( - content: &str, - language: windmill_api_client::types::ScriptLang, - path: &str, - lock: Option, - parent_hash: Option, - ) -> NewScript { - NewScript { - content: content.into(), - language, - lock, - parent_hash, - path: path.into(), - concurrent_limit: None, - concurrency_time_window_s: None, - cache_ttl: None, - dedicated_worker: None, - description: "".to_string(), - draft_only: None, - envs: vec![], - is_template: None, - kind: None, - summary: "".to_string(), - tag: None, - schema: std::collections::HashMap::new(), - ws_error_handler_muted: Some(false), - priority: None, - delete_after_use: None, - timeout: None, - restart_unless_cancelled: None, - deployment_message: None, - concurrency_key: None, - visible_to_runner_only: None, - no_main_func: None, - codebase: None, - has_preprocessor: None, - on_behalf_of_email: None, - assets: vec![], - } - } - lazy_static::lazy_static! { pub static ref CORRECT_DMAP: Vec<(&'static str, &'static str, &'static str, &'static str)> = vec![ ("f/rel/branch", "script", "f/rel/leaf_1", ""), @@ -170,13 +138,16 @@ mod dependency_map { ("f/rel/root_app", "app", "f/rel/branch", "youcanpressme")]; } + // TODO: + // Test that checks that we can run rebuild_dmap multiple times in tests. + #[cfg(feature = "python")] #[sqlx::test(fixtures("base", "dependency_map"))] async fn relative_imports_test_rebuild_correctness(db: Pool) -> anyhow::Result<()> { let (client, _port, _s) = init(db.clone()).await; assert_dmap(&db, None, CORRECT_DMAP.clone()).await; // rebuild map - assert!(rebuild_dmap(&client).await); + assert!(super::common::rebuild_dmap(&client).await); assert_dmap(&db, None, CORRECT_DMAP.clone()).await; Ok(()) } @@ -189,7 +160,7 @@ mod dependency_map { // Spawn first rebuild let handle = { let client = client.clone(); - tokio::spawn(async move { rebuild_dmap(&client).await }) + tokio::spawn(async move { super::common::rebuild_dmap(&client).await }) }; // Immidiately spawn another @@ -235,7 +206,7 @@ def main(): ", windmill_api_client::types::ScriptLang::Python3, "f/rel/root_script", - Some(format!("# from requirements.txt")), + Some("# from requirements.txt".to_string()), Some("000000000005165B".into()), ), ) @@ -287,14 +258,14 @@ def main(): windmill_api_client::types::ScriptLang::Python3, "f/rel/root_script", // We still want to pass lock to it. - Some(format!("# py311")), + Some("# py311".to_string()), Some("000000000005165B".into()), ), ) .await .unwrap(); assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - tokio::time::sleep(std::time::Duration::from_secs(13)).await; + // tokio::time::sleep(std::time::Duration::from_secs(13)).await; assert_dmap(&db, None, CORRECT_DMAP.clone()).await; Ok(()) } @@ -547,3 +518,1847 @@ def main(): Ok(()) } } + +#[cfg(feature = "test_job_debouncing")] +mod job_debouncing { + async fn trigger_djob_for( + client: &windmill_api_client::Client, + path: &str, + parent_hash: &str, + content: Option, + ) { + use super::quick_ns; + use windmill_api_client::types::ScriptLang; + client + .create_script( + "test-workspace", + &quick_ns( + &content.unwrap_or( + " +def main(): + pass + " + .into(), + ), + ScriptLang::Python3, + path, + None, + Some(parent_hash.into()), + ), + ) + .await + .unwrap(); + } + // TODO: test workspaces specific things, + + /// # Double referenced even + /// It follows this topology: + /// + /// ┌─FLOW──────────┐ + /// │┌───┐┌───┐┌───┐│ + /// ││ A ││ B ││ C ││ + /// │└─▲─┘▲───▲└─▲─┘│ + /// └──┼──┼───┼──┼──┘ + /// ┌┴──┴┐ ┌┴──┴┐ + /// │L_LF│ │R_LF│ + /// └────┘ └────┘ + /// + /// p.s: "LF" stands for "Leaf", "L" - "Left", "R" - "Right" + mod flows { + use crate::common::{in_test_worker, init_client, listen_for_completed_jobs}; + use crate::job_debouncing::trigger_djob_for; + use std::time::Duration; + use tokio::time::sleep; + use tokio_stream::StreamExt; + + /// 1. LLF and RLF create two djobs for flow at the same and fall into single debounce + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_1(db: sqlx::Pool) -> anyhow::Result<()> { + // This tests if debouncing and consolidation works. + // Also makes sures that dependency job does not create new flow version + + let (client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // Verify locks are empty + { + assert_eq!( + sqlx::query_scalar!("SELECT jsonb_array_elements(value->'modules')->'value'->>'lock' AS lock FROM flow") + .fetch_all(&db) + .await + .unwrap(), + vec![ + Some("# py: 3.11\n".into()), + Some("# py: 3.11\n".into()), + Some("# py: 3.11\n".into()) + ] + ); + } + + // Trigger both at the same time. + { + trigger_djob_for( + &client, + "f/dre/leaf_left", + "0000000000051658", + Some("#requirements:\n#bottle==0.13.2\ndef main():\npass".into()), + ) + .await; + + trigger_djob_for( + &client, + "f/dre/leaf_right", + "000000000005165B", + Some("#requirements:\n#tiny==0.1.3\ndef main():\npass".into()), + ) + .await; + } + + in_test_worker( + &db, + async { + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre/leaf_left" + ); + + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre/leaf_right" + ); + + // Let jobs propagate + sleep(Duration::from_secs(2)).await; + + // Verify there is only one queued job that is scheduled for atleast 3s ahead. + { + let q = sqlx::query_scalar!( + "SELECT (scheduled_for - created_at) FROM v2_job_queue" + ) + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(1, q.len()); + assert!(dbg!(q[0].unwrap().microseconds) > 1_000_000 /* 1 second */); + } + + // Verify debounce_stale_data and debounce_key + { + let q = sqlx::query!( + "SELECT + dsd.to_relock, + dk.key + FROM debounce_key dk + JOIN debounce_stale_data dsd ON dk.job_id = dsd.job_id" + ) + .fetch_all(&db) + .await + .unwrap(); + + // Should be single entry + assert!(q.len() == 1); + + // This verifies that all nodes_to_relock are consolidated correctly + // AND there is no doublicats + assert_eq!( + q[0].to_relock.clone().unwrap(), + vec!["a".to_owned(), "b".to_owned(), "c".to_owned()] + ); + + // Should be workspace specific and these specific tests cover only dependency job debouncing + assert_eq!( + q[0].key.clone(), + "test-workspace:f/dre/flow:dependency".to_owned(), + ); + } + + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre/flow" + ); + }, + port, + ) + .await; + + // Verify latest flow.version property + { + // Latest flow version should not be initial one + assert_eq!( + 1, // Automatically assigned + dbg!(sqlx::query_scalar!( + "SELECT versions[2] FROM flow WHERE path = 'f/dre/flow'" + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap()) + ); + + // Only second element should be our initial version + assert_eq!( + 1443253234253454, // < Predefined in fixture + dbg!(sqlx::query_scalar!( + "SELECT versions[1] FROM flow WHERE path = 'f/dre/flow'" + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap()) + ); + } + + // Verify that there is only two versions of flow in global flow_version + { + assert_eq!( + 2, + sqlx::query_scalar!( + "SELECT COUNT(*) FROM flow_version WHERE path = 'f/dre/flow'" + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + } + + // Verify locks + { + assert_eq!( + sqlx::query_scalar!("SELECT jsonb_array_elements(value->'modules')->'value'->>'lock' AS lock FROM flow") + .fetch_all(&db) + .await + .unwrap(), + vec![ + Some("# py: 3.11\nbottle==0.13.2".into()), + Some("# py: 3.11\nbottle==0.13.2\ntiny==0.1.3".into()), + Some("# py: 3.11\ntiny==0.1.3".into()) + ] + ); + } + + // TODO: + // tracing_assertions::assert_has_events!([info("This is supposed to be called")]); + // 2025-10-06T14:31:10.832469Z WARN windmill-worker/src/worker.rs:1593: pull took more than 0.1s (0.222477345) this is a sign that the database is undersized for this load. empty: true, err: true worker=wk-default-nixos-EzDEL hostname=nixos + + // Verify cleanup + { + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_key") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_stale_data") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + } + + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_left(db: sqlx::Pool) -> anyhow::Result<()> { + use crate::common::RunJob; + + // TODO: We don't care about timer. If there is no timer, it will be set automatically for djobs?? + let (_client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // Trigger both at the same time. + { + let mut args = std::collections::HashMap::new(); + args.insert( + "dbg_djob_sleep".to_owned(), + // Execution should take this seconds + windmill_common::worker::to_raw_value(&20), + ); + + args.insert( + "triggered_by_relative_import".to_owned(), + // Execution should take this seconds + windmill_common::worker::to_raw_value(&()), + ); + + let (_flow_id, new_tx) = windmill_queue::push( + &db, + windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + "test-workspace", + windmill_common::jobs::JobPayload::FlowDependencies { + path: "f/dre/flow".to_owned(), + dedicated_worker: None, + version: 1443253234253454, + }, + windmill_queue::PushArgs { args: &args, extra: None }, + "admin", + "admin@windmill.dev", + "admin".to_owned(), + Some("trigger.dependents.to.recompute.dependencies"), + // Debounce period + Some(chrono::Utc::now() + chrono::Duration::seconds(5)), + None, + None, + None, + None, + None, + false, + false, + None, + true, + Some("dependency".into()), + None, + None, + None, + None, + false, + None, + None, + ) + .await + .unwrap(); + new_tx.commit().await.unwrap(); + + // let handle = { + // // let mut completed = listen_for_completed_jobs(&db).await; + // let db2 = db.clone(); + // // let uuid = flow_id.clone(); + // tokio::spawn(async move { + // in_test_worker( + // &db2, + // tokio::time::sleep(tokio::time::Duration::from_secs(60)), + // // completed.find(&uuid), + // port, + // ) + // .await; + // }) + // }; + + let db2 = db.clone(); + in_test_worker( + &db2, + async { + // This job should execute and then try to start another job that will get debounced. + RunJob::from(windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre/leaf_right".to_owned(), + hash: 333403.into(), + language: windmill_common::scripts::ScriptLang::Python3, + dedicated_worker: None, + }) + // .arg("dbg_djob_sleep", serde_json::json!(10)) + .run_until_complete(&db, port) + .await; + + RunJob::from(windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre/leaf_left".to_owned(), + hash: 333400.into(), + language: windmill_common::scripts::ScriptLang::Python3, + dedicated_worker: None, + }) + // So set it to this long + .arg("dbg_djob_sleep", serde_json::json!(10)) + .run_until_complete(&db, port) + .await; + + completed.next().await; // leaf_right + completed.next().await; // leaf_left + completed.next().await; // importer + completed.next().await; // importer + }, + port, + ) + .await; + } + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 0 + ); + + let r = sqlx::query_scalar!("SELECT runnable_id FROM v2_job ORDER BY created_at DESC") + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(r.len(), 4); + assert!(r.contains(&Some(1))); + assert!(r.contains(&Some(333400))); + assert!(r.contains(&Some(333403))); + assert!(r.contains(&Some(1443253234253454))); + + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_2(db: sqlx::Pool) -> anyhow::Result<()> { + let (_client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // Function to create a dependency job + let create_dependency_job = + |delay, + nodes_to_relock, + db: sqlx::Pool, + version, + debounce_job_id_o| async move { + let mut args = std::collections::HashMap::new(); + args.insert( + "dbg_sleep_between_pull_and_debounce_key_removal".to_owned(), + windmill_common::worker::to_raw_value(&delay), + ); + + args.insert( + "nodes_to_relock".to_owned(), + windmill_common::worker::to_raw_value(&nodes_to_relock), + ); + + args.insert( + "triggered_by_relative_import".to_string(), + windmill_common::worker::to_raw_value(&()), + ); + + args.insert( + "dbg_create_job_for_unexistant_flow_version".to_string(), + windmill_common::worker::to_raw_value(&()), + ); + + let (job_uuid, new_tx) = windmill_queue::push( + &db, + windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + "test-workspace", + windmill_common::jobs::JobPayload::FlowDependencies { + path: "f/dre/flow".to_owned(), + dedicated_worker: None, + version, + }, + windmill_queue::PushArgs { args: &args, extra: None }, + "admin", + "admin@windmill.dev", + "admin".to_owned(), + Some("trigger.dependents.to.recompute.dependencies"), + Some(chrono::Utc::now()), // Schedule immediately + None, + None, + None, + None, + None, + false, + false, + None, + true, + Some("dependency".into()), + None, + None, + None, + None, + false, + None, + debounce_job_id_o, + ) + .await + .unwrap(); + + new_tx.commit().await.unwrap(); + job_uuid + }; + + // Push the first dependency job + let job1 = + create_dependency_job(2, vec!["a", "b"], db.clone(), 1443253234253454, None).await; + + let db2 = db.clone(); + in_test_worker( + &db2, + async { + windmill_common::worker::update_min_version( + &windmill_common::worker::Connection::Sql(db2.clone()), + ) + .await; + // Small delay to ensure the job is marked as running + tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; + + // Now is the time when the job is pulled, but debounce_key is not yet cleared. + { + assert!(sqlx::query_scalar!( + "SELECT running FROM v2_job_queue WHERE id = $1", + job1 + ) + .fetch_one(&db) + .await + .unwrap()); + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM debounce_key") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 1 + ); + } + + // Block all tests using this variable until we are done + // let mut min_v = windmill_common::worker::MIN_VERSION_IS_AT_LEAST_1_440 + // .write() + // .await; + + // // Save initial min_v value; + // let initi_min_v = *min_v; + + // // Make it true for this test. + // *min_v = true; + + // Now push a second dependency job while the first is being processed + // This should trigger the race condition handling code + let job2 = + create_dependency_job(0, vec!["b", "c"], db.clone(), 1, Some(job1)).await; + + // Set it back to initial + // *min_v = initi_min_v; + + // Unblock all other tests + // drop(min_v); + + // Process the first job completion, and the second job should also get debounced by this one + completed.next().await; + + // Verify that both jobs were created and processed + assert_eq!(job1, job2, "Second job should be debounced"); + }, + port, + ) + .await; + + assert_eq!( + vec![1443253234253454, 1], + sqlx::query_scalar!("SELECT versions FROM flow WHERE path = 'f/dre/flow'") + .fetch_one(&db) + .await + .unwrap() + ); + + // Verify cleanup - all debounce entries should be cleaned up + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_key") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "All debounce_key entries should be cleaned up after job completion" + ); + + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_stale_data") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "All debounce_stale_data entries should be cleaned up after job completion" + ); + + // Verify locks + { + assert_eq!( + sqlx::query_scalar!("SELECT jsonb_array_elements(value->'modules')->'value'->>'lock' AS lock FROM flow") + .fetch_all(&db) + .await + .unwrap(), + vec![ + Some("# py: 3.11\nbottle==0.13.2".into()), + Some("# py: 3.11\nbottle==0.13.2\ntiny==0.1.3".into()), + Some("# py: 3.11\ntiny==0.1.3".into()) + ] + ); + } + + Ok(()) + } + /// 2. Same as second test, however first flow djob will take longer than second debounce. + /// NOTE: This test should be ran in debug mode with `private` features enabled. In release it will not work properly. + #[cfg(all(feature = "python", feature = "private"))] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + // #[windmill::all_min_versions] + async fn test_3(db: sqlx::Pool) -> anyhow::Result<()> { + // This tests checks if concurrency limit works correcly and there is no race conditions. + + use windmill_common::worker::Connection; + let (_client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // At this point we should have two + let mut job_ids = vec![]; + let push_job = |delay, version, db, nodes_to_relock, debounce_job_id_o| async move { + let mut args = std::collections::HashMap::new(); + args.insert( + "dbg_djob_sleep".to_owned(), + // First one will create delay for 5 seconds + // The second will have no delay at all. + windmill_common::worker::to_raw_value(&delay), + ); + + args.insert( + "nodes_to_relock".to_owned(), + windmill_common::worker::to_raw_value(&nodes_to_relock), + ); + + args.insert( + "triggered_by_relative_import".to_string(), + windmill_common::worker::to_raw_value(&()), + ); + + let (job_uuid, new_tx) = windmill_queue::push( + &db, + windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + "test-workspace", + windmill_common::jobs::JobPayload::FlowDependencies { + path: "f/dre/flow".to_owned(), + dedicated_worker: None, + // In newest versions we pass the current version to the djob + // version: 1443253234253454, + version, + }, + windmill_queue::PushArgs { args: &args, extra: None }, + "admin", + "admin@windmill.dev", + "admin".to_owned(), + Some("trigger.dependents.to.recompute.dependencies"), + // Schedule for now. + Some(chrono::Utc::now()), + None, + None, + None, + None, + None, + false, + false, + None, + true, + Some("dependency".into()), + None, + None, + None, + None, + false, + None, + debounce_job_id_o, + ) + .await + .unwrap(); + + new_tx.commit().await.unwrap(); + + job_uuid + }; + + // Push first + job_ids.push(push_job(5, 1443253234253454, db.clone(), ["a", "b"], None).await); + + // Verify debounce_stale_data and debounce_key + { + let q = sqlx::query!("SELECT COUNT(*) FROM debounce_key") + .fetch_all(&db) + .await + .unwrap(); + + // Should be single entry + assert_eq!(q.len(), 1); + } + + // Start the first one in the background + let handle = { + let mut completed = listen_for_completed_jobs(&db).await; + let db2 = db.clone(); + tokio::spawn(async move { + in_test_worker( + &db2, + // sleep(Duration::from_secs(7)), + completed.next(), // Only wait for the single job. We are going to spawn another worker for second one. + port, + ) + .await; + }) + }; + + // Wait for the job to be created and started + // This way next job is not going to be consumed by the first one. + sleep(Duration::from_secs(2)).await; + + // Push second + job_ids.push(push_job(0, 1, db.clone(), ["b", "c"], None).await); + + // Wait for the second one to finish in separate worker. + // in_test_worker(&db, completed.next(), port).await; + in_test_worker( + &db, + async { + // First job will be pulled + completed.next().await; + // However since we have concurrency limit enabled it will get rescheduled by creation of new djob. + // So we have to wait for that one as well. + completed.next().await; + }, + port, + ) + .await; + + // Wait for the first one + handle.await.unwrap(); + + // Verify locks + { + assert_eq!( + sqlx::query_scalar!("SELECT jsonb_array_elements(value->'modules')->'value'->>'lock' AS lock FROM flow") + .fetch_all(&db) + .await + .unwrap(), + vec![ + Some("# py: 3.11\nbottle==0.13.2".into()), + Some("# py: 3.11\nbottle==0.13.2\ntiny==0.1.3".into()), + Some("# py: 3.11\ntiny==0.1.3".into()) + ] + ); + } + // Verify that we have expected outcome + { + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job",) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 2 + ); + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_completed",) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 2 + ); + // Check that two jobs were executed sequentially + assert!(sqlx::query_scalar!( + " +SELECT + j1.completed_at < j2.started_at +FROM + v2_job_completed j1, + v2_job_completed j2 +WHERE + j1.id = $1 + AND j2.id = $2", + job_ids[0], + job_ids[1], + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap()); + } + Ok(()) + } + + // TODO: + // test that update or create flow that should bypass debouncing + } + + /// ## Testing for Apps + /// For apps we are going to do similar tests that we did for flows + mod apps { + use crate::common::{in_test_worker, init_client, listen_for_completed_jobs}; + use crate::job_debouncing::trigger_djob_for; + use std::time::Duration; + use tokio::time::sleep; + use tokio_stream::StreamExt; + + /// 1. LLF and RLF create two djobs for flow at the same and fall into single debounce + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_1(db: sqlx::Pool) -> anyhow::Result<()> { + // This tests if debouncing and consolidation works. + // Also makes sures that dependency job does not create new flow version + + let (client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 0 + ); + + // Trigger both at the same time. + // It will create two immediate dependency jobs + { + trigger_djob_for( + &client, + "f/dre_app/leaf_left", + "0000000000069CF8", + Some("#requirements:\n#bottle==0.13.2\ndef main():\npass".into()), + ) + .await; + + trigger_djob_for( + &client, + "f/dre_app/leaf_right", + "0000000000069CFB", + Some("#requirements:\n#tiny==0.1.3\ndef main():\npass".into()), + ) + .await; + } + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 2 + ); + + // Spawn single worker. + in_test_worker( + &db, + async { + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre_app/leaf_left" + ); + + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre_app/leaf_right" + ); + + // Verify there is only one queued job that is scheduled for atleast 3s ahead. + { + let q = sqlx::query_scalar!( + "SELECT (scheduled_for - created_at) FROM v2_job_queue" + ) + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(1, q.len()); + assert!(dbg!(q[0].unwrap().microseconds) > 2_000_000); + } + + // Verify debounce_stale_data and debounce_key + { + let q = sqlx::query!( + "SELECT + dsd.to_relock, + dk.key + FROM debounce_key dk + JOIN debounce_stale_data dsd ON dk.job_id = dsd.job_id" + ) + .fetch_all(&db) + .await + .unwrap(); + + // Should be single entry + assert!(q.len() == 1); + + // This verifies that all nodes_to_relock are consolidated correctly + // AND there is no doublicats + assert_eq!( + q[0].to_relock.clone().unwrap(), + vec!["a".to_owned(), "b".to_owned(), "c".to_owned()] + ); + + // Should be workspace specific and these specific tests cover only dependency job debouncing + assert_eq!( + q[0].key.clone(), + "test-workspace:f/dre_app/app:dependency".to_owned(), + ); + } + + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre_app/app" + ); + }, + port, + ) + .await; + + // Verify App states + { + let q = dbg!(sqlx::query_scalar!( + "SELECT versions FROM app WHERE path = 'f/dre_app/app'" + ) + .fetch_one(&db) + .await + .unwrap()); + + assert_eq!(2, q.len()); + + // There is also supposed to be this amount of app_versions + assert_eq!( + 2, + sqlx::query_scalar!("SELECT COUNT(*) FROM app_version WHERE app_id = '2'") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + } + + // Verify cleanup + { + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_key") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_stale_data") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + } + + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_left(db: sqlx::Pool) -> anyhow::Result<()> { + use crate::common::RunJob; + + // TODO: We don't care about timer. If there is no timer, it will be set automatically for djobs?? + let (_client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + let mut args = std::collections::HashMap::new(); + args.insert( + "dbg_djob_sleep".to_owned(), + // Execution should take this seconds + windmill_common::worker::to_raw_value(&20), + ); + args.insert( + "triggered_by_relative_import".to_owned(), + // Execution should take this seconds + windmill_common::worker::to_raw_value(&()), + ); + + let (_flow_id, new_tx) = windmill_queue::push( + &db, + windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + "test-workspace", + windmill_common::jobs::JobPayload::AppDependencies { + path: "f/dre_app/app".to_owned(), + version: 0, + }, + windmill_queue::PushArgs { args: &args, extra: None }, + "admin", + "admin@windmill.dev", + "admin".to_owned(), + Some("trigger.dependents.to.recompute.dependencies"), + // Debounce period + Some(chrono::Utc::now() + chrono::Duration::seconds(5)), + None, + None, + None, + None, + None, + false, + false, + None, + true, + Some("dependency".into()), + None, + None, + None, + None, + false, + None, + None, + ) + .await + .unwrap(); + new_tx.commit().await.unwrap(); + + // let mut handle = { + // let mut completed = listen_for_completed_jobs(&db).await; + // let db2 = db.clone(); + // let uuid = flow_id.clone(); + // tokio::spawn(async move { + // in_test_worker( + // &db2, + // // tokio::time::sleep(tokio::time::Duration::from_secs(60)), + // async move { + // completed.find(&uuid).await; + // }, + // port, + // ) + // .await; + // }) + // }; + + let db2 = db.clone(); + in_test_worker( + &db2, + async { + // This job should execute and then try to start another job that will get debounced. + RunJob::from(windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre_app/leaf_right".to_owned(), + hash: 433403.into(), + language: windmill_common::scripts::ScriptLang::Python3, + dedicated_worker: None, + }) + // .arg("dbg_djob_sleep", serde_json::json!(10)) + .run_until_complete(&db, port) + .await; + + RunJob::from(windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre_app/leaf_left".to_owned(), + hash: 433400.into(), + language: windmill_common::scripts::ScriptLang::Python3, + dedicated_worker: None, + }) + // So set it to this long + .arg("dbg_djob_sleep", serde_json::json!(10)) + .run_until_complete(&db, port) + .await; + + completed.next().await; // leaf_right + completed.next().await; // leaf_left + completed.next().await; // importer + completed.next().await; // importer + }, + port, + ) + .await; + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 0 + ); + + let r = sqlx::query_scalar!("SELECT runnable_id FROM v2_job ORDER BY created_at DESC") + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(r.len(), 4); + assert!(r.contains(&Some(9))); + assert!(r.contains(&Some(433400))); + assert!(r.contains(&Some(433403))); + assert!(r.contains(&Some(0))); + + // handle.await.unwrap(); + + Ok(()) + } + /// 2. Same as second test, however first app djob will take longer than second debounce. + /// NOTE: This test should be ran in debug mode. In release it will not work properly. + #[cfg(all(feature = "python", feature = "private"))] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_3(db: sqlx::Pool) -> anyhow::Result<()> { + // This tests checks if concurrency limit works correcly and there is no race conditions. + let (_client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // At this point we should have two + let mut job_ids = vec![]; + let push_job = |delay, db| async move { + let mut args = std::collections::HashMap::new(); + args.insert( + "dbg_djob_sleep".to_owned(), + // First one will create delay for 5 seconds + // The second will have no delay at all. + windmill_common::worker::to_raw_value(&delay), + ); + + args.insert( + "triggered_by_relative_import".to_string(), + windmill_common::worker::to_raw_value(&()), + ); + + let (job_uuid, new_tx) = windmill_queue::push( + &db, + windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + "test-workspace", + windmill_common::jobs::JobPayload::AppDependencies { + path: "f/dre_app/app".to_owned(), + // In newest versions we pass the current version to the djob + version: 0, + }, + windmill_queue::PushArgs { args: &args, extra: None }, + "admin", + "admin@windmill.dev", + "admin".to_owned(), + Some("trigger.dependents.to.recompute.dependencies"), + // Schedule for now. + Some(chrono::Utc::now()), + None, + None, + None, + None, + None, + false, + false, + None, + true, + Some("dependency".into()), + None, + None, + None, + None, + false, + None, + None, + ) + .await + .unwrap(); + + new_tx.commit().await.unwrap(); + + job_uuid + }; + + // TODO: Verify concurrency key. + // Push first + job_ids.push(push_job(5, db.clone()).await); + + // Verify debounce_stale_data and debounce_key + { + let q = sqlx::query!("SELECT COUNT(*) FROM debounce_key") + .fetch_all(&db) + .await + .unwrap(); + + // Should be single entry + assert_eq!(q.len(), 1); + } + + // Start the first one in the background + let handle = { + let mut completed = listen_for_completed_jobs(&db).await; + let db2 = db.clone(); + tokio::spawn(async move { + in_test_worker( + &db2, + // sleep(Duration::from_secs(7)), + completed.next(), // Only wait for the single job. We are going to spawn another worker for second one. + port, + ) + .await; + }) + }; + + // Wait for the job to be created and started + // This way next job is not going to be consumed by the first one. + sleep(Duration::from_secs(2)).await; + + // Push second + job_ids.push(push_job(0, db.clone()).await); + + // Wait for the second one to finish in separate worker. + // in_test_worker(&db, completed.next(), port).await; + in_test_worker( + &db, + async { + // First job will be pulled + completed.next().await; + // However since we have concurrency limit enabled it will get rescheduled by creation of new djob. + // So we have to wait for that one as well. + completed.next().await; + }, + port, + ) + .await; + + // Wait for the first one + handle.await.unwrap(); + + // Verify that we have expected outcome + { + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job",) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 2 + ); + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_completed",) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 2 + ); + // Check that two jobs were executed sequentially + assert!(sqlx::query_scalar!( + " +SELECT + j1.completed_at < j2.started_at +FROM + v2_job_completed j1, + v2_job_completed j2 +WHERE + j1.id = $1 + AND j2.id = $2", + job_ids[0], + job_ids[1], + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap()); + } + Ok(()) + } + } + + // TODO: Test debounce reassignment works + + /// ## Testing for Scripts + mod scripts { + use crate::common::{in_test_worker, init_client, listen_for_completed_jobs}; + use crate::job_debouncing::trigger_djob_for; + use std::time::Duration; + use tokio::time::sleep; + use tokio_stream::StreamExt; + + /// 1. LLF and RLF create two djobs for flow at the same and fall into single debounce + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + // TODO: Same test_but script fails. + async fn test_1(db: sqlx::Pool) -> anyhow::Result<()> { + // This tests if debouncing and consolidation works. + // Also makes sures that dependency job does not create new flow version + let (client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // Verify lock is empty + { + assert_eq!( + sqlx::query_scalar!( + "SELECT lock FROM script WHERE path = 'f/dre_script/script'" + ) + .fetch_one(&db) + .await + .unwrap(), + Some("".into()) + ); + } + + // Trigger both at the same time. + { + trigger_djob_for( + &client, + "f/dre_script/leaf_left", + "0000000000082398", + Some("#requirements:\n#bottle==0.13.2\ndef main():\npass".into()), + ) + .await; + trigger_djob_for( + &client, + "f/dre_script/leaf_right", + "000000000008239B", + Some("#requirements:\n#tiny==0.1.3\ndef main():\npass".into()), + ) + .await; + } + + sleep(Duration::from_secs(1)).await; + + in_test_worker( + &db, + async { + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre_script/leaf_left" + ); + + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre_script/leaf_right" + ); + + // handle.await.unwrap(); + + // Let jobs propagate + + tokio::select!( + _ = async { + while sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue WHERE running = false") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + == 0 + { + sleep(Duration::from_secs(1)).await; + } + } => {}, + _ = sleep(Duration::from_secs(60)) => { panic!("Timeout") } + ); + // Verify there is only one queued job that is scheduled for atleast 3s ahead. + { + for r in + sqlx::query_scalar!("SELECT id FROM v2_job_queue WHERE running = false") + .fetch_all(&db) + .await + .unwrap() + { + dbg!( + sqlx::query!("SELECT runnable_path FROM v2_job WHERE id = $1", r) + .fetch_all(&db) + .await + .unwrap() + ); + } + for r in sqlx::query_scalar!("SELECT id FROM v2_job_completed") + .fetch_all(&db) + .await + .unwrap() + { + dbg!( + sqlx::query!("SELECT runnable_path FROM v2_job WHERE id = $1", r) + .fetch_all(&db) + .await + .unwrap() + ); + } + + dbg!(sqlx::query!("SELECT runnable_path FROM v2_job") + .fetch_all(&db) + .await + .unwrap()); + + let q = sqlx::query_scalar!( + "SELECT (scheduled_for - created_at) FROM v2_job_queue WHERE running = false" + ) + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(1, q.len()); + assert!(dbg!(q[0].unwrap().microseconds) > 1_000_000 /* 1 second */); + } + + // Verify debounce_stale_data and debounce_key + { + let q = sqlx::query_scalar!("SELECT key FROM debounce_key") + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(q.len(), 1); + + assert_eq!( + q[0].clone(), + "test-workspace:f/dre_script/script:dependency".to_owned(), + ); + + // Stale data is empty for scripts + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM debounce_stale_data") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 0 + ); + } + + // Wait until debounce delay is complete + // sleep(Duration::from_secs(6)).await; + + assert_eq!( + &sqlx::query_scalar!( + "SELECT runnable_path FROM v2_job WHERE id = $1", + completed.next().await.unwrap() + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + "f/dre_script/script" + ); + }, + port, + ) + .await; + + // completed.next().await.unwrap(); + + // Verify + { + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from v2_job_queue") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + assert_eq!( + vec![533404], + dbg!(sqlx::query_scalar!( + "SELECT hash FROM script WHERE path = 'f/dre_script/script' AND archived = true" + ) + .fetch_all(&db) + .await + .unwrap()) + ); + + assert_ne!( + 533404, + sqlx::query_scalar!( + "SELECT hash FROM script WHERE path = 'f/dre_script/script' AND archived = false" + ) + .fetch_one(&db) + .await + .unwrap() + ); + + assert_eq!( + vec![533404], + sqlx::query_scalar!( + "SELECT parent_hashes FROM script WHERE path = 'f/dre_script/script' AND archived = false" + ) + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + } + + // Verify cleanup + { + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_key") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + assert_eq!( + 0, + sqlx::query_scalar!("SELECT COUNT(*) from debounce_stale_data") + .fetch_one(&db) + .await + .unwrap() + .unwrap() + ); + } + + // handle.await.unwrap(); + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "djob_debouncing"))] + async fn test_left(db: sqlx::Pool) -> anyhow::Result<()> { + use crate::common::RunJob; + + // TODO: We don't care about timer. If there is no timer, it will be set automatically for djobs?? + let (_client, port, _s) = init_client(db.clone()).await; + let mut completed = listen_for_completed_jobs(&db).await; + + // Trigger both at the same time. + { + let mut args = std::collections::HashMap::new(); + args.insert( + "dbg_djob_sleep".to_owned(), + // Execution should take this seconds + windmill_common::worker::to_raw_value(&20), + ); + + args.insert( + "triggered_by_relative_import".to_owned(), + // Execution should take this seconds + windmill_common::worker::to_raw_value(&()), + ); + + let (_flow_id, new_tx) = windmill_queue::push( + &db, + windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + "test-workspace", + windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre_script/script".to_owned(), + dedicated_worker: None, + language: windmill_common::scripts::ScriptLang::Python3, + hash: 533404.into(), + }, + windmill_queue::PushArgs { args: &args, extra: None }, + "admin", + "admin@windmill.dev", + "admin".to_owned(), + Some("trigger.dependents.to.recompute.dependencies"), + // Debounce period + Some(chrono::Utc::now() + chrono::Duration::seconds(5)), + None, + None, + None, + None, + None, + false, + false, + None, + true, + Some("dependency".into()), + None, + None, + None, + None, + false, + None, + None, + ) + .await + .unwrap(); + new_tx.commit().await.unwrap(); + + let db2 = db.clone(); + in_test_worker( + &db2, + async { + // This job should execute and then try to start another job that will get debounced. + RunJob::from(windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre_script/leaf_right".to_owned(), + hash: 533403.into(), + language: windmill_common::scripts::ScriptLang::Python3, + dedicated_worker: None, + }) + .run_until_complete(&db, port) + .await; + + // This one is supposed to be started after flow djob has debounced and started but haven't finished yet. + RunJob::from(windmill_common::jobs::JobPayload::Dependencies { + path: "f/dre_script/leaf_left".to_owned(), + hash: 533400.into(), + language: windmill_common::scripts::ScriptLang::Python3, + dedicated_worker: None, + }) + // So set it to this long + .arg("dbg_djob_sleep", serde_json::json!(10)) + .run_until_complete(&db, port) + .await; + + completed.next().await; // leaf_right + completed.next().await; // leaf_left + completed.next().await; // importer + completed.next().await; // importer + }, + port, + ) + .await; + } + + assert_eq!( + sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue") + .fetch_one(&db) + .await + .unwrap() + .unwrap(), + 0 + ); + + let r = sqlx::query_scalar!("SELECT runnable_id FROM v2_job ORDER BY created_at DESC") + .fetch_all(&db) + .await + .unwrap(); + + assert_eq!(r.len(), 4); + assert!(r.contains(&Some(-221349019907577876))); + assert!(r.contains(&Some(533400))); + assert!(r.contains(&Some(533403))); + assert!(r.contains(&Some(533404))); + + Ok(()) + } + + // // TODO: we don't need scripts to have concurrency limit + // /// 3. Same as second test, however first app djob will take longer than second debounce. + // /// NOTE: This test should be ran in debug mode. In release it will not work properly. + // #[cfg(all(feature = "python", feature = "private"))] + // #[sqlx::test(fixtures("base", "djob_debouncing"))] + // async fn test_3(db: sqlx::Pool) -> anyhow::Result<()> { + // // This tests checks if concurrency limit works correcly and there is no race conditions. + // let (client, port, _s) = init_client(db.clone()).await; + // let mut completed = listen_for_completed_jobs(&db).await; + + // // At this point we should have two + // let mut job_ids = vec![]; + // let push_job = |delay, db| async move { + // let mut args = std::collections::HashMap::new(); + // args.insert( + // "dbg_djob_sleep".to_owned(), + // // First one will create delay for 5 seconds + // // The second will have no delay at all. + // windmill_common::worker::to_raw_value(&delay), + // ); + + // args.insert( + // "triggered_by_relative_import".to_string(), + // windmill_common::worker::to_raw_value(&()), + // ); + + // let (job_uuid, new_tx) = windmill_queue::push( + // &db, + // windmill_queue::PushIsolationLevel::IsolatedRoot(db.clone()), + // "test-workspace", + // windmill_common::jobs::JobPayload::Dependencies { + // path: "f/dre_script/script".to_owned(), + // language: windmill_common::scripts::ScriptLang::Python3, + // dedicated_worker: None, + // hash: windmill_common::scripts::ScriptHash(533404), + // }, + // windmill_queue::PushArgs { args: &args, extra: None }, + // "admin", + // "admin@windmill.dev", + // "admin".to_owned(), + // Some("trigger.dependents.to.recompute.dependencies"), + // // Schedule for now. + // Some(chrono::Utc::now()), + // None, + // None, + // None, + // None, + // None, + // false, + // false, + // None, + // true, + // Some("dependency".into()), + // None, + // None, + // None, + // None, + // false, + // None, + // None, + // ) + // .await + // .unwrap(); + + // new_tx.commit().await.unwrap(); + + // job_uuid + // }; + + // // Push first + // job_ids.push(push_job(5, db.clone()).await); + // sleep(Duration::from_millis(300)).await; + + // // Verify debounce_stale_data and debounce_key + // { + // let q = sqlx::query_scalar!("SELECT key FROM debounce_key") + // .fetch_all(&db) + // .await + // .unwrap(); + + // assert_eq!(q.len(), 1); + + // assert_eq!( + // q[0].clone(), + // "test-workspace:f/dre_script/script:dependency".to_owned(), + // ); + + // // Stale data is empty for scripts + // assert_eq!( + // sqlx::query_scalar!("SELECT COUNT(*) FROM debounce_stale_data") + // .fetch_one(&db) + // .await + // .unwrap() + // .unwrap(), + // 0 + // ); + // } + + // // Start the first one in the background + // let handle = { + // let mut completed = listen_for_completed_jobs(&db).await; + // let db2 = db.clone(); + // tokio::spawn(async move { + // in_test_worker( + // &db2, + // // sleep(Duration::from_secs(7)), + // completed.next(), // Only wait for the single job. We are going to spawn another worker for second one. + // port, + // ) + // .await; + // }) + // }; + + // // Wait for the job to be created and started + // // This way next job is not going to be consumed by the first one. + // sleep(Duration::from_secs(1)).await; + + // // Push second + // job_ids.push(push_job(0, db.clone()).await); + + // // Wait for the second one to finish in separate worker. + // in_test_worker( + // &db, + // async { + // // First job will be pulled + // completed.next().await; + // // However since we have concurrency limit enabled it will get rescheduled by creation of new djob. + // // So we have to wait for that one as well. + // completed.next().await; + // }, + // port, + // ) + // .await; + + // // Wait for the first one + // handle.await.unwrap(); + + // // Verify that we have expected outcome + // { + // assert_eq!( + // sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue",) + // .fetch_one(&db) + // .await + // .unwrap() + // .unwrap(), + // 0 + // ); + // // Verify lock + // { + // assert_eq!( + // sqlx::query_scalar!( + // "SELECT lock FROM script WHERE path = 'f/dre_script/script'" + // ) + // .fetch_one(&db) + // .await + // .unwrap(), + // Some("# py: 3.11\nbottle==0.13.2\ntiny==0.1.3".into()) + // ); + // } + // assert_eq!( + // sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job",) + // .fetch_one(&db) + // .await + // .unwrap() + // .unwrap(), + // 2 + // ); + + // assert_eq!( + // sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_completed",) + // .fetch_one(&db) + // .await + // .unwrap() + // .unwrap(), + // 2 + // ); + // // Check that two jobs were executed sequentially + // assert!(sqlx::query_scalar!( + // " + // SELECT + // j1.completed_at < j2.started_at + // FROM + // v2_job_completed j1, + // v2_job_completed j2 + // WHERE + // j1.id = $1 + // AND j2.id = $2", + // job_ids[0], + // job_ids[1], + // ) + // .fetch_one(&db) + // .await + // .unwrap() + // .unwrap()); + // } + // Ok(()) + // } + } + // TODO: Test git sync +} diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 362df943a0..72d753c48c 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -20,7 +20,6 @@ use windmill_common::flows::InputTransform; #[cfg(any(feature = "python", feature = "deno_core"))] use windmill_common::flow_status::RestartedFrom; - use windmill_common::{ flows::FlowValue, jobs::{JobPayload, RawCode}, @@ -31,6 +30,8 @@ use common::*; #[cfg(feature = "enterprise")] use futures::StreamExt; +use windmill_common::flows::FlowModule; +use windmill_common::flows::FlowModuleValue; // async fn _print_job(id: Uuid, db: &Pool) -> Result<(), anyhow::Error> { // tracing::info!( @@ -332,9 +333,6 @@ async fn test_identity(db: Pool) -> anyhow::Result<()> { Ok(()) } -use windmill_common::flows::FlowModule; -use windmill_common::flows::FlowModuleValue; - #[cfg(feature = "deno_core")] #[sqlx::test(fixtures("base"))] async fn test_deno_flow_same_worker(db: Pool) -> anyhow::Result<()> { @@ -2083,14 +2081,14 @@ async fn test_flow_lock_all(db: Pool) -> anyhow::Result<()> { language: windmill_api_client::types::RawScriptLanguage::Bash, lock: Some(ref lock), .. - }) if lock == "") + }) if lock.is_empty()) || matches!( m.value, windmill_api_client::types::FlowModuleValue::RawScript(RawScript{ language: windmill_api_client::types::RawScriptLanguage::Go | windmill_api_client::types::RawScriptLanguage::Python3 | windmill_api_client::types::RawScriptLanguage::Deno, lock: Some(ref lock), .. - }) if lock.len() > 0), + }) if !lock.is_empty()), "{:?}", m.value ); }); @@ -2749,7 +2747,7 @@ async fn test_result_format(db: Pool) -> anyhow::Result<()> { assert_eq!(job_result.get(), correct_result); let response = windmill_api::jobs::run_wait_result( - &db.into(), + &db, Uuid::parse_str(ordered_result_job_id).unwrap(), "test-workspace".to_string(), None, @@ -2760,8 +2758,7 @@ async fn test_result_format(db: Pool) -> anyhow::Result<()> { let result: Box = serde_json::from_slice( &axum::body::to_bytes(response.into_body(), usize::MAX) .await - .unwrap() - .to_vec(), + .unwrap(), ) .unwrap(); assert_eq!(result.get(), correct_result); @@ -2805,7 +2802,7 @@ async fn test_job_labels(db: Pool) -> anyhow::Result<()> { restarted_from: None, }) .arg("world", json!("you")) - .run_until_complete_with(&db, port, |id| async move { + .run_until_complete_with(db, port, |id| async move { sqlx::query!( "UPDATE v2_job SET labels = $2 WHERE id = $1 AND $2::TEXT[] IS NOT NULL", id, @@ -2871,7 +2868,7 @@ async fn test_workflow_as_code(db: Pool) -> anyhow::Result<()> { // workflow as code require at least 2 workers: let db = &db; in_test_worker( - &db, + db, async move { let job = RunJob::from(JobPayload::Code(RawCode { language: ScriptLang::Python3, @@ -2879,7 +2876,7 @@ async fn test_workflow_as_code(db: Pool) -> anyhow::Result<()> { ..RawCode::default() })) .arg("n", json!(3)) - .run_until_complete(&db, port) + .run_until_complete(db, port) .await; assert_eq!(job.json_result().unwrap(), json!(["OK", 3])); diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 15a89d762f..1cd0750919 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -60,7 +60,7 @@ use windmill_common::{ users::username_to_permissioned_as, utils::{ http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, require_admin, - Pagination, RunnableKind, StripPath, + Pagination, RunnableKind, StripPath, WarnAfterExt, }, variables::{build_crypt, build_crypt_with_key_suffix, encrypt}, worker::{to_raw_value, CLOUD_HOSTED}, @@ -1240,6 +1240,7 @@ async fn create_app_internal<'a>( Some(&authed.clone().into()), false, None, + None, ) .await?; tracing::info!("Pushed app dependency job {}", dependency_job_uuid); @@ -1523,6 +1524,14 @@ async fn update_app_internal<'a>( path.to_owned() }; let v_id = if let Some(nvalue) = &ns.value { + // Row lock debounce key for path. We need this to make all updates of runnables sequential and predictable. + tokio::time::timeout( + core::time::Duration::from_secs(60), + windmill_common::jobs::lock_debounce_key(&w_id, &npath, &mut tx), + ) + .warn_after_seconds(10) + .await??; + let app_id = sqlx::query_scalar!( "SELECT id FROM app WHERE path = $1 AND workspace_id = $2", npath, @@ -1620,6 +1629,7 @@ async fn update_app_internal<'a>( Some(&authed.clone().into()), false, None, + None, ) .await?; tracing::info!("Pushed app dependency job {}", dependency_job_uuid); @@ -1938,6 +1948,7 @@ async fn execute_component( None, false, end_user_email, + None, ) .await?; tx.commit().await?; diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index c983fb8d1f..4d1e7aecc1 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -32,7 +32,7 @@ use sql_builder::prelude::*; use sqlx::{FromRow, Postgres, Transaction}; use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; -use windmill_common::utils::query_elems_from_hub; +use windmill_common::utils::{query_elems_from_hub, WarnAfterExt}; use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; use windmill_common::HUB_BASE_URL; use windmill_common::{ @@ -546,6 +546,7 @@ async fn create_flow( Some(&authed.clone().into()), false, None, + None, ) .await?; @@ -884,6 +885,15 @@ async fn update_flow( .await?; } + // Row lock debounce key for path. We need this to make all updates of runnables sequential and predictable. + tokio::time::timeout( + core::time::Duration::from_secs(60), + windmill_common::jobs::lock_debounce_key(&w_id, &nf.path, &mut tx), + ) + .warn_after_seconds(10) + .await??; + + // This will lock anyone who is trying to iterate on flow_versions with given path and parameters. let version = sqlx::query_scalar!( "INSERT INTO flow_version (workspace_id, path, value, schema, created_by) VALUES ($1, $2, $3, $4::text::json, $5) RETURNING id", w_id, @@ -900,6 +910,7 @@ async fn update_flow( )) })?; + // TODO: This should happen only after we are done with dependency job. sqlx::query!( "UPDATE flow SET versions = array_append(versions, $1) WHERE path = $2 AND workspace_id = $3", version, nf.path, w_id @@ -1014,8 +1025,10 @@ async fn update_flow( Some(&authed.clone().into()), false, None, + None, ) .await?; + sqlx::query!( "UPDATE flow SET dependency_job = $1 WHERE path = $2 AND workspace_id = $3", dependency_job_uuid, diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 47c3260fc1..e54a61df9f 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -4101,6 +4101,7 @@ pub async fn run_flow_by_path_inner( push_authed.as_ref(), false, None, + None, ) .await?; @@ -4217,6 +4218,7 @@ pub async fn restart_flow( Some(&authed.clone().into()), false, None, + None, ) .await?; tx.commit().await?; @@ -4320,6 +4322,7 @@ pub async fn run_script_by_path_inner( push_authed.as_ref(), false, None, + None, ) .await?; tx.commit().await?; @@ -4473,6 +4476,7 @@ pub async fn run_workflow_as_code( push_authed.as_ref(), false, None, + None, ) .await?; @@ -5017,6 +5021,7 @@ pub async fn run_wait_result_job_by_path_get( push_authed.as_ref(), false, None, + None, ) .await?; tx.commit().await?; @@ -5170,6 +5175,7 @@ pub async fn run_wait_result_script_by_path_internal( push_authed.as_ref(), false, None, + None, ) .await?; tx.commit().await?; @@ -5287,6 +5293,7 @@ pub async fn run_wait_result_script_by_hash( push_authed.as_ref(), false, None, + None, ) .await?; tx.commit().await?; @@ -5599,6 +5606,7 @@ pub async fn run_wait_result_flow_by_path_internal( push_authed.as_ref(), false, None, + None, ) .await?; @@ -5690,6 +5698,7 @@ async fn run_preview_script( Some(&authed.clone().into()), false, None, + None, ) .await?; tx.commit().await?; @@ -5807,6 +5816,7 @@ async fn run_bundle_preview_script( Some(&authed.clone().into()), false, None, + None, ) .await?; job_id = Some(uuid); @@ -5945,6 +5955,7 @@ async fn run_dependencies_job( Some(&authed.clone().into()), false, None, + None, ) .await?; tx.commit().await?; @@ -6013,6 +6024,7 @@ async fn run_flow_dependencies_job( Some(&authed.clone().into()), false, None, + None, ) .await?; tx.commit().await?; @@ -6357,6 +6369,7 @@ async fn run_preview_flow_job( Some(&authed.clone().into()), false, None, + None, ) .await?; tx.commit().await?; @@ -6531,6 +6544,7 @@ async fn run_dynamic_select( Some(&authed.clone().into()), false, None, + None, ) .await?; tx.commit().await?; @@ -6659,6 +6673,7 @@ pub async fn run_job_by_hash_inner( push_authed.as_ref(), false, None, + None, ) .await?; tx.commit().await?; diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 56107f055e..394e5e62d2 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -769,6 +769,14 @@ async fn create_script_internal<'c>( } }; + // Row lock debounce key for path. We need this to make all updates of runnables sequential and predictable. + tokio::time::timeout( + core::time::Duration::from_secs(60), + windmill_common::jobs::lock_debounce_key(&w_id, &ns.path, &mut tx), + ) + .warn_after_seconds(10) + .await??; + sqlx::query!( "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \ content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ @@ -817,6 +825,7 @@ async fn create_script_internal<'c>( ) .execute(&mut *tx) .await?; + let p_path_opt = parent_hashes_and_perms.as_ref().map(|x| x.p_path.clone()); if let Some(ref p_path) = p_path_opt { sqlx::query!( @@ -1000,6 +1009,7 @@ async fn create_script_internal<'c>( Some(&authed.clone().into()), false, None, + None, ) .await?; Ok((hash, new_tx, None)) diff --git a/backend/windmill-api/src/triggers/trigger_helpers.rs b/backend/windmill-api/src/triggers/trigger_helpers.rs index 5da92541bb..993f458343 100644 --- a/backend/windmill-api/src/triggers/trigger_helpers.rs +++ b/backend/windmill-api/src/triggers/trigger_helpers.rs @@ -861,6 +861,7 @@ async fn trigger_script_with_retry_and_error_handler( push_authed.as_ref(), false, None, + None, ) .await?; tx.commit().await?; diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index 1c41bdc9b5..d9644fbba9 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -188,6 +188,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: tokio::time::error::Elapsed) -> Self { + Self::InternalErr(value.to_string()) + } +} + impl Error { /// https://docs.rs/anyhow/1/anyhow/struct.Error.html#display-representations pub fn alt(&self) -> String { diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index 4608fe72b3..edb9f1f0cf 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -327,6 +327,7 @@ pub enum JobPayload { path: String, apply_preprocessor: bool, }, + ScriptHash { hash: ScriptHash, path: String, @@ -339,51 +340,71 @@ pub enum JobPayload { priority: Option, apply_preprocessor: bool, }, + FlowScript { id: FlowNodeId, // flow_node(id). language: ScriptLang, + /// Override default concurrency key custom_concurrency_key: Option, + /// How many jobs can run at the same time concurrent_limit: Option, + /// In seconds concurrency_time_window_s: Option, cache_ttl: Option, dedicated_worker: Option, path: String, }, + FlowNode { id: FlowNodeId, // flow_node(id). path: String, // flow node inner path (e.g. `outer/branchall-42`). }, + AppScript { id: AppScriptId, // app_script(id). path: Option, language: ScriptLang, cache_ttl: Option, }, + Code(RawCode), + + /// Script Dependency Job Dependencies { path: String, hash: ScriptHash, language: ScriptLang, dedicated_worker: Option, }, + + /// Flow Dependency Job FlowDependencies { path: String, dedicated_worker: Option, version: i64, }, + + /// App Dependency Job AppDependencies { path: String, version: i64, }, + + /// Flow Dependency Job, exposed with API. Requirements can be partially or fully predefined RawFlowDependencies { path: String, flow_value: FlowValue, }, + + /// Dependency Job, exposed with API. Requirements can be predefined RawScriptDependencies { script_path: String, + /// Will reflect raw requirements content (e.g. requirements.txt) content: String, language: ScriptLang, }, + + /// Flow Job Flow { path: String, dedicated_worker: Option, @@ -400,6 +421,8 @@ pub enum JobPayload { path: Option, restarted_from: Option, }, + + /// Flow consisting of single script SingleStepFlow { path: String, hash: Option, @@ -545,7 +568,7 @@ pub async fn script_path_to_payload<'e>( custom_concurrency_key: concurrency_key, concurrent_limit, concurrency_time_window_s, - cache_ttl: cache_ttl, + cache_ttl, language, dedicated_worker, priority, @@ -775,3 +798,26 @@ pub async fn check_tag_available_for_workspace_internal( return Ok(()); } + +pub async fn lock_debounce_key<'c>( + w_id: &str, + runnable_path: &str, + tx: &mut sqlx::Transaction<'c, sqlx::Postgres>, +) -> error::Result> { + let key = format!("{w_id}:{runnable_path}:dependency"); + + tracing::debug!( + workspace_id = %w_id, + runnable_path = %runnable_path, + debounce_key = %key, + "Locking debounce_key for dependency job scheduling" + ); + + sqlx::query_scalar!( + "SELECT job_id FROM debounce_key WHERE key = $1 FOR UPDATE", + &key + ) + .fetch_optional(&mut **tx) + .await + .map_err(error::Error::from) +} diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 32ed0c3c2d..9c1af245d8 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -9,6 +9,7 @@ use std::{ fmt::{self, Display}, hash::{Hash, Hasher}, + ops::Deref, str::FromStr, }; @@ -131,6 +132,13 @@ impl FromStr for ScriptLang { #[sqlx(transparent)] pub struct ScriptHash(pub i64); +impl Deref for ScriptHash { + type Target = i64; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl Into for ScriptHash { fn into(self) -> u64 { self.0 as u64 @@ -217,7 +225,10 @@ const PREVIEW_IS_ESM_CODEBASE_HASH: i64 = -44; const PREVIEW_IS_TAR_ESM_CODEBASE_HASH: i64 = -45; pub fn is_special_codebase_hash(hash: i64) -> bool { - hash == PREVIEW_IS_CODEBASE_HASH || hash == PREVIEW_IS_TAR_CODEBASE_HASH || hash == PREVIEW_IS_ESM_CODEBASE_HASH || hash == PREVIEW_IS_TAR_ESM_CODEBASE_HASH + hash == PREVIEW_IS_CODEBASE_HASH + || hash == PREVIEW_IS_TAR_CODEBASE_HASH + || hash == PREVIEW_IS_ESM_CODEBASE_HASH + || hash == PREVIEW_IS_TAR_ESM_CODEBASE_HASH } pub fn codebase_to_hash(is_tar: bool, is_esm: bool) -> i64 { @@ -236,7 +247,6 @@ pub fn codebase_to_hash(is_tar: bool, is_esm: bool) -> i64 { } } - pub fn hash_to_codebase_id(job_id: &str, hash: i64) -> Option { match hash { PREVIEW_IS_CODEBASE_HASH => Some(job_id.to_string()), @@ -247,7 +257,6 @@ pub fn hash_to_codebase_id(job_id: &str, hash: i64) -> Option { } } - pub struct CodebaseInfo { pub is_tar: bool, pub is_esm: bool, @@ -714,3 +723,91 @@ pub fn hash_script(ns: &NewScript) -> i64 { ns.hash(&mut dh); dh.finish() as i64 } + +pub async fn clone_script<'c>( + base_hash: ScriptHash, + w_id: &str, + deployment_message: Option, + tx: &mut sqlx::Transaction<'c, sqlx::Postgres>, +) -> crate::error::Result { + let s = + sqlx::query_as::<_, Script>("SELECT * FROM script WHERE hash = $1 AND workspace_id = $2") + .bind(base_hash.0) + .bind(w_id) + .fetch_one(&mut **tx) + .await?; + + let ns = NewScript { + path: s.path.clone(), + parent_hash: Some(base_hash), + summary: s.summary, + description: s.description, + content: s.content, + schema: s.schema, + is_template: Some(s.is_template), + // TODO: Make it either None everywhere (particularly when raw reqs are calculated) + // Or handle this case and conditionally make Some (only with raw reqs) + lock: None, + language: s.language, + kind: Some(s.kind), + tag: s.tag, + draft_only: s.draft_only, + envs: s.envs, + concurrent_limit: s.concurrent_limit, + concurrency_time_window_s: s.concurrency_time_window_s, + cache_ttl: s.cache_ttl, + dedicated_worker: s.dedicated_worker, + ws_error_handler_muted: s.ws_error_handler_muted, + priority: s.priority, + timeout: s.timeout, + delete_after_use: s.delete_after_use, + restart_unless_cancelled: s.restart_unless_cancelled, + deployment_message, + concurrency_key: s.concurrency_key, + visible_to_runner_only: s.visible_to_runner_only, + no_main_func: s.no_main_func, + codebase: s.codebase, + has_preprocessor: s.has_preprocessor, + on_behalf_of_email: s.on_behalf_of_email, + assets: s.assets, + }; + + let new_hash = hash_script(&ns); + + tracing::debug!( + "cloning script at path {} from '{}' to '{}'", + s.path, + *base_hash, + new_hash + ); + + sqlx::query!(" + INSERT INTO script + (workspace_id, hash, path, parent_hashes, summary, description, content, \ + created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ + draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ + dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ + delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, \ + codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) + + SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, \ + content, created_by, schema, is_template, extra_perms, NULL, language, kind, tag, \ + draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ + dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ + delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, \ + codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets + + FROM script WHERE hash = $2 AND workspace_id = $3; + ", new_hash, base_hash.0, w_id).execute(&mut **tx).await?; + + // Archive base. + sqlx::query!( + "UPDATE script SET archived = true WHERE hash = $1 AND workspace_id = $2", + *base_hash, + w_id + ) + .execute(&mut **tx) + .await?; + + Ok(new_hash) +} diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 5cec1ac6f2..8dd2f4d3f8 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -111,6 +111,9 @@ lazy_static::lazy_static! { .ok() .and_then(|x| x.parse().ok()) .unwrap_or(false); + + // TODO: Remove + static ref WMDEBUG_NO_DJOB_DEBOUNCING: bool = std::env::var("WMDEBUG_NO_DJOB_DEBOUNCING").is_ok(); } #[cfg(feature = "cloud")] @@ -450,6 +453,7 @@ pub async fn push_init_job<'c>( None, false, None, + None, ) .await?; inner_tx.commit().await?; @@ -505,6 +509,7 @@ pub async fn push_periodic_bash_job<'c>( None, false, None, + None, ) .await?; inner_tx.commit().await?; @@ -1401,6 +1406,7 @@ async fn restart_job_if_perpetual_inner( None, false, None, + None, ) .await?; tx.commit().await?; @@ -1889,6 +1895,7 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>( None, false, None, + None, ) .await?; tx.commit().await?; @@ -2180,6 +2187,12 @@ impl std::ops::Deref for PulledJob { } } +impl std::ops::DerefMut for PulledJob { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.job + } +} + lazy_static::lazy_static! { pub static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true"); } @@ -2270,10 +2283,13 @@ impl PulledJobResult { } } +/// Pull the job from queue pub async fn pull( db: &Pool, + // Whether or not try to pull from suspended jobs first suspend_first: bool, worker_name: &str, + // Execute queries supplied by caller instead of generic one query_o: Option<&(String, String)>, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, ) -> windmill_common::error::Result { @@ -2292,6 +2308,7 @@ pub async fn pull( missing_concurrency_key: false, }); } + if let Some((query_suspended, query_no_suspend)) = query_o { let njob = { let job = if query_suspended.is_empty() { @@ -2313,9 +2330,13 @@ pub async fn pull( (job, false) }; - #[cfg(all(feature = "enterprise", feature = "private"))] let pulled_job_result = match job { - Some(job) if job.concurrent_limit.is_some() => { + #[cfg(feature = "private")] + Some(job) + if job.concurrent_limit.is_some() + // Concurrency limit is available for either enterprise job or dependency job + && (cfg!(feature = "enterprise") || (job.is_dependency() && !*WMDEBUG_NO_DJOB_DEBOUNCING)) => + { let job = crate::jobs_ee::apply_concurrency_limit( db, pull_loop_count, @@ -2332,10 +2353,6 @@ pub async fn pull( _ => PulledJobResult { job, suspended, missing_concurrency_key: false }, }; - #[cfg(not(all(feature = "enterprise", feature = "private")))] - let pulled_job_result = - PulledJobResult { job, suspended, missing_concurrency_key: false }; - Ok::<_, Error>(pulled_job_result) }?; @@ -2364,6 +2381,7 @@ pub async fn pull( } return Ok(njob); }; + let (job, suspended) = pull_single_job_and_mark_as_running_no_concurrency_limit( db, suspend_first, @@ -2372,7 +2390,6 @@ pub async fn pull( bench, ) .await?; - let Some(job) = job else { return Ok(PulledJobResult { job: None, suspended, missing_concurrency_key: false }); }; @@ -2380,12 +2397,14 @@ pub async fn pull( let has_concurent_limit = job.concurrent_limit.is_some(); #[cfg(not(feature = "enterprise"))] - if has_concurent_limit { + if has_concurent_limit && !job.is_dependency() { tracing::error!("Concurrent limits are an EE feature only, ignoring constraints") } #[cfg(not(feature = "enterprise"))] - let has_concurent_limit = false; + let has_concurent_limit = false + || (job.is_dependency() && cfg!(feature = "private") && !*WMDEBUG_NO_DJOB_DEBOUNCING); + // if we don't have private flag, we don't have concurrency limit // concurrency check. If more than X jobs for this path are already running, we re-queue and pull another job from the queue let pulled_job = job; @@ -2404,12 +2423,16 @@ pub async fn pull( }); } - #[cfg(all(feature = "enterprise", feature = "private"))] - if let Some(pulled_job) = - crate::jobs_ee::apply_concurrency_limit(db, pull_loop_count, suspended, pulled_job) - .await? + #[cfg(feature = "private")] + if cfg!(feature = "enterprise") + || (pulled_job.is_dependency() && !*WMDEBUG_NO_DJOB_DEBOUNCING) { - return Ok(pulled_job); + if let Some(pulled_job) = + crate::jobs_ee::apply_concurrency_limit(db, pull_loop_count, suspended, pulled_job) + .await? + { + return Ok(pulled_job); + } } } } @@ -2444,6 +2467,7 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<'c>( } else { None }; + if r.is_none() { // #[cfg(feature = "benchmark")] // let instant = Instant::now(); @@ -2531,6 +2555,69 @@ pub async fn concurrency_key( }) } +pub async fn custom_debounce_key( + db: &Pool, + job_id: &Uuid, +) -> Result, sqlx::Error> { + let fut = async || { + sqlx::query_scalar!("SELECT key FROM debounce_key WHERE job_id = $1", job_id) + .fetch_optional(db) // this should no longer be fetch optional + .await + }; + fut.retry( + ConstantBuilder::default() + .with_delay(std::time::Duration::from_secs(3)) + .with_max_times(5) + .build(), + ) + .notify(|err, dur| { + tracing::error!( + "Could not get debounce key for job {job_id}, retrying in {dur:#?}, err: {err:#?}" + ); + }) + .await +} + +/// Helper function to extract nodes/components to relock from job arguments +/// Returns the list of nodes to relock if present in either nodes_to_relock (flows) or components_to_relock (apps) +fn extract_to_relock_from_args(args: &HashMap>) -> Option> { + args.get("nodes_to_relock") // For flows + .or(args.get("components_to_relock")) // For apps + .and_then(|rv| { + serde_json::from_str::>(&rv.to_string()) + .map_err(|e| tracing::warn!("Failed to deserialize relock data: {}", e)) + .ok() + }) +} + +/// Helper function to accumulate nodes/components to relock for a debounced job +/// This merges new items with existing ones, removing duplicates +async fn accumulate_debounce_stale_data( + tx: &mut Transaction<'_, Postgres>, + job_id: &Uuid, + to_relock: &[String], +) -> Result<(), Error> { + sqlx::query!( + " + INSERT INTO debounce_stale_data (job_id, to_relock) + VALUES ($1, $2) + ON CONFLICT (job_id) + DO UPDATE SET to_relock = ( + SELECT array_agg(DISTINCT x) + FROM unnest( + -- Combine existing array with new values, removing duplicates + array_cat(debounce_stale_data.to_relock, EXCLUDED.to_relock) + ) AS x + ) + ", + job_id, + to_relock + ) + .execute(&mut **tx) + .await?; + Ok(()) +} + pub fn interpolate_args(x: String, args: &PushArgs, workspace_id: &str) -> String { // Save this value to avoid parsing twice let workspaced = x.as_str().replace("$workspace", workspace_id).to_string(); @@ -3183,6 +3270,8 @@ pub async fn push<'c, 'd>( authed: Option<&Authed>, running: bool, // whether the job is already running: only set this to true if you don't want the job to be picked up by a worker from the queue. It will also set started_at to now. end_user_email: Option, + // If we know there is already a debounce job, we can use this for debouncing. + debounce_job_id_o: Option, ) -> Result<(Uuid, Transaction<'c, Postgres>), Error> { #[cfg(feature = "cloud")] if *CLOUD_HOSTED { @@ -3383,8 +3472,8 @@ pub async fn push<'c, 'd>( raw_flow, flow_status, language, - custom_concurrency_key, - concurrent_limit, + mut custom_concurrency_key, + mut concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, @@ -3555,7 +3644,7 @@ pub async fn push<'c, 'd>( ), JobPayload::Dependencies { hash, language, path, dedicated_worker } => ( Some(hash.0), - Some(path), + Some(path.clone()), None, JobKind::Dependencies, None, @@ -3568,6 +3657,8 @@ pub async fn push<'c, 'd>( dedicated_worker, None, ), + + // CLI usage, is not modifying db, no need for debouncing. JobPayload::RawScriptDependencies { script_path, content, language } => ( None, Some(script_path), @@ -3583,6 +3674,8 @@ pub async fn push<'c, 'd>( None, None, ), + + // CLI usage, is not modifying db, no need for debouncing. JobPayload::RawFlowDependencies { path, flow_value } => ( None, Some(path), @@ -3599,9 +3692,17 @@ pub async fn push<'c, 'd>( None, ), JobPayload::FlowDependencies { path, dedicated_worker, version } => { + #[cfg(test)] + let skip_compat = args + .args + .contains_key("dbg_create_job_for_unexistant_flow_version"); + + #[cfg(not(test))] + let skip_compat = false; + // Keep inserting `value` if not all workers are updated. // Starting at `v1.440`, the value is fetched on pull from the version id. - let value_o = if !*MIN_VERSION_IS_AT_LEAST_1_440.read().await { + let value_o = if !*MIN_VERSION_IS_AT_LEAST_1_440.read().await && !skip_compat { let mut ntx = tx.into_tx().await?; // The version has been inserted only within the transaction. let data = cache::flow::fetch_version(&mut *ntx, version).await?; @@ -3613,7 +3714,7 @@ pub async fn push<'c, 'd>( }; ( Some(version), - Some(path), + Some(path.clone()), None, JobKind::FlowDependencies, value_o, @@ -3629,7 +3730,7 @@ pub async fn push<'c, 'd>( } JobPayload::AppDependencies { path, version } => ( Some(version), - Some(path), + Some(path.clone()), None, JobKind::AppDependencies, None, @@ -4088,6 +4189,19 @@ pub async fn push<'c, 'd>( ), }; + // Enforce concurrency limit on all dependency jobs. + // TODO: We can ignore this for scripts djobs. The main reason we need all djobs to be sequential is because we have + // nodes_to_relock and we need all locks whose corresponding steps aren't in nodes_to_relock be already present. + // + // This is not the case for scripts, so we can potentially have multiple djobs for scripts at the same time. + if let (Some(path), true) = ( + &script_path, + cfg!(feature = "private") && job_kind.is_dependency() && !*WMDEBUG_NO_DJOB_DEBOUNCING, + ) { + custom_concurrency_key = Some(format!("dependency:{workspace_id}/{path}")); + concurrent_limit = Some(1); + } + let final_priority: Option; #[cfg(not(feature = "enterprise"))] { @@ -4218,6 +4332,184 @@ pub async fn push<'c, 'd>( Ulid::new().into() }; + // Dependency job debouncing: When multiple dependency jobs are scheduled for the same script/flow/app, + // we want to deduplicate them to avoid redundant work. The debouncing mechanism works by: + // 1. Creating a unique debounce key for each dependency target (dependency:workspace/type/path) + // 2. Reusing existing jobs when possible, or creating new ones when the existing job is already running + // 3. Accumulating the nodes/components that need relocking across all debounced requests + match ( + scheduled_for_o.is_some(), + job_kind.is_dependency(), + script_path.clone(), + *WMDEBUG_NO_DJOB_DEBOUNCING, + // We only do debouncing for jobs triggered by relative imports + // We do not want this be the case for normal djobs, since they will always be sequential. + args.args.contains_key("triggered_by_relative_import"), + ) { + // === DEPENDENCY JOB DEBOUNCING === + // + // Debouncing consolidates multiple dependency job requests into a single execution, + // reducing redundant work when many scripts/flows/apps are updated simultaneously. + // + // Prerequisites for debouncing (all must be true): + // 1. Job is scheduled in the future (debounce_delay is not None) - provides consolidation window + // 2. Job is a dependency job + // 3. Object path is provided (script/flow/app path) + // 4. Fallback mode is disabled (normal operation) + // 5. Job was created by relative imports (triggered by dependency chain) + // + // How it works: + // + // PHASE 1 - PUSH (in jobs.rs::push): + // When a dependency job is scheduled with delay, check debounce_key table + // - If key exists: Merge request into existing job, accumulate nodes/components + // - If key doesn't exist: Create new entry and store initial nodes/components + // + // PHASE 2 - ACCUMULATION: + // During the debounce window (typically 5-15 seconds), multiple requests merge + // - Each request adds nodes/components to debounce_stale_data table + // - SQL DISTINCT automatically removes duplicates during merge + // + // PHASE 3 - PULL (in jobs.rs::pull): + // When the delayed job finally executes: + // - Lock debounce_key FOR UPDATE to prevent races + // - Retrieve all accumulated nodes/components from debounce_stale_data + // - Process all collected dependencies in single execution + // - Clean up both debounce_key and debounce_stale_data entries + (true, true, Some(obj_path), false, true) => { + // Generate unique debounce key: "workspace_id:object_path:dependency" + // This ensures each workspace+path combination has independent debounce window + let debounce_key = format!("{workspace_id}:{obj_path}:dependency"); + + tracing::debug!( + workspace_id = %workspace_id, + object_path = %obj_path, + debounce_key = %debounce_key, + "Checking for existing debounced dependency job" + ); + + // Check if there's already a pending job registered for this debounce key + // The debounce_job_id_o is passed in by the caller after locking the key FOR UPDATE + // IMPORTANT: This is assumed that the caller will lock debounce_key row in this transaction. + // We do this to block puller from further actions until we are done with consolidation and stuff that we do here in push. + if let Some(debounce_job_id) = debounce_job_id_o { + tracing::debug!( + existing_job_id = %debounce_job_id, + new_job_id = %job_id, + "Found existing debounced job, merging this request" + ); + + // NOTE: Race condition handling: + // In rare cases, the debounce_key entry may still exist even though the job + // has been pulled and is running. This can happen because: + // - Job pull marks job as running first + // - Then debounce_key cleanup happens (without transaction for performance) + // - Between these steps, new requests might see the old debounce_key + // + // This is acceptable because the puller will be blocked and cannot proceed until this transaction finishes. + // This will give us some space to add consolidated data (if such) and debounce the request. + // Once tx is commited, the puller will be unblocked and continue execution. + // Accumulate the nodes/components that need relocking from this request + + // This ensures all dependency updates are handled even if jobs are debounced + if let Some(to_relock) = extract_to_relock_from_args(&args.args) { + tracing::debug!( + job_id = %debounce_job_id, + node_count = to_relock.len(), + nodes = ?to_relock, + "Accumulating nodes/components to existing debounced job" + ); + + accumulate_debounce_stale_data(&mut tx, &debounce_job_id, &to_relock) + .await + .map_err(|e| { + tracing::error!( + error = %e, + job_id = %debounce_job_id, + debounce_key = %debounce_key, + "Failed to accumulate stale data for debounced job" + ); + e + })?; + } else { + tracing::trace!( + job_id = %debounce_job_id, + "No nodes to relock in this request, skipping accumulation" + ); + } + + // Return the existing job ID, effectively debouncing this request + // The new job_id we generated won't be used + tracing::debug!( + returned_job_id = %debounce_job_id, + skipped_job_id = %job_id, + "Debounced: returning existing job ID instead of creating new job" + ); + + // We will skip some of the work downstream and just debounce the job. + return Ok((debounce_job_id, tx)); + } else { + // No existing debounce entry - this is the first request in the debounce window + tracing::debug!( + job_id = %job_id, + debounce_key = %debounce_key, + "Creating new debounce entry (first request in window)" + ); + + sqlx::query!( + "INSERT INTO debounce_key (key, job_id) VALUES ($1, $2)", + &debounce_key, + job_id, + ) + .execute(&mut *tx) + .await + .map_err(|e| { + tracing::error!( + error = %e, + debounce_key = %debounce_key, + job_id = %job_id, + "Failed to insert debounce_key entry" + ); + Error::InternalErr(format!("Failed to create debounce entry: {}", e)) + })?; + + // Store initial nodes/components to relock if provided + if let Some(to_relock) = extract_to_relock_from_args(&args.args) { + tracing::debug!( + job_id = %job_id, + node_count = to_relock.len(), + nodes = ?to_relock, + "Storing initial nodes/components for new debounced job" + ); + + accumulate_debounce_stale_data(&mut tx, &job_id, &to_relock) + .await + .map_err(|e| { + tracing::error!( + error = %e, + job_id = %job_id, + "Failed to store initial stale data for debounced job" + ); + e + })?; + } else { + tracing::trace!( + job_id = %job_id, + "No initial nodes to relock, debounce entry created without stale data" + ); + } + } + } + _ => { + // Debouncing not applicable - proceed with normal job creation + tracing::trace!( + job_id = %job_id, + job_kind = ?job_kind, + "Debouncing conditions not met, proceeding with normal job creation" + ); + } + }; + if concurrent_limit.is_some() { insert_concurrency_key( workspace_id, @@ -4230,7 +4522,6 @@ pub async fn push<'c, 'd>( ) .await?; } - let stringified_args = if *JOB_ARGS_AUDIT_LOGS { Some(serde_json::to_string(&args).map_err(|e| { Error::internal_err(format!( @@ -4520,6 +4811,39 @@ pub async fn insert_concurrency_key<'d, 'c>( Ok(()) } +// pub async fn insert_debounce_key<'d, 'c>( +// workspace_id: &str, +// args: &PushArgs<'d>, +// script_path: &Option, +// job_kind: JobKind, +// custom_concurrency_key: Option, +// tx: &mut Transaction<'c, Postgres>, +// job_id: Uuid, +// ) -> Result<(), Error> { +// let concurrency_key = custom_concurrency_key +// .map(|x| interpolate_args(x, args, workspace_id)) +// .unwrap_or(fullpath_with_workspace( +// workspace_id, +// script_path.as_ref(), +// &job_kind, +// )); +// sqlx::query!( +// "WITH inserted_concurrency_counter AS ( +// INSERT INTO concurrency_counter (concurrency_id, job_uuids) +// VALUES ($1, '{}'::jsonb) +// ON CONFLICT DO NOTHING +// ) +// INSERT INTO concurrency_key(key, job_id) VALUES ($1, $2)", +// concurrency_key, +// job_id, +// ) +// .execute(&mut **tx) +// .warn_after_seconds(3) +// .await +// .map_err(|e| Error::internal_err(format!("Could not insert concurrency_key={concurrency_key} for job_id={job_id} script_path={script_path:?} workspace_id={workspace_id}: {e:#}")))?; +// Ok(()) +// } + pub fn canceled_job_to_result(job: &MiniPulledJob) -> serde_json::Value { let reason = job .canceled_reason @@ -4809,3 +5133,232 @@ pub async fn get_same_worker_job( )) }) } + +pub async fn preprocess_dependency_job(job: &mut PulledJob, db: &DB) -> error::Result<()> { + let kind = job.kind; + // Handle dependency job debouncing cleanup when a job is pulled for execution + if kind.is_dependency() && !*WMDEBUG_NO_DJOB_DEBOUNCING { + // Only used for testing in tests/relative_imports.rs + // Give us some space to work with. + #[cfg(debug_assertions)] + if let Some(duration) = job + .args + .as_ref() + .map(|x| { + x.get("dbg_sleep_between_pull_and_debounce_key_removal") + .map(|v| serde_json::from_str::(v.get()).ok()) + .flatten() + }) + .flatten() + { + tracing::debug!("going to sleep",); + sleep(std::time::Duration::from_secs(duration as u64)).await; + } + + tracing::debug!( + "Processing debounce cleanup for dependency job {} at path {:?}", + &job.id, + &job.runnable_path + ); + + let key = format!("{}:{}:dependency", &job.workspace_id, job.runnable_path()); + let mut tx = db.begin().await?; + + // === DEBOUNCE CLEANUP === + // + // Clean up the debounce_key entry for this job (if it exists). + // + // IMPORTANT: We delete by key (not job_id) to avoid race conditions: + // If pusher has locked this row then this call will be blocked until all txs are commited. + // + // The idea is that the worker_lockfiles::trigger_dependents_to_recompute_locks will fetch the latest version of the obj. + // This object needs to be created before the djob is executed and it happens right here. + // + // This way the next pusher can fetch the latest version of object and base their djob payload on newest version. + // The concurrency limit on djobs will make sure that by the time next djob is started executing the base version it is referencing + // has already calculated all locks. This way even next djob will always use the fully finalized version of object. + // + // + // + // Note: We don't use a transaction here for performance (it's called during job pull). + // This means there's a tiny window where the job is running but key isn't deleted yet, + // which is acceptable because new requests will just accumulate data to this job. + tracing::debug!( + job_id = %job.id, + "Cleaning up debounce_key entry for completed/pulled job" + ); + + // This will either: + // 1. Block until pusher pushed. Which gives us: + // - If there was any stale data in pusher, then we will read it here (couple of lines below) + // 2. Block pusher until we are done here. This gives us: + // - We will clone objects and retrieve the latest version. So when we are done the pusher can read latest version. + sqlx::query!("DELETE FROM debounce_key WHERE key = $1", &key) + .execute(&mut *tx) + .await + .map_err(|e| { + tracing::error!( + error = %e, + job_id = %job.id, + "Failed to delete debounce_key" + ); + e + })?; + + if job + .args + .as_ref() + .map(|x| x.get("triggered_by_relative_import").is_some()) + .unwrap_or_default() + { + let Some(base_hash) = job.runnable_id else { + return Err(Error::InternalErr( + "Missing runnable_id for dependency job triggered by relative import" + .to_string(), + )); + }; + + tracing::debug!( + job_id = %job.id, + base_hash = %base_hash, + job_kind = ?kind, + "Creating new version for dependency job triggered by relative import" + ); + + let new_id = match kind { + JobKind::Dependencies => { + let deployment_message = job + .args + .clone() + .map(|hashmap| { + hashmap + .get("deployment_message") + .map(|map_value| { + serde_json::from_str::(map_value.get()).ok() + }) + .flatten() + }) + .flatten(); + + // This way we tell downstream which script we should archive when the resolution is finished. + // (not used at the moment) + job.args + .as_mut() + .map(|args| args.insert("base_hash".to_owned(), to_raw_value(&*base_hash))); + + let new_hash = windmill_common::scripts::clone_script( + base_hash, + &job.workspace_id, + deployment_message, + &mut tx, + ) + .await?; + + new_hash + } + JobKind::FlowDependencies => { + sqlx::query_scalar!( + "INSERT INTO flow_version + (workspace_id, path, value, schema, created_by) + + SELECT workspace_id, path, value, schema, created_by + FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3 + + RETURNING id + ", + job.runnable_path(), + job.workspace_id, + *base_hash, + ) + .fetch_one(&mut *tx) + .await? + } + JobKind::AppDependencies => { + sqlx::query_scalar!( + "INSERT INTO app_version + (app_id, value, created_by, raw_app) + SELECT app_id, value, created_by, raw_app + FROM app_version WHERE id = $1 + RETURNING id", + *base_hash + ) + .fetch_one(&mut *tx) + .await? + } + _ => { + return Err(Error::InternalErr(format!( + "Matched unexpected JobKind ({:?}). This is a bug!", + kind + ))) + } + }; + + job.runnable_id.replace(new_id.into()); + } + + // === RETRIEVE ACCUMULATED DEBOUNCE DATA === + // + // For flows and apps, retrieve all nodes/components that were accumulated + // during the debounce window. This data comes from requests that were merged + // into this job instead of creating their own jobs. + // + // Scripts don't need this because they don't have nodes/components to relock. + if let Some(to_relock_field) = match &job.kind { + JobKind::FlowDependencies => Some("nodes_to_relock"), + JobKind::AppDependencies => Some("components_to_relock"), + _ => None, // Scripts don't use accumulated stale data + } { + tracing::debug!( + job_id = %job.id, + job_kind = ?job.kind, + field = %to_relock_field, + "Retrieving accumulated stale data from debounced requests" + ); + + if let Some(stale_data) = sqlx::query_scalar!( + "DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock", + &job.id + ) + .fetch_optional(&mut *tx) + .await + .map_err(|e| { + tracing::error!( + error = %e, + job_id = %job.id, + "Failed to retrieve debounce_stale_data" + ); + e + })? + .flatten() + { + tracing::debug!( + job_id = %job.id, + node_count = stale_data.len(), + nodes = ?stale_data, + "Retrieved accumulated nodes/components from {} debounced requests", + stale_data.len() + ); + + // Replace the job's relock list with the accumulated data + // This ensures all nodes from all debounced requests are processed + if let Some(args) = job.args.as_mut() { + args.insert(to_relock_field.to_owned(), to_raw_value(&stale_data)); + tracing::debug!( + field = %to_relock_field, + "Updated job args with accumulated debounce data" + ); + } + } else { + tracing::trace!( + job_id = %job.id, + "No accumulated stale data found (no debounced requests or already cleaned up)" + ); + } + } + + // This will unblock pusher. + tx.commit().await?; + } + + Ok(()) +} diff --git a/backend/windmill-queue/src/schedule.rs b/backend/windmill-queue/src/schedule.rs index c35a94a2dc..1aa4a11a10 100644 --- a/backend/windmill-queue/src/schedule.rs +++ b/backend/windmill-queue/src/schedule.rs @@ -23,8 +23,8 @@ use windmill_common::jobs::check_tag_available_for_workspace_internal; use windmill_common::jobs::JobPayload; use windmill_common::schedule::schedule_to_user; use windmill_common::scripts::ScriptHash; -use windmill_common::worker::to_raw_value; use windmill_common::utils::WarnAfterExt; +use windmill_common::worker::to_raw_value; use windmill_common::FlowVersionInfo; use windmill_common::DB; use windmill_common::{ @@ -39,13 +39,13 @@ async fn get_schedule_metadata<'c>( tx: &mut sqlx::Transaction<'c, sqlx::Postgres>, schedule: &Schedule, ) -> Result<( - Option, // tag - Option, // timeout - Option, // on_behalf_of_email - String, // created_by - Option, // hash (for scripts) - Option, // flow_version (for flows) - Option, // retry + Option, // tag + Option, // timeout + Option, // on_behalf_of_email + String, // created_by + Option, // hash (for scripts) + Option, // flow_version (for flows) + Option, // retry )> { let parsed_retry = schedule .retry @@ -62,20 +62,24 @@ async fn get_schedule_metadata<'c>( ) .await?; - let FlowVersionInfo { + let FlowVersionInfo { tag, on_behalf_of_email, edited_by, .. } = + get_latest_flow_version_info_for_path_from_version( + &mut **tx, + version, + &schedule.workspace_id, + &schedule.script_path, + ) + .await?; + + Ok(( tag, + None, on_behalf_of_email, edited_by, - .. - } = get_latest_flow_version_info_for_path_from_version( - &mut **tx, - version, - &schedule.workspace_id, - &schedule.script_path, - ) - .await?; - - Ok((tag, None, on_behalf_of_email, edited_by, None, Some(version), parsed_retry)) + None, + Some(version), + parsed_retry, + )) } else { let ( hash, @@ -98,7 +102,15 @@ async fn get_schedule_metadata<'c>( ) .await?; - Ok((tag, timeout, on_behalf_of_email, created_by, Some(hash), None, parsed_retry)) + Ok(( + tag, + timeout, + on_behalf_of_email, + created_by, + Some(hash), + None, + parsed_retry, + )) } } @@ -208,7 +220,9 @@ pub async fn push_scheduled_job<'c>( // If schedule handler is defined, wrap the scheduled job in a synthetic flow // with the handler as the first step (with stop_after_if to skip if handler returns false) - let (payload, tag, timeout, on_behalf_of_email, created_by) = if let Some(handler_path) = &schedule.dynamic_skip { + let (payload, tag, timeout, on_behalf_of_email, created_by) = if let Some(handler_path) = + &schedule.dynamic_skip + { // Build skip handler args let mut skip_handler_args = HashMap::>::new(); skip_handler_args.insert( @@ -472,6 +486,7 @@ pub async fn push_scheduled_job<'c>( push_authed, false, None, + None, ) .warn_after_seconds_with_sql(1, "push in push_scheduled_job".to_string()) .await?; diff --git a/backend/windmill-worker/src/ai_executor.rs b/backend/windmill-worker/src/ai_executor.rs index 01a4b36b71..bbd40979fe 100644 --- a/backend/windmill-worker/src/ai_executor.rs +++ b/backend/windmill-worker/src/ai_executor.rs @@ -969,6 +969,7 @@ pub async fn run_agent( job_perms.as_ref(), true, None, + None, ) .await?; diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 893cb9ef15..39f1944d6d 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -11,6 +11,7 @@ use anyhow::anyhow; use futures::TryFutureExt; +use tokio::time::sleep; use tokio::time::timeout; use windmill_common::client::AuthedClient; use windmill_common::scripts::hash_to_codebase_id; @@ -57,6 +58,7 @@ use std::{ time::Duration, }; use windmill_parser::MainArgSignature; +use windmill_queue::preprocess_dependency_job; use windmill_queue::PulledJobResultToJobErr; use uuid::Uuid; @@ -954,6 +956,7 @@ pub async fn run_worker( ); } + dbg!("start"); let start_time = Instant::now(); let worker_dir = format!("{TMP_DIR}/{worker_name}"); @@ -994,6 +997,8 @@ pub async fn run_worker( }); } + dbg!("python stuff is done"); + if let Some(ref netrc) = *NETRC { tracing::info!(worker = %worker_name, hostname = %hostname, "Writing netrc at {}/.netrc", HOME_ENV.as_str()); write_file(&HOME_ENV, ".netrc", netrc).expect("could not write netrc"); @@ -1001,6 +1006,8 @@ pub async fn run_worker( create_directory_async(&worker_dir).await; + dbg!("worker dir created"); + if !*DISABLE_NSJAIL { let _ = write_file( &worker_dir, @@ -1383,6 +1390,7 @@ pub async fn run_worker( let mut killpill_rx2 = killpill_rx.resubscribe(); + dbg!("starting loop"); loop { let last_processing_duration_secs = last_processing_duration.load(Ordering::SeqCst); if last_processing_duration_secs > 5 { @@ -1503,7 +1511,6 @@ pub async fn run_worker( match &conn { Connection::Sql(db) => { let job = get_same_worker_job(db, &same_worker_job).await; - // tracing::error!("r: {:?}", r); if job.is_err() && !same_worker_job.recoverable { tracing::error!( worker = %worker_name, hostname = %hostname, @@ -1559,6 +1566,7 @@ pub async fn run_worker( Connection::Sql(db) => { let pull_time = Instant::now(); let likelihood_of_suspend = last_30jobs_suspended as f64 / 30.0; + let suspend_first = suspend_first_success || rand::random::() < likelihood_of_suspend || last_suspend_first.elapsed().as_secs_f64() > 5.0; @@ -1566,8 +1574,7 @@ pub async fn run_worker( if suspend_first { last_suspend_first = Instant::now(); } - - let job = match timeout( + let mut job = match timeout( Duration::from_secs(10), pull( &db, @@ -1589,6 +1596,31 @@ pub async fn run_worker( } }; + // Essential debouncing job preprocessing. + if let Ok(windmill_queue::PulledJobResult { + job: Some(ref mut pulled_job), + .. + }) = &mut job + { + match timeout( + core::time::Duration::from_secs(10), + preprocess_dependency_job(pulled_job, &db), + ) + .warn_after_seconds(2) + .await + { + Ok(Err(e)) => { + tracing::error!(worker = %worker_name, hostname = %hostname, "critical: debouncing job preprocessor failed: {e:?}"); + job = Err(e.into()); + } + Err(e) => { + tracing::error!(worker = %worker_name, hostname = %hostname, "critical: debouncing job preprocessor has timed out: {e:?}"); + job = Err(e.into()); + } + _ => {} + } + } + add_time!(bench, "job pulled from DB"); let duration_pull_s = pull_time.elapsed().as_secs_f64(); let err_pull = job.is_ok(); @@ -1658,6 +1690,7 @@ pub async fn run_worker( Err(err) => Err(err), } } + Connection::Http(client) => crate::agent_workers::pull_job(&client, None, None) .await .map_err(|e| error::Error::InternalErr(e.to_string())) @@ -2527,6 +2560,23 @@ pub async fn handle_queued_job( logs.push_str("---\n"); } + // Only used for testing in tests/relative_imports.rs + // Give us some space to work with. + #[cfg(debug_assertions)] + if let Some(dbg_djob_sleep) = job + .args + .as_ref() + .map(|x| { + x.get("dbg_djob_sleep") + .map(|v| serde_json::from_str::(v.get()).ok()) + .flatten() + }) + .flatten() + { + tracing::debug!("Debug: {} going to sleep for {}", job.id, dbg_djob_sleep); + sleep(std::time::Duration::from_secs(dbg_djob_sleep as u64)).await; + } + tracing::debug!( workspace_id = %job.workspace_id, "handling job {}", @@ -2564,7 +2614,7 @@ pub async fn handle_queued_job( JobKind::FlowDependencies => match conn { Connection::Sql(db) => { handle_flow_dependency_job( - &job, + (*job).clone(), preview_data.as_ref(), &mut mem_peak, &mut canceled_by, @@ -2586,7 +2636,7 @@ pub async fn handle_queued_job( }, JobKind::AppDependencies => match conn { Connection::Sql(db) => handle_app_dependency_job( - &job, + (*job).clone(), &mut mem_peak, &mut canceled_by, job_dir, diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index ad5b40ee4a..7a75068fcf 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -3160,6 +3160,7 @@ async fn push_next_flow_job( job_perms.as_ref(), false, None, + None, ) .warn_after_seconds(2) .await?; diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 59f0f87552..f4fbf5bb92 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -5,21 +5,23 @@ use std::path::{Component, Path, PathBuf}; #[cfg(feature = "python")] use crate::ansible_executor::{get_git_repos_lock, AnsibleDependencyLocks}; -use crate::scoped_dependency_map::{ScopedDependencyMap, WMDEBUG_NO_DMAP_DISSOLVE}; +use crate::scoped_dependency_map::ScopedDependencyMap; use async_recursion::async_recursion; +use chrono::{Duration, Utc}; use itertools::Itertools; use serde_json::value::RawValue; use serde_json::{from_value, json, Value}; use sha2::Digest; use sqlx::types::Json; +use tokio::time::timeout; use uuid::Uuid; use windmill_common::assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind}; use windmill_common::error::Error; use windmill_common::error::Result; use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId}; -use windmill_common::get_latest_deployed_hash_for_path; use windmill_common::jobs::JobPayload; -use windmill_common::scripts::{hash_script, NewScript, ScriptHash}; +use windmill_common::scripts::ScriptHash; +use windmill_common::utils::WarnAfterExt; #[cfg(feature = "python")] use windmill_common::worker::PythonAnnotations; use windmill_common::worker::{to_raw_value, to_raw_value_owned, write_file, Connection}; @@ -46,6 +48,9 @@ lazy_static::lazy_static! { static ref WMDEBUG_NO_NEW_FLOW_VERSION_ON_DJ: bool = std::env::var("WMDEBUG_NO_NEW_FLOW_VERSION_ON_DJ").is_ok(); static ref WMDEBUG_NO_NEW_APP_VERSION_ON_DJ: bool = std::env::var("WMDEBUG_NO_NEW_APP_VERSION_ON_DJ").is_ok(); static ref WMDEBUG_NO_COMPONENTS_TO_RELOCK: bool = std::env::var("WMDEBUG_NO_COMPONENTS_TO_RELOCK").is_ok(); + static ref DEPENDENCY_JOB_DEBOUNCE_DELAY: usize = std::env::var("DEPENDENCY_JOB_DEBOUNCE_DELAY").ok().and_then(|flag| flag.parse().ok()).unwrap_or( + if cfg!(test) { /* if test we want increased debouncing delay */ 15 } else { 5 } + ); } use crate::common::OccupancyMetrics; @@ -144,6 +149,12 @@ pub async fn handle_dependency_job( token: &str, occupancy_metrics: &mut OccupancyMetrics, ) -> error::Result> { + // Processing a dependency job - these jobs handle lockfile generation and dependency updates + // for scripts, flows, and apps when their dependencies or imported scripts change + tracing::debug!( + "Processing dependency job for path: {:?}", + job.runnable_path() + ); let script_path = job.runnable_path(); let raw_deps = job .args @@ -243,141 +254,25 @@ pub async fn handle_dependency_job( let current_hash = job.runnable_id.unwrap_or(ScriptHash(0)); let w_id = &job.workspace_id; + let (deployment_message, parent_path) = get_deployment_msg_and_parent_path_from_args(job.args.clone()); - let script_info = sqlx::query_as::<_, windmill_common::scripts::Script>( - "SELECT * FROM script WHERE hash = $1 AND workspace_id = $2", + // We do not create new row for this update + // That means we can keep current hash and just update lock + sqlx::query!( + "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3", + &content, + ¤t_hash.0, + w_id ) - .bind(¤t_hash.0) - .bind(w_id) - .fetch_one(db) + .execute(db) .await?; - // DependencyJob can be triggered only from 2 places: - // 1. create_script function in windmill-api/src/scripts.rs - // 2. trigger_dependents_to_recompute_dependencies (in this file) - // - // First will **always** produce script with null in `lock` - // where Second will **always** do with lock being not null - let deployed_hash = if script_info.lock.is_some() && !*WMDEBUG_NO_HASH_CHANGE_ON_DJ { - let path = script_info.path.clone(); - - let mut tx = db.begin().await?; - // This entire section exists to solve following problem: - // - // 2 workers, one script that depend on another in python - // run the original script on both workers - // you update the dependenecy of a relative import, - // run it again until you ran it on both, normally it should fail on one of those - // - // It happens because every worker has cached their own script versions. - // However usual dependency job does not update hash of the script (and cache is keyed by the hash). - // This logical branch will create new script which will update the hash and automatically invalidate cache. - // - // IMPORTANT: This will **only** be triggered by another DependencyJob. It will never be triggered by script (re)deployement - - let ns = NewScript { - path: script_info.path, - parent_hash: Some(current_hash), - summary: script_info.summary, - description: script_info.description, - content: script_info.content, - schema: script_info.schema, - is_template: Some(script_info.is_template), - // TODO: Make it either None everywhere (particularely when raw reqs are calculated) - // Or handle this case and conditionally make Some (only with raw reqs) - lock: None, - language: script_info.language, - kind: Some(script_info.kind), - tag: script_info.tag, - draft_only: script_info.draft_only, - envs: script_info.envs, - concurrent_limit: script_info.concurrent_limit, - concurrency_time_window_s: script_info.concurrency_time_window_s, - cache_ttl: script_info.cache_ttl, - dedicated_worker: script_info.dedicated_worker, - ws_error_handler_muted: script_info.ws_error_handler_muted, - priority: script_info.priority, - timeout: script_info.timeout, - delete_after_use: script_info.delete_after_use, - restart_unless_cancelled: script_info.restart_unless_cancelled, - deployment_message: deployment_message.clone(), - concurrency_key: script_info.concurrency_key, - visible_to_runner_only: script_info.visible_to_runner_only, - no_main_func: script_info.no_main_func, - codebase: script_info.codebase, - has_preprocessor: script_info.has_preprocessor, - on_behalf_of_email: script_info.on_behalf_of_email, - assets: script_info.assets, - }; - - let new_hash = hash_script(&ns); - - sqlx::query!(" - INSERT INTO script - (workspace_id, hash, path, parent_hashes, summary, description, content, \ - created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ - draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ - dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ - delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, \ - codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) - - SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, \ - content, created_by, schema, is_template, extra_perms, $4, language, kind, tag, \ - draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, \ - dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, \ - delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, \ - codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets - - FROM script WHERE hash = $2 AND workspace_id = $3; - ", - new_hash, current_hash.0, w_id, &content).execute(db).await?; - tracing::info!( - "Updated script at path {} with hash {} to new hash {}", - path, - current_hash.0, - new_hash - ); - // Archive current - sqlx::query!( - "UPDATE script SET archived = true WHERE hash = $1 AND workspace_id = $2", - current_hash.0, - w_id - ) - .execute(&mut *tx) - .await?; - tracing::info!( - "Archived script at path {} from dependency job {}", - path, - current_hash.0 - ); - tx.commit().await?; - - ScriptHash(new_hash) - } else { - // We do not create new row for this update - // That means we can keep current hash and just update lock - sqlx::query!( - "UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3", - &content, - ¤t_hash.0, - w_id - ) - .execute(db) - .await?; - - // `lock` has been updated; invalidate the cache. - // Since only worker that ran this Dependency Job has the cache - // we do not need to think about invalidating cache for other workers. - cache::script::invalidate(current_hash); - - if *WMDEBUG_NO_HASH_CHANGE_ON_DJ { - tracing::warn!("WMDEBUG_NO_HASH_CHANGE_ON_DJ usually should not be used. Behavior might be unstable. Please contact Windmill Team for support.") - } - - current_hash - }; + // `lock` has been updated; invalidate the cache. + // Since only worker that ran this Dependency Job has the cache + // we do not need to think about invalidating cache for other workers. + cache::script::invalidate(current_hash); if let Err(e) = handle_deployment_metadata( &job.permissioned_as_email, @@ -385,7 +280,7 @@ pub async fn handle_dependency_job( &db, &w_id, DeployedObject::Script { - hash: deployed_hash, + hash: current_hash, path: script_path.to_string(), parent_path: parent_path.clone(), }, @@ -520,20 +415,30 @@ pub async fn process_relative_imports( // But currently we will do this extra db call for every script regardless of whether they have relative imports or not // Script might have no relative imports but still be referenced by someone else. - if let Err(e) = trigger_dependents_to_recompute_dependencies( - w_id, - script_path, - deployment_message, - parent_path, - permissioned_as_email, - created_by, - permissioned_as, - db, - already_visited, + match timeout( + core::time::Duration::from_secs(60), + trigger_dependents_to_recompute_dependencies( + w_id, + script_path, + deployment_message, + parent_path, + permissioned_as_email, + created_by, + permissioned_as, + db, + already_visited, + ), ) + .warn_after_seconds(10) .await { - tracing::error!(%e, "error triggering dependents to recompute dependencies"); + Ok(Err(e)) => { + tracing::error!(%e, "error triggering dependents to recompute dependencies") + } + Err(e) => { + tracing::error!(%e, "triggering dependents to recompute dependencies has timed out") + } + _ => {} } } @@ -551,6 +456,15 @@ pub async fn trigger_dependents_to_recompute_dependencies( db: &sqlx::Pool, mut already_visited: Vec, ) -> error::Result<()> { + // TODO: There is a race-condition. + // This can be old version. + // + // Check lines of code below, you will find that we get the latest version of the script/app/flow + // + // However the latest version does not necessarily mean that it is finalized. + // Instead we assume that this would be the version we would base on. + // + // So the script_importers might be behind. Thus some information like nodes_to_relock might be lost. let script_importers = sqlx::query!( "SELECT importer_path, importer_kind::text, array_agg(importer_node_id) as importer_node_ids FROM dependency_map WHERE imported_path = $1 @@ -562,13 +476,20 @@ pub async fn trigger_dependents_to_recompute_dependencies( .fetch_all(db) .await?; + tracing::debug!( + "Triggering dependents to recompute dependencies for: {}", + &script_path + ); + already_visited.push(script_path.to_string()); for s in script_importers.iter() { + tracing::trace!("Processing dependency: {:?}", &s); if already_visited.contains(&s.importer_path) { + tracing::trace!("Skipping already visited dependency"); continue; } - let tx = PushIsolationLevel::IsolatedRoot(db.clone()); + let mut tx = db.clone().begin().await?; let mut args: HashMap> = HashMap::new(); if let Some(ref dm) = deployment_message { args.insert("deployment_message".to_string(), to_raw_value(&dm)); @@ -586,137 +507,104 @@ pub async fn trigger_dependents_to_recompute_dependencies( to_raw_value(&already_visited), ); + args.insert( + "triggered_by_relative_import".to_string(), + to_raw_value(&()), + ); + + // Lock the debounce_key entry FOR UPDATE to coordinate with the push side. + // This prevents concurrent modifications during dependency job scheduling. + // + // The lock serves two purposes: + // 1. Ensures we get the current debounce_job_id atomically + // 2. Blocks new push requests from modifying this key until we commit + // 3. Blocks puller from actually starting the job and gives us a chance to still squeeze the debounce in. + // + // After our transaction commits, any pending push/pull requests can proceed with + // their debounce logic. + let debounce_job_id_o = + windmill_common::jobs::lock_debounce_key(w_id, &s.importer_path, &mut tx).await?; + + tracing::debug!( + debounce_job_id = ?debounce_job_id_o, + importer_path = %s.importer_path, + "Retrieved debounce job ID (if exists)" + ); + let kind = s.importer_kind.clone().unwrap_or_default(); let job_payload = if kind == "script" { - let r = - // TODO: Not sure if this is safe: - // might have race conditions in edge-cases - get_latest_deployed_hash_for_path(None, db.clone(), w_id, s.importer_path.as_str()) - .await; - match r { - // We will create Dependency job as is. But the Dep Job Handler will detect that the job originates - // from [[trigger_dependents_to_recompute_dependencies]] and will create new script with new hash instead - Ok(r) => JobPayload::Dependencies { - path: s.importer_path.clone(), - hash: ScriptHash(r.hash), - language: r.language, - dedicated_worker: r.dedicated_worker, - }, - Err(err) => { - tracing::error!( - "error getting latest deployed hash for path {path}: {err}", - path = s.importer_path, - err = err - ); + match sqlx::query_scalar!( + "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2 AND deleted = false ORDER BY created_at DESC LIMIT 1", + s.importer_path.clone(), + w_id + ) + .fetch_optional(&mut *tx) + .await? + { + Some(hash) => { + tracing::debug!("newest hash for {} is: {hash}", &s.importer_path); + + let info = + windmill_common::get_script_info_for_hash(None, db, w_id, hash).await?; + + JobPayload::Dependencies { + path: s.importer_path.clone(), + hash: ScriptHash(hash), + language: info.language, + dedicated_worker: info.dedicated_worker, + } + } + None => { + ScopedDependencyMap::clear_map_for_item( + &s.importer_path, + w_id, + "script", + tx, + &None, + ) + .await + .commit() + .await?; continue; } } } else if kind == "flow" { - // Unlike 'script', 'flow' will not delegate redeployment of new flow to the Dep Job Handler. - // We will create new flow in-place. - // It would be harder to do otherwise. + tracing::debug!("Handling flow dependency update for: {}", s.importer_path); - // Create transaction to make operation atomic. - let mut flow_tx = db.begin().await?; args.insert( "nodes_to_relock".to_string(), to_raw_value(&s.importer_node_ids), ); - let r = sqlx::query_scalar!( - "SELECT versions[array_upper(versions, 1)] FROM flow WHERE path = $1 AND workspace_id = $2", - s.importer_path, - w_id, - ).fetch_optional(&mut *flow_tx) - .await - .map_err(to_anyhow).map(Option::flatten); - - match r { - // TODO: Fallback - remove eventually. - Ok(Some(version)) if *WMDEBUG_NO_NEW_FLOW_VERSION_ON_DJ => { - tracing::warn!("WMDEBUG_NO_NEW_FLOW_VERSION_ON_DJ usually should not be used. Behavior might be unstable. Please contact Windmill Team for support."); - JobPayload::FlowDependencies { - path: s.importer_path.clone(), - dedicated_worker: None, - version, - } - } - // Get current version of current flow. - Ok(Some(cur_version)) => { - // NOTE: Temporary solution. See the usage for more details. - args.insert( - "triggered_by_relative_import".to_string(), - to_raw_value(&()), - ); - // Find out what would be the next version. - // Also clone current flow_version to get new_version (which is usually c_v + 1). - // NOTE: It is fine if something goes wrong downstream and `flow` is not being appended with this new version. - // This version will just remain in db and cause no trouble. - let new_version = sqlx::query_scalar!( - "INSERT INTO flow_version - (workspace_id, path, value, schema, created_by) - - SELECT workspace_id, path, value, schema, created_by - FROM flow_version WHERE path = $1 AND workspace_id = $2 AND id = $3 - - RETURNING id", + match sqlx::query_scalar!( + "SELECT id FROM flow_version WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1", + s.importer_path.clone(), + w_id + ) + .fetch_optional(&mut *tx) + .await? + { + Some(version) => JobPayload::FlowDependencies { + path: s.importer_path.clone(), + version, + dedicated_worker: None, + }, + None => { + ScopedDependencyMap::clear_map_for_item( &s.importer_path, w_id, - cur_version + "flow", + tx, + &None, ) - .fetch_one(&mut *flow_tx) .await - .map_err(|e| { - error::Error::internal_err(format!( - "Error updating flow due to flow history insert: {e:#}" - )) - })?; - - // Commit the transaction. - // NOTE: - // We do not append flow.versions with new version. - // We will do this in the end of the dependency job handler. - // Otherwise it might become a source of race-conditions. - flow_tx.commit().await?; - JobPayload::FlowDependencies { - path: s.importer_path.clone(), - dedicated_worker: None, - // Point Dep Job to the new version. - // We do this since we want to assume old ones are immutable. - version: new_version, - } - } - Ok(None) => { - if *WMDEBUG_NO_DMAP_DISSOLVE { - tracing::warn!("WMDEBUG_NO_DMAP_DISSOLVE usually should not be used. Behavior might be unstable."); - } else { - // Remember the path we used to query the flow was fetched just now from dependency_map - // if dependency_map advertise unexistent path, as part of self-healing it should be removed - ScopedDependencyMap::clear_map_for_item( - &s.importer_path, - w_id, - "flow", - flow_tx, - &None, - ) - .await - .commit() - .await?; - } - continue; - } - Err(err) => { - tracing::error!( - "error getting latest deployed flow version for path {path}: {err}", - path = s.importer_path, - ); - // Do not commit the transaction. It will be dropped and rollbacked + .commit() + .await?; continue; } } } else if kind == "app" && !*WMDEBUG_NO_NEW_APP_VERSION_ON_DJ { - // Create transaction to make operation atomic. - let mut tx = db.begin().await?; + tracing::debug!("Handling flow dependency update for: {}", s.importer_path); args.insert( "components_to_relock".to_string(), @@ -724,77 +612,28 @@ pub async fn trigger_dependents_to_recompute_dependencies( to_raw_value(&s.importer_node_ids), ); - let r = sqlx::query_scalar!( - "SELECT versions[array_upper(versions, 1)] FROM app WHERE path = $1 AND workspace_id = $2", - s.importer_path, - w_id, - ).fetch_optional(&mut *tx) - .await - .map_err(to_anyhow).map(Option::flatten); - - match r { - // Get current version of current flow. - Ok(Some(cur_version)) => { - // NOTE: Temporary solution. See the usage for more details. - args.insert( - "triggered_by_relative_import".to_string(), - to_raw_value(&()), - ); - - let new_version = sqlx::query_scalar!( - "INSERT INTO app_version - (app_id, value, created_by, raw_app) - SELECT app_id, value, created_by, raw_app - FROM app_version WHERE id = $1 - RETURNING id", - cur_version + match sqlx::query_scalar!( + "SELECT id FROM app_version WHERE app_id = (SELECT id FROM app WHERE path = $1 AND workspace_id = $2) ORDER BY created_at DESC LIMIT 1", + s.importer_path.clone(), + w_id + ) + .fetch_optional(&mut *tx) + .await? + { + Some(version) => { + JobPayload::AppDependencies { path: s.importer_path.clone(), version } + } + None => { + ScopedDependencyMap::clear_map_for_item( + &s.importer_path, + w_id, + "app", + tx, + &None, ) - .fetch_one(&mut *tx) .await - .map_err(|e| { - error::Error::internal_err(format!( - "Error updating App due to App history insert: {e:#}" - )) - })?; - - // Commit the transaction. - // NOTE: - // We do not append app.versions with new version. - // We will do this in the end of the dependency job handler. - // Otherwise it might become a source of race-conditions. - tx.commit().await?; - JobPayload::AppDependencies { - path: s.importer_path.clone(), - // Point Dep Job to the new version. - // We do this since we want to assume old ones are immutable. - version: new_version, - } - } - Ok(None) => { - if *WMDEBUG_NO_DMAP_DISSOLVE { - tracing::warn!("WMDEBUG_NO_DMAP_DISSOLVE usually should not be used. Behavior might be unstable."); - } else { - // Remember the path we used to query the flow was fetched just now from dependency_map - // if dependency_map advertise unexistent path, as part of self-healing it should be removed - ScopedDependencyMap::clear_map_for_item( - &s.importer_path, - w_id, - "app", - tx, - &None, - ) - .await - .commit() - .await?; - } - continue; - } - Err(err) => { - tracing::error!( - "error getting latest deployed app version for path {path}: {err}", - path = s.importer_path, - ); - // Do not commit the transaction. It will be dropped and rollbacked + .commit() + .await?; continue; } } @@ -807,9 +646,10 @@ pub async fn trigger_dependents_to_recompute_dependencies( continue; }; + tracing::debug!("Pushing dependency job for: {}", s.importer_path); let (job_uuid, new_tx) = windmill_queue::push( db, - tx, + PushIsolationLevel::Transaction(tx), &w_id, job_payload, windmill_queue::PushArgs { args: &args, extra: None }, @@ -817,7 +657,8 @@ pub async fn trigger_dependents_to_recompute_dependencies( email, permissioned_as.to_string(), Some("trigger.dependents.to.recompute.dependencies"), - None, + // Schedule for future for debouncing. + Some(Utc::now() + Duration::seconds(*DEPENDENCY_JOB_DEBOUNCE_DELAY as i64)), None, None, None, @@ -827,15 +668,17 @@ pub async fn trigger_dependents_to_recompute_dependencies( false, None, true, - None, + Some("dependency".into()), None, None, None, None, false, None, + debounce_job_id_o, ) .await?; + tracing::info!( "pushed dependency job due to common python path: {job_uuid} for path {path}", path = s.importer_path, @@ -846,7 +689,7 @@ pub async fn trigger_dependents_to_recompute_dependencies( } pub async fn handle_flow_dependency_job( - job: &MiniPulledJob, + job: MiniPulledJob, preview_data: Option<&RawData>, mem_peak: &mut i32, canceled_by: &mut Option, @@ -858,6 +701,9 @@ pub async fn handle_flow_dependency_job( token: &str, occupancy_metrics: &mut OccupancyMetrics, ) -> error::Result> { + tracing::debug!("Processing flow dependency job"); + tracing::trace!("Job details: {:?}", &job); + tracing::trace!("Preview data: {:?}", &preview_data); let job_path = job.runnable_path.clone().ok_or_else(|| { error::Error::internal_err( "Cannot resolve flow dependencies for flow without path".to_string(), @@ -875,6 +721,12 @@ pub async fn handle_flow_dependency_job( .flatten() .unwrap_or(false); + let triggered_by_relative_import = job + .args + .as_ref() + .map(|x| x.get("triggered_by_relative_import").is_some()) + .unwrap_or_default(); + let version = if skip_flow_update { None } else { @@ -890,6 +742,7 @@ pub async fn handle_flow_dependency_job( ) }; + tracing::trace!("Job details: {:?}", &job); let (deployment_message, parent_path) = get_deployment_msg_and_parent_path_from_args(job.args.clone()); @@ -903,6 +756,7 @@ pub async fn handle_flow_dependency_job( }) .flatten(); + tracing::debug!("Nodes to relock: {:?}", &nodes_to_relock); let raw_deps = job .args .as_ref() @@ -913,12 +767,6 @@ pub async fn handle_flow_dependency_job( }) .flatten(); - let triggered_by_relative_import = job - .args - .as_ref() - .map(|x| x.get("triggered_by_relative_import").is_some()) - .unwrap_or_default(); - // `JobKind::FlowDependencies` job store either: // - A saved flow version `id` in the `script_hash` column. // - Preview raw flow in the `queue` or `job` table. @@ -959,7 +807,7 @@ pub async fn handle_flow_dependency_job( let errors; (flow.modules, tx, modified_ids, errors) = lock_modules( flow.modules, - job, + &job, mem_peak, canceled_by, job_dir, @@ -1094,6 +942,8 @@ pub async fn handle_flow_dependency_job( &job_path, &job.workspace_id, ).execute(&mut *tx).await?; + tracing::debug!("Marked flow version as latest"); + tracing::debug!("Flow version: {}", version); } tx.commit().await?; @@ -2091,7 +1941,7 @@ async fn lock_modules_app( } pub async fn handle_app_dependency_job( - job: &MiniPulledJob, + job: MiniPulledJob, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, @@ -2158,7 +2008,7 @@ pub async fn handle_app_dependency_job( if let Some((app_id, value)) = record { let value = lock_modules_app( value, - job, + &job, mem_peak, canceled_by, job_dir, From 0c4e14e81c1a86dcdc9e00bbe6da3e1d91f15e81 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 17:21:09 +0000 Subject: [PATCH 19/33] fix compile --- backend/windmill-worker/src/ansible_executor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index 395341d698..ac1ae4e233 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -13,7 +13,7 @@ use tokio::process::Command; use uuid::Uuid; use windmill_common::{ error, - git_sync_oss::{get_github_app_token_internal, prepend_token_to_github_url}, + git_sync_oss::{prepend_token_to_github_url}, worker::{ is_allowed_file_location, to_raw_value, write_file, write_file_at_user_defined_location, Connection, WORKER_CONFIG, @@ -958,7 +958,7 @@ pub async fn handle_ansible_job( #[cfg(feature = "enterprise")] if is_github_app { if let Connection::Sql(db) = conn { - let token = get_github_app_token_internal(db, &client.token).await?; + let token = windmill_common::git_sync_oss::get_github_app_token_internal(db, &client.token).await?; secret_url = prepend_token_to_github_url(&secret_url, &token)?; } else { return Err(windmill_common::error::Error::BadRequest("Github App authentication is currently unavailable for agent workers. Contact the windmill team to request this feature".to_string())); From 952a15a877affce7feb6d26b65b5e4e2fadd3ff2 Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:29:57 +0200 Subject: [PATCH 20/33] remove dbg! leftovers (#6842) Signed-off-by: pyranota --- backend/windmill-worker/src/worker.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 39f1944d6d..ca3b1583f8 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -956,7 +956,6 @@ pub async fn run_worker( ); } - dbg!("start"); let start_time = Instant::now(); let worker_dir = format!("{TMP_DIR}/{worker_name}"); @@ -997,8 +996,6 @@ pub async fn run_worker( }); } - dbg!("python stuff is done"); - if let Some(ref netrc) = *NETRC { tracing::info!(worker = %worker_name, hostname = %hostname, "Writing netrc at {}/.netrc", HOME_ENV.as_str()); write_file(&HOME_ENV, ".netrc", netrc).expect("could not write netrc"); @@ -1006,8 +1003,6 @@ pub async fn run_worker( create_directory_async(&worker_dir).await; - dbg!("worker dir created"); - if !*DISABLE_NSJAIL { let _ = write_file( &worker_dir, @@ -1390,7 +1385,6 @@ pub async fn run_worker( let mut killpill_rx2 = killpill_rx.resubscribe(); - dbg!("starting loop"); loop { let last_processing_duration_secs = last_processing_duration.load(Ordering::SeqCst); if last_processing_duration_secs > 5 { From 6dfa4ff5ba26bc4912b1be1a9709728cf6c2d119 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 18:06:44 +0000 Subject: [PATCH 21/33] nit test colors --- .github/workflows/backend-test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 0f82ec30b9..5fa4a289f2 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -69,11 +69,10 @@ jobs: ./substitute_ee_code.sh --copy --dir ./windmill-ee-private - name: cargo test timeout-minutes: 16 - run: - deno --version && bun -v && go version && python3 --version && + run: deno --version && bun -v && go version && python3 --version && SQLX_OFFLINE=true DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill - DISABLE_EMBEDDING=true RUST_LOG=info + DISABLE_EMBEDDING=true RUST_LOG=info RUST_LOG_STYLE=never DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,license,python,rust,scoped_cache,private --all -- From 83337b20356aa32af316fc718c163399e929bdb1 Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:24:41 +0200 Subject: [PATCH 22/33] Ansible repo update hubPath + fixes (#6843) * Allow ssh auth * ssh changes pt2 * Fix unused * Update package.json and hubPaths --- .../parsers/windmill-parser-wasm/src/lib.rs | 1 - .../parsers/windmill-parser-yaml/src/lib.rs | 61 ++++++--- backend/windmill-api/openapi.yaml | 4 + backend/windmill-api/src/resources.rs | 127 +++++++++++++++++- frontend/package-lock.json | 8 +- frontend/package.json | 2 +- .../components/GitRepoResourcePicker.svelte | 7 +- .../src/lib/components/GitRepoViewer.svelte | 12 +- .../src/lib/components/ScriptEditor.svelte | 18 ++- frontend/src/lib/hubPaths.json | 3 +- frontend/src/lib/infer.ts | 2 +- 11 files changed, 195 insertions(+), 50 deletions(-) diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index 34969f8e5f..d855910edd 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -222,7 +222,6 @@ pub fn parse_assets_ansible(code: &str) -> String { return serde_json::to_string(&r).unwrap(); } else { return format!("err: {:?}", o.err().unwrap()); - return "Invalid".to_string(); } } diff --git a/backend/parsers/windmill-parser-yaml/src/lib.rs b/backend/parsers/windmill-parser-yaml/src/lib.rs index dee9da5745..5e3af4f831 100644 --- a/backend/parsers/windmill-parser-yaml/src/lib.rs +++ b/backend/parsers/windmill-parser-yaml/src/lib.rs @@ -399,18 +399,31 @@ fn parse_inventories(inventory_yaml: &Yaml) -> anyhow::Result anyhow::Result> { +#[derive(Debug, Clone, Serialize)] +pub struct DelegateWithSSHAuth { + delegate_to_git_repo_details: Option, + git_ssh_identity: Vec, +} + +pub fn parse_delegate_to_git_repo(inner_content: &str) -> anyhow::Result { let docs = YamlLoader::load_from_str(inner_content) .map_err(|e| anyhow!("Failed to parse yaml: {}", e))?; + let mut git_ssh_identity: Vec = vec![]; + if let Yaml::Hash(doc) = &docs[0] { + if let Some(v) = doc.get(&Yaml::String("git_ssh_identity".to_string())) { + let _ = extract_ssh_identity(&v, &mut git_ssh_identity); + } if let Some(v) = doc.get(&Yaml::String("delegate_to_git_repo".to_string())) { - return Ok(extract_delegate_to_git_repo_details(v)); + return Ok(DelegateWithSSHAuth { + delegate_to_git_repo_details: extract_delegate_to_git_repo_details(v), + git_ssh_identity, + }); } } - return Ok(None); + + Ok(DelegateWithSSHAuth { delegate_to_git_repo_details: None, git_ssh_identity }) } pub fn parse_ansible_reqs( @@ -420,7 +433,6 @@ pub fn parse_ansible_reqs( let docs = YamlLoader::load_from_str(inner_content) .map_err(|e| anyhow!("Failed to parse yaml: {}", e))?; - let mut ret = AnsibleRequirements::default(); if let Yaml::Hash(doc) = &docs[0] { @@ -528,23 +540,9 @@ pub fn parse_ansible_reqs( } } Yaml::String(key) if key == "git_ssh_identity" => { - let Yaml::Array(indentities) = &value else { - return Err(anyhow!( - "git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs" - )); - }; - - for r in indentities { - let Yaml::String(file_name) = r else { - return Err(anyhow!( - "Git ssh identity file must be a string path to a Windmill variable/secret" - )); - }; - - ret.git_ssh_identity.push(file_name.clone()); - } + extract_ssh_identity(&value, &mut ret.git_ssh_identity)?; } - Yaml::String(key) if key == "delegate_to_git_repo" => {} + Yaml::String(key) if key == "delegate_to_git_repo" => {} // Skip this because it was already parsed before Yaml::String(key) => logs.push_str(&format!("\nUnknown field `{}`. Ignoring", key)), _ => (), } @@ -560,6 +558,25 @@ pub fn parse_ansible_reqs( Ok((logs, Some(ret), out_str)) } +fn extract_ssh_identity(value: &Yaml, ret: &mut Vec) -> anyhow::Result<()> { + let Yaml::Array(indentities) = value else { + return Err(anyhow!( + "git_ssh_identity expects an array of windmill variables (or secrets) containing ssh IDs" + )); + }; + + for r in indentities { + let Yaml::String(file_name) = r else { + return Err(anyhow!( + "Git ssh identity file must be a string path to a Windmill variable/secret" + )); + }; + + ret.push(file_name.clone()); + } + Ok(()) +} + fn extract_delegate_to_git_repo_details(value: &Yaml) -> Option { if let Yaml::Hash(v) = value { if let Some(resource) = v diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index ea799547a1..c90c69ef82 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -4201,6 +4201,10 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - name: git_ssh_identity + in: query + schema: + type: string responses: "200": description: git commit hash diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index 9decd976f8..faf525ade0 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -13,6 +13,7 @@ use crate::{ users::{maybe_refresh_folders, require_owner_of_path, Tokened}, utils::{check_scopes, require_super_admin, BulkDeleteRequest}, var_resource_cache::{cache_resource, get_cached_resource}, + variables::get_value_internal, webhook_util::{WebhookMessage, WebhookShared}, }; use axum::{ @@ -34,12 +35,12 @@ use uuid::Uuid; use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; use windmill_common::{ - db::{UserDB, UserDbWithOptAuthed}, - error::{Error, JsonResult, Result}, + db::{UserDB, UserDbWithAuthed, UserDbWithOptAuthed}, + error::{self, Error, JsonResult, Result}, get_database_url, parse_postgres_url, utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, variables, - worker::CLOUD_HOSTED, + worker::{CLOUD_HOSTED, TMP_DIR}, workspaces::get_ducklake_instance_pg_catalog_password, }; @@ -1403,12 +1404,18 @@ struct GitCommitHashResponse { commit_hash: String, } +#[derive(Deserialize)] +struct GitCommitHashQuery { + git_ssh_identity: Option, +} + async fn get_git_commit_hash( authed: ApiAuthed, Extension(user_db): Extension, Extension(db): Extension, Tokened { token }: Tokened, Path((w_id, path)): Path<(String, StripPath)>, + Query(query): Query, ) -> JsonResult { let path = path.to_path(); @@ -1416,7 +1423,7 @@ async fn get_git_commit_hash( let git_repo_resource_value = get_resource_value_interpolated_internal( &authed, - Some(user_db), + Some(user_db.clone()), &db, &w_id, path, @@ -1434,12 +1441,115 @@ async fn get_git_commit_hash( None => return Err(Error::NotFound(format!("Resource {} not found", path)).into()), }; - let commit_hash = get_repo_latest_commit_hash(&git_resource).await?; + let identities: Vec = query + .git_ssh_identity + .map(|s| s.split(",").map(|s| s.to_string()).collect()) + .unwrap_or(vec![]); - Ok(Json(GitCommitHashResponse { commit_hash })) + let (git_ssh_cmd, filenames) = + get_git_ssh_cmd(&authed, &user_db, &db, &w_id, identities).await?; + + let commit_hash = get_repo_latest_commit_hash(&git_resource, git_ssh_cmd).await; + + delete_paths(&filenames).await; + + Ok(Json(GitCommitHashResponse { commit_hash: commit_hash? })) } -async fn get_repo_latest_commit_hash(git_resource: &GitRepositoryResource) -> Result { +async fn write_ssh_file( + authed: &ApiAuthed, + user_db: &UserDB, + db: &DB, + w_id: &str, + var_path: &str, +) -> std::result::Result { + let id_file_name = format!(".ssh_id_priv_{}", Uuid::new_v4()); + let loc = std::path::Path::new(TMP_DIR) + .join("ssh_ids") + .join(id_file_name); + + let userdb_authed = UserDbWithAuthed { db: user_db.clone(), authed: &authed.to_authed_ref() }; + let mut content = get_value_internal(&userdb_authed, db, w_id, var_path, authed, false) + .await + .map_err(|e| { + ( + error::Error::NotFound(format!( + "Variable {var_path} not found for git ssh identity: {e:#}" + )), + loc.clone(), + ) + })?; + content.push_str("\n"); + + if let Some(p) = &loc.parent() { + tokio::fs::create_dir_all(p) + .await + .map_err(|e| (e.into(), loc.clone()))?; + } + tokio::fs::write(&loc, content) + .await + .map_err(|e| (e.into(), loc.clone()))?; + + #[cfg(unix)] + { + let perm = std::os::unix::fs::PermissionsExt::from_mode(0o600); + tokio::fs::set_permissions(&loc, perm) + .await + .map_err(|e| (e.into(), loc.clone()))?; + } + + return Ok(loc); +} + +async fn delete_paths(paths: &Vec) { + for path in paths { + let _ = tokio::fs::remove_file(&path).await; + } +} + +async fn get_git_ssh_cmd( + authed: &ApiAuthed, + user_db: &UserDB, + db: &DB, + w_id: &str, + git_ssh_identity: Vec, +) -> error::Result<(Option, Vec)> { + if git_ssh_identity.len() > 5 { + return Err(error::Error::BadRequest( + "Too many ssh identities, try using at most 1".to_string(), + )); + } + if git_ssh_identity.len() == 0 { + return Ok((None, vec![])); + } + + let mut ssh_id_files = vec![]; + let mut file_paths = vec![]; + for var_path in git_ssh_identity.iter() { + match write_ssh_file(authed, user_db, db, w_id, &var_path).await { + Ok(loc) => { + ssh_id_files.push(format!( + " -i '{}'", + loc.to_string_lossy().replace('\'', r"'\''") + )); + file_paths.push(loc); + } + Err((e, loc)) => { + file_paths.push(loc); + delete_paths(&file_paths).await; + return Err(e); + } + } + } + + let git_ssh_cmd = format!("ssh -o StrictHostKeyChecking=no{}", ssh_id_files.join("")); + Ok((Some(git_ssh_cmd), file_paths)) +} + +async fn get_repo_latest_commit_hash( + git_resource: &GitRepositoryResource, + git_ssh_command: Option, +) -> Result { let mut git_cmd = Command::new("git"); let ref_spec = git_resource @@ -1449,6 +1559,9 @@ async fn get_repo_latest_commit_hash(git_resource: &GitRepositoryResource) -> Re .unwrap_or("HEAD"); git_cmd.args(["ls-remote", &git_resource.url, ref_spec]); + if let Some(git_ssh_command) = git_ssh_command { + git_cmd.env("GIT_SSH_COMMAND", git_ssh_command); + } git_cmd.stderr(Stdio::piped()); let output = git_cmd diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4f95518603..0de6d52aaa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -83,7 +83,7 @@ "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", "windmill-parser-wasm-ts": "1.538.0", - "windmill-parser-wasm-yaml": "1.558.1", + "windmill-parser-wasm-yaml": "1.561.0", "windmill-sql-datatype-parser-wasm": "1.512.0", "windmill-utils-internal": "^1.3.1", "xterm": "^5.3.0", @@ -13779,9 +13779,9 @@ "integrity": "sha512-hHhMIVIPhmsHx0lsNCGMoIa7cDBFlVWhhd9j/5yOIq2sxwqg5sl5juQIIJGvuwg5umdsD0ChlSm2/uES78DLYg==" }, "node_modules/windmill-parser-wasm-yaml": { - "version": "1.558.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-yaml/-/windmill-parser-wasm-yaml-1.558.1.tgz", - "integrity": "sha512-KBaSekkFiLJP5GpeArctupHStfp9/aWpNeT9my8Cp+QJB2TziJSf3FQ4k9mxJcKbjUQ65vO0jBtdNDvrnYufqQ==" + "version": "1.561.0", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-yaml/-/windmill-parser-wasm-yaml-1.561.0.tgz", + "integrity": "sha512-UbyxsRxJ/QDE+RFjj8q6cMZqr57gxHXBM+W8VLXnQ8I79W5KI+FhKcNFraUpXzqQjalZJ3cVZXXr8C7cTlJ8IQ==" }, "node_modules/windmill-sql-datatype-parser-wasm": { "version": "1.512.0", diff --git a/frontend/package.json b/frontend/package.json index 3fda798c10..f5aebe9181 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -148,7 +148,7 @@ "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", "windmill-parser-wasm-ts": "1.538.0", - "windmill-parser-wasm-yaml": "1.558.1", + "windmill-parser-wasm-yaml": "1.561.0", "windmill-sql-datatype-parser-wasm": "1.512.0", "windmill-utils-internal": "^1.3.1", "xterm": "^5.3.0", diff --git a/frontend/src/lib/components/GitRepoResourcePicker.svelte b/frontend/src/lib/components/GitRepoResourcePicker.svelte index bc5676370f..225f4507bc 100644 --- a/frontend/src/lib/components/GitRepoResourcePicker.svelte +++ b/frontend/src/lib/components/GitRepoResourcePicker.svelte @@ -12,6 +12,7 @@ currentCommit?: string currentInventories?: string currentPlaybook?: string + gitSshIdentity?: string[] } let { @@ -19,7 +20,8 @@ currentResource = undefined, currentCommit = undefined, currentInventories = undefined, - currentPlaybook = undefined + currentPlaybook = undefined, + gitSshIdentity = undefined }: Props = $props() const dispatch = createEventDispatcher<{ @@ -120,7 +122,8 @@ try { const result = await ResourceService.getGitCommitHash({ workspace: $workspaceStore!, - path: selectedResource + path: selectedResource, + gitSshIdentity: gitSshIdentity?.join(",") }) commitHash = result.commit_hash } catch (err) { diff --git a/frontend/src/lib/components/GitRepoViewer.svelte b/frontend/src/lib/components/GitRepoViewer.svelte index 35ada1ee61..4924a89529 100644 --- a/frontend/src/lib/components/GitRepoViewer.svelte +++ b/frontend/src/lib/components/GitRepoViewer.svelte @@ -18,10 +18,11 @@ interface Props { gitRepoResourcePath: string + gitSshIdentity?: string[] commitHashInput?: string } - let { gitRepoResourcePath, commitHashInput = $bindable() }: Props = $props() + let { gitRepoResourcePath, gitSshIdentity, commitHashInput = $bindable() }: Props = $props() let commitHash = $derived(commitHashInput); @@ -31,7 +32,9 @@ const payload = { workspace: workspace, - resource_path: gitRepoResourcePath + resource_path: gitRepoResourcePath, + git_ssh_identity: gitSshIdentity, + commit: commitHash, } isLoadingRepoClone = true @@ -47,7 +50,6 @@ tryCode: async () => { const testResult = await JobService.getCompletedJob({ workspace, id: jobId }) jobSuccess = !!testResult.success - console.log("res", testResult) if (jobSuccess) { await JobService.getCompletedJobResult({ workspace, id: jobId }) } else { @@ -79,10 +81,10 @@ if (!commitHash) { isLoadingCommitHash = true error = null - const result = await ResourceService.getGitCommitHash({ workspace: $workspaceStore!, - path: gitRepoResourcePath + path: gitRepoResourcePath, + gitSshIdentity: gitSshIdentity?.join(",") }) commitHashInput = result.commit_hash diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index fb7743cad1..d82b859fef 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -163,13 +163,15 @@ inferAnsibleExecutionMode(code).then((v) => { if ( v !== undefined && - (v === null || - v.resource !== ansibleAlternativeExecutionMode?.resource || - v.playbook !== ansibleAlternativeExecutionMode?.playbook || - v.inventories_location !== ansibleAlternativeExecutionMode?.inventories_location || - v.commit !== ansibleAlternativeExecutionMode?.commit) + (v.delegate_to_git_repo_details === null || + v.delegate_to_git_repo_details.resource !== ansibleAlternativeExecutionMode?.resource || + v.delegate_to_git_repo_details.playbook !== ansibleAlternativeExecutionMode?.playbook || + v.delegate_to_git_repo_details.inventories_location !== ansibleAlternativeExecutionMode?.inventories_location || + v.delegate_to_git_repo_details.commit !== ansibleAlternativeExecutionMode?.commit || + v.git_ssh_identity !== ansibleGitSshIdentity) ) { - ansibleAlternativeExecutionMode = v + ansibleAlternativeExecutionMode = v.delegate_to_git_repo_details + ansibleGitSshIdentity = v.git_ssh_identity } }) } @@ -200,6 +202,7 @@ | null | undefined >() + let ansibleGitSshIdentity = $state([]) const url = new URL(window.location.toString()) let initialCollab = /true|1/i.test(url.searchParams.get('collab') ?? '0') @@ -612,7 +615,9 @@
@@ -917,6 +922,7 @@ currentCommit={commitHashForGitRepo || ansibleAlternativeExecutionMode?.commit} currentInventories={ansibleAlternativeExecutionMode?.inventories_location} currentPlaybook={ansibleAlternativeExecutionMode?.playbook} + gitSshIdentity={ansibleGitSshIdentity} on:selected={handleDelegateConfigUpdate} on:addInventories={handleAddInventories} /> diff --git a/frontend/src/lib/hubPaths.json b/frontend/src/lib/hubPaths.json index 3a82035564..2a015e9c38 100644 --- a/frontend/src/lib/hubPaths.json +++ b/frontend/src/lib/hubPaths.json @@ -37,5 +37,6 @@ "slackReport": "hub/9084/slack", "discordReport": "hub/9085/discord", "smtpReport": "hub/9086/smtp", - "cloneRepoToS3forGitRepoViewer": "hub/19825/clone_repo_and_upload_to_instance_storage" + "cloneRepoToS3forGitRepoViewer_0": "hub/19825/clone_repo_and_upload_to_instance_storage", + "cloneRepoToS3forGitRepoViewer": "hub/19827/clone_repo_and_upload_to_instance_storage" } diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index 3510d84bcc..6f1d6d7829 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -114,7 +114,7 @@ export async function inferAssets( return [] } -export async function inferAnsibleExecutionMode(code: string) { +export async function inferAnsibleExecutionMode(code: string): any { try { await initWasmYaml() return JSON.parse(parse_ansible_delegate(code)) From 6543a83d9f55541caff88c6f2013ce1f811d3ae4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 20:01:30 +0000 Subject: [PATCH 23/33] fix rare stack overflow bc of async size --- backend/windmill-worker/src/worker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index ca3b1583f8..0e2f87f7d0 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -1598,7 +1598,7 @@ pub async fn run_worker( { match timeout( core::time::Duration::from_secs(10), - preprocess_dependency_job(pulled_job, &db), + Box::pin(preprocess_dependency_job(pulled_job, &db)), ) .warn_after_seconds(2) .await From 3dd75ad18de28ddd392c380973a2b97a99cb3ef9 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 20:05:09 +0000 Subject: [PATCH 24/33] fix rare stack overflow bc of async size --- backend/windmill-queue/src/jobs.rs | 390 +++++++++++++------------- backend/windmill-worker/src/worker.rs | 2 +- 2 files changed, 198 insertions(+), 194 deletions(-) diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 8dd2f4d3f8..85180d1791 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -3838,7 +3838,8 @@ pub async fn push<'c, 'd>( if let Some(skip_handler) = skip_handler { let mut skip_input_transforms = HashMap::::new(); for (arg_name, arg_value) in skip_handler.args { - skip_input_transforms.insert(arg_name, InputTransform::Static { value: arg_value }); + skip_input_transforms + .insert(arg_name, InputTransform::Static { value: arg_value }); } modules.push(FlowModule { @@ -3973,7 +3974,7 @@ pub async fn push<'c, 'd>( // this is a new flow being pushed, flow_status is set to flow_value: let flow_status: FlowStatus = FlowStatus::new(&flow_value); ( - None, // No version needed - flow is stored in raw_flow like FlowPreview + None, // No version needed - flow is stored in raw_flow like FlowPreview Some(path), None, JobKind::SingleStepFlow, @@ -5138,127 +5139,128 @@ pub async fn preprocess_dependency_job(job: &mut PulledJob, db: &DB) -> error::R let kind = job.kind; // Handle dependency job debouncing cleanup when a job is pulled for execution if kind.is_dependency() && !*WMDEBUG_NO_DJOB_DEBOUNCING { - // Only used for testing in tests/relative_imports.rs - // Give us some space to work with. - #[cfg(debug_assertions)] - if let Some(duration) = job - .args - .as_ref() - .map(|x| { - x.get("dbg_sleep_between_pull_and_debounce_key_removal") - .map(|v| serde_json::from_str::(v.get()).ok()) - .flatten() - }) - .flatten() - { - tracing::debug!("going to sleep",); - sleep(std::time::Duration::from_secs(duration as u64)).await; - } - - tracing::debug!( - "Processing debounce cleanup for dependency job {} at path {:?}", - &job.id, - &job.runnable_path - ); - - let key = format!("{}:{}:dependency", &job.workspace_id, job.runnable_path()); - let mut tx = db.begin().await?; - - // === DEBOUNCE CLEANUP === - // - // Clean up the debounce_key entry for this job (if it exists). - // - // IMPORTANT: We delete by key (not job_id) to avoid race conditions: - // If pusher has locked this row then this call will be blocked until all txs are commited. - // - // The idea is that the worker_lockfiles::trigger_dependents_to_recompute_locks will fetch the latest version of the obj. - // This object needs to be created before the djob is executed and it happens right here. - // - // This way the next pusher can fetch the latest version of object and base their djob payload on newest version. - // The concurrency limit on djobs will make sure that by the time next djob is started executing the base version it is referencing - // has already calculated all locks. This way even next djob will always use the fully finalized version of object. - // - // - // - // Note: We don't use a transaction here for performance (it's called during job pull). - // This means there's a tiny window where the job is running but key isn't deleted yet, - // which is acceptable because new requests will just accumulate data to this job. - tracing::debug!( - job_id = %job.id, - "Cleaning up debounce_key entry for completed/pulled job" - ); - - // This will either: - // 1. Block until pusher pushed. Which gives us: - // - If there was any stale data in pusher, then we will read it here (couple of lines below) - // 2. Block pusher until we are done here. This gives us: - // - We will clone objects and retrieve the latest version. So when we are done the pusher can read latest version. - sqlx::query!("DELETE FROM debounce_key WHERE key = $1", &key) - .execute(&mut *tx) - .await - .map_err(|e| { - tracing::error!( - error = %e, - job_id = %job.id, - "Failed to delete debounce_key" - ); - e - })?; - - if job - .args - .as_ref() - .map(|x| x.get("triggered_by_relative_import").is_some()) - .unwrap_or_default() - { - let Some(base_hash) = job.runnable_id else { - return Err(Error::InternalErr( - "Missing runnable_id for dependency job triggered by relative import" - .to_string(), - )); - }; + return Box::pin(async move { + // Only used for testing in tests/relative_imports.rs + // Give us some space to work with. + #[cfg(debug_assertions)] + if let Some(duration) = job + .args + .as_ref() + .map(|x| { + x.get("dbg_sleep_between_pull_and_debounce_key_removal") + .map(|v| serde_json::from_str::(v.get()).ok()) + .flatten() + }) + .flatten() + { + tracing::debug!("going to sleep",); + sleep(std::time::Duration::from_secs(duration as u64)).await; + } tracing::debug!( - job_id = %job.id, - base_hash = %base_hash, - job_kind = ?kind, - "Creating new version for dependency job triggered by relative import" + "Processing debounce cleanup for dependency job {} at path {:?}", + &job.id, + &job.runnable_path ); - let new_id = match kind { - JobKind::Dependencies => { - let deployment_message = job - .args - .clone() - .map(|hashmap| { - hashmap - .get("deployment_message") - .map(|map_value| { - serde_json::from_str::(map_value.get()).ok() - }) - .flatten() - }) - .flatten(); + let key = format!("{}:{}:dependency", &job.workspace_id, job.runnable_path()); + let mut tx = db.begin().await?; - // This way we tell downstream which script we should archive when the resolution is finished. - // (not used at the moment) - job.args - .as_mut() - .map(|args| args.insert("base_hash".to_owned(), to_raw_value(&*base_hash))); + // === DEBOUNCE CLEANUP === + // + // Clean up the debounce_key entry for this job (if it exists). + // + // IMPORTANT: We delete by key (not job_id) to avoid race conditions: + // If pusher has locked this row then this call will be blocked until all txs are commited. + // + // The idea is that the worker_lockfiles::trigger_dependents_to_recompute_locks will fetch the latest version of the obj. + // This object needs to be created before the djob is executed and it happens right here. + // + // This way the next pusher can fetch the latest version of object and base their djob payload on newest version. + // The concurrency limit on djobs will make sure that by the time next djob is started executing the base version it is referencing + // has already calculated all locks. This way even next djob will always use the fully finalized version of object. + // + // + // + // Note: We don't use a transaction here for performance (it's called during job pull). + // This means there's a tiny window where the job is running but key isn't deleted yet, + // which is acceptable because new requests will just accumulate data to this job. + tracing::debug!( + job_id = %job.id, + "Cleaning up debounce_key entry for completed/pulled job" + ); - let new_hash = windmill_common::scripts::clone_script( - base_hash, - &job.workspace_id, - deployment_message, - &mut tx, - ) - .await?; + // This will either: + // 1. Block until pusher pushed. Which gives us: + // - If there was any stale data in pusher, then we will read it here (couple of lines below) + // 2. Block pusher until we are done here. This gives us: + // - We will clone objects and retrieve the latest version. So when we are done the pusher can read latest version. + sqlx::query!("DELETE FROM debounce_key WHERE key = $1", &key) + .execute(&mut *tx) + .await + .map_err(|e| { + tracing::error!( + error = %e, + job_id = %job.id, + "Failed to delete debounce_key" + ); + e + })?; - new_hash - } - JobKind::FlowDependencies => { - sqlx::query_scalar!( - "INSERT INTO flow_version + if job + .args + .as_ref() + .map(|x| x.get("triggered_by_relative_import").is_some()) + .unwrap_or_default() + { + let Some(base_hash) = job.runnable_id else { + return Err(Error::InternalErr( + "Missing runnable_id for dependency job triggered by relative import" + .to_string(), + )); + }; + + tracing::debug!( + job_id = %job.id, + base_hash = %base_hash, + job_kind = ?kind, + "Creating new version for dependency job triggered by relative import" + ); + + let new_id = match kind { + JobKind::Dependencies => { + let deployment_message = job + .args + .clone() + .map(|hashmap| { + hashmap + .get("deployment_message") + .map(|map_value| { + serde_json::from_str::(map_value.get()).ok() + }) + .flatten() + }) + .flatten(); + + // This way we tell downstream which script we should archive when the resolution is finished. + // (not used at the moment) + job.args.as_mut().map(|args| { + args.insert("base_hash".to_owned(), to_raw_value(&*base_hash)) + }); + + let new_hash = windmill_common::scripts::clone_script( + base_hash, + &job.workspace_id, + deployment_message, + &mut tx, + ) + .await?; + + new_hash + } + JobKind::FlowDependencies => { + sqlx::query_scalar!( + "INSERT INTO flow_version (workspace_id, path, value, schema, created_by) SELECT workspace_id, path, value, schema, created_by @@ -5266,98 +5268,100 @@ pub async fn preprocess_dependency_job(job: &mut PulledJob, db: &DB) -> error::R RETURNING id ", - job.runnable_path(), - job.workspace_id, - *base_hash, - ) - .fetch_one(&mut *tx) - .await? - } - JobKind::AppDependencies => { - sqlx::query_scalar!( - "INSERT INTO app_version + job.runnable_path(), + job.workspace_id, + *base_hash, + ) + .fetch_one(&mut *tx) + .await? + } + JobKind::AppDependencies => { + sqlx::query_scalar!( + "INSERT INTO app_version (app_id, value, created_by, raw_app) SELECT app_id, value, created_by, raw_app FROM app_version WHERE id = $1 RETURNING id", - *base_hash - ) - .fetch_one(&mut *tx) - .await? - } - _ => { - return Err(Error::InternalErr(format!( - "Matched unexpected JobKind ({:?}). This is a bug!", - kind - ))) - } - }; + *base_hash + ) + .fetch_one(&mut *tx) + .await? + } + _ => { + return Err(Error::InternalErr(format!( + "Matched unexpected JobKind ({:?}). This is a bug!", + kind + ))) + } + }; - job.runnable_id.replace(new_id.into()); - } + job.runnable_id.replace(new_id.into()); + } - // === RETRIEVE ACCUMULATED DEBOUNCE DATA === - // - // For flows and apps, retrieve all nodes/components that were accumulated - // during the debounce window. This data comes from requests that were merged - // into this job instead of creating their own jobs. - // - // Scripts don't need this because they don't have nodes/components to relock. - if let Some(to_relock_field) = match &job.kind { - JobKind::FlowDependencies => Some("nodes_to_relock"), - JobKind::AppDependencies => Some("components_to_relock"), - _ => None, // Scripts don't use accumulated stale data - } { - tracing::debug!( - job_id = %job.id, - job_kind = ?job.kind, - field = %to_relock_field, - "Retrieving accumulated stale data from debounced requests" - ); - - if let Some(stale_data) = sqlx::query_scalar!( - "DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock", - &job.id - ) - .fetch_optional(&mut *tx) - .await - .map_err(|e| { - tracing::error!( - error = %e, - job_id = %job.id, - "Failed to retrieve debounce_stale_data" - ); - e - })? - .flatten() - { + // === RETRIEVE ACCUMULATED DEBOUNCE DATA === + // + // For flows and apps, retrieve all nodes/components that were accumulated + // during the debounce window. This data comes from requests that were merged + // into this job instead of creating their own jobs. + // + // Scripts don't need this because they don't have nodes/components to relock. + if let Some(to_relock_field) = match &job.kind { + JobKind::FlowDependencies => Some("nodes_to_relock"), + JobKind::AppDependencies => Some("components_to_relock"), + _ => None, // Scripts don't use accumulated stale data + } { tracing::debug!( job_id = %job.id, - node_count = stale_data.len(), - nodes = ?stale_data, - "Retrieved accumulated nodes/components from {} debounced requests", - stale_data.len() + job_kind = ?job.kind, + field = %to_relock_field, + "Retrieving accumulated stale data from debounced requests" ); - // Replace the job's relock list with the accumulated data - // This ensures all nodes from all debounced requests are processed - if let Some(args) = job.args.as_mut() { - args.insert(to_relock_field.to_owned(), to_raw_value(&stale_data)); + if let Some(stale_data) = sqlx::query_scalar!( + "DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock", + &job.id + ) + .fetch_optional(&mut *tx) + .await + .map_err(|e| { + tracing::error!( + error = %e, + job_id = %job.id, + "Failed to retrieve debounce_stale_data" + ); + e + })? + .flatten() + { tracing::debug!( - field = %to_relock_field, - "Updated job args with accumulated debounce data" + job_id = %job.id, + node_count = stale_data.len(), + nodes = ?stale_data, + "Retrieved accumulated nodes/components from {} debounced requests", + stale_data.len() + ); + + // Replace the job's relock list with the accumulated data + // This ensures all nodes from all debounced requests are processed + if let Some(args) = job.args.as_mut() { + args.insert(to_relock_field.to_owned(), to_raw_value(&stale_data)); + tracing::debug!( + field = %to_relock_field, + "Updated job args with accumulated debounce data" + ); + } + } else { + tracing::trace!( + job_id = %job.id, + "No accumulated stale data found (no debounced requests or already cleaned up)" ); } - } else { - tracing::trace!( - job_id = %job.id, - "No accumulated stale data found (no debounced requests or already cleaned up)" - ); } - } - // This will unblock pusher. - tx.commit().await?; + // This will unblock pusher. + tx.commit().await?; + Ok(()) + }).await; } Ok(()) diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 0e2f87f7d0..ca3b1583f8 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -1598,7 +1598,7 @@ pub async fn run_worker( { match timeout( core::time::Duration::from_secs(10), - Box::pin(preprocess_dependency_job(pulled_job, &db)), + preprocess_dependency_job(pulled_job, &db), ) .warn_after_seconds(2) .await From e5a11a7bc632c7a3a882264a7b62a299901d8c04 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 20:31:46 +0000 Subject: [PATCH 25/33] split main async with a box pin --- backend/windmill-queue/src/jobs.rs | 1 + backend/windmill-worker/src/worker.rs | 701 +++++++++++++------------- 2 files changed, 357 insertions(+), 345 deletions(-) diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 85180d1791..ff46165af1 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -5140,6 +5140,7 @@ pub async fn preprocess_dependency_job(job: &mut PulledJob, db: &DB) -> error::R // Handle dependency job debouncing cleanup when a job is pulled for execution if kind.is_dependency() && !*WMDEBUG_NO_DJOB_DEBOUNCING { return Box::pin(async move { + // Only used for testing in tests/relative_imports.rs // Give us some space to work with. #[cfg(debug_assertions)] diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index ca3b1583f8..eb16e3f5f4 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -2355,393 +2355,404 @@ pub async fn handle_queued_job( precomputed_agent_info: Option, #[cfg(feature = "benchmark")] _bench: &mut BenchmarkIter, ) -> windmill_common::error::Result { - // Extract the active span from the context + return Box::pin(async move { + // Extract the active span from the context - if job.canceled_by.is_some() { - return Err(Error::JsonErr(canceled_job_to_result(&job))); - } - if let Some(e) = &job.pre_run_error { - return Err(Error::ExecutionErr(e.to_string())); - } + if job.canceled_by.is_some() { + return Err(Error::JsonErr(canceled_job_to_result(&job))); + } + if let Some(e) = &job.pre_run_error { + return Err(Error::ExecutionErr(e.to_string())); + } - #[cfg(any(not(feature = "enterprise"), feature = "sqlx"))] - match conn { - Connection::Sql(db) => { - if job.parent_job.is_none() && job.created_by.starts_with("email-") { - let daily_count = sqlx::query!( + #[cfg(any(not(feature = "enterprise"), feature = "sqlx"))] + match conn { + Connection::Sql(db) => { + if job.parent_job.is_none() && job.created_by.starts_with("email-") { + let daily_count = sqlx::query!( "SELECT value FROM metrics WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day' ORDER BY created_at DESC LIMIT 1" ).fetch_optional(db) .warn_after_seconds(5) .await?.map(|x| serde_json::from_value::(x.value).unwrap_or(1)); - if let Some(count) = daily_count { - if count >= 100 { - return Err(error::Error::QuotaExceeded(format!( - "Email trigger usage limit of 100 per day has been reached." - ))); - } else { - sqlx::query!( + if let Some(count) = daily_count { + if count >= 100 { + return Err(error::Error::QuotaExceeded(format!( + "Email trigger usage limit of 100 per day has been reached." + ))); + } else { + sqlx::query!( "UPDATE metrics SET value = $1 WHERE id = 'email_trigger_usage' AND created_at > NOW() - INTERVAL '1 day'", serde_json::json!(count + 1) ) .execute(db) .warn_after_seconds(5) .await?; - } - } else { - sqlx::query!( + } + } else { + sqlx::query!( "INSERT INTO metrics (id, value) VALUES ('email_trigger_usage', to_jsonb(1))" ) - .execute(db) - .warn_after_seconds(5) - .await?; + .execute(db) + .warn_after_seconds(5) + .await?; + } } } + Connection::Http(_) => { + return Err(Error::internal_err(format!( + "Could not check email trigger usage for job with agent worker {}", + job.id + ))) + } } - Connection::Http(_) => { - return Err(Error::internal_err(format!( - "Could not check email trigger usage for job with agent worker {}", - job.id - ))) + + // no need to mark job as started if http conn, it's done by the server when pulled + if let Connection::Sql(db) = conn { + job.mark_as_started_if_step(db).await?; } - } - // no need to mark job as started if http conn, it's done by the server when pulled - if let Connection::Sql(db) = conn { - job.mark_as_started_if_step(db).await?; - } - - let started = Instant::now(); - // Pre-fetch preview jobs raw values if necessary. - // The `raw_*` values passed to this function are the original raw values from `queue` tables, - // they are kept for backward compatibility as they have been moved to the `job` table. - let preview_data = match (job.kind, job.runnable_id) { - ( - JobKind::Preview - | JobKind::Dependencies - | JobKind::FlowPreview - | JobKind::Flow - | JobKind::FlowDependencies - | JobKind::SingleStepFlow, - x, - ) => { - if x.map(|x| x.0).is_none_or(|x| is_special_codebase_hash(x)) { - Some( - cache::job::fetch_preview(conn, &job.id, raw_lock, raw_code, raw_flow.clone()) + let started = Instant::now(); + // Pre-fetch preview jobs raw values if necessary. + // The `raw_*` values passed to this function are the original raw values from `queue` tables, + // they are kept for backward compatibility as they have been moved to the `job` table. + let preview_data = match (job.kind, job.runnable_id) { + ( + JobKind::Preview + | JobKind::Dependencies + | JobKind::FlowPreview + | JobKind::Flow + | JobKind::FlowDependencies + | JobKind::SingleStepFlow, + x, + ) => { + if x.map(|x| x.0).is_none_or(|x| is_special_codebase_hash(x)) { + Some( + cache::job::fetch_preview( + conn, + &job.id, + raw_lock, + raw_code, + raw_flow.clone(), + ) .await?, - ) - } else { - None - } - } - _ => None, - }; - - let cached_res_path = if job.cache_ttl.is_some() { - match conn { - Connection::Sql(db) => { - Some(cached_result_path(db, &client, &job, preview_data.as_ref()).await) - } - Connection::Http(_) => None, - } - } else { - None - }; - - if let Some(db) = conn.as_sql() { - if let Some(cached_res_path) = cached_res_path.as_ref() { - let cached_result_maybe = get_cached_resource_value_if_valid( - db, - &client, - &job.id, - &job.workspace_id, - &cached_res_path, - ) - .warn_after_seconds(5) - .await; - if let Some(result) = cached_result_maybe { - { - let logs = "Job skipped because args & path found in cache and not expired" - .to_string(); - append_logs(&job.id, &job.workspace_id, logs, conn).await; - } - let result = job_completed_tx - .send_job( - JobCompleted { - preprocessed_args: None, - job, - result, - result_columns: None, - mem_peak: 0, - canceled_by: None, - success: true, - cached_res_path: None, - token: client.token.clone(), - duration: None, - has_stream: Some(false), - from_cache: Some(true), - }, - true, ) - .await; - - match result { - Ok(_) => { - tracing::debug!("Send job completed") - } - Err(err) => { - tracing::error!("An error occurred while sending job completed: {:#?}", err) - } + } else { + None } - - return Ok(true); } + _ => None, }; - } - if job.is_flow() { - if let Some(db) = conn.as_sql() { - let flow_data = match preview_data { - Some(RawData::Flow(data)) => data, - // Not a preview: fetch from the cache or the database. - _ => cache::job::fetch_flow(db, &job.kind, job.runnable_id).await?, - }; - handle_flow( - job, - &flow_data, - db, - &client, - None, - &same_worker_tx.expect(SAME_WORKER_REQUIREMENTS), - worker_dir, - job_completed_tx.clone(), - worker_name, - ) - .warn_after_seconds(10) - .await?; - Ok(true) + + let cached_res_path = if job.cache_ttl.is_some() { + match conn { + Connection::Sql(db) => { + Some(cached_result_path(db, &client, &job, preview_data.as_ref()).await) + } + Connection::Http(_) => None, + } } else { - return Err(Error::internal_err( - "Could not handle flow job with agent worker".to_string(), - )); - } - } else { - let mut logs = "".to_string(); - let mut mem_peak: i32 = 0; - let mut canceled_by: Option = None; - // println!("handle queue {:?}", SystemTime::now()); + None + }; - logs.push_str(&format!( - "job={} {}={} worker={} hostname={}\n", - &job.id, *LOG_TAG_NAME, &job.tag, &worker_name, &hostname - )); - - if *NO_LOGS_AT_ALL { - logs.push_str("Logs are fully disabled for this worker\n"); - } - - if *NO_LOGS { - logs.push_str("Logs are disabled for this worker\n"); - } - - if *SLOW_LOGS { - logs.push_str("Logs are 10x less frequent for this worker\n"); - } - - #[cfg(not(feature = "enterprise"))] - if job.concurrent_limit.is_some() { - logs.push_str("---\n"); - logs.push_str("WARNING: This job has concurrency limits enabled. Concurrency limits are an EE feature and the setting is ignored.\n"); - logs.push_str("---\n"); - } - - // Only used for testing in tests/relative_imports.rs - // Give us some space to work with. - #[cfg(debug_assertions)] - if let Some(dbg_djob_sleep) = job - .args - .as_ref() - .map(|x| { - x.get("dbg_djob_sleep") - .map(|v| serde_json::from_str::(v.get()).ok()) - .flatten() - }) - .flatten() - { - tracing::debug!("Debug: {} going to sleep for {}", job.id, dbg_djob_sleep); - sleep(std::time::Duration::from_secs(dbg_djob_sleep as u64)).await; - } - - tracing::debug!( - workspace_id = %job.workspace_id, - "handling job {}", - job.id - ); - append_logs(&job.id, &job.workspace_id, logs, conn).await; - - let mut column_order: Option> = None; - let mut new_args: Option>> = None; - let mut has_stream = false; - let result = match job.kind { - JobKind::Dependencies => match conn { - Connection::Sql(db) => { - handle_dependency_job( - &job, - preview_data.as_ref(), - &mut mem_peak, - &mut canceled_by, - job_dir, - db, - worker_name, - worker_dir, - base_internal_url, - &client.token, - occupancy_metrics, - ) - .await - } - Connection::Http(_) => { - return Err(Error::internal_err( - "Could not handle dependency job with agent worker".to_string(), - )); - } - }, - JobKind::FlowDependencies => match conn { - Connection::Sql(db) => { - handle_flow_dependency_job( - (*job).clone(), - preview_data.as_ref(), - &mut mem_peak, - &mut canceled_by, - job_dir, - db, - worker_name, - worker_dir, - base_internal_url, - &client.token, - occupancy_metrics, - ) - .await - } - Connection::Http(_) => { - return Err(Error::internal_err( - "Could not handle flow dependency job with agent worker".to_string(), - )); - } - }, - JobKind::AppDependencies => match conn { - Connection::Sql(db) => handle_app_dependency_job( - (*job).clone(), - &mut mem_peak, - &mut canceled_by, - job_dir, + if let Some(db) = conn.as_sql() { + if let Some(cached_res_path) = cached_res_path.as_ref() { + let cached_result_maybe = get_cached_resource_value_if_valid( db, - worker_name, - worker_dir, - base_internal_url, - &client.token, - occupancy_metrics, + &client, + &job.id, + &job.workspace_id, + &cached_res_path, ) - .await - .map(|()| serde_json::from_str("{}").unwrap()), - Connection::Http(_) => { - return Err(Error::internal_err( - "Could not handle app dependency job with agent worker".to_string(), - )); + .warn_after_seconds(5) + .await; + if let Some(result) = cached_result_maybe { + { + let logs = "Job skipped because args & path found in cache and not expired" + .to_string(); + append_logs(&job.id, &job.workspace_id, logs, conn).await; + } + let result = job_completed_tx + .send_job( + JobCompleted { + preprocessed_args: None, + job, + result, + result_columns: None, + mem_peak: 0, + canceled_by: None, + success: true, + cached_res_path: None, + token: client.token.clone(), + duration: None, + has_stream: Some(false), + from_cache: Some(true), + }, + true, + ) + .await; + + match result { + Ok(_) => { + tracing::debug!("Send job completed") + } + Err(err) => { + tracing::error!( + "An error occurred while sending job completed: {:#?}", + err + ) + } + } + + return Ok(true); } - }, - JobKind::Identity => Ok(job + }; + } + if job.is_flow() { + if let Some(db) = conn.as_sql() { + let flow_data = match preview_data { + Some(RawData::Flow(data)) => data, + // Not a preview: fetch from the cache or the database. + _ => cache::job::fetch_flow(db, &job.kind, job.runnable_id).await?, + }; + handle_flow( + job, + &flow_data, + db, + &client, + None, + &same_worker_tx.expect(SAME_WORKER_REQUIREMENTS), + worker_dir, + job_completed_tx.clone(), + worker_name, + ) + .warn_after_seconds(10) + .await?; + Ok(true) + } else { + return Err(Error::internal_err( + "Could not handle flow job with agent worker".to_string(), + )); + } + } else { + let mut logs = "".to_string(); + let mut mem_peak: i32 = 0; + let mut canceled_by: Option = None; + // println!("handle queue {:?}", SystemTime::now()); + + logs.push_str(&format!( + "job={} {}={} worker={} hostname={}\n", + &job.id, *LOG_TAG_NAME, &job.tag, &worker_name, &hostname + )); + + if *NO_LOGS_AT_ALL { + logs.push_str("Logs are fully disabled for this worker\n"); + } + + if *NO_LOGS { + logs.push_str("Logs are disabled for this worker\n"); + } + + if *SLOW_LOGS { + logs.push_str("Logs are 10x less frequent for this worker\n"); + } + + #[cfg(not(feature = "enterprise"))] + if job.concurrent_limit.is_some() { + logs.push_str("---\n"); + logs.push_str("WARNING: This job has concurrency limits enabled. Concurrency limits are an EE feature and the setting is ignored.\n"); + logs.push_str("---\n"); + } + + // Only used for testing in tests/relative_imports.rs + // Give us some space to work with. + #[cfg(debug_assertions)] + if let Some(dbg_djob_sleep) = job .args .as_ref() - .map(|x| x.get("previous_result")) + .map(|x| { + x.get("dbg_djob_sleep") + .map(|v| serde_json::from_str::(v.get()).ok()) + .flatten() + }) .flatten() - .map(|x| x.to_owned()) - .unwrap_or_else(|| serde_json::from_str("{}").unwrap())), - JobKind::AIAgent => match conn { - Connection::Sql(db) => { - handle_ai_agent_job( - conn, - db, - job.as_ref(), - &client, - &mut canceled_by, + { + tracing::debug!("Debug: {} going to sleep for {}", job.id, dbg_djob_sleep); + sleep(std::time::Duration::from_secs(dbg_djob_sleep as u64)).await; + } + + tracing::debug!( + workspace_id = %job.workspace_id, + "handling job {}", + job.id + ); + append_logs(&job.id, &job.workspace_id, logs, conn).await; + + let mut column_order: Option> = None; + let mut new_args: Option>> = None; + let mut has_stream = false; + let result = match job.kind { + JobKind::Dependencies => match conn { + Connection::Sql(db) => { + handle_dependency_job( + &job, + preview_data.as_ref(), + &mut mem_peak, + &mut canceled_by, + job_dir, + db, + worker_name, + worker_dir, + base_internal_url, + &client.token, + occupancy_metrics, + ) + .await + } + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle dependency job with agent worker".to_string(), + )); + } + }, + JobKind::FlowDependencies => match conn { + Connection::Sql(db) => { + handle_flow_dependency_job( + (*job).clone(), + preview_data.as_ref(), + &mut mem_peak, + &mut canceled_by, + job_dir, + db, + worker_name, + worker_dir, + base_internal_url, + &client.token, + occupancy_metrics, + ) + .await + } + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle flow dependency job with agent worker".to_string(), + )); + } + }, + JobKind::AppDependencies => match conn { + Connection::Sql(db) => handle_app_dependency_job( + (*job).clone(), &mut mem_peak, - &mut *occupancy_metrics, - &job_completed_tx, + &mut canceled_by, + job_dir, + db, + worker_name, worker_dir, base_internal_url, - worker_name, - hostname, - killpill_rx, - &mut has_stream, + &client.token, + occupancy_metrics, ) .await + .map(|()| serde_json::from_str("{}").unwrap()), + Connection::Http(_) => { + return Err(Error::internal_err( + "Could not handle app dependency job with agent worker".to_string(), + )); + } + }, + JobKind::Identity => Ok(job + .args + .as_ref() + .map(|x| x.get("previous_result")) + .flatten() + .map(|x| x.to_owned()) + .unwrap_or_else(|| serde_json::from_str("{}").unwrap())), + JobKind::AIAgent => match conn { + Connection::Sql(db) => { + handle_ai_agent_job( + conn, + db, + job.as_ref(), + &client, + &mut canceled_by, + &mut mem_peak, + &mut *occupancy_metrics, + &job_completed_tx, + worker_dir, + base_internal_url, + worker_name, + hostname, + killpill_rx, + &mut has_stream, + ) + .await + } + Connection::Http(_) => { + return Err(Error::internal_err( + "Agent worker does not support ai agent jobs".to_string(), + )); + } + }, + _ => { + let metric_timer = Instant::now(); + let preview_data = preview_data.and_then(|data| match data { + RawData::Script(data) => Some(data), + _ => None, + }); + let r = handle_code_execution_job( + job.as_ref(), + preview_data, + conn, + client, + parent_runnable_path, + job_dir, + worker_dir, + &mut mem_peak, + &mut canceled_by, + base_internal_url, + worker_name, + &mut column_order, + &mut new_args, + occupancy_metrics, + killpill_rx, + precomputed_agent_info, + &mut has_stream, + ) + .await; + occupancy_metrics.total_duration_of_running_jobs += + metric_timer.elapsed().as_secs_f32(); + r } - Connection::Http(_) => { - return Err(Error::internal_err( - "Agent worker does not support ai agent jobs".to_string(), - )); - } - }, - _ => { - let metric_timer = Instant::now(); - let preview_data = preview_data.and_then(|data| match data { - RawData::Script(data) => Some(data), - _ => None, - }); - let r = handle_code_execution_job( - job.as_ref(), - preview_data, - conn, - client, - parent_runnable_path, - job_dir, - worker_dir, - &mut mem_peak, - &mut canceled_by, - base_internal_url, - worker_name, - &mut column_order, - &mut new_args, - occupancy_metrics, - killpill_rx, - precomputed_agent_info, - &mut has_stream, - ) - .await; - occupancy_metrics.total_duration_of_running_jobs += - metric_timer.elapsed().as_secs_f32(); - r + }; + + //it's a test job, no need to update the db + if job.as_ref().workspace_id == "" { + return Ok(true); } - }; - //it's a test job, no need to update the db - if job.as_ref().workspace_id == "" { - return Ok(true); + if result + .as_ref() + .is_err_and(|err| matches!(err, &Error::AlreadyCompleted(_))) + { + return Ok(false); + } + process_result( + job, + result.map(|x| Arc::new(x)), + job_dir, + job_completed_tx, + mem_peak, + canceled_by, + cached_res_path, + &client.token, + column_order, + new_args, + conn, + Some(started.elapsed().as_millis() as i64), + has_stream, + ) + .await } - - if result - .as_ref() - .is_err_and(|err| matches!(err, &Error::AlreadyCompleted(_))) - { - return Ok(false); - } - process_result( - job, - result.map(|x| Arc::new(x)), - job_dir, - job_completed_tx, - mem_peak, - canceled_by, - cached_res_path, - &client.token, - column_order, - new_args, - conn, - Some(started.elapsed().as_millis() as i64), - has_stream, - ) - .await - } + }).await; } pub fn build_envs( From a4a502cf3af3cd288471fc71a12651de6641bd3f Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:39:11 +0200 Subject: [PATCH 26/33] ducklake safety for instance_settings.yaml users (#6844) --- ...nstance_settings_safety_migration.down.sql | 0 ..._instance_settings_safety_migration.up.sql | 27 +++++++++++++++++++ backend/windmill-api/src/settings.rs | 4 +++ 3 files changed, 31 insertions(+) create mode 100644 backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.down.sql create mode 100644 backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql diff --git a/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.down.sql b/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.down.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql b/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql new file mode 100644 index 0000000000..05a111edfd --- /dev/null +++ b/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql @@ -0,0 +1,27 @@ +-- Copy of 20250731132157_ducklake_instance_settings.up.sql +-- Pushing a instance_settings.yaml without ducklake_user_pg_pwd will remove it from the global settings +-- And the next migration will fail because it will try to insert a NULL value + +INSERT INTO global_settings (name, value) +VALUES ('ducklake_user_pg_pwd', ('"' || gen_random_uuid()::text || '"')::jsonb) +ON CONFLICT DO NOTHING; + +-- Cannot simply create the user because Postgres expect a static string for the password +-- Also we cannot drop the user easily in the down migration because databases will depend on it +-- And we cannot drop databases in transactions (migrations) + +DO $$ +DECLARE + pwd text; +BEGIN + SELECT trim(both '"' from value::text) INTO pwd FROM global_settings WHERE name = 'ducklake_user_pg_pwd'; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ducklake_user') THEN + EXECUTE format('CREATE USER ducklake_user WITH PASSWORD %L', pwd); + ELSE + EXECUTE format('ALTER USER ducklake_user WITH PASSWORD %L', pwd); + END IF; +EXCEPTION + WHEN others THEN + RAISE NOTICE 'ducklake_user migration error, skipping.'; +END +$$; \ No newline at end of file diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index b1c9df8536..4851130286 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -218,6 +218,10 @@ pub struct Value { } pub async fn delete_global_setting(db: &DB, key: &str) -> error::Result<()> { + if key == "ducklake_user_pg_pwd" || key == "ducklake_settings" { + tracing::error!("Tried to unset global setting {}, ignored", key); + return Ok(()); + } sqlx::query!("DELETE FROM global_settings WHERE name = $1", key,) .execute(db) .await?; From c86b3448b86e008f14a25280285cc2f498eb926a Mon Sep 17 00:00:00 2001 From: hugocasa Date: Thu, 16 Oct 2025 22:40:25 +0200 Subject: [PATCH 27/33] feat: add support for sage intacct oauth (#6794) Co-authored-by: Ruben Fiszel --- backend/oauth_connect.json | 67 ++++++------------- .../src/lib/components/AuthSettings.svelte | 1 + .../src/lib/components/icons/SageIcon.svelte | 15 +++++ frontend/src/lib/components/icons/index.ts | 2 + 4 files changed, 37 insertions(+), 48 deletions(-) create mode 100644 frontend/src/lib/components/icons/SageIcon.svelte diff --git a/backend/oauth_connect.json b/backend/oauth_connect.json index 6f7f38770a..4c071fea09 100644 --- a/backend/oauth_connect.json +++ b/backend/oauth_connect.json @@ -2,47 +2,32 @@ "github": { "auth_url": "https://github.com/login/oauth/authorize", "token_url": "https://github.com/login/oauth/access_token", - "scopes": [ - "workflow", - "repo" - ] + "scopes": ["workflow", "repo"] }, "gitlab": { "auth_url": "https://gitlab.com/oauth/authorize", "token_url": "https://gitlab.com/oauth/token", - "scopes": [ - "api" - ] + "scopes": ["api"] }, "bitbucket": { "auth_url": "https://bitbucket.org/site/oauth2/authorize", "token_url": "https://bitbucket.org/site/oauth2/access_token", - "scopes": [ - "repository" - ] + "scopes": ["repository"] }, "slack": { "auth_url": "https://slack.com/oauth/authorize", "token_url": "https://slack.com/api/oauth.access", - "scopes": [ - "chat:write:user", - "users:read", - "users:read.email" - ] + "scopes": ["chat:write:user", "users:read", "users:read.email"] }, "supabase_wizard": { "auth_url": "https://api.supabase.com/v1/oauth/authorize", "token_url": "https://api.supabase.com/v1/oauth/token", - "scopes": [ - "all" - ] + "scopes": ["all"] }, "gsheets": { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", "token_url": "https://oauth2.googleapis.com/token", - "scopes": [ - "https://www.googleapis.com/auth/spreadsheets" - ], + "scopes": ["https://www.googleapis.com/auth/spreadsheets"], "extra_params": { "access_type": "offline", "prompt": "consent" @@ -51,9 +36,7 @@ "gdrive": { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", "token_url": "https://oauth2.googleapis.com/token", - "scopes": [ - "https://www.googleapis.com/auth/drive" - ], + "scopes": ["https://www.googleapis.com/auth/drive"], "extra_params": { "access_type": "offline", "prompt": "consent" @@ -62,9 +45,7 @@ "gmail": { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", "token_url": "https://oauth2.googleapis.com/token", - "scopes": [ - "https://www.googleapis.com/auth/gmail.send" - ], + "scopes": ["https://www.googleapis.com/auth/gmail.send"], "extra_params": { "access_type": "offline", "prompt": "consent" @@ -73,9 +54,7 @@ "gcal": { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", "token_url": "https://oauth2.googleapis.com/token", - "scopes": [ - "https://www.googleapis.com/auth/calendar.events" - ], + "scopes": ["https://www.googleapis.com/auth/calendar.events"], "extra_params": { "access_type": "offline", "prompt": "consent" @@ -84,9 +63,7 @@ "gforms": { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", "token_url": "https://oauth2.googleapis.com/token", - "scopes": [ - "https://www.googleapis.com/auth/forms" - ], + "scopes": ["https://www.googleapis.com/auth/forms"], "extra_params": { "access_type": "offline", "prompt": "consent" @@ -95,9 +72,7 @@ "gcloud": { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth", "token_url": "https://oauth2.googleapis.com/token", - "scopes": [ - "https://www.googleapis.com/auth/cloud-platform" - ], + "scopes": ["https://www.googleapis.com/auth/cloud-platform"], "extra_params": { "access_type": "offline", "prompt": "consent" @@ -128,19 +103,13 @@ "linkedin": { "auth_url": "https://www.linkedin.com/oauth/v2/authorization", "token_url": "https://www.linkedin.com/oauth/v2/accessToken", - "scopes": [ - "w_member_social", - "r_liteprofile", - "r_emailaddress" - ], + "scopes": ["w_member_social", "r_liteprofile", "r_emailaddress"], "req_body_auth": true }, "quickbooks": { "auth_url": "https://appcenter.intuit.com/connect/oauth2", "token_url": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer", - "scopes": [ - "com.intuit.quickbooks.accounting" - ] + "scopes": ["com.intuit.quickbooks.accounting"] }, "visma": { "auth_url": "https://connect.visma.com/connect/authorize", @@ -153,6 +122,11 @@ "vismanet_erp_interactive_api:update" ] }, + "sage_intacct": { + "auth_url": "https://api.intacct.com/ia/api/v1/oauth2/authorize", + "token_url": "https://api.intacct.com/ia/api/v1/oauth2/token", + "scopes": ["offline_access"] + }, "spotify": { "auth_url": "https://accounts.spotify.com/authorize", "token_url": "https://accounts.spotify.com/api/token", @@ -175,10 +149,7 @@ "xero": { "auth_url": "https://login.xero.com/identity/connect/authorize", "token_url": "https://identity.xero.com/connect/token", - "scopes": [ - "offline_access", - "accounting.transactions" - ] + "scopes": ["offline_access", "accounting.transactions"] }, "zoho": { "auth_url": "https://accounts.zoho.com/oauth/v2/auth", diff --git a/frontend/src/lib/components/AuthSettings.svelte b/frontend/src/lib/components/AuthSettings.svelte index eba6ac086a..e86adb1b4d 100644 --- a/frontend/src/lib/components/AuthSettings.svelte +++ b/frontend/src/lib/components/AuthSettings.svelte @@ -65,6 +65,7 @@ 'linkedin', 'quickbooks', 'visma', + 'sage_intacct', 'spotify', 'snowflake_oauth', 'teams', diff --git a/frontend/src/lib/components/icons/SageIcon.svelte b/frontend/src/lib/components/icons/SageIcon.svelte new file mode 100644 index 0000000000..118f827c8e --- /dev/null +++ b/frontend/src/lib/components/icons/SageIcon.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/frontend/src/lib/components/icons/index.ts b/frontend/src/lib/components/icons/index.ts index 885c6f0b59..bc558628c3 100644 --- a/frontend/src/lib/components/icons/index.ts +++ b/frontend/src/lib/components/icons/index.ts @@ -99,6 +99,7 @@ import XeroIcon from './XeroIcon.svelte' import KafkaIcon from './KafkaIcon.svelte' import NatsIcon from './NatsIcon.svelte' import MqttIcon from './MqttIcon.svelte' +import SageIcon from './SageIcon.svelte' import ZohoIcon from './ZohoIcon.svelte' export const APP_TO_ICON_COMPONENT = { postgresql: PostgresIcon, @@ -199,6 +200,7 @@ export const APP_TO_ICON_COMPONENT = { jumpcloud: JumpCloudIcon, keycloak: KeycloakIcon, zitadel: ZitadelIcon, + sage_intacct: SageIcon, spotify: SpotifyIcon, xero: XeroIcon, kafka: KafkaIcon, From 56ca67a11d2e9be84606111ffa692e50aac7338b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 20:53:32 +0000 Subject: [PATCH 28/33] nit --- frontend/src/lib/infer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index 6f1d6d7829..31aae4aaab 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -114,7 +114,7 @@ export async function inferAssets( return [] } -export async function inferAnsibleExecutionMode(code: string): any { +export async function inferAnsibleExecutionMode(code: string): Promise { try { await initWasmYaml() return JSON.parse(parse_ansible_delegate(code)) From ea8c9cc7666ac650f97bfca2285893c3b5c8d596 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 21:29:44 +0000 Subject: [PATCH 29/33] remove safety migration --- ...nstance_settings_safety_migration.down.sql | 0 ..._instance_settings_safety_migration.up.sql | 27 ------------------- 2 files changed, 27 deletions(-) delete mode 100644 backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.down.sql delete mode 100644 backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql diff --git a/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.down.sql b/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.down.sql deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql b/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql deleted file mode 100644 index 05a111edfd..0000000000 --- a/backend/migrations/20251006143821_ducklake_instance_settings_safety_migration.up.sql +++ /dev/null @@ -1,27 +0,0 @@ --- Copy of 20250731132157_ducklake_instance_settings.up.sql --- Pushing a instance_settings.yaml without ducklake_user_pg_pwd will remove it from the global settings --- And the next migration will fail because it will try to insert a NULL value - -INSERT INTO global_settings (name, value) -VALUES ('ducklake_user_pg_pwd', ('"' || gen_random_uuid()::text || '"')::jsonb) -ON CONFLICT DO NOTHING; - --- Cannot simply create the user because Postgres expect a static string for the password --- Also we cannot drop the user easily in the down migration because databases will depend on it --- And we cannot drop databases in transactions (migrations) - -DO $$ -DECLARE - pwd text; -BEGIN - SELECT trim(both '"' from value::text) INTO pwd FROM global_settings WHERE name = 'ducklake_user_pg_pwd'; - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ducklake_user') THEN - EXECUTE format('CREATE USER ducklake_user WITH PASSWORD %L', pwd); - ELSE - EXECUTE format('ALTER USER ducklake_user WITH PASSWORD %L', pwd); - END IF; -EXCEPTION - WHEN others THEN - RAISE NOTICE 'ducklake_user migration error, skipping.'; -END -$$; \ No newline at end of file From 72c6bad0dde0b0a877036d9e51951ffb6c1e5ca1 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 16 Oct 2025 23:54:00 +0200 Subject: [PATCH 30/33] create ducklake_user_pg_pwd if deleted by CLI (#6845) --- ...6143821_ducklake_safety_migration.down.sql | 1 + ...006143821_ducklake_safety_migration.up.sql | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 backend/migrations/20251006143821_ducklake_safety_migration.down.sql create mode 100644 backend/migrations/20251006143821_ducklake_safety_migration.up.sql diff --git a/backend/migrations/20251006143821_ducklake_safety_migration.down.sql b/backend/migrations/20251006143821_ducklake_safety_migration.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20251006143821_ducklake_safety_migration.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20251006143821_ducklake_safety_migration.up.sql b/backend/migrations/20251006143821_ducklake_safety_migration.up.sql new file mode 100644 index 0000000000..d08237c4f8 --- /dev/null +++ b/backend/migrations/20251006143821_ducklake_safety_migration.up.sql @@ -0,0 +1,37 @@ + +-- Users of instance_settings.yaml would have issues where it deletes ducklake_user_pg_pwd +-- and then the next migration fails because it tries to insert a NULL value + +-- When everything is fine (i.e ducklake_user_pg_pwd or ducklake_settings is present) +-- this should be a no-op + +DO $$ +DECLARE + new_settings_value text; + old_setting_value text; +BEGIN + SELECT value INTO new_settings_value FROM global_settings WHERE name = 'ducklake_settings'; + SELECT trim(both '"' from value::text) INTO old_setting_value FROM global_settings WHERE name = 'ducklake_user_pg_pwd'; + + IF new_settings_value IS NULL AND old_setting_value IS NULL THEN + -- Copied from 20250731132157_ducklake_instance_settings.up.sql + + INSERT INTO global_settings (name, value) + VALUES ('ducklake_user_pg_pwd', ('"' || gen_random_uuid()::text || '"')::jsonb) + ON CONFLICT DO NOTHING; + + -- Cannot simply create the user because Postgres expect a static string for the password + -- Also we cannot drop the user easily in the down migration because databases will depend on it + -- And we cannot drop databases in transactions (migrations) + + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ducklake_user') THEN + EXECUTE format('CREATE USER ducklake_user WITH PASSWORD %L', old_setting_value); + ELSE + EXECUTE format('ALTER USER ducklake_user WITH PASSWORD %L', old_setting_value); + END IF; + END IF; +EXCEPTION + WHEN others THEN + RAISE NOTICE 'ducklake_user migration error, skipping.'; +END +$$; \ No newline at end of file From cc64f8acefece575f9fad343bfdf316b1b91fc80 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 16 Oct 2025 21:54:50 +0000 Subject: [PATCH 31/33] chore(main): release 1.562.0 (#6841) * chore(main): release 1.562.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 13 +++++ backend/Cargo.lock | 56 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 58 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c724f781..2ce11316cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.562.0](https://github.com/windmill-labs/windmill/compare/v1.561.0...v1.562.0) (2025-10-16) + + +### Features + +* add support for sage intacct oauth ([#6794](https://github.com/windmill-labs/windmill/issues/6794)) ([c86b344](https://github.com/windmill-labs/windmill/commit/c86b3448b86e008f14a25280285cc2f498eb926a)) +* dependency job debouncing ([#6769](https://github.com/windmill-labs/windmill/issues/6769)) ([defb6c9](https://github.com/windmill-labs/windmill/commit/defb6c9694ac294dbf19ba5cd42ce7399ad1b9ac)) + + +### Bug Fixes + +* add configurable timeout sse stream ([f723a1f](https://github.com/windmill-labs/windmill/commit/f723a1fb7227ae45661fea5cf2e6f9928a39672b)) + ## [1.561.0](https://github.com/windmill-labs/windmill/compare/v1.560.0...v1.561.0) (2025-10-16) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 730e4d390f..7b6a14573e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15122,7 +15122,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15182,7 +15182,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "argon2", @@ -15302,7 +15302,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.561.0" +version = "1.562.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15317,7 +15317,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.561.0" +version = "1.562.0" dependencies = [ "chrono", "serde", @@ -15330,7 +15330,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "axum", @@ -15349,7 +15349,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "async-recursion", @@ -15433,7 +15433,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.561.0" +version = "1.562.0" dependencies = [ "regex", "serde", @@ -15448,7 +15448,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "bytes", @@ -15472,7 +15472,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.561.0" +version = "1.562.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15484,7 +15484,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.561.0" +version = "1.562.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15493,7 +15493,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "lazy_static", @@ -15505,7 +15505,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "serde_json", @@ -15517,7 +15517,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "gosyn", @@ -15529,7 +15529,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "lazy_static", @@ -15541,7 +15541,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "serde_json", @@ -15553,7 +15553,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "nu-parser", @@ -15564,7 +15564,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15575,7 +15575,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15587,7 +15587,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "async-recursion", @@ -15610,7 +15610,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "lazy_static", @@ -15624,7 +15624,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15641,7 +15641,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "lazy_static", @@ -15655,7 +15655,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "lazy_static", @@ -15673,7 +15673,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15698,7 +15698,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "serde", @@ -15709,7 +15709,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "async-recursion", @@ -15742,7 +15742,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.561.0" +version = "1.562.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15752,7 +15752,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.561.0" +version = "1.562.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 58ecd617dd..9942e3b99d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.561.0" +version = "1.562.0" authors.workspace = true edition.workspace = true @@ -34,7 +34,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.561.0" +version = "1.562.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index c90c69ef82..d1c1a6e5bc 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.561.0 + version: 1.562.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 1ed1ed65e4..f8a4bdf629 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.561.0"; +export const VERSION = "v1.562.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 5b0f7493eb..63753faa7e 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.561.0"; +export const VERSION = "1.562.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0de6d52aaa..9e18e5503e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.561.0", + "version": "1.562.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.561.0", + "version": "1.562.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index f5aebe9181..92b9ebbd82 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.561.0", + "version": "1.562.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index c736768087..1d2c57a495 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.561.0" -wmill_pg = ">=1.561.0" +wmill = ">=1.562.0" +wmill_pg = ">=1.562.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index ff3fca7d07..b946fb6691 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.561.0 + version: 1.562.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index b698f03b85..6e7771ed51 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.561.0' + ModuleVersion = '1.562.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index b02878072d..28d9a988e5 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.561.0" +version = "1.562.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 807e5d7ab9..1d6a2e9ae1 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.561.0" +version = "1.562.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 8aec478d53..f868400b83 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.561.0", + "version": "1.562.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 76fd97760f..753425e79c 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.561.0", + "version": "1.562.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index cb4c46e580..f09b704825 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.561.0 +1.562.0 From 34cd68676ba7a315be08128216f45f0d1ffade2a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 17 Oct 2025 03:42:41 +0000 Subject: [PATCH 32/33] fix migration error --- ...d7d1a2e10342bbbc7f8486df0b73f5657a493.json | 20 -------------- ...71dcc58cb037a59afe08cd1372b51791b4165.json | 20 -------------- ...54334eb7375b483e8ffd80364c60c3aad04b4.json | 20 -------------- ...dc18a4db630616b3aa80c27b54e4bb4e20f30.json | 20 -------------- ...9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json | 20 -------------- ...3b465b4bbf83bc86c0efacd6133bb432ee13d.json | 20 -------------- ...2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json | 20 -------------- ...df8567579218d5c4332b01e169732c836ebdc.json | 20 -------------- ...dafc044d1861048857ec2fc70f929ab358373.json | 20 -------------- ...583902596c4faed6d83e668be85aba8eb644a.json | 20 -------------- ...8f8ca36db091c67cf9ec5e7cec40486532ab9.json | 20 -------------- ...a626245abe58c306392a76710252d16d0bd44.json | 20 -------------- ...46fed29a76e579e72d6c8539cd0de73b424db.json | 20 -------------- ...a0fd360da8a9f06e0ab59d9e724851ced4247.json | 20 -------------- ...476226c66fe42c9f992bd4c6c232a68ade2bd.json | 20 -------------- ...5fd981473f45d1b71b4b8236e021f7c8682d.json} | 4 +-- ...a4fc58818c109deda6678c79474bf45428966.json | 22 ---------------- ...467dcabc2c3367fb33592e5608bd985c9e436.json | 26 ------------------- ...423b891b57e9a65a02b52966377c1960ab89e.json | 20 -------------- ...0b45df00a1ae022b3c29730af2347bd76deef.json | 20 -------------- ...3e0822cb91bde2324327f1828236112d278b1.json | 20 -------------- ...f231912db86c05c881aff1ffb5460f9711390.json | 20 -------------- ...0f6929f5dcbf29f3938e40c7f93d98aa7f49c.json | 20 -------------- ...7694378b2be15f3a40e7e6690f31157bdb5af.json | 20 -------------- ...24d3b06170d3a02a06a4c209e49e5ef175916.json | 20 -------------- ...4d0ab538043fff345a0438a40708c4066771b.json | 20 -------------- ...928e1be4696120c5d78c1649d5e430a5dae4c.json | 20 -------------- ...a0854975ad4c3f6fe24557b87a197485dff39.json | 20 -------------- ...f97f2301599a39668fcd20e1642fd828e3eec.json | 23 ---------------- ...2680159db11c8aeef82056ec30b498f8129da.json | 20 -------------- ...768ab79733e33bf8f9110a4f4d75a3c07da67.json | 20 -------------- ...143820_ducklake_safety_migration.down.sql} | 0 ...06143820_ducklake_safety_migration.up.sql} | 0 backend/windmill-api/src/db.rs | 2 +- 34 files changed, 3 insertions(+), 614 deletions(-) delete mode 100644 backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json delete mode 100644 backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json delete mode 100644 backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json delete mode 100644 backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json delete mode 100644 backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json delete mode 100644 backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json delete mode 100644 backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json delete mode 100644 backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json delete mode 100644 backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json delete mode 100644 backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json delete mode 100644 backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json delete mode 100644 backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json delete mode 100644 backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json delete mode 100644 backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json delete mode 100644 backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json rename backend/.sqlx/{query-618ec69c9c78f1c9e3539d2770392e3f783f29cd2cc58c0fc87d14ecef32b467.json => query-8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d.json} (60%) delete mode 100644 backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json delete mode 100644 backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json delete mode 100644 backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json delete mode 100644 backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json delete mode 100644 backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json delete mode 100644 backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json delete mode 100644 backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json delete mode 100644 backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json delete mode 100644 backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json delete mode 100644 backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json delete mode 100644 backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json delete mode 100644 backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json delete mode 100644 backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json delete mode 100644 backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json delete mode 100644 backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json rename backend/migrations/{20251006143821_ducklake_safety_migration.down.sql => 20251006143820_ducklake_safety_migration.down.sql} (100%) rename backend/migrations/{20251006143821_ducklake_safety_migration.up.sql => 20251006143820_ducklake_safety_migration.up.sql} (100%) diff --git a/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json b/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json deleted file mode 100644 index 2878e54920..0000000000 --- a/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM v2_job", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493" -} diff --git a/backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json b/backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json deleted file mode 100644 index 2fdaa1a63d..0000000000 --- a/backend/.sqlx/query-08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM v2_job_completed", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "08e5832c4002a0970d4105bb80371dcc58cb037a59afe08cd1372b51791b4165" -} diff --git a/backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json b/backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json deleted file mode 100644 index 7162b07bef..0000000000 --- a/backend/.sqlx/query-1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM app_version WHERE app_id = '2'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "1c047fef05e8cfc07aef7ea9a5454334eb7375b483e8ffd80364c60c3aad04b4" -} diff --git a/backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json b/backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json deleted file mode 100644 index 3d444ae09b..0000000000 --- a/backend/.sqlx/query-1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT runnable_id FROM v2_job ORDER BY created_at DESC", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "runnable_id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true - ] - }, - "hash": "1c739fddea33331fdb5acbfe614dc18a4db630616b3aa80c27b54e4bb4e20f30" -} diff --git a/backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json b/backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json deleted file mode 100644 index fbeff75f56..0000000000 --- a/backend/.sqlx/query-3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT versions FROM flow WHERE path = 'f/dre/flow'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "versions", - "type_info": "Int8Array" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "3528fbb71569b9bfd2a66d0692f9f0d1ecabb750d3d508f5c6a5cb3eaf3cd209" -} diff --git a/backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json b/backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json deleted file mode 100644 index 623fab4416..0000000000 --- a/backend/.sqlx/query-3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT (scheduled_for - created_at) FROM v2_job_queue WHERE running = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Interval" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "3af3c1080cde18cfbeb08e290f63b465b4bbf83bc86c0efacd6133bb432ee13d" -} diff --git a/backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json b/backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json deleted file mode 100644 index 8d828b265a..0000000000 --- a/backend/.sqlx/query-421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM flow_version WHERE path = 'f/dre/flow'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "421c7e0388326889b33fae4c8fa2fd7cb4a7be6dc50c9f7507eaa15f138484dc" -} diff --git a/backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json b/backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json deleted file mode 100644 index 41c08565b0..0000000000 --- a/backend/.sqlx/query-44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT hash FROM script WHERE path = 'f/dre_script/script' AND archived = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "hash", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "44d3e7dce67967471fa638f2beedf8567579218d5c4332b01e169732c836ebdc" -} diff --git a/backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json b/backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json deleted file mode 100644 index 7876a6f7f0..0000000000 --- a/backend/.sqlx/query-47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id FROM v2_job_queue WHERE running = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "47a55edc0f5c54ac9f5e48665c8dafc044d1861048857ec2fc70f929ab358373" -} diff --git a/backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json b/backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json deleted file mode 100644 index d567e90159..0000000000 --- a/backend/.sqlx/query-53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) from debounce_key", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "53b236c65e8790b7474e919daa0583902596c4faed6d83e668be85aba8eb644a" -} diff --git a/backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json b/backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json deleted file mode 100644 index ae9532cdee..0000000000 --- a/backend/.sqlx/query-5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM v2_job_queue WHERE running = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "5848d416e2d96eb20c7417dec608f8ca36db091c67cf9ec5e7cec40486532ab9" -} diff --git a/backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json b/backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json deleted file mode 100644 index a7733f5403..0000000000 --- a/backend/.sqlx/query-5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT versions FROM app WHERE path = 'f/dre_app/app'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "versions", - "type_info": "Int8Array" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "5bedbfe98b3cacd1c6f4b2344a9a626245abe58c306392a76710252d16d0bd44" -} diff --git a/backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json b/backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json deleted file mode 100644 index a193aa5b98..0000000000 --- a/backend/.sqlx/query-6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM debounce_stale_data", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "6641d63a691d712bc24be9c972646fed29a76e579e72d6c8539cd0de73b424db" -} diff --git a/backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json b/backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json deleted file mode 100644 index 8239f32a08..0000000000 --- a/backend/.sqlx/query-720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) from debounce_stale_data", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "720d7f0d258d52a6b89e2f4b32ea0fd360da8a9f06e0ab59d9e724851ced4247" -} diff --git a/backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json b/backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json deleted file mode 100644 index 6d3b24293f..0000000000 --- a/backend/.sqlx/query-77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT hash FROM script WHERE path = 'f/dre_script/script' AND archived = true", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "hash", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "77f33de1d95e38a44968ea7f026476226c66fe42c9f992bd4c6c232a68ade2bd" -} diff --git a/backend/.sqlx/query-618ec69c9c78f1c9e3539d2770392e3f783f29cd2cc58c0fc87d14ecef32b467.json b/backend/.sqlx/query-8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d.json similarity index 60% rename from backend/.sqlx/query-618ec69c9c78f1c9e3539d2770392e3f783f29cd2cc58c0fc87d14ecef32b467.json rename to backend/.sqlx/query-8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d.json index 2aa95c8d2c..662bc5f6d6 100644 --- a/backend/.sqlx/query-618ec69c9c78f1c9e3539d2770392e3f783f29cd2cc58c0fc87d14ecef32b467.json +++ b/backend/.sqlx/query-8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632", + "query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632 OR version=20251006143821", "describe": { "columns": [], "parameters": { @@ -8,5 +8,5 @@ }, "nullable": [] }, - "hash": "618ec69c9c78f1c9e3539d2770392e3f783f29cd2cc58c0fc87d14ecef32b467" + "hash": "8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d" } diff --git a/backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json b/backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json deleted file mode 100644 index d41b7811ee..0000000000 --- a/backend/.sqlx/query-91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT running FROM v2_job_queue WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "running", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false - ] - }, - "hash": "91a13927f7e0577e52755fd8463a4fc58818c109deda6678c79474bf45428966" -} diff --git a/backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json b/backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json deleted file mode 100644 index 751c25d839..0000000000 --- a/backend/.sqlx/query-9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n dsd.to_relock,\n dk.key\n FROM debounce_key dk\n JOIN debounce_stale_data dsd ON dk.job_id = dsd.job_id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "to_relock", - "type_info": "TextArray" - }, - { - "ordinal": 1, - "name": "key", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true, - false - ] - }, - "hash": "9b37ec5aa9b979393c6e5c8d98f467dcabc2c3367fb33592e5608bd985c9e436" -} diff --git a/backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json b/backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json deleted file mode 100644 index 5b0998c259..0000000000 --- a/backend/.sqlx/query-9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT versions[1] FROM flow WHERE path = 'f/dre/flow'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "versions", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "9d1bd189940d345f27e4850651c423b891b57e9a65a02b52966377c1960ab89e" -} diff --git a/backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json b/backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json deleted file mode 100644 index 3df9d93ef0..0000000000 --- a/backend/.sqlx/query-a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id FROM v2_job_completed", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "a14270c6a2af936539c7d7e4a950b45df00a1ae022b3c29730af2347bd76deef" -} diff --git a/backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json b/backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json deleted file mode 100644 index cdcab1c509..0000000000 --- a/backend/.sqlx/query-a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT (scheduled_for - created_at) FROM v2_job_queue", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Interval" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "a206326b6c13c88b773adcd1f7b3e0822cb91bde2324327f1828236112d278b1" -} diff --git a/backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json b/backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json deleted file mode 100644 index bfd7cd16bd..0000000000 --- a/backend/.sqlx/query-bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT parent_hashes FROM script WHERE path = 'f/dre_script/script' AND archived = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "parent_hashes", - "type_info": "Int8Array" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true - ] - }, - "hash": "bfc96c870fe0c50ebb9e9bc5ccff231912db86c05c881aff1ffb5460f9711390" -} diff --git a/backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json b/backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json deleted file mode 100644 index ddc6263aff..0000000000 --- a/backend/.sqlx/query-c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT runnable_path FROM v2_job", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "runnable_path", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true - ] - }, - "hash": "c42326e2121b79d1381a3c91ef90f6929f5dcbf29f3938e40c7f93d98aa7f49c" -} diff --git a/backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json b/backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json deleted file mode 100644 index 4c4921e2a0..0000000000 --- a/backend/.sqlx/query-cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT key FROM debounce_key", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "key", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "cbc8e3e22862a76e1a8386cca247694378b2be15f3a40e7e6690f31157bdb5af" -} diff --git a/backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json b/backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json deleted file mode 100644 index b69e04115f..0000000000 --- a/backend/.sqlx/query-d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT lock FROM script WHERE path = 'f/dre_script/script'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "lock", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - true - ] - }, - "hash": "d6a060b255f02a2a776a6a3ac4024d3b06170d3a02a06a4c209e49e5ef175916" -} diff --git a/backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json b/backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json deleted file mode 100644 index 99c0aa49ad..0000000000 --- a/backend/.sqlx/query-db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT versions[2] FROM flow WHERE path = 'f/dre/flow'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "versions", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "db7a756d541dbedbdeb23ab8f914d0ab538043fff345a0438a40708c4066771b" -} diff --git a/backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json b/backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json deleted file mode 100644 index cd4130b542..0000000000 --- a/backend/.sqlx/query-e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT jsonb_array_elements(value->'modules')->'value'->>'lock' AS lock FROM flow", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "lock", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "e49b72cf5a05b47f76ebcdc5306928e1be4696120c5d78c1649d5e430a5dae4c" -} diff --git a/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json b/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json deleted file mode 100644 index 8e8a5c98e9..0000000000 --- a/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM v2_job_queue", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39" -} diff --git a/backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json b/backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json deleted file mode 100644 index e866bd4001..0000000000 --- a/backend/.sqlx/query-eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\nSELECT\n j1.completed_at < j2.started_at\nFROM\n v2_job_completed j1,\n v2_job_completed j2\nWHERE\n j1.id = $1 \n AND j2.id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Uuid" - ] - }, - "nullable": [ - null - ] - }, - "hash": "eaeeb4708a6d9bf6be3265dcaaaf97f2301599a39668fcd20e1642fd828e3eec" -} diff --git a/backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json b/backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json deleted file mode 100644 index 0e1f9620e0..0000000000 --- a/backend/.sqlx/query-ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) from v2_job_queue", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "ee96e97f1a8bd2ac592665ad3e02680159db11c8aeef82056ec30b498f8129da" -} diff --git a/backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json b/backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json deleted file mode 100644 index 6a48df6e2b..0000000000 --- a/backend/.sqlx/query-eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM debounce_key", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "eff9926fa211497ddbc53866444768ab79733e33bf8f9110a4f4d75a3c07da67" -} diff --git a/backend/migrations/20251006143821_ducklake_safety_migration.down.sql b/backend/migrations/20251006143820_ducklake_safety_migration.down.sql similarity index 100% rename from backend/migrations/20251006143821_ducklake_safety_migration.down.sql rename to backend/migrations/20251006143820_ducklake_safety_migration.down.sql diff --git a/backend/migrations/20251006143821_ducklake_safety_migration.up.sql b/backend/migrations/20251006143820_ducklake_safety_migration.up.sql similarity index 100% rename from backend/migrations/20251006143821_ducklake_safety_migration.up.sql rename to backend/migrations/20251006143820_ducklake_safety_migration.up.sql diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index c7ab6feeec..c5d16ebffe 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -212,7 +212,7 @@ pub async fn migrate( if let Err(err) = sqlx::query!( "DELETE FROM _sqlx_migrations WHERE version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR - version=20250201145631 OR version=20250201145632" + version=20250201145631 OR version=20250201145632 OR version=20251006143821" ) .execute(db) .await From b93931622f549fec3e33e2678fee56c416bb0d6f Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:24:57 +0200 Subject: [PATCH 33/33] add svelte 5 mcp (#6847) --- .mcp.json | 8 ++++++++ frontend/CLAUDE.md | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .mcp.json diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000000..8a587025cd --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "svelte": { + "type": "http", + "url": "https://mcp.svelte.dev/mcp" + } + } +} \ No newline at end of file diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 6aa8ef3c48..9fe6c877ab 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -21,26 +21,32 @@ Use these semantic color classes that automatically handle light/dark modes: #### Backgrounds + - `bg-surface` - Main surface background -- `bg-surface-secondary` - Secondary/elevated surfaces +- `bg-surface-secondary` - Secondary/elevated surfaces - `bg-surface-hover` - Hover states for interactive elements #### Text Colors + - `text-primary` - Primary text color - `text-secondary` - Secondary text (less prominent) - `text-tertiary` - Tertiary text (subtle/muted) #### Borders + - `border-gray-200 dark:border-gray-700` - Standard borders that adapt to theme #### Status Colors + Use standard Tailwind color classes with dark mode variants: + - Success: `text-green-500`, `bg-green-100 dark:bg-green-900/30` -- Error: `text-red-500`, `bg-red-50 dark:bg-red-900/20` +- Error: `text-red-500`, `bg-red-50 dark:bg-red-900/20` - Warning: `text-yellow-500`, `bg-yellow-100 dark:bg-yellow-900/30` - Info: `text-blue-500`, `bg-blue-100 dark:bg-blue-900/30` #### Typography + - `font-mono` - For code/technical content - `text-xs`, `text-sm`, `text-2xs` - Standard text sizes - Use `font-medium`, `font-semibold` for emphasis @@ -115,3 +121,27 @@ AuditService.listAuditLogs({ operations?: string // from operations parameter }) ``` + +## Svelte 5 documentation + +You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively: + +### 1. list-sections + +Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths. +When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections. + +### 2. get-documentation + +Retrieves full documentation content for specific sections. Accepts single or multiple sections. +After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task. + +### 3. svelte-autofixer + +Analyzes Svelte code and returns issues and suggestions. +You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned. + +### 4. playground-link + +Generates a Svelte Playground link with the provided code. +After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.