tangle-cli 0.1.9__tar.gz → 0.1.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/PKG-INFO +51 -1
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/README.md +50 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +37 -15
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +179 -3
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +12 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +20 -2
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +18 -2
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +4 -5
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/pyproject.toml +1 -1
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/pyproject.toml.orig +1 -1
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/api_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/api_schema.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/api_transport.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/args_container.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/cli_options.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/client.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_from_func.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_generator.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipelines.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/schema_validation.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/OSS-CONVENTIONS.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/PORT-README.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/SKILL.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/auth-wizard.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/builder.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/debugger.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/reporter.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/researcher.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/reviewer.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/agents/scenario-builder.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/data-sources.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/event-log.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/iterating-on-runs.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/knowledge-corpus.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/secrets.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/setup.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-0-initialize.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-1-analyze.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-2-hypothesize.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-3-submit.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-4-monitor.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-5-evaluate.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-6-synthesize.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/step-7-decide.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/tangle-tools.md +0 -0
- {tangle_cli-0.1.9 → tangle_cli-0.1.10}/skills/tangent/references/uploading-artifacts.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: tangle-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: CLI for Tangle, the open-source ML pipeline orchestration platform
|
|
5
5
|
Author: Alexey Volkov, Tangle authors, Volv Grebennikov
|
|
6
6
|
Author-email: Alexey Volkov <alexey.volkov@ark-kun.com>, Volv Grebennikov <volv.grebennikov@shopify.com>
|
|
@@ -439,6 +439,56 @@ def collision(runtime_condition: In[str]) -> Out[str]:
|
|
|
439
439
|
|
|
440
440
|
The bound value remains under `arguments.is_enabled`; the call-site value emits as `isEnabled`. Tangle does not evaluate conditions on graph-component tasks, so `subpipeline(...)(is_enabled=...)` is rejected with guidance to condition tasks inside the child pipeline. A child graph input with that name remains available through `subpipeline(...).bind(is_enabled=...)(...)`. There is no `condition` alias.
|
|
441
441
|
|
|
442
|
+
##### Task execution options and caching
|
|
443
|
+
|
|
444
|
+
Tangle caches task results, so a task that reads state which changes between runs (a run's `createdBy`, wall-clock time, an external table that the graph does not depend on) must opt out of caching explicitly. Use the reserved task-call metadata keyword `max_cache_staleness=`:
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
@pipeline("Scheduled-run gate")
|
|
448
|
+
def scheduled_gate() -> Out[str]:
|
|
449
|
+
is_scheduled = read_runtime_state(
|
|
450
|
+
name="CLOUD_PIPELINES_PIPELINE_RUN_CREATED_BY",
|
|
451
|
+
max_cache_staleness="P0D",
|
|
452
|
+
)
|
|
453
|
+
return is_scheduled.Output
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
This emits the canonical task field rather than a component argument:
|
|
457
|
+
|
|
458
|
+
```yaml
|
|
459
|
+
executionOptions:
|
|
460
|
+
cachingStrategy:
|
|
461
|
+
maxCacheStaleness: P0D
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
`P0D` means "never reuse a cached result"; any other ISO-8601 duration (`P7D`) caps how stale a reusable result may be. For the rest of `ExecutionOptionsSpec`, use the general `execution_options=` passthrough:
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
uploaded = flaky_upload(
|
|
468
|
+
payload=data.Output,
|
|
469
|
+
execution_options={"retryStrategy": {"maxRetries": 3}},
|
|
470
|
+
)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Both keywords may be combined; `max_cache_staleness=` wins over a `cachingStrategy.maxCacheStaleness` supplied through `execution_options=`, and every other passthrough field is preserved. A mapping passed as `execution_options=` is never mutated, so one shared constant can be reused across tasks.
|
|
474
|
+
|
|
475
|
+
Tangle models exactly two execution-option groups today — `cachingStrategy.maxCacheStaleness` and `retryStrategy.maxRetries` (required whenever `retryStrategy` is present). Any other key is rejected at compile time: the backend ignores unmodeled keys silently, so accepting one would advertise a setting that never takes effect.
|
|
476
|
+
|
|
477
|
+
Execution options are STATIC compile-time settings, so `max_cache_staleness` takes an RFC3339 duration string and `retryStrategy.maxRetries` a non-negative integer. Graph inputs, task outputs, `dynamic_secret(...)`, and `raw(...)` values are rejected because the backend does not resolve them for `executionOptions`. Passing an empty `execution_options={}` is an error — omit the keyword instead.
|
|
478
|
+
|
|
479
|
+
If a component itself declares an input named `max_cache_staleness` or `execution_options`, bind that component argument separately while using the call-site keyword for task metadata:
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
result = work.bind(max_cache_staleness="component-input-value")(
|
|
483
|
+
message="hello",
|
|
484
|
+
max_cache_staleness="P0D",
|
|
485
|
+
)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The bound value remains under `arguments.max_cache_staleness`; the call-site value emits as `executionOptions`. Tangle applies caching and retries to container-component tasks, so `subpipeline(...)(max_cache_staleness=...)` and `subpipeline(...)(execution_options=...)` are rejected with guidance to set them on tasks inside the child pipeline. A child graph input with either name remains available through `subpipeline(...).bind(...)`.
|
|
489
|
+
|
|
490
|
+
See `examples/python_pipeline/execution_options_pipeline.py` for a runnable example.
|
|
491
|
+
|
|
442
492
|
##### Task images, dependencies, and image IDs
|
|
443
493
|
|
|
444
494
|
Use `@task(image="...")` to write the component image directly. Use `dependencies_from="pyproject.toml"` when generated components need to install Python dependencies. Several tasks can share one authoring-only `TaskEnv`:
|
|
@@ -413,6 +413,56 @@ def collision(runtime_condition: In[str]) -> Out[str]:
|
|
|
413
413
|
|
|
414
414
|
The bound value remains under `arguments.is_enabled`; the call-site value emits as `isEnabled`. Tangle does not evaluate conditions on graph-component tasks, so `subpipeline(...)(is_enabled=...)` is rejected with guidance to condition tasks inside the child pipeline. A child graph input with that name remains available through `subpipeline(...).bind(is_enabled=...)(...)`. There is no `condition` alias.
|
|
415
415
|
|
|
416
|
+
##### Task execution options and caching
|
|
417
|
+
|
|
418
|
+
Tangle caches task results, so a task that reads state which changes between runs (a run's `createdBy`, wall-clock time, an external table that the graph does not depend on) must opt out of caching explicitly. Use the reserved task-call metadata keyword `max_cache_staleness=`:
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
@pipeline("Scheduled-run gate")
|
|
422
|
+
def scheduled_gate() -> Out[str]:
|
|
423
|
+
is_scheduled = read_runtime_state(
|
|
424
|
+
name="CLOUD_PIPELINES_PIPELINE_RUN_CREATED_BY",
|
|
425
|
+
max_cache_staleness="P0D",
|
|
426
|
+
)
|
|
427
|
+
return is_scheduled.Output
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
This emits the canonical task field rather than a component argument:
|
|
431
|
+
|
|
432
|
+
```yaml
|
|
433
|
+
executionOptions:
|
|
434
|
+
cachingStrategy:
|
|
435
|
+
maxCacheStaleness: P0D
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`P0D` means "never reuse a cached result"; any other ISO-8601 duration (`P7D`) caps how stale a reusable result may be. For the rest of `ExecutionOptionsSpec`, use the general `execution_options=` passthrough:
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
uploaded = flaky_upload(
|
|
442
|
+
payload=data.Output,
|
|
443
|
+
execution_options={"retryStrategy": {"maxRetries": 3}},
|
|
444
|
+
)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Both keywords may be combined; `max_cache_staleness=` wins over a `cachingStrategy.maxCacheStaleness` supplied through `execution_options=`, and every other passthrough field is preserved. A mapping passed as `execution_options=` is never mutated, so one shared constant can be reused across tasks.
|
|
448
|
+
|
|
449
|
+
Tangle models exactly two execution-option groups today — `cachingStrategy.maxCacheStaleness` and `retryStrategy.maxRetries` (required whenever `retryStrategy` is present). Any other key is rejected at compile time: the backend ignores unmodeled keys silently, so accepting one would advertise a setting that never takes effect.
|
|
450
|
+
|
|
451
|
+
Execution options are STATIC compile-time settings, so `max_cache_staleness` takes an RFC3339 duration string and `retryStrategy.maxRetries` a non-negative integer. Graph inputs, task outputs, `dynamic_secret(...)`, and `raw(...)` values are rejected because the backend does not resolve them for `executionOptions`. Passing an empty `execution_options={}` is an error — omit the keyword instead.
|
|
452
|
+
|
|
453
|
+
If a component itself declares an input named `max_cache_staleness` or `execution_options`, bind that component argument separately while using the call-site keyword for task metadata:
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
result = work.bind(max_cache_staleness="component-input-value")(
|
|
457
|
+
message="hello",
|
|
458
|
+
max_cache_staleness="P0D",
|
|
459
|
+
)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The bound value remains under `arguments.max_cache_staleness`; the call-site value emits as `executionOptions`. Tangle applies caching and retries to container-component tasks, so `subpipeline(...)(max_cache_staleness=...)` and `subpipeline(...)(execution_options=...)` are rejected with guidance to set them on tasks inside the child pipeline. A child graph input with either name remains available through `subpipeline(...).bind(...)`.
|
|
463
|
+
|
|
464
|
+
See `examples/python_pipeline/execution_options_pipeline.py` for a runnable example.
|
|
465
|
+
|
|
416
466
|
##### Task images, dependencies, and image IDs
|
|
417
467
|
|
|
418
468
|
Use `@task(image="...")` to write the component image directly. Use `dependencies_from="pyproject.toml"` when generated components need to install Python dependencies. Several tasks can share one authoring-only `TaskEnv`:
|
|
@@ -14,6 +14,6 @@ from tangle_cli.dynamic_discovery_client import TangleDynamicDiscoveryClient
|
|
|
14
14
|
try:
|
|
15
15
|
__version__ = metadata_version("tangle-cli")
|
|
16
16
|
except PackageNotFoundError:
|
|
17
|
-
__version__ = "0.1.
|
|
17
|
+
__version__ = "0.1.10"
|
|
18
18
|
|
|
19
19
|
__all__ = ["TangleDynamicDiscoveryClient", "__version__"]
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py
RENAMED
|
@@ -24,7 +24,7 @@ import urllib.parse
|
|
|
24
24
|
import urllib.request
|
|
25
25
|
from collections.abc import Sequence
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
from typing import Any
|
|
27
|
+
from typing import Any, NamedTuple
|
|
28
28
|
|
|
29
29
|
from .parser import (
|
|
30
30
|
DEFAULT_OPENAPI_PATH,
|
|
@@ -384,7 +384,7 @@ from pydantic import BaseModel
|
|
|
384
384
|
try:
|
|
385
385
|
from pydantic import ConfigDict
|
|
386
386
|
except ImportError: # pragma: no cover - pydantic v1 fallback
|
|
387
|
-
ConfigDict = None # type: ignore[assignment]
|
|
387
|
+
ConfigDict = None # type: ignore[misc, assignment]
|
|
388
388
|
|
|
389
389
|
|
|
390
390
|
class TangleGeneratedModel(BaseModel):
|
|
@@ -464,6 +464,21 @@ def generate_models(
|
|
|
464
464
|
lines.append("")
|
|
465
465
|
return "\n".join(lines)
|
|
466
466
|
|
|
467
|
+
|
|
468
|
+
class _ParamName(NamedTuple):
|
|
469
|
+
"""Pair a generated Python parameter name with its OpenAPI wire name.
|
|
470
|
+
|
|
471
|
+
``local_name`` is the collision-avoided Python identifier used in the
|
|
472
|
+
generated method signature; ``wire_name`` is the schema/OpenAPI name that
|
|
473
|
+
must be used as the JSON/query/path key sent to the backend. They differ
|
|
474
|
+
whenever the parser had to rename a field (for example a request-body field
|
|
475
|
+
literally named ``body`` becomes the local ``body_2``).
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
local_name: str
|
|
479
|
+
wire_name: str
|
|
480
|
+
|
|
481
|
+
|
|
467
482
|
def _method_name(group_name: str, command_name: str) -> str:
|
|
468
483
|
return f"{_safe_identifier(group_name)}_{_safe_identifier(command_name)}"
|
|
469
484
|
|
|
@@ -481,7 +496,7 @@ def _param_signature(
|
|
|
481
496
|
has_request_body: bool,
|
|
482
497
|
*,
|
|
483
498
|
raw_body_override: bool = False,
|
|
484
|
-
) -> tuple[str, list[
|
|
499
|
+
) -> tuple[str, list[_ParamName], list[_ParamName], list[_ParamName], set[str], bool]:
|
|
485
500
|
required: list[Any] = []
|
|
486
501
|
optional: list[Any] = []
|
|
487
502
|
for parameter in parameters:
|
|
@@ -489,25 +504,32 @@ def _param_signature(
|
|
|
489
504
|
ordered = required + optional
|
|
490
505
|
seen: set[str] = set()
|
|
491
506
|
signature_parts: list[str] = []
|
|
492
|
-
path_names: list[
|
|
493
|
-
query_names: list[
|
|
494
|
-
body_names: list[
|
|
507
|
+
path_names: list[_ParamName] = []
|
|
508
|
+
query_names: list[_ParamName] = []
|
|
509
|
+
body_names: list[_ParamName] = []
|
|
495
510
|
required_body_names: set[str] = set()
|
|
496
511
|
for parameter in ordered:
|
|
497
512
|
name = _safe_identifier(parameter.local_name)
|
|
498
513
|
if name in seen:
|
|
499
514
|
continue
|
|
500
515
|
seen.add(name)
|
|
516
|
+
# Collision-avoiding Python locals (e.g. ``body_2``) must never leak into
|
|
517
|
+
# the emitted wire payload; the schema field name is the wire key. Only
|
|
518
|
+
# a missing name falls back to the local identifier, so schema-valid
|
|
519
|
+
# empty property names keep their exact wire spelling.
|
|
520
|
+
original_name = getattr(parameter, "original_name", None)
|
|
521
|
+
wire_name = name if original_name is None else str(original_name)
|
|
522
|
+
entry = _ParamName(local_name=name, wire_name=wire_name)
|
|
501
523
|
if parameter.required:
|
|
502
524
|
signature_parts.append(f"{name}: Any")
|
|
503
525
|
else:
|
|
504
526
|
signature_parts.append(f"{name}: Any = None")
|
|
505
527
|
if parameter.location == "path":
|
|
506
|
-
path_names.append(
|
|
528
|
+
path_names.append(entry)
|
|
507
529
|
elif parameter.location == "query":
|
|
508
|
-
query_names.append(
|
|
530
|
+
query_names.append(entry)
|
|
509
531
|
elif parameter.location == "body":
|
|
510
|
-
body_names.append(
|
|
532
|
+
body_names.append(entry)
|
|
511
533
|
if parameter.required:
|
|
512
534
|
required_body_names.add(name)
|
|
513
535
|
include_body = has_request_body
|
|
@@ -517,27 +539,27 @@ def _param_signature(
|
|
|
517
539
|
return ", ".join(signature_parts), path_names, query_names, body_names, required_body_names, include_body
|
|
518
540
|
|
|
519
541
|
|
|
520
|
-
def _dict_literal(names: list[
|
|
542
|
+
def _dict_literal(names: list[_ParamName]) -> str:
|
|
521
543
|
if not names:
|
|
522
544
|
return "None"
|
|
523
|
-
return "{" + ", ".join(f"{name!r}: {name}" for name in names) + "}"
|
|
545
|
+
return "{" + ", ".join(f"{name.wire_name!r}: {name.local_name}" for name in names) + "}"
|
|
524
546
|
|
|
525
547
|
|
|
526
|
-
def _body_dict_literal(names: list[
|
|
548
|
+
def _body_dict_literal(names: list[_ParamName], required_names: set[str]) -> str:
|
|
527
549
|
if not names:
|
|
528
550
|
return "None"
|
|
529
|
-
optional_names = [name for name in names if name not in required_names]
|
|
551
|
+
optional_names = [name for name in names if name.local_name not in required_names]
|
|
530
552
|
if not optional_names:
|
|
531
553
|
return _dict_literal(names)
|
|
532
554
|
optional_literal = _dict_literal(optional_names)
|
|
533
555
|
optional_expr = f"key: value for key, value in {optional_literal}.items() if value is not None"
|
|
534
556
|
if not required_names:
|
|
535
557
|
return "{" + optional_expr + "}"
|
|
536
|
-
required_literal = _dict_literal([name for name in names if name in required_names])
|
|
558
|
+
required_literal = _dict_literal([name for name in names if name.local_name in required_names])
|
|
537
559
|
return "{" + f"**{required_literal}, **{{{optional_expr}}}" + "}"
|
|
538
560
|
|
|
539
561
|
|
|
540
|
-
def _merged_body_dict_literal(names: list[
|
|
562
|
+
def _merged_body_dict_literal(names: list[_ParamName], required_names: set[str]) -> str:
|
|
541
563
|
"""Return request JSON with generic body fields overridden by named fields."""
|
|
542
564
|
|
|
543
565
|
return f"{{**(body or {{}}), **{_body_dict_literal(names, required_names)}}}"
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py
RENAMED
|
@@ -4,7 +4,7 @@ Canonical top-level key order:
|
|
|
4
4
|
name, description, metadata, inputs, outputs, implementation
|
|
5
5
|
|
|
6
6
|
Per-task key order:
|
|
7
|
-
annotations?, componentRef, arguments?, isEnabled?
|
|
7
|
+
annotations?, componentRef, arguments?, isEnabled?, executionOptions?
|
|
8
8
|
|
|
9
9
|
Argument values are emitted in the runnable ``ArgumentValue`` shape,
|
|
10
10
|
dispatched purely on the VALUE's runtime type — never on the argument
|
|
@@ -34,11 +34,18 @@ The literal key the user wrote (``wait_for``, ``depends_on``,
|
|
|
34
34
|
"""
|
|
35
35
|
from __future__ import annotations
|
|
36
36
|
|
|
37
|
+
from collections.abc import Mapping
|
|
37
38
|
from typing import Any
|
|
38
39
|
|
|
39
40
|
from .dynamic_data import DynamicData
|
|
40
41
|
from .errors import CompileError, InvalidArgumentTypeError
|
|
41
|
-
from .graph import
|
|
42
|
+
from .graph import (
|
|
43
|
+
EXECUTION_OPTIONS_UNSET,
|
|
44
|
+
IS_ENABLED_UNSET,
|
|
45
|
+
EdgeRef,
|
|
46
|
+
GraphBuilder,
|
|
47
|
+
TaskNode,
|
|
48
|
+
)
|
|
42
49
|
from .placeholders import GraphInputPlaceholder, TaskOutputProxy
|
|
43
50
|
from .raw import Raw
|
|
44
51
|
|
|
@@ -141,7 +148,7 @@ def _emit_task(
|
|
|
141
148
|
node: TaskNode, task_path: str, exempt_paths: set[str]
|
|
142
149
|
) -> dict[str, Any]:
|
|
143
150
|
"""Build the per-task body dict in canonical key order:
|
|
144
|
-
``annotations?, componentRef, arguments?, isEnabled?``.
|
|
151
|
+
``annotations?, componentRef, arguments?, isEnabled?, executionOptions?``.
|
|
145
152
|
|
|
146
153
|
``task_path`` is this task's dot-delimited JSON path
|
|
147
154
|
(``implementation.graph.tasks.<task_id>``); each argument's path is
|
|
@@ -167,6 +174,10 @@ def _emit_task(
|
|
|
167
174
|
if node.is_enabled is not IS_ENABLED_UNSET:
|
|
168
175
|
body["isEnabled"] = _emit_is_enabled(node.is_enabled)
|
|
169
176
|
|
|
177
|
+
execution_options = _emit_execution_options(node)
|
|
178
|
+
if execution_options is not None:
|
|
179
|
+
body["executionOptions"] = execution_options
|
|
180
|
+
|
|
170
181
|
return body
|
|
171
182
|
|
|
172
183
|
|
|
@@ -316,6 +327,171 @@ def _emit_is_enabled(value: Any) -> Any:
|
|
|
316
327
|
)
|
|
317
328
|
|
|
318
329
|
|
|
330
|
+
# The fields the BACKEND actually models on ``ExecutionOptionsSpec`` —
|
|
331
|
+
# mirrored from ``cloud_pipelines_backend.component_structures`` and vendored
|
|
332
|
+
# into ``schemas/pipeline_schema.json`` (kept honest by
|
|
333
|
+
# ``test_execution_option_fields_match_generated_schema``).
|
|
334
|
+
#
|
|
335
|
+
# This allowlist exists because the backend's pydantic models leave ``extra``
|
|
336
|
+
# unset, i.e. ``extra="ignore"``: an unmodeled key such as ``timeout`` or
|
|
337
|
+
# ``retryStrategy.backoff`` is SILENTLY DROPPED server-side rather than
|
|
338
|
+
# rejected. Accepting one here would let an author believe a timeout/backoff
|
|
339
|
+
# is in effect when nothing applies it, so the passthrough fails closed.
|
|
340
|
+
_EXECUTION_OPTION_FIELDS: dict[str, frozenset[str]] = {
|
|
341
|
+
"cachingStrategy": frozenset({"maxCacheStaleness"}),
|
|
342
|
+
"retryStrategy": frozenset({"maxRetries"}),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# ``RetryStrategySpec.max_retries`` is a required (non-Optional) backend field,
|
|
346
|
+
# so a retryStrategy without it is a submit-time validation failure.
|
|
347
|
+
_REQUIRED_EXECUTION_OPTION_FIELDS: dict[str, frozenset[str]] = {
|
|
348
|
+
"retryStrategy": frozenset({"maxRetries"}),
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _emit_execution_options(node: TaskNode) -> dict[str, Any] | None:
|
|
353
|
+
"""Merge the reserved execution-option keywords into ``executionOptions``.
|
|
354
|
+
|
|
355
|
+
``execution_options=`` is the general passthrough for the modeled
|
|
356
|
+
``ExecutionOptionsSpec`` (``cachingStrategy`` and ``retryStrategy``).
|
|
357
|
+
``max_cache_staleness=`` is the narrow ergonomic knob for the common
|
|
358
|
+
"do not cache this task" case and WINS over any
|
|
359
|
+
``cachingStrategy.maxCacheStaleness`` supplied through the passthrough.
|
|
360
|
+
|
|
361
|
+
Returns ``None`` when neither keyword was authored, so the key is omitted
|
|
362
|
+
entirely. Unlike arguments and ``isEnabled``, execution options are STATIC
|
|
363
|
+
compile-time settings: graph inputs, task outputs, dynamic data, and raw
|
|
364
|
+
values are rejected because the backend does not resolve them here. Keys
|
|
365
|
+
the backend does not model are rejected too — see
|
|
366
|
+
:data:`_EXECUTION_OPTION_FIELDS`.
|
|
367
|
+
"""
|
|
368
|
+
options_value = node.execution_options
|
|
369
|
+
staleness = node.max_cache_staleness
|
|
370
|
+
|
|
371
|
+
if (
|
|
372
|
+
options_value is EXECUTION_OPTIONS_UNSET
|
|
373
|
+
and staleness is EXECUTION_OPTIONS_UNSET
|
|
374
|
+
):
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
options: dict[str, Any] = {}
|
|
378
|
+
if options_value is not EXECUTION_OPTIONS_UNSET:
|
|
379
|
+
if not isinstance(options_value, Mapping):
|
|
380
|
+
raise InvalidArgumentTypeError(
|
|
381
|
+
"unsupported execution_options value type "
|
|
382
|
+
f"{type(options_value).__name__!r}. Task execution options must "
|
|
383
|
+
"be a mapping such as "
|
|
384
|
+
'{"cachingStrategy": {"maxCacheStaleness": "P0D"}}.'
|
|
385
|
+
)
|
|
386
|
+
options = _normalize_execution_option_value(
|
|
387
|
+
options_value, "execution_options"
|
|
388
|
+
)
|
|
389
|
+
_validate_execution_option_fields(options)
|
|
390
|
+
|
|
391
|
+
if staleness is not EXECUTION_OPTIONS_UNSET:
|
|
392
|
+
if not isinstance(staleness, str):
|
|
393
|
+
raise InvalidArgumentTypeError(
|
|
394
|
+
"unsupported max_cache_staleness value type "
|
|
395
|
+
f"{type(staleness).__name__!r}. Task cache staleness must be a "
|
|
396
|
+
"duration string such as 'P0D' (never reuse cached results) or "
|
|
397
|
+
"'P7D'."
|
|
398
|
+
)
|
|
399
|
+
caching = options.get("cachingStrategy")
|
|
400
|
+
if not isinstance(caching, dict):
|
|
401
|
+
caching = {}
|
|
402
|
+
# The narrow keyword is the authoritative source for this one field.
|
|
403
|
+
caching["maxCacheStaleness"] = staleness
|
|
404
|
+
options["cachingStrategy"] = caching
|
|
405
|
+
|
|
406
|
+
if not options:
|
|
407
|
+
raise CompileError(
|
|
408
|
+
"execution_options={} is empty; omit the keyword instead of "
|
|
409
|
+
"passing an empty mapping so the task emits no executionOptions."
|
|
410
|
+
)
|
|
411
|
+
return options
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _validate_execution_option_fields(options: dict[str, Any]) -> None:
|
|
415
|
+
"""Reject execution-option keys the Tangle backend does not model.
|
|
416
|
+
|
|
417
|
+
The backend ignores (silently discards) unknown keys, so a typo or an
|
|
418
|
+
aspirational field would compile, submit, and quietly do nothing. Failing
|
|
419
|
+
at compile time keeps the passthrough as fail-closed as every other value
|
|
420
|
+
the Python authoring surface emits.
|
|
421
|
+
"""
|
|
422
|
+
for group, value in options.items():
|
|
423
|
+
known_fields = _EXECUTION_OPTION_FIELDS.get(group)
|
|
424
|
+
if known_fields is None:
|
|
425
|
+
supported = ", ".join(sorted(_EXECUTION_OPTION_FIELDS))
|
|
426
|
+
raise InvalidArgumentTypeError(
|
|
427
|
+
f"unknown execution_options key {group!r}. Tangle models only "
|
|
428
|
+
f"{supported}; unmodeled keys are silently ignored by the "
|
|
429
|
+
"backend, so they are rejected here instead of looking like "
|
|
430
|
+
"a setting that never takes effect."
|
|
431
|
+
)
|
|
432
|
+
if not isinstance(value, dict):
|
|
433
|
+
raise InvalidArgumentTypeError(
|
|
434
|
+
f"execution_options.{group} must be a mapping; got "
|
|
435
|
+
f"{type(value).__name__!r}."
|
|
436
|
+
)
|
|
437
|
+
unknown = sorted(set(value) - known_fields)
|
|
438
|
+
if unknown:
|
|
439
|
+
supported = ", ".join(sorted(known_fields))
|
|
440
|
+
raise InvalidArgumentTypeError(
|
|
441
|
+
f"unknown execution_options.{group} field {unknown[0]!r}. "
|
|
442
|
+
f"Tangle models only {supported} here; unmodeled keys are "
|
|
443
|
+
"silently ignored by the backend, so they are rejected at "
|
|
444
|
+
"compile time."
|
|
445
|
+
)
|
|
446
|
+
required = _REQUIRED_EXECUTION_OPTION_FIELDS.get(group, frozenset())
|
|
447
|
+
missing = sorted(required - set(value))
|
|
448
|
+
if missing:
|
|
449
|
+
raise InvalidArgumentTypeError(
|
|
450
|
+
f"execution_options.{group} requires {missing[0]!r}. The "
|
|
451
|
+
"backend rejects a partial "
|
|
452
|
+
f"{group} spec."
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _normalize_execution_option_value(value: Any, path: str) -> Any:
|
|
457
|
+
"""Recursively validate/normalize one static execution-option value.
|
|
458
|
+
|
|
459
|
+
Mappings and sequences are copied (so a caller's dict is never mutated by
|
|
460
|
+
the ``max_cache_staleness`` merge) and tuples become lists so the result is
|
|
461
|
+
plain YAML-serializable data. Anything else — a task output, graph input,
|
|
462
|
+
``dynamic_secret(...)``, ``raw(...)`` value, or an arbitrary object — is
|
|
463
|
+
rejected: ``executionOptions`` is applied at submit time, not resolved from
|
|
464
|
+
the running graph.
|
|
465
|
+
"""
|
|
466
|
+
if isinstance(value, Mapping):
|
|
467
|
+
normalized: dict[str, Any] = {}
|
|
468
|
+
for key, item in value.items():
|
|
469
|
+
if not isinstance(key, str):
|
|
470
|
+
raise InvalidArgumentTypeError(
|
|
471
|
+
f"unsupported execution_options key type "
|
|
472
|
+
f"{type(key).__name__!r} at {path}. Execution option keys "
|
|
473
|
+
"must be strings."
|
|
474
|
+
)
|
|
475
|
+
normalized[key] = _normalize_execution_option_value(
|
|
476
|
+
item, f"{path}.{key}"
|
|
477
|
+
)
|
|
478
|
+
return normalized
|
|
479
|
+
if isinstance(value, (list, tuple)):
|
|
480
|
+
return [
|
|
481
|
+
_normalize_execution_option_value(item, f"{path}[{index}]")
|
|
482
|
+
for index, item in enumerate(value)
|
|
483
|
+
]
|
|
484
|
+
if value is None or isinstance(value, (str, bool, int, float)):
|
|
485
|
+
return value
|
|
486
|
+
raise InvalidArgumentTypeError(
|
|
487
|
+
f"unsupported execution_options value type {type(value).__name__!r} at "
|
|
488
|
+
f"{path}. Task execution options are static compile-time settings, so "
|
|
489
|
+
"only strings, numbers, booleans, null, lists, and nested mappings are "
|
|
490
|
+
"supported; graph inputs, task outputs, dynamic data, and raw values "
|
|
491
|
+
"are not evaluated for executionOptions."
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
319
495
|
def _emit_edge_value(edge: EdgeRef) -> dict[str, Any]:
|
|
320
496
|
"""Render an :class:`EdgeRef` as a dehydrated ``ArgumentValue``
|
|
321
497
|
sub-dict (``{taskOutput|graphInput: {...}}``) used in
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py
RENAMED
|
@@ -15,6 +15,11 @@ from typing import Any, Literal
|
|
|
15
15
|
# instead of being silently omitted.
|
|
16
16
|
IS_ENABLED_UNSET = object()
|
|
17
17
|
|
|
18
|
+
# Distinguishes omitted task execution options from an explicitly supplied
|
|
19
|
+
# value, so ``execution_options=None`` / ``max_cache_staleness=None`` fail
|
|
20
|
+
# closed in the emitter instead of being silently dropped.
|
|
21
|
+
EXECUTION_OPTIONS_UNSET = object()
|
|
22
|
+
|
|
18
23
|
|
|
19
24
|
@dataclass
|
|
20
25
|
class EdgeRef:
|
|
@@ -37,6 +42,11 @@ class TaskNode:
|
|
|
37
42
|
``arguments`` values may be plain strings, TaskOutputProxy objects, or
|
|
38
43
|
GraphInputPlaceholder objects. ``is_enabled`` is separate task metadata;
|
|
39
44
|
the emitter normalizes and serializes it as ``isEnabled`` when supplied.
|
|
45
|
+
|
|
46
|
+
``execution_options`` (general passthrough) and ``max_cache_staleness``
|
|
47
|
+
(narrow ergonomic knob) are likewise task metadata, not component inputs.
|
|
48
|
+
The emitter merges them — the narrow keyword wins — and serializes the
|
|
49
|
+
result as the canonical ``executionOptions`` task field.
|
|
40
50
|
"""
|
|
41
51
|
|
|
42
52
|
task_id: str
|
|
@@ -46,6 +56,8 @@ class TaskNode:
|
|
|
46
56
|
arguments: dict[str, Any] = field(default_factory=dict)
|
|
47
57
|
annotations: dict[str, str] | None = None
|
|
48
58
|
is_enabled: Any = IS_ENABLED_UNSET
|
|
59
|
+
execution_options: Any = EXECUTION_OPTIONS_UNSET
|
|
60
|
+
max_cache_staleness: Any = EXECUTION_OPTIONS_UNSET
|
|
49
61
|
|
|
50
62
|
|
|
51
63
|
@dataclass
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py
RENAMED
|
@@ -16,7 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
18
|
from .errors import CompileError
|
|
19
|
-
from .graph import IS_ENABLED_UNSET
|
|
19
|
+
from .graph import EXECUTION_OPTIONS_UNSET, IS_ENABLED_UNSET
|
|
20
20
|
|
|
21
21
|
_UNWRAPPED_KEY_RE = re.compile(r"^[A-Za-z0-9_-]+$")
|
|
22
22
|
|
|
@@ -296,6 +296,8 @@ class CallableRef:
|
|
|
296
296
|
self,
|
|
297
297
|
*,
|
|
298
298
|
is_enabled: Any = IS_ENABLED_UNSET,
|
|
299
|
+
execution_options: Any = EXECUTION_OPTIONS_UNSET,
|
|
300
|
+
max_cache_staleness: Any = EXECUTION_OPTIONS_UNSET,
|
|
299
301
|
**kwargs: Any,
|
|
300
302
|
) -> Any:
|
|
301
303
|
"""Trace-mode invocation.
|
|
@@ -313,7 +315,21 @@ class CallableRef:
|
|
|
313
315
|
input named ``is_enabled``, bind that input with
|
|
314
316
|
``ref(...).bind(is_enabled=...)``; bound kwargs remain component
|
|
315
317
|
arguments while the reserved call-site keyword remains task metadata,
|
|
316
|
-
so both may be used on the same task.
|
|
318
|
+
so both may be used on the same task.
|
|
319
|
+
|
|
320
|
+
``execution_options`` and ``max_cache_staleness`` follow the same
|
|
321
|
+
reserved-keyword convention and are emitted as the canonical
|
|
322
|
+
``executionOptions`` task field. ``execution_options`` takes the whole
|
|
323
|
+
``ExecutionOptionsSpec`` mapping (``cachingStrategy`` and
|
|
324
|
+
``retryStrategy`` — the only groups Tangle models);
|
|
325
|
+
``max_cache_staleness`` is the narrow knob for
|
|
326
|
+
``cachingStrategy.maxCacheStaleness`` (e.g. ``"P0D"`` to never reuse
|
|
327
|
+
cached results) and wins when both supply that field.
|
|
328
|
+
A component input named ``execution_options`` or
|
|
329
|
+
``max_cache_staleness`` is bound the same way, with
|
|
330
|
+
``ref(...).bind(max_cache_staleness=...)``.
|
|
331
|
+
|
|
332
|
+
Edge kwargs (``wait_for`` /
|
|
317
333
|
``depends_on``) and regular kwargs share one ``arguments`` dict in the
|
|
318
334
|
IR; the value-vs-key dispatch happens at emit time. ``.bind(...)``
|
|
319
335
|
kwargs are merged in last so call-site kwargs win on conflict (same
|
|
@@ -377,6 +393,8 @@ class CallableRef:
|
|
|
377
393
|
arguments=merged,
|
|
378
394
|
annotations=dict(self.annotations) if self.annotations else None,
|
|
379
395
|
is_enabled=is_enabled,
|
|
396
|
+
execution_options=execution_options,
|
|
397
|
+
max_cache_staleness=max_cache_staleness,
|
|
380
398
|
)
|
|
381
399
|
builder.add_task(node)
|
|
382
400
|
|
|
@@ -16,8 +16,9 @@ ergonomics (``.bind`` / ``.named`` / ``.with_annotations`` and call-site
|
|
|
16
16
|
kwargs). Calling the handle inside an active ``@pipeline`` trace records
|
|
17
17
|
ONE parent task (never the child's internals) and returns a
|
|
18
18
|
:class:`tangle_cli.python_pipeline.placeholders.TaskOutputProxy`. Tangle only
|
|
19
|
-
supports conditional execution for container-component
|
|
20
|
-
call-site ``is_enabled=``
|
|
19
|
+
supports conditional execution and execution options for container-component
|
|
20
|
+
tasks, so the reserved call-site ``is_enabled=``, ``execution_options=`` and
|
|
21
|
+
``max_cache_staleness=`` metadata keywords are rejected on subpipeline boundary
|
|
21
22
|
(graph-component) tasks.
|
|
22
23
|
|
|
23
24
|
The child body is NOT executed into the parent's :class:`GraphBuilder`.
|
|
@@ -128,6 +129,10 @@ class SubpipelineRef:
|
|
|
128
129
|
evaluate conditions on graph-component tasks. A child graph input with
|
|
129
130
|
that name remains available through ``.bind(is_enabled=...)``, matching
|
|
130
131
|
the reserved-metadata collision convention used by ``CallableRef``.
|
|
132
|
+
``execution_options`` / ``max_cache_staleness`` are rejected for the
|
|
133
|
+
same reason: caching and retries apply to the container executions
|
|
134
|
+
inside the child graph, so silently emitting them on the boundary task
|
|
135
|
+
would look like a setting that is not actually applied.
|
|
131
136
|
"""
|
|
132
137
|
import sys
|
|
133
138
|
|
|
@@ -154,6 +159,17 @@ class SubpipelineRef:
|
|
|
154
159
|
"input with .bind(is_enabled=...)."
|
|
155
160
|
)
|
|
156
161
|
|
|
162
|
+
for reserved in ("execution_options", "max_cache_staleness"):
|
|
163
|
+
if reserved in kwargs:
|
|
164
|
+
raise CompileError(
|
|
165
|
+
f"subpipeline tasks do not support call-site {reserved}= "
|
|
166
|
+
"because Tangle execution options (caching, retries) "
|
|
167
|
+
"apply to container-component tasks. Set "
|
|
168
|
+
f"{reserved}= on the tasks inside the child pipeline. If "
|
|
169
|
+
f"the child declares a graph input named {reserved!r}, pass "
|
|
170
|
+
f"that input with .bind({reserved}=...)."
|
|
171
|
+
)
|
|
172
|
+
|
|
157
173
|
# Resolve the parent task ID. ``.named(...)`` always wins over the
|
|
158
174
|
# AST-derived auto ID.
|
|
159
175
|
if self.task_id_hint is not None:
|
|
@@ -263,8 +263,7 @@
|
|
|
263
263
|
"additionalProperties": true,
|
|
264
264
|
"properties": {
|
|
265
265
|
"cachingStrategy": { "$ref": "#/$defs/CachingStrategySpec" },
|
|
266
|
-
"retryStrategy": { "$ref": "#/$defs/RetryStrategySpec" }
|
|
267
|
-
"timeout": { "type": "string", "description": "Go-style duration, e.g. `30m`, `2h`." }
|
|
266
|
+
"retryStrategy": { "$ref": "#/$defs/RetryStrategySpec" }
|
|
268
267
|
}
|
|
269
268
|
},
|
|
270
269
|
|
|
@@ -274,7 +273,7 @@
|
|
|
274
273
|
"properties": {
|
|
275
274
|
"maxCacheStaleness": {
|
|
276
275
|
"type": ["string", "null"],
|
|
277
|
-
"description": "Maximum allowed cache age, e.g. `P7D`
|
|
276
|
+
"description": "Maximum allowed cache age as an RFC3339 duration, e.g. `P7D`; `P0D` never reuses a cached result."
|
|
278
277
|
}
|
|
279
278
|
}
|
|
280
279
|
},
|
|
@@ -282,9 +281,9 @@
|
|
|
282
281
|
"RetryStrategySpec": {
|
|
283
282
|
"type": "object",
|
|
284
283
|
"additionalProperties": true,
|
|
284
|
+
"required": ["maxRetries"],
|
|
285
285
|
"properties": {
|
|
286
|
-
"maxRetries": { "type": "integer", "minimum": 0 }
|
|
287
|
-
"backoff": { "type": "string", "description": "Go-style duration, e.g. `30s`." }
|
|
286
|
+
"maxRetries": { "type": "integer", "minimum": 0 }
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
289
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_from_func.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_generator.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_inspector.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/component_publisher.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/hydration_trust.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py
RENAMED
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py
RENAMED
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py
RENAMED
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py
RENAMED
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/schema_validation.py
RENAMED
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tangle_cli-0.1.9 → tangle_cli-0.1.10}/packages/tangle-cli/src/tangle_cli/version_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|