tangle-cli 0.1.6__tar.gz → 0.1.7__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.6 → tangle_cli-0.1.7}/PKG-INFO +127 -2
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/README.md +126 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/component_from_func.py +325 -4
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/component_generator.py +93 -4
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +103 -17
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +28 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +7 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +131 -7
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +36 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/pyproject.toml +1 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/api_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/api_schema.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/api_transport.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/args_container.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/cli_options.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/client.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipelines.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/schema_validation.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/OSS-CONVENTIONS.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/PORT-README.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/SKILL.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/auth-wizard.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/builder.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/debugger.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/reporter.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/researcher.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/reviewer.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/agents/scenario-builder.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/data-sources.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/event-log.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/iterating-on-runs.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/knowledge-corpus.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/secrets.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/setup.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-0-initialize.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-1-analyze.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-2-hypothesize.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-3-submit.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-4-monitor.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-5-evaluate.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-6-synthesize.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/step-7-decide.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/skills/tangent/references/tangle-tools.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.7}/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.7
|
|
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>
|
|
@@ -317,7 +317,132 @@ uv run tangle sdk pipeline-runs annotations set RUN_ID key value
|
|
|
317
317
|
uv run tangle sdk pipeline-runs export RUN_ID --output pipeline.yaml
|
|
318
318
|
```
|
|
319
319
|
|
|
320
|
-
|
|
320
|
+
#### Python pipeline authoring
|
|
321
|
+
|
|
322
|
+
Python-authored pipelines live in normal `.py` files and compile with:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml
|
|
326
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml --pipeline pipeline_fn_name
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
A minimal graph uses `@pipeline` for the graph and `@task` for local Python components. `@task` functions are not executed at compile time; the compiler records call sites, emits a sibling `<output>.components.yaml` with `local_from_python` entries, and rewrites task component refs to that sidecar. Hydrate later regenerates the same component YAML from the Python source.
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
from cloud_pipelines import components
|
|
333
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, task
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@task(image="python:3.12")
|
|
337
|
+
def write_greeting(out: components.OutputPath("Text"), who: str, greeting: str = "hello"):
|
|
338
|
+
with open(out, "w") as fh:
|
|
339
|
+
fh.write(f"{greeting} {who}")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@pipeline("Greeting pipeline", output_name="greeting_file")
|
|
343
|
+
def greeting_pipeline(who: In[str], cfg) -> Out[str]:
|
|
344
|
+
greeting = write_greeting(who=who, greeting=cfg.greeting)
|
|
345
|
+
return greeting.out
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`In[T]` parameters become runtime graph inputs. A single `-> Out[T]` return exposes one graph output; use `@pipeline(output_name=...)` to name that output. For multiple outputs, define a frozen dataclass subclass of `Outputs` with `Out[T]` fields and return an instance. A pipeline that accepts a `cfg` parameter reads `config.yaml` (or the path passed via `@pipeline(config="...")`) at compile time, with `--override key=value` values overlaid by the compile command.
|
|
349
|
+
|
|
350
|
+
Task IDs default from the left-hand variable name at the call site, converted to title case. If there is no simple left-hand variable, or if you want a stable explicit label, call `.named("Task Id")` before invoking the task. Use `.bind(...)` to pre-fill task arguments and `.with_annotations({...})` to add per-task annotations.
|
|
351
|
+
|
|
352
|
+
##### Task images, dependencies, and image IDs
|
|
353
|
+
|
|
354
|
+
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`:
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from tangle_cli.python_pipeline import TaskEnv, task
|
|
358
|
+
|
|
359
|
+
EVAL = TaskEnv(image="python:3.12", dependencies_from="pyproject.toml")
|
|
360
|
+
|
|
361
|
+
@task(env=EVAL)
|
|
362
|
+
def score(...):
|
|
363
|
+
...
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Use `@task(image_id="eval-slim")` when source should carry a logical image name instead of a concrete registry reference. Downstream code can register defaults with `register_image_id(...)`, and callers can override at compile time with repeatable `--image ID=REF`:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml \
|
|
370
|
+
--image eval-slim=registry.example/eval-slim@sha256:...
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
An explicit `image="..."` wins over `image_id=...`. If an `image_id` has neither a registered default nor a `--image` override, compile fails.
|
|
374
|
+
|
|
375
|
+
##### Subpipelines and existing components
|
|
376
|
+
|
|
377
|
+
Use `subpipeline(child_pipeline)(...)` to call another Python `@pipeline` as one task in a parent graph. The child compiles to a subgraph sidecar under `<output>.subgraphs/`, and the returned handle exposes the child pipeline's declared outputs.
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, subpipeline
|
|
381
|
+
|
|
382
|
+
@pipeline("Child")
|
|
383
|
+
def child(seed: In[str]) -> Out[str]:
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
@pipeline("Parent")
|
|
387
|
+
def parent(seed: In[str]) -> Out[str]:
|
|
388
|
+
child_result = subpipeline(child).named("Run child")(seed=seed)
|
|
389
|
+
return child_result.wait_for_output
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Use `ref(url=...)`, `ref(name=...)`, or `ref(digest=...)` to call an existing component YAML or published component instead of authoring a local `@task`. Use `@registered(fragment=..., gen_config=...)` for operation wrappers that are already present in an existing `gen_config.yaml`; the compiler rewrites those calls to `resolve://...#fragment` without generating a new sidecar.
|
|
393
|
+
|
|
394
|
+
##### Dynamic arguments and runtime placeholders
|
|
395
|
+
|
|
396
|
+
Task argument values can be literals, graph inputs, task outputs, or supported dynamic data. Use `dynamic_secret("NAME")` to emit a runtime secret reference:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from tangle_cli.python_pipeline import dynamic_secret
|
|
400
|
+
|
|
401
|
+
call_api(api_key=dynamic_secret("OPENAI_API_KEY"))
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Use `raw("...")` only for string values that intentionally contain a `{{name}}` runtime placeholder substituted by the component itself. `raw()` is not a compile-time Jinja escape hatch; `{% ... %}` and `{# ... #}` are rejected.
|
|
405
|
+
|
|
406
|
+
##### Unwrapped dict task inputs
|
|
407
|
+
|
|
408
|
+
Python-authored pipelines can mark one or more `dict[str, T]` task parameters for unwrapping:
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from cloud_pipelines import components
|
|
412
|
+
from tangle_cli.python_pipeline import Out, pipeline, task
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@task(image="python:3.12")
|
|
416
|
+
def make_greeting(name: str) -> str:
|
|
417
|
+
return f"hello {name}"
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@task(image="python:3.12", unwrap="items")
|
|
421
|
+
def join_greetings(out: components.OutputPath("Text"), items: dict[str, str], prefix: str = "joined"):
|
|
422
|
+
with open(out, "w") as fh:
|
|
423
|
+
fh.write(f"{prefix}: " + " | ".join(items[key] for key in sorted(items)))
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@pipeline("Greeting pipeline")
|
|
427
|
+
def greeting_pipeline() -> Out[str]:
|
|
428
|
+
world = make_greeting.named("world_source")(name="world")
|
|
429
|
+
tangle = make_greeting.named("tangle_source")(name="tangle")
|
|
430
|
+
joined = join_greetings.named("join")(
|
|
431
|
+
prefix="demo",
|
|
432
|
+
items={
|
|
433
|
+
"who_1": world.Output,
|
|
434
|
+
"who_2": tangle.Output,
|
|
435
|
+
"literal": "plain value",
|
|
436
|
+
},
|
|
437
|
+
)
|
|
438
|
+
return joined.out
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
`unwrap="items"` tells the compiler that the caller-provided entries in `items` should become explicit component inputs. The call above compiles the consumer task arguments as `items__who_1`, `items__who_2`, and `items__literal`; task-output values remain normal graph edges and literal values remain literals. The generated components sidecar persists the exact flattened schema under `local_from_python.unwrapped_inputs`, including the generated input names and inferred value type. Hydrate passes that schema back into Python component generation so the regenerated component has the same flattened inputs even though hydrate no longer has access to the original Python call-site dict. The generated runtime wrapper then re-wraps those CLI arguments back into the original `items` dict before calling `join_greetings(...)`.
|
|
442
|
+
|
|
443
|
+
Use `unwrap=["items", "metadata"]` to unwrap multiple dict parameters. The caller owns the key names; keys may contain letters, numbers, `_`, and `-`, and become `param__<key>` component inputs. Empty dicts are rejected because they do not define a component interface. If a generated name would collide with a fixed parameter or another generated name, compile fails before writing artifacts. Equivalent key sets are canonicalized for schema hashing, so two call sites with the same keys in different insertion orders dedupe to the same component fragment.
|
|
444
|
+
|
|
445
|
+
#### Pipeline run submission and validation
|
|
321
446
|
|
|
322
447
|
`submit` hydrates refs by default and builds an API submit payload with `root_task.componentRef.spec`. Use `--no-hydrate` to submit the local YAML structure as-is. Use `--dry-run` to print the payload without creating a run.
|
|
323
448
|
|
|
@@ -291,7 +291,132 @@ uv run tangle sdk pipeline-runs annotations set RUN_ID key value
|
|
|
291
291
|
uv run tangle sdk pipeline-runs export RUN_ID --output pipeline.yaml
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
#### Python pipeline authoring
|
|
295
|
+
|
|
296
|
+
Python-authored pipelines live in normal `.py` files and compile with:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml
|
|
300
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml --pipeline pipeline_fn_name
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
A minimal graph uses `@pipeline` for the graph and `@task` for local Python components. `@task` functions are not executed at compile time; the compiler records call sites, emits a sibling `<output>.components.yaml` with `local_from_python` entries, and rewrites task component refs to that sidecar. Hydrate later regenerates the same component YAML from the Python source.
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from cloud_pipelines import components
|
|
307
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, task
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@task(image="python:3.12")
|
|
311
|
+
def write_greeting(out: components.OutputPath("Text"), who: str, greeting: str = "hello"):
|
|
312
|
+
with open(out, "w") as fh:
|
|
313
|
+
fh.write(f"{greeting} {who}")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@pipeline("Greeting pipeline", output_name="greeting_file")
|
|
317
|
+
def greeting_pipeline(who: In[str], cfg) -> Out[str]:
|
|
318
|
+
greeting = write_greeting(who=who, greeting=cfg.greeting)
|
|
319
|
+
return greeting.out
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`In[T]` parameters become runtime graph inputs. A single `-> Out[T]` return exposes one graph output; use `@pipeline(output_name=...)` to name that output. For multiple outputs, define a frozen dataclass subclass of `Outputs` with `Out[T]` fields and return an instance. A pipeline that accepts a `cfg` parameter reads `config.yaml` (or the path passed via `@pipeline(config="...")`) at compile time, with `--override key=value` values overlaid by the compile command.
|
|
323
|
+
|
|
324
|
+
Task IDs default from the left-hand variable name at the call site, converted to title case. If there is no simple left-hand variable, or if you want a stable explicit label, call `.named("Task Id")` before invoking the task. Use `.bind(...)` to pre-fill task arguments and `.with_annotations({...})` to add per-task annotations.
|
|
325
|
+
|
|
326
|
+
##### Task images, dependencies, and image IDs
|
|
327
|
+
|
|
328
|
+
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`:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
from tangle_cli.python_pipeline import TaskEnv, task
|
|
332
|
+
|
|
333
|
+
EVAL = TaskEnv(image="python:3.12", dependencies_from="pyproject.toml")
|
|
334
|
+
|
|
335
|
+
@task(env=EVAL)
|
|
336
|
+
def score(...):
|
|
337
|
+
...
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Use `@task(image_id="eval-slim")` when source should carry a logical image name instead of a concrete registry reference. Downstream code can register defaults with `register_image_id(...)`, and callers can override at compile time with repeatable `--image ID=REF`:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml \
|
|
344
|
+
--image eval-slim=registry.example/eval-slim@sha256:...
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
An explicit `image="..."` wins over `image_id=...`. If an `image_id` has neither a registered default nor a `--image` override, compile fails.
|
|
348
|
+
|
|
349
|
+
##### Subpipelines and existing components
|
|
350
|
+
|
|
351
|
+
Use `subpipeline(child_pipeline)(...)` to call another Python `@pipeline` as one task in a parent graph. The child compiles to a subgraph sidecar under `<output>.subgraphs/`, and the returned handle exposes the child pipeline's declared outputs.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, subpipeline
|
|
355
|
+
|
|
356
|
+
@pipeline("Child")
|
|
357
|
+
def child(seed: In[str]) -> Out[str]:
|
|
358
|
+
...
|
|
359
|
+
|
|
360
|
+
@pipeline("Parent")
|
|
361
|
+
def parent(seed: In[str]) -> Out[str]:
|
|
362
|
+
child_result = subpipeline(child).named("Run child")(seed=seed)
|
|
363
|
+
return child_result.wait_for_output
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Use `ref(url=...)`, `ref(name=...)`, or `ref(digest=...)` to call an existing component YAML or published component instead of authoring a local `@task`. Use `@registered(fragment=..., gen_config=...)` for operation wrappers that are already present in an existing `gen_config.yaml`; the compiler rewrites those calls to `resolve://...#fragment` without generating a new sidecar.
|
|
367
|
+
|
|
368
|
+
##### Dynamic arguments and runtime placeholders
|
|
369
|
+
|
|
370
|
+
Task argument values can be literals, graph inputs, task outputs, or supported dynamic data. Use `dynamic_secret("NAME")` to emit a runtime secret reference:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from tangle_cli.python_pipeline import dynamic_secret
|
|
374
|
+
|
|
375
|
+
call_api(api_key=dynamic_secret("OPENAI_API_KEY"))
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Use `raw("...")` only for string values that intentionally contain a `{{name}}` runtime placeholder substituted by the component itself. `raw()` is not a compile-time Jinja escape hatch; `{% ... %}` and `{# ... #}` are rejected.
|
|
379
|
+
|
|
380
|
+
##### Unwrapped dict task inputs
|
|
381
|
+
|
|
382
|
+
Python-authored pipelines can mark one or more `dict[str, T]` task parameters for unwrapping:
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
from cloud_pipelines import components
|
|
386
|
+
from tangle_cli.python_pipeline import Out, pipeline, task
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@task(image="python:3.12")
|
|
390
|
+
def make_greeting(name: str) -> str:
|
|
391
|
+
return f"hello {name}"
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@task(image="python:3.12", unwrap="items")
|
|
395
|
+
def join_greetings(out: components.OutputPath("Text"), items: dict[str, str], prefix: str = "joined"):
|
|
396
|
+
with open(out, "w") as fh:
|
|
397
|
+
fh.write(f"{prefix}: " + " | ".join(items[key] for key in sorted(items)))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@pipeline("Greeting pipeline")
|
|
401
|
+
def greeting_pipeline() -> Out[str]:
|
|
402
|
+
world = make_greeting.named("world_source")(name="world")
|
|
403
|
+
tangle = make_greeting.named("tangle_source")(name="tangle")
|
|
404
|
+
joined = join_greetings.named("join")(
|
|
405
|
+
prefix="demo",
|
|
406
|
+
items={
|
|
407
|
+
"who_1": world.Output,
|
|
408
|
+
"who_2": tangle.Output,
|
|
409
|
+
"literal": "plain value",
|
|
410
|
+
},
|
|
411
|
+
)
|
|
412
|
+
return joined.out
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`unwrap="items"` tells the compiler that the caller-provided entries in `items` should become explicit component inputs. The call above compiles the consumer task arguments as `items__who_1`, `items__who_2`, and `items__literal`; task-output values remain normal graph edges and literal values remain literals. The generated components sidecar persists the exact flattened schema under `local_from_python.unwrapped_inputs`, including the generated input names and inferred value type. Hydrate passes that schema back into Python component generation so the regenerated component has the same flattened inputs even though hydrate no longer has access to the original Python call-site dict. The generated runtime wrapper then re-wraps those CLI arguments back into the original `items` dict before calling `join_greetings(...)`.
|
|
416
|
+
|
|
417
|
+
Use `unwrap=["items", "metadata"]` to unwrap multiple dict parameters. The caller owns the key names; keys may contain letters, numbers, `_`, and `-`, and become `param__<key>` component inputs. Empty dicts are rejected because they do not define a component interface. If a generated name would collide with a fixed parameter or another generated name, compile fails before writing artifacts. Equivalent key sets are canonicalized for schema hashing, so two call sites with the same keys in different insertion orders dedupe to the same component fragment.
|
|
418
|
+
|
|
419
|
+
#### Pipeline run submission and validation
|
|
295
420
|
|
|
296
421
|
`submit` hydrates refs by default and builds an API submit payload with `root_task.componentRef.spec`. Use `--no-hydrate` to submit the local YAML structure as-is. Use `--dry-run` to print the payload without creating a run.
|
|
297
422
|
|
|
@@ -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.7"
|
|
18
18
|
|
|
19
19
|
__all__ = ["TangleDynamicDiscoveryClient", "__version__"]
|