mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-12 08:02:44 +00:00
fix: add retry to runScriptByPathAsync and runScriptByHashAsync (#11485)
* feat: add retry to runScriptByPathAsync and runScriptByHashAsync Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse retry with invisible_to_owner and keep the parent priority bump Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: refuse retry when a preprocessor would run and export the options type on jsr Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the dispatching job as parent of a retried run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: cancel pending native retries with their caller and stop the chain on cancel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1c2667cd55
commit
3f3b1c2c30
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT q.id FROM v2_job_queue q\n JOIN native_retry_attempt n ON n.job_id = q.id\n JOIN v2_job r ON r.id = q.id\n JOIN v2_job a ON a.id = r.parent_job\n WHERE q.workspace_id = $2 AND a.parent_job = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "de1adae1306fb9d877ec6f2d52235b3f57d4fa4c27861b51da6bfe8c74abd429"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO job_resolution (job_id, workspace_id, resolved_by, note, automatic)\n SELECT c.id, c.workspace_id, NULL, NULL, true\n FROM v2_job_completed c\n JOIN v2_job j ON j.id = c.id\n WHERE c.status = 'failure'\n AND c.workspace_id = $2\n AND j.flow_step_id IS NULL\n AND j.runnable_id IS NOT DISTINCT FROM $3\n AND EXISTS (\n SELECT 1 FROM v2_job_completed sc\n JOIN v2_job sj ON sj.id = sc.id\n JOIN native_retry_attempt nra ON nra.job_id = sc.id\n WHERE sc.status = 'success'\n AND sc.workspace_id = $2\n AND sj.parent_job = $1\n AND sj.runnable_id IS NOT DISTINCT FROM $3\n )\n AND (\n (j.parent_job = $1\n AND EXISTS (SELECT 1 FROM native_retry_attempt WHERE job_id = c.id))\n OR (c.id = $1 AND j.parent_job IS NULL\n AND NOT EXISTS (SELECT 1 FROM native_retry_attempt WHERE job_id = c.id))\n )\n ON CONFLICT (job_id) DO NOTHING",
|
||||
"query": "INSERT INTO job_resolution (job_id, workspace_id, resolved_by, note, automatic)\n SELECT c.id, c.workspace_id, NULL, NULL, true\n FROM v2_job_completed c\n JOIN v2_job j ON j.id = c.id\n WHERE c.status = 'failure'\n AND c.workspace_id = $2\n AND j.flow_step_id IS NULL\n AND j.runnable_id IS NOT DISTINCT FROM $3\n AND EXISTS (\n SELECT 1 FROM v2_job_completed sc\n JOIN v2_job sj ON sj.id = sc.id\n JOIN native_retry_attempt nra ON nra.job_id = sc.id\n WHERE sc.status = 'success'\n AND sc.workspace_id = $2\n AND sj.parent_job = $1\n AND sj.runnable_id IS NOT DISTINCT FROM $3\n )\n AND (\n (j.parent_job = $1\n AND EXISTS (SELECT 1 FROM native_retry_attempt WHERE job_id = c.id))\n OR (c.id = $1\n AND NOT EXISTS (SELECT 1 FROM native_retry_attempt WHERE job_id = c.id))\n )\n ON CONFLICT (job_id) DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,5 +12,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e34cb7cd6ff9d80e77a51ae47c3cfb9b0772d5d3d239273fc5cea322cae4e258"
|
||||
"hash": "eecea4a5ba0de73129bde6ed0c229690c322bc376b1bfc13ed719126f72cabb9"
|
||||
}
|
||||
@@ -33,6 +33,7 @@ use windmill_common::{
|
||||
flow_conversations::{
|
||||
add_message_to_conversation_tx, message_attachments, MessageExtras, MessageType,
|
||||
},
|
||||
flows::Retry,
|
||||
get_latest_flow_version_info_for_path,
|
||||
jobs::{
|
||||
check_tag_available_for_workspace_internal, format_result, script_path_to_payload,
|
||||
@@ -126,6 +127,71 @@ pub async fn drop_unclaimable_run_lineage(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies the run's `retry` policy by handing `push` a one-step-flow request, which it
|
||||
/// materializes as a native retryable `Script`.
|
||||
pub fn with_run_retry(
|
||||
run_query: &RunJobQuery,
|
||||
payload: JobPayload,
|
||||
args: &PushArgs<'_>,
|
||||
tag: &Option<String>,
|
||||
) -> error::Result<JobPayload> {
|
||||
let Some(retry) = run_query.retry.as_deref() else {
|
||||
return Ok(payload);
|
||||
};
|
||||
// Retry attempts are always pushed visible to the owner, which would leak a private run.
|
||||
if run_query.invisible_to_owner.unwrap_or(false) {
|
||||
return Err(Error::BadRequest(
|
||||
"retry cannot be combined with invisible_to_owner".to_string(),
|
||||
));
|
||||
}
|
||||
let retry: Retry = serde_json::from_str(retry)
|
||||
.map_err(|e| Error::BadRequest(format!("invalid retry policy: {e}")))?;
|
||||
let JobPayload::ScriptHash {
|
||||
hash,
|
||||
path,
|
||||
cache_ttl,
|
||||
cache_ignore_s3_path,
|
||||
language,
|
||||
priority,
|
||||
apply_preprocessor,
|
||||
debouncing_settings,
|
||||
concurrency_settings,
|
||||
..
|
||||
} = payload
|
||||
else {
|
||||
return Err(Error::BadRequest(
|
||||
"retry is only supported for workspace scripts".to_string(),
|
||||
));
|
||||
};
|
||||
// A failed attempt keeps its raw args and the retry re-push skips the preprocessor, so
|
||||
// every retry would call `main` with un-preprocessed input.
|
||||
if apply_preprocessor {
|
||||
return Err(Error::BadRequest(
|
||||
"retry is not supported for a script with a preprocessor unless skip_preprocessor is set"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
Ok(JobPayload::SingleStepFlow {
|
||||
path,
|
||||
hash: Some(hash),
|
||||
flow_version: None,
|
||||
language: Some(language),
|
||||
args: HashMap::from(args),
|
||||
retry: Some(retry),
|
||||
error_handler_path: None,
|
||||
error_handler_args: None,
|
||||
skip_handler: None,
|
||||
cache_ttl,
|
||||
cache_ignore_s3_path,
|
||||
priority,
|
||||
tag_override: tag.clone(),
|
||||
trigger_path: None,
|
||||
apply_preprocessor,
|
||||
concurrency_settings,
|
||||
debouncing_settings,
|
||||
})
|
||||
}
|
||||
|
||||
/// The jobs of `referenced` that `authed` cannot claim as its own run lineage: anything but the
|
||||
/// token's own job and that job's `parent_job`, `root_job` and `flow_innermost_root_job`, or for
|
||||
/// a workspace admin anything outside the workspace. A restricted job token never gets the admin
|
||||
@@ -1151,6 +1217,7 @@ pub async fn push_script_job_by_path_into_queue<'c>(
|
||||
let tag = run_query.tag.clone().or(tag);
|
||||
let push_args = PushArgs { args: &args.args, extra: args.extra };
|
||||
check_tag_available_for_workspace(&db, &w_id, &tag, &push_args, &authed).await?;
|
||||
let job_payload = with_run_retry(&run_query, job_payload, &push_args, &tag)?;
|
||||
|
||||
let return_tx = tx_o.is_some();
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ pub struct RunJobQuery {
|
||||
pub trigger_external_id: Option<String>,
|
||||
pub service_name: Option<String>,
|
||||
pub suspended_mode: Option<bool>,
|
||||
/// A JSON-encoded `Retry` policy for a script run; see `with_run_retry`.
|
||||
pub retry: Option<String>,
|
||||
}
|
||||
|
||||
impl RunJobQuery {
|
||||
|
||||
@@ -12012,6 +12012,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/RunRetry"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
@@ -16016,6 +16017,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkerTag"
|
||||
- $ref: "#/components/parameters/CacheTtl"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/RunRetry"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
@@ -27830,6 +27832,12 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
RunRetry:
|
||||
name: retry
|
||||
description: JSON-encoded retry policy (same shape as a schedule's retry) re-running the job when it fails. The returned id is the first attempt's; each retry is a job of its own, recorded as a child of the first attempt. Not supported for hub scripts, with invisible_to_owner, or for a script whose preprocessor would run.
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
NewJobId:
|
||||
name: job_id
|
||||
description:
|
||||
|
||||
@@ -8541,6 +8541,11 @@ pub async fn stream_job(
|
||||
run_query: RunJobQuery,
|
||||
is_get: bool,
|
||||
) -> error::Result<Response> {
|
||||
if run_query.retry.is_some() {
|
||||
return Err(error::Error::BadRequest(
|
||||
"retry is only supported when running a script asynchronously".to_string(),
|
||||
));
|
||||
}
|
||||
let args = if is_get {
|
||||
let payload_as_args = run_query.payload_as_args()?;
|
||||
|
||||
@@ -10483,26 +10488,29 @@ pub async fn run_job_by_hash_inner(
|
||||
)
|
||||
};
|
||||
|
||||
let job_payload = JobPayload::ScriptHash {
|
||||
hash: ScriptHash(hash),
|
||||
path: path,
|
||||
concurrency_settings,
|
||||
debouncing_settings,
|
||||
cache_ttl,
|
||||
cache_ignore_s3_path,
|
||||
language,
|
||||
dedicated_worker,
|
||||
priority,
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
labels,
|
||||
job_token_scopes,
|
||||
};
|
||||
let job_payload = with_run_retry(&run_query, job_payload, &push_args, &tag)?;
|
||||
|
||||
let scope_ceiling = windmill_api_auth::caller_scope_ceiling(&db, &authed).await?;
|
||||
let (uuid, tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::ScriptHash {
|
||||
hash: ScriptHash(hash),
|
||||
path: path,
|
||||
concurrency_settings,
|
||||
debouncing_settings,
|
||||
cache_ttl,
|
||||
cache_ignore_s3_path,
|
||||
language,
|
||||
dedicated_worker,
|
||||
priority,
|
||||
apply_preprocessor: !run_query.skip_preprocessor.unwrap_or(false)
|
||||
&& has_preprocessor.unwrap_or(false),
|
||||
labels,
|
||||
job_token_scopes,
|
||||
},
|
||||
job_payload,
|
||||
push_args,
|
||||
authed.display_username(),
|
||||
email,
|
||||
|
||||
@@ -359,6 +359,22 @@ ORDER BY depth, id
|
||||
.filter_map(|r| r.id.clone())
|
||||
.collect_vec();
|
||||
|
||||
// A native retry is the child of its first attempt, which has completed by the time the
|
||||
// retry is queued, so the walk above stops before reaching it.
|
||||
let tree = [&[job.id][..], &jobs_to_cancel[..]].concat();
|
||||
let pending_retries = sqlx::query_scalar!(
|
||||
"SELECT q.id FROM v2_job_queue q
|
||||
JOIN native_retry_attempt n ON n.job_id = q.id
|
||||
JOIN v2_job r ON r.id = q.id
|
||||
JOIN v2_job a ON a.id = r.parent_job
|
||||
WHERE q.workspace_id = $2 AND a.parent_job = ANY($1)",
|
||||
tree.as_slice(),
|
||||
w_id
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
jobs_to_cancel.extend(pending_retries);
|
||||
|
||||
jobs_to_cancel.reverse();
|
||||
if !jobs_to_cancel.is_empty() {
|
||||
tracing::info!("Found {} child jobs to cancel", jobs_to_cancel.len());
|
||||
@@ -1391,7 +1407,18 @@ pub async fn add_completed_job<T: Serialize + Send + Sync + ValidableJson>(
|
||||
.then(|| completed_job.parent_job)
|
||||
.flatten()
|
||||
} else if !success && !skipped && retry_pending {
|
||||
Some(completed_job.parent_job.unwrap_or(completed_job.id))
|
||||
let is_retry_attempt = sqlx::query_scalar!(
|
||||
"SELECT attempt FROM native_retry_attempt WHERE job_id = $1",
|
||||
completed_job.id,
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::error!("reading the retry marker of {}: {e:#}", completed_job.id);
|
||||
None
|
||||
})
|
||||
.is_some();
|
||||
Some(native_retry_root(completed_job, is_retry_attempt))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -2194,18 +2221,17 @@ async fn restart_job_if_perpetual_inner(
|
||||
/// resolution too; `ON CONFLICT DO NOTHING` keeps a human's note intact.
|
||||
///
|
||||
/// Membership of the chain must be *proven* per row, never inferred from `parent_job`
|
||||
/// alone: `root` is `job.parent_job.unwrap_or(job.id)`, so for a job launched with an
|
||||
/// explicit `parent_job` (WAC inline children, SDK-launched children) `root` is the
|
||||
/// *calling* job, and its other failed children are unrelated. Resolving those would hide
|
||||
/// exactly the failures this feature exists to surface, so each row must be either
|
||||
/// alone: the success-side caller passes the succeeding job's `parent_job`, which for a
|
||||
/// job launched with an explicit parent (WAC inline children, SDK-launched children) is
|
||||
/// the *calling* job, and its other failed children are unrelated. Resolving those would
|
||||
/// hide exactly the failures this feature exists to surface, so each row must be either
|
||||
/// - a job carrying a `native_retry_attempt` marker under `root` (provably an attempt), or
|
||||
/// - `root` itself as the original attempt, which is only provable when `root` is
|
||||
/// parentless and unmarked.
|
||||
/// - `root` itself as the original attempt: unmarked, and proven a chain root by the
|
||||
/// marked success under it, since only a chain root is ever a marked job's parent
|
||||
/// (see [`native_retry_root`]).
|
||||
///
|
||||
/// Both arms also require the same `runnable_id` as the succeeding attempt, since every
|
||||
/// attempt in a chain runs the same runnable. The deliberate cost is a miss, not an
|
||||
/// over-reach: when the original attempt had a parent it is an unmarked sibling
|
||||
/// indistinguishable from any other child of the caller, so it stays red.
|
||||
/// attempt in a chain runs the same runnable.
|
||||
///
|
||||
/// Nothing is trusted of the caller (this writes through a privileged pool): the gate is
|
||||
/// "some marked attempt under `root` running `runnable_id` has succeeded", evaluated in
|
||||
@@ -2249,7 +2275,7 @@ pub async fn resolve_retry_chain_if_succeeded(
|
||||
AND (
|
||||
(j.parent_job = $1
|
||||
AND EXISTS (SELECT 1 FROM native_retry_attempt WHERE job_id = c.id))
|
||||
OR (c.id = $1 AND j.parent_job IS NULL
|
||||
OR (c.id = $1
|
||||
AND NOT EXISTS (SELECT 1 FROM native_retry_attempt WHERE job_id = c.id))
|
||||
)
|
||||
ON CONFLICT (job_id) DO NOTHING",
|
||||
@@ -2303,6 +2329,18 @@ async fn eval_retry_if(
|
||||
false
|
||||
}
|
||||
|
||||
/// The first attempt of `job`'s native retry chain, which every later attempt is pushed
|
||||
/// with as `parent_job` and whose id seeds theirs. A first attempt may have a parent of its
|
||||
/// own (a script dispatched from another job), so only a marked attempt's parent is the root:
|
||||
/// rooting at that outer parent would merge the chains of its retried children.
|
||||
fn native_retry_root(job: &MiniCompletedJob, is_retry_attempt: bool) -> Uuid {
|
||||
if is_retry_attempt {
|
||||
job.parent_job.unwrap_or(job.id)
|
||||
} else {
|
||||
job.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Native script retry. When a failed `Script` or `Script_Hub` job carries a retry
|
||||
/// policy (via `runnable_settings_handle`) and has attempts left, enqueue a fresh
|
||||
/// attempt of the same script after the policy's backoff delay — instead of having
|
||||
@@ -2346,6 +2384,18 @@ pub async fn maybe_enqueue_native_script_retry(
|
||||
if !policy.has_attempts() {
|
||||
return Ok(false);
|
||||
}
|
||||
// Cancelling a job that has a parent (every retry attempt does) only flags its queue row,
|
||||
// and the worker that pulls it completes it without a `canceled_by`: read the flag here.
|
||||
let soft_canceled = sqlx::query_scalar!(
|
||||
"SELECT canceled_by IS NOT NULL AS \"canceled!\" FROM v2_job_queue WHERE id = $1",
|
||||
job.id,
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if soft_canceled {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Attempt counter for this job: its `native_retry_attempt` marker, or 0 for
|
||||
// the first (un-marked) attempt. The marker is persistent (unlike the queue
|
||||
@@ -2358,7 +2408,7 @@ pub async fn maybe_enqueue_native_script_retry(
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.unwrap_or(0) as u32;
|
||||
let root = job.parent_job.unwrap_or(job.id);
|
||||
let root = native_retry_root(job, prev_attempts > 0);
|
||||
// Scheduled chains keep the schedule trigger so the terminal attempt drives
|
||||
// the schedule completion handlers (on_failure/on_success); `parent_job`
|
||||
// keeps every retry out of the per-occurrence handler counting queries.
|
||||
|
||||
@@ -561,4 +561,123 @@ mod native_retry {
|
||||
.unwrap();
|
||||
assert_eq!(status, "failure", "resolving must not change job status");
|
||||
}
|
||||
|
||||
// A script dispatched from another job keeps that job as its parent, so its chain is
|
||||
// rooted at the first attempt itself: sibling dispatches must not share retry ids, and
|
||||
// a parented first attempt is still resolved once its retry succeeds.
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn parented_first_attempts_root_their_own_chains(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
let retry = Retry {
|
||||
constant: ConstantDelay { attempts: 1, seconds: 0 },
|
||||
exponential: Default::default(),
|
||||
retry_if: None,
|
||||
};
|
||||
let handle = insert_rs(
|
||||
RunnableSettings {
|
||||
debouncing_settings: None,
|
||||
concurrency_settings: None,
|
||||
retry_settings: RetrySettings::from(&retry).insert_cached(&db).await?,
|
||||
},
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
let dispatcher = Uuid::new_v4();
|
||||
let (a, b) = (Uuid::new_v4(), Uuid::new_v4());
|
||||
for first in [a, b] {
|
||||
let attempt = mini(first, Some(dispatcher), handle);
|
||||
assert!(maybe_enqueue_native_script_retry(&db, &attempt, &None, &no_result).await?);
|
||||
}
|
||||
assert!(
|
||||
retry_by_attempt(&db, a, 1).await.is_some(),
|
||||
"a's retry hangs off a"
|
||||
);
|
||||
assert!(
|
||||
retry_by_attempt(&db, b, 1).await.is_some(),
|
||||
"b's retry is not mistaken for a's"
|
||||
);
|
||||
|
||||
let (c, c_retry, c_sibling) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4());
|
||||
seed_job(&db, c, Some(dispatcher), false, "failure").await;
|
||||
seed_job(&db, c_sibling, Some(dispatcher), false, "failure").await;
|
||||
seed_job(&db, c_retry, Some(c), true, "success").await;
|
||||
windmill_queue::jobs::resolve_retry_chain_if_succeeded(&db, c, WS, None).await?;
|
||||
windmill_queue::jobs::resolve_retry_chain_if_succeeded(&db, dispatcher, WS, None).await?;
|
||||
let resolved: Vec<Uuid> = sqlx::query_scalar("SELECT job_id FROM job_resolution")
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
resolved,
|
||||
vec![c],
|
||||
"only the parented first attempt resolves, not its sibling"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Cancelling the job that dispatched a script reaches that script's pending retry, which
|
||||
// hangs off a first attempt that has already completed.
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn cancelling_the_caller_cancels_a_pending_retry(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
let retry = Retry {
|
||||
constant: ConstantDelay { attempts: 2, seconds: 60 },
|
||||
exponential: Default::default(),
|
||||
retry_if: None,
|
||||
};
|
||||
let handle = insert_rs(
|
||||
RunnableSettings {
|
||||
debouncing_settings: None,
|
||||
concurrency_settings: None,
|
||||
retry_settings: RetrySettings::from(&retry).insert_cached(&db).await?,
|
||||
},
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
let (caller, first) = (Uuid::new_v4(), Uuid::new_v4());
|
||||
sqlx::query(
|
||||
"INSERT INTO v2_job (id, workspace_id, kind, runnable_path) VALUES ($1, $2, 'script', $3)",
|
||||
)
|
||||
.bind(caller)
|
||||
.bind(WS)
|
||||
.bind(SCRIPT)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, running) VALUES ($1, $2, now(), true)",
|
||||
)
|
||||
.bind(caller)
|
||||
.bind(WS)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
seed_job(&db, first, Some(caller), false, "failure").await;
|
||||
let attempt = mini(first, Some(caller), handle);
|
||||
assert!(maybe_enqueue_native_script_retry(&db, &attempt, &None, &no_result).await?);
|
||||
let (retry_id, ..) = retry_by_attempt(&db, first, 1)
|
||||
.await
|
||||
.expect("retry is queued");
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let (tx, _) =
|
||||
windmill_queue::jobs::cancel_job("test-user", None, caller, WS, tx, &db, false, false)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let canceled_by: Option<String> =
|
||||
sqlx::query_scalar("SELECT canceled_by FROM v2_job_queue WHERE id = $1")
|
||||
.bind(retry_id)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(canceled_by.as_deref(), Some("test-user"));
|
||||
|
||||
// The worker that pulls the canceled retry completes it without a `canceled_by`.
|
||||
let (_, _, _, _, retry_handle) = retry_by_attempt(&db, first, 1).await.unwrap();
|
||||
let canceled_retry = mini(retry_id, Some(first), retry_handle);
|
||||
assert!(
|
||||
!maybe_enqueue_native_script_retry(&db, &canceled_retry, &None, &no_result).await?,
|
||||
"a canceled retry does not enqueue the next attempt"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+25
-15
@@ -767,21 +767,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
@@ -1600,21 +1602,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
@@ -2539,21 +2543,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
@@ -4434,10 +4440,14 @@ def post(endpoint, raise_for_status = True, **kwargs) -> httpx.Response
|
||||
def create_token(duration = dt.timedelta(days=1)) -> str
|
||||
|
||||
# Create a script job by path and return its job id.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
#
|
||||
# \`\`retry\`\` re-runs the job when it fails, with the policy a schedule takes,
|
||||
# e.g. \`\`{"constant": {"attempts": 3, "seconds": 10}}\`\`. The returned id is
|
||||
# the first attempt's; each retry is a job of its own, recorded as a child of it.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a script job by hash and return its job id.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
# Create a script job by hash and return its job id. See \`\`run_script_by_path_async\`\` for \`\`retry\`\`.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a flow job and return its job id.
|
||||
def run_flow_async(path: str, args: dict = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True, tag: str = None) -> str
|
||||
|
||||
@@ -196,12 +196,15 @@ class Windmill:
|
||||
args: dict = None,
|
||||
scheduled_in_secs: int = None,
|
||||
tag: str = None,
|
||||
retry: dict = None,
|
||||
) -> str:
|
||||
"""Internal helper for running scripts asynchronously."""
|
||||
args = args or {}
|
||||
params = {"scheduled_in_secs": scheduled_in_secs} if scheduled_in_secs else {}
|
||||
if tag:
|
||||
params["tag"] = tag
|
||||
if retry:
|
||||
params["retry"] = json.dumps(retry)
|
||||
if os.environ.get("WM_JOB_ID"):
|
||||
params["parent_job"] = os.environ.get("WM_JOB_ID")
|
||||
if os.environ.get("WM_ROOT_FLOW_JOB_ID"):
|
||||
@@ -222,9 +225,15 @@ class Windmill:
|
||||
args: dict = None,
|
||||
scheduled_in_secs: int = None,
|
||||
tag: str = None,
|
||||
retry: dict = None,
|
||||
) -> str:
|
||||
"""Create a script job by path and return its job id."""
|
||||
return self._run_script_async_internal(path=path, args=args, scheduled_in_secs=scheduled_in_secs, tag=tag)
|
||||
"""Create a script job by path and return its job id.
|
||||
|
||||
``retry`` re-runs the job when it fails, with the policy a schedule takes,
|
||||
e.g. ``{"constant": {"attempts": 3, "seconds": 10}}``. The returned id is
|
||||
the first attempt's; each retry is a job of its own, recorded as a child of it.
|
||||
"""
|
||||
return self._run_script_async_internal(path=path, args=args, scheduled_in_secs=scheduled_in_secs, tag=tag, retry=retry)
|
||||
|
||||
def run_script_by_hash_async(
|
||||
self,
|
||||
@@ -232,9 +241,10 @@ class Windmill:
|
||||
args: dict = None,
|
||||
scheduled_in_secs: int = None,
|
||||
tag: str = None,
|
||||
retry: dict = None,
|
||||
) -> str:
|
||||
"""Create a script job by hash and return its job id."""
|
||||
return self._run_script_async_internal(hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs, tag=tag)
|
||||
"""Create a script job by hash and return its job id. See ``run_script_by_path_async`` for ``retry``."""
|
||||
return self._run_script_async_internal(hash_=hash_, args=args, scheduled_in_secs=scheduled_in_secs, tag=tag, retry=retry)
|
||||
|
||||
def run_flow_async(
|
||||
self,
|
||||
@@ -1618,6 +1628,7 @@ def run_script_by_path_async(
|
||||
args: Dict[str, Any] = None,
|
||||
scheduled_in_secs: Union[None, int] = None,
|
||||
tag: str = None,
|
||||
retry: Dict[str, Any] = None,
|
||||
) -> str:
|
||||
"""Create a script job by path and return its job ID.
|
||||
|
||||
@@ -1626,6 +1637,8 @@ def run_script_by_path_async(
|
||||
args: Script arguments
|
||||
scheduled_in_secs: Delay before execution in seconds
|
||||
tag: Override the worker tag the job runs on
|
||||
retry: Re-run the job when it fails, with the policy a schedule takes,
|
||||
e.g. ``{"constant": {"attempts": 3, "seconds": 10}}``
|
||||
|
||||
Returns:
|
||||
Job ID string
|
||||
@@ -1635,6 +1648,7 @@ def run_script_by_path_async(
|
||||
args=args,
|
||||
scheduled_in_secs=scheduled_in_secs,
|
||||
tag=tag,
|
||||
retry=retry,
|
||||
)
|
||||
|
||||
|
||||
@@ -1644,6 +1658,7 @@ def run_script_by_hash_async(
|
||||
args: Dict[str, Any] = None,
|
||||
scheduled_in_secs: Union[None, int] = None,
|
||||
tag: str = None,
|
||||
retry: Dict[str, Any] = None,
|
||||
) -> str:
|
||||
"""Create a script job by hash and return its job ID.
|
||||
|
||||
@@ -1652,6 +1667,7 @@ def run_script_by_hash_async(
|
||||
args: Script arguments
|
||||
scheduled_in_secs: Delay before execution in seconds
|
||||
tag: Override the worker tag the job runs on
|
||||
retry: Re-run the job when it fails, see ``run_script_by_path_async``
|
||||
|
||||
Returns:
|
||||
Job ID string
|
||||
@@ -1661,6 +1677,7 @@ def run_script_by_hash_async(
|
||||
args=args,
|
||||
scheduled_in_secs=scheduled_in_secs,
|
||||
tag=tag,
|
||||
retry=retry,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1652,21 +1652,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* \`{ scheduledInSeconds?, tag?, retry? }\`, e.g. \`{ retry: { constant: { attempts: 3, seconds: 10 } } }\`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
@@ -2214,10 +2216,14 @@ def post(endpoint, raise_for_status = True, **kwargs) -> httpx.Response
|
||||
def create_token(duration = dt.timedelta(days=1)) -> str
|
||||
|
||||
# Create a script job by path and return its job id.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
#
|
||||
# \`\`retry\`\` re-runs the job when it fails, with the policy a schedule takes,
|
||||
# e.g. \`\`{"constant": {"attempts": 3, "seconds": 10}}\`\`. The returned id is
|
||||
# the first attempt's; each retry is a job of its own, recorded as a child of it.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a script job by hash and return its job id.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
# Create a script job by hash and return its job id. See \`\`run_script_by_path_async\`\` for \`\`retry\`\`.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a flow job and return its job id.
|
||||
def run_flow_async(path: str, args: dict = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True, tag: str = None) -> str
|
||||
|
||||
@@ -1567,21 +1567,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
@@ -2129,10 +2131,14 @@ def post(endpoint, raise_for_status = True, **kwargs) -> httpx.Response
|
||||
def create_token(duration = dt.timedelta(days=1)) -> str
|
||||
|
||||
# Create a script job by path and return its job id.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
#
|
||||
# ``retry`` re-runs the job when it fails, with the policy a schedule takes,
|
||||
# e.g. ``{"constant": {"attempts": 3, "seconds": 10}}``. The returned id is
|
||||
# the first attempt's; each retry is a job of its own, recorded as a child of it.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a script job by hash and return its job id.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
# Create a script job by hash and return its job id. See ``run_script_by_path_async`` for ``retry``.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a flow job and return its job id.
|
||||
def run_flow_async(path: str, args: dict = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True, tag: str = None) -> str
|
||||
|
||||
@@ -60,10 +60,14 @@ def post(endpoint, raise_for_status = True, **kwargs) -> httpx.Response
|
||||
def create_token(duration = dt.timedelta(days=1)) -> str
|
||||
|
||||
# Create a script job by path and return its job id.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
#
|
||||
# ``retry`` re-runs the job when it fails, with the policy a schedule takes,
|
||||
# e.g. ``{"constant": {"attempts": 3, "seconds": 10}}``. The returned id is
|
||||
# the first attempt's; each retry is a job of its own, recorded as a child of it.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a script job by hash and return its job id.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
# Create a script job by hash and return its job id. See ``run_script_by_path_async`` for ``retry``.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a flow job and return its job id.
|
||||
def run_flow_async(path: str, args: dict = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True, tag: str = None) -> str
|
||||
|
||||
@@ -124,21 +124,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
|
||||
@@ -320,21 +320,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
|
||||
@@ -332,21 +332,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
|
||||
@@ -322,21 +322,23 @@ async cancelJob(jobId: string, reason: string | undefined = undefined): Promise<
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByPathAsync(path: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null = null, tag: string | null = null): Promise<string>
|
||||
async runScriptByHashAsync(hash_: string, args: Record<string, any> | null = null, scheduledInSeconds: number | null | RunScriptAsyncOptions = null, tag: string | null = null): Promise<string>
|
||||
|
||||
/**
|
||||
* Run a flow asynchronously by its path
|
||||
|
||||
@@ -268,10 +268,14 @@ def post(endpoint, raise_for_status = True, **kwargs) -> httpx.Response
|
||||
def create_token(duration = dt.timedelta(days=1)) -> str
|
||||
|
||||
# Create a script job by path and return its job id.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
#
|
||||
# ``retry`` re-runs the job when it fails, with the policy a schedule takes,
|
||||
# e.g. ``{"constant": {"attempts": 3, "seconds": 10}}``. The returned id is
|
||||
# the first attempt's; each retry is a job of its own, recorded as a child of it.
|
||||
def run_script_by_path_async(path: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a script job by hash and return its job id.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None) -> str
|
||||
# Create a script job by hash and return its job id. See ``run_script_by_path_async`` for ``retry``.
|
||||
def run_script_by_hash_async(hash_: str, args: dict = None, scheduled_in_secs: int = None, tag: str = None, retry: dict = None) -> str
|
||||
|
||||
# Create a flow job and return its job id.
|
||||
def run_flow_async(path: str, args: dict = None, scheduled_in_secs: int = None, do_not_track_in_parent: bool = True, tag: str = None) -> str
|
||||
|
||||
@@ -22,6 +22,6 @@ cp "${script_dirpath}/sqlUtils.ts" "${script_dirpath}/src/"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export type { DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getWorkspace, workerHasInternalServer, SHARED_FOLDER, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, cancelJob, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getWorkspace, workerHasInternalServer, SHARED_FOLDER, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, cancelJob, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, type RunScriptAsyncOptions, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ cp "${script_dirpath}/sqlUtils.ts" "${script_dirpath}/src/"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export type { DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getWorkspace, workerHasInternalServer, SHARED_FOLDER, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, cancelJob, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, getApprovalUrls, type TaskOptions, type TaskRetry, type Jsonified, type JsonifiedFn, WorkflowCtx, _workflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI, commitKafkaOffsets } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getWorkspace, workerHasInternalServer, SHARED_FOLDER, getStatePath, getInternalState, setInternalState, getResumeEndpoints, getResult, getResultMaybe, resolveDefaultResource, databaseUrlFromResource, base64ToUint8Array, uint8ArrayToBase64, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, cancelJob, loadS3FileStream, loadS3File, writeS3File, deleteS3File, signS3Objects, signS3Object, getPresignedS3PublicUrls, getPresignedS3PublicUrl, task, taskScript, taskFlow, workflow, step, sleep, parallel, waitForApproval, getApprovalUrls, type TaskOptions, type TaskRetry, type Jsonified, type JsonifiedFn, WorkflowCtx, _workflowCtx, setWorkflowCtx, StepSuspend, runScript, runScriptAsync, runScriptByPath, runScriptByHash, runScriptByPathAsync, runScriptByHashAsync, type RunScriptAsyncOptions, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, type Sql, requestInteractiveTeamsApproval, appendToResultStream, streamResult, datatable, ducklake, upsertPartition, appendPartition, type DucklakeMaterializeOptions, type SqlStatement, type DatatableSqlTemplateFunction, type SqlTemplateFunction, type S3Object, type S3ObjectRecord, type S3ObjectURI, commitKafkaOffsets } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
|
||||
# Build default export by combining client utilities + services
|
||||
# This preserves backward compatibility for `import wmill from "windmill-client"`
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
UserService,
|
||||
KafkaTriggerService,
|
||||
} from "./services.gen";
|
||||
import type { Retry } from "./types.gen";
|
||||
import { OpenAPI } from "./core/OpenAPI";
|
||||
import { isSuspendSignal, stepErrorMarker, taskErrorFromMarker } from "./wacError";
|
||||
// import type { DenoS3LightClientSettings } from "./index";
|
||||
@@ -420,12 +421,26 @@ export async function runScriptAsync(
|
||||
return _runScriptAsyncInternal(path, hash_, args, scheduledInSeconds, tag);
|
||||
}
|
||||
|
||||
/** Options for {@link runScriptByPathAsync} and {@link runScriptByHashAsync}, passed in
|
||||
* place of `scheduledInSeconds`; a positional `tag` is then ignored. */
|
||||
export interface RunScriptAsyncOptions {
|
||||
/** Schedule execution for a future time (in seconds) */
|
||||
scheduledInSeconds?: number;
|
||||
/** Override the worker tag the job runs on */
|
||||
tag?: string;
|
||||
/** Re-run the job when it fails, with the same policy a schedule takes, e.g.
|
||||
* `{ constant: { attempts: 3, seconds: 10 } }`. The returned id is the first
|
||||
* attempt's; each retry is a job of its own, recorded as a child of it. */
|
||||
retry?: Retry;
|
||||
}
|
||||
|
||||
async function _runScriptAsyncInternal(
|
||||
path: string | null = null,
|
||||
hash_: string | null = null,
|
||||
args: Record<string, any> | null = null,
|
||||
scheduledInSeconds: number | null = null,
|
||||
tag: string | null = null
|
||||
tag: string | null = null,
|
||||
retry: Retry | null = null
|
||||
): Promise<string> {
|
||||
// Create a script job and return its job id.
|
||||
args = args || {};
|
||||
@@ -439,6 +454,10 @@ async function _runScriptAsyncInternal(
|
||||
params["tag"] = tag;
|
||||
}
|
||||
|
||||
if (retry) {
|
||||
params["retry"] = JSON.stringify(retry);
|
||||
}
|
||||
|
||||
let parentJobId = getEnv("WM_JOB_ID");
|
||||
if (parentJobId !== undefined) {
|
||||
params["parent_job"] = parentJobId;
|
||||
@@ -475,34 +494,47 @@ async function _runScriptAsyncInternal(
|
||||
* Run a script asynchronously by its path
|
||||
* @param path - Script path in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
export async function runScriptByPathAsync(
|
||||
path: string,
|
||||
args: Record<string, any> | null = null,
|
||||
scheduledInSeconds: number | null = null,
|
||||
scheduledInSeconds: number | null | RunScriptAsyncOptions = null,
|
||||
tag: string | null = null
|
||||
): Promise<string> {
|
||||
return _runScriptAsyncInternal(path, null, args, scheduledInSeconds, tag);
|
||||
const o = asyncRunOptions(scheduledInSeconds, tag);
|
||||
return _runScriptAsyncInternal(path, null, args, o.scheduledInSeconds, o.tag, o.retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a script asynchronously by its hash
|
||||
* @param hash_ - Script hash in Windmill
|
||||
* @param args - Arguments to pass to the script
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds)
|
||||
* @param scheduledInSeconds - Schedule execution for a future time (in seconds), or {@link RunScriptAsyncOptions}
|
||||
* `{ scheduledInSeconds?, tag?, retry? }`, e.g. `{ retry: { constant: { attempts: 3, seconds: 10 } } }`
|
||||
* @param tag - Override the worker tag the job runs on
|
||||
* @returns Job ID of the created job
|
||||
*/
|
||||
export async function runScriptByHashAsync(
|
||||
hash_: string,
|
||||
args: Record<string, any> | null = null,
|
||||
scheduledInSeconds: number | null = null,
|
||||
scheduledInSeconds: number | null | RunScriptAsyncOptions = null,
|
||||
tag: string | null = null
|
||||
): Promise<string> {
|
||||
return _runScriptAsyncInternal(null, hash_, args, scheduledInSeconds, tag);
|
||||
const o = asyncRunOptions(scheduledInSeconds, tag);
|
||||
return _runScriptAsyncInternal(null, hash_, args, o.scheduledInSeconds, o.tag, o.retry);
|
||||
}
|
||||
|
||||
function asyncRunOptions(
|
||||
scheduledInSeconds: number | null | RunScriptAsyncOptions,
|
||||
tag: string | null
|
||||
): RunScriptAsyncOptions {
|
||||
return typeof scheduledInSeconds === "object" && scheduledInSeconds !== null
|
||||
? scheduledInSeconds
|
||||
: { scheduledInSeconds: scheduledInSeconds ?? undefined, tag: tag ?? undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user