Add Python task execution options authoring (v0.1.10) - #52
Merged
Volv-G merged 1 commit intoSep 2, 2026
Conversation
Python-authored pipelines could not emit task-level `executionOptions`,
so there was no way to author a non-cached task: `TaskNode` had no field
for it and `_emit_task` wrote only annotations/componentRef/arguments/
isEnabled. Tangle's "do not cache this task" knob
(`executionOptions.cachingStrategy.maxCacheStaleness: P0D`) was therefore
unreachable from Python.
Add two reserved call-site keywords on container-component tasks,
following the existing `is_enabled=` precedent:
gate = read_runtime_state(name="...", max_cache_staleness="P0D")
uploaded = flaky_upload(
payload=..., execution_options={"retryStrategy": {"maxRetries": 3}}
)
`max_cache_staleness=` is the narrow ergonomic knob for the common
no-cache case; `execution_options=` is the general passthrough. The
narrow keyword wins over a `cachingStrategy.maxCacheStaleness` supplied
through the passthrough and preserves sibling fields. Both work on
`ref(...)`, `@task` and `@registered` tasks (one shared call path), and
both use the same collision escape as `is_enabled`: a component input of
that name is bound with `.bind(...)` and stays under `arguments`.
Subpipeline (graph-component) tasks reject both keywords, mirroring
`is_enabled`, because caching and retries apply to the container
executions inside the child graph.
Correct the vendored dehydrated schema against the backend models
(`cloud_pipelines_backend.component_structures`):
* Drop `executionOptions.timeout` and `retryStrategy.backoff`. Neither
exists on the backend; because its pydantic models leave `extra`
unset (i.e. `extra="ignore"`), both were silently discarded on submit
rather than rejected. Verified by round-tripping through the pinned
backend model: `{"retryStrategy": {"maxRetries": 3, "backoff": "30s"},
"timeout": "30m"}` parses to `{"retryStrategy": {"maxRetries": 3}}`.
* Require `maxRetries` on `RetryStrategySpec`, matching the generated
`pipeline_schema.json` and the backend's non-Optional field. An empty
or backoff-only retry strategy previously passed dehydrated validation
and then failed server-side.
Because unmodeled keys are silently ignored rather than rejected, the
`execution_options=` passthrough validates against the fields the
backend actually models instead of accepting anything: an author cannot
be told a setting applies when nothing applies it. The allowlist is
pinned to the generated schema by a test, so a backend field addition
fails loudly rather than staying silently unauthorable.
Assisted-By: devx/a88c98b2-381c-4e70-ad73-be1ff5ef7d84
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Python-authored pipelines cannot emit task-level
executionOptions, so there is no way to author a non-cached task.TaskNodehas no field for it and_emit_taskwrites onlyannotations/componentRef/arguments/isEnabled. Tangle's "do not cache this task" knob —executionOptions.cachingStrategy.maxCacheStaleness: P0D— is unreachable from Python.This blocks tasks that read runtime state (e.g. a run's
CLOUD_PIPELINES_PIPELINE_RUN_CREATED_BY), where a cached result is silently wrong.Authoring API
Two reserved call-site keywords on container-component tasks, following the existing
is_enabled=precedent:Emits:
max_cache_staleness=wins over acachingStrategy.maxCacheStalenesssupplied throughexecution_options=, preserving sibling fields. The authored mapping is deep-copied, so a shared constant reused across tasks is never mutated.ref(...),@taskand@registeredtasks (one shared call path).is_enabled: a component input of that name is bound with.bind(...)and stays underarguments.is_enabled— caching and retries apply to the container executions inside the child graph.isEnabledin canonical task key order.Schema corrections
Reviewers questioned the
timeout/retryStrategy.backofffields used in the first draft of the docs. They do not exist. Verified against the pinned backend (cloud_pipelines_backend.component_structures,ExecutionOptionsSpec=caching_strategy+retry_strategy;RetryStrategySpec= requiredmax_retries), and reproduced through the model itself:The backend's models leave
extraunset (extra="ignore"), so those keys are silently discarded on submit — the worst failure mode, since docs would promise a timeout that never takes effect.dehydrated_pipeline_schema.jsonwas the only place they existed; the generatedpipeline_schema.jsonand the OpenAPI schema never had them.executionOptions.timeoutandretryStrategy.backofffrom the dehydrated schema.required: ["maxRetries"]toRetryStrategySpec, matching the generated schema and the backend's non-Optional field. Previously{"retryStrategy": {}}passed dehydrated validation and then failed server-side.Because unmodeled keys are ignored rather than rejected, the
execution_options=passthrough validates against the fields the backend actually models rather than accepting anything — consistent with the fail-closed treatment of every other authored value.test_execution_option_fields_match_generated_schemapins that allowlist topipeline_schema.json(regenerated from the backend byscripts/refresh_pipeline_schema.py), so a backend field addition fails a test instead of silently staying unauthorable.Review history
Reviewed locally before submission; all findings resolved:
timeout/backoff(example, README, docstrings, subpipeline error text) — removed; schema aligned rather than cementing the divergence.required: ["maxRetries"]added at the schema layer plus an actionable emit-time error; regression cases added.cachingStrategywas masked by the narrow-wins merge ({"cachingStrategy": "bad"}silently replaced) — validation now runs before the merge, so scalar/null parents raise instead of being overwritten.Note on the schema edit: removing the ghost
propertiesdoes loosen validation for those keys (timeout: 3was previously rejected, now accepted underadditionalProperties: true). That is deliberate — constraining the shape of fields the backend discards enforces nothing, and compiler-authored input is fail-closed via the allowlist. ThemaxRetriesrequirement is the intentional tightening.Version
Patch bump
0.1.9→0.1.10acrosspyproject.toml,uv.lock,tangle_cli.__init__fallback, and the packaging test assertion.Tests
1126 passedlocally (up from 1108), including the packaging suite building a wheel at 0.1.10. Coverage added for: emitted shape and key order, narrow-wins merge, no mutation of shared mappings,@taskpath, unknown/partial/malformed option rejection, subpipeline rejection for both keywords, dehydrator round-trip, schema accept/reject cases, and the allowlist-vs-generated-schema drift guard.End-to-end verification: the example compiles, and the emitted
executionOptionsround-trip losslessly through the real backend model for both tasks.