tangle-cli 0.1.6__tar.gz → 0.1.9__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.9}/PKG-INFO +217 -2
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/README.md +216 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/api_cli.py +14 -2
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/api_schema.py +8 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/api_transport.py +126 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +1 -0
- tangle_cli-0.1.9/packages/tangle-cli/src/tangle_cli/cli.py +118 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/cli_options.py +24 -0
- tangle_cli-0.1.9/packages/tangle-cli/src/tangle_cli/client.py +1153 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_from_func.py +325 -4
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_generator.py +93 -4
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +15 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +103 -17
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +29 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +576 -27
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +134 -4
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +28 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipelines.py +5 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +1 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +8 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +36 -3
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +17 -3
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +150 -13
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +18 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +36 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schema_validation.py +18 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +10 -1
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -6
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +5 -2
- tangle_cli-0.1.9/pyproject.toml +88 -0
- tangle_cli-0.1.6/pyproject.toml → tangle_cli-0.1.9/pyproject.toml.orig +1 -1
- tangle_cli-0.1.6/packages/tangle-cli/src/tangle_cli/cli.py +0 -57
- tangle_cli-0.1.6/packages/tangle-cli/src/tangle_cli/client.py +0 -684
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/args_container.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/OSS-CONVENTIONS.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/PORT-README.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/SKILL.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/auth-wizard.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/builder.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/debugger.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/reporter.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/researcher.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/reviewer.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/agents/scenario-builder.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/data-sources.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/event-log.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/iterating-on-runs.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/knowledge-corpus.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/secrets.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/setup.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-0-initialize.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-1-analyze.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-2-hypothesize.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-3-submit.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-4-monitor.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-5-evaluate.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-6-synthesize.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/step-7-decide.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/skills/tangent/references/tangle-tools.md +0 -0
- {tangle_cli-0.1.6 → tangle_cli-0.1.9}/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.9
|
|
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>
|
|
@@ -86,6 +86,57 @@ API-backed commands commonly accept these options. Explicit CLI options win over
|
|
|
86
86
|
| `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`. |
|
|
87
87
|
| `--log-type` | SDK progress logs: `console`, `none`, or `file`. Logs go to stderr or a temp log file so structured stdout stays parseable. |
|
|
88
88
|
| `TANGLE_VERBOSE=1` | Redacted HTTP request/response diagnostics only. This is separate from normal progress logging. |
|
|
89
|
+
| `--ca-bundle` | Global CLI flag: path to a PEM CA bundle used as the TLS trust store for every transport. Overrides `TANGLE_API_CA_BUNDLE`. Place before the subcommand. |
|
|
90
|
+
| `--verify-tls` / `--no-verify-tls` | Global CLI flag: enable or disable TLS verification for every transport. Overrides `TANGLE_API_VERIFY_TLS`. `--no-verify-tls` is local-development only. Place before the subcommand. |
|
|
91
|
+
| `TANGLE_API_CA_BUNDLE` | Path to a PEM CA bundle used to verify TLS for every transport. Use this to trust a private or corporate CA without disabling verification. |
|
|
92
|
+
| `TANGLE_API_VERIFY_TLS` | TLS verification toggle. Values `0`, `false`, or `no` (case/space-insensitive) disable verification; any other nonempty value keeps it on. |
|
|
93
|
+
|
|
94
|
+
### TLS verification
|
|
95
|
+
|
|
96
|
+
TLS certificate verification is enabled by default for all HTTP transports (schema
|
|
97
|
+
fetches, `tangle api` calls, and the programmatic clients). The effective setting is
|
|
98
|
+
resolved with the following precedence, highest to lowest:
|
|
99
|
+
|
|
100
|
+
1. An explicit `verify=` argument to the Python clients (a `bool` or a path to a CA bundle).
|
|
101
|
+
2. The global CLI flags `--ca-bundle` / `--verify-tls` / `--no-verify-tls`.
|
|
102
|
+
3. `TANGLE_API_CA_BUNDLE` — verify against the given CA bundle.
|
|
103
|
+
4. `TANGLE_API_VERIFY_TLS` — enable or disable verification.
|
|
104
|
+
5. The secure default: verification enabled against the system trust store.
|
|
105
|
+
|
|
106
|
+
The global CLI flags are true root options that apply to every command — the static
|
|
107
|
+
`tangle sdk ...` clients, the dynamic `tangle api ...` commands, and `tangle api refresh`.
|
|
108
|
+
Place them **before** the subcommand, for example `tangle --ca-bundle ca.pem api ...` or
|
|
109
|
+
`tangle --no-verify-tls sdk ...`. They are honored even by the dynamic OpenAPI schema
|
|
110
|
+
discovery that runs before command dispatch. A defaulted (absent) flag does not override
|
|
111
|
+
the environment: when a flag is not supplied, the `TANGLE_API_*` variables and the standard
|
|
112
|
+
`REQUESTS_CA_BUNDLE` / `CURL_CA_BUNDLE` handling still apply. `--ca-bundle` combined with an
|
|
113
|
+
explicit `--no-verify-tls` is contradictory and fails fast before any request; `--ca-bundle`
|
|
114
|
+
with `--verify-tls` is redundant but accepted.
|
|
115
|
+
|
|
116
|
+
If both env vars are set, `TANGLE_API_CA_BUNDLE` wins and TLS stays verified against the
|
|
117
|
+
bundle. Empty values are treated as unset. A `--ca-bundle` or `TANGLE_API_CA_BUNDLE` that
|
|
118
|
+
does not point to an existing file fails fast with an actionable error before any request is
|
|
119
|
+
made. When no Tangle-specific setting is provided, the standard `REQUESTS_CA_BUNDLE` /
|
|
120
|
+
`CURL_CA_BUNDLE` handling and any caller-supplied `requests.Session.verify` are left
|
|
121
|
+
untouched.
|
|
122
|
+
|
|
123
|
+
For a private CA, prefer `--ca-bundle` / `TANGLE_API_CA_BUNDLE` over disabling verification:
|
|
124
|
+
it keeps certificates verified against a trusted root. `--no-verify-tls` /
|
|
125
|
+
`TANGLE_API_VERIFY_TLS=0` disables verification entirely and is intended for local
|
|
126
|
+
development only — never use it against production endpoints.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Trust a private CA with the global flag (recommended for internal/self-hosted APIs)
|
|
130
|
+
uv run tangle --ca-bundle /etc/ssl/private-ca.pem \
|
|
131
|
+
api refresh --base-url https://internal.example
|
|
132
|
+
|
|
133
|
+
# Or via environment variable
|
|
134
|
+
TANGLE_API_CA_BUNDLE=/etc/ssl/private-ca.pem \
|
|
135
|
+
uv run tangle api refresh --base-url https://internal.example
|
|
136
|
+
|
|
137
|
+
# Disable verification (local development only)
|
|
138
|
+
uv run tangle --no-verify-tls api refresh --base-url https://localhost:8443
|
|
139
|
+
```
|
|
89
140
|
|
|
90
141
|
Examples for protected APIs:
|
|
91
142
|
|
|
@@ -317,7 +368,171 @@ uv run tangle sdk pipeline-runs annotations set RUN_ID key value
|
|
|
317
368
|
uv run tangle sdk pipeline-runs export RUN_ID --output pipeline.yaml
|
|
318
369
|
```
|
|
319
370
|
|
|
320
|
-
|
|
371
|
+
#### Python pipeline authoring
|
|
372
|
+
|
|
373
|
+
Python-authored pipelines live in normal `.py` files and compile with:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml
|
|
377
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml --pipeline pipeline_fn_name
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
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.
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
from cloud_pipelines import components
|
|
384
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, task
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@task(image="python:3.12")
|
|
388
|
+
def write_greeting(out: components.OutputPath("Text"), who: str, greeting: str = "hello"):
|
|
389
|
+
with open(out, "w") as fh:
|
|
390
|
+
fh.write(f"{greeting} {who}")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@pipeline("Greeting pipeline", output_name="greeting_file")
|
|
394
|
+
def greeting_pipeline(who: In[str], cfg) -> Out[str]:
|
|
395
|
+
greeting = write_greeting(who=who, greeting=cfg.greeting)
|
|
396
|
+
return greeting.out
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`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.
|
|
400
|
+
|
|
401
|
+
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.
|
|
402
|
+
|
|
403
|
+
##### Conditional task execution
|
|
404
|
+
|
|
405
|
+
Pipeline inputs used as conditions are ordinary `In[str]` values; there is no special conditional input annotation. Pass the value through the reserved task-call metadata keyword `is_enabled=`:
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
@pipeline("Conditional greeting")
|
|
409
|
+
def conditional_greeting(enabled: In[str]) -> Out[str]:
|
|
410
|
+
greeting = write_greeting(who="world", is_enabled=enabled)
|
|
411
|
+
return greeting.out
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This emits the canonical task field rather than a component argument:
|
|
415
|
+
|
|
416
|
+
```yaml
|
|
417
|
+
isEnabled:
|
|
418
|
+
graphInput:
|
|
419
|
+
inputName: enabled
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`is_enabled=` supports Python booleans (serialized as lowercase `"true"` / `"false"` strings), string constants, `In[...]` graph inputs, and previous task outputs such as `is_enabled=gate.Output`. It is container-component task metadata; component function parameters are not implicitly conditions.
|
|
423
|
+
|
|
424
|
+
If a component itself declares an input named `is_enabled`, bind that component argument separately while using the call-site keyword for task metadata:
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
@task(image="python:3.12")
|
|
428
|
+
def work(is_enabled: str, message: str) -> str:
|
|
429
|
+
return message
|
|
430
|
+
|
|
431
|
+
@pipeline("Input-name collision")
|
|
432
|
+
def collision(runtime_condition: In[str]) -> Out[str]:
|
|
433
|
+
result = work.bind(is_enabled="component-input-value")(
|
|
434
|
+
message="hello",
|
|
435
|
+
is_enabled=runtime_condition,
|
|
436
|
+
)
|
|
437
|
+
return result.Output
|
|
438
|
+
```
|
|
439
|
+
|
|
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
|
+
|
|
442
|
+
##### Task images, dependencies, and image IDs
|
|
443
|
+
|
|
444
|
+
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`:
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
from tangle_cli.python_pipeline import TaskEnv, task
|
|
448
|
+
|
|
449
|
+
EVAL = TaskEnv(image="python:3.12", dependencies_from="pyproject.toml")
|
|
450
|
+
|
|
451
|
+
@task(env=EVAL)
|
|
452
|
+
def score(...):
|
|
453
|
+
...
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
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`:
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml \
|
|
460
|
+
--image eval-slim=registry.example/eval-slim@sha256:...
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
An explicit `image="..."` wins over `image_id=...`. If an `image_id` has neither a registered default nor a `--image` override, compile fails.
|
|
464
|
+
|
|
465
|
+
##### Subpipelines and existing components
|
|
466
|
+
|
|
467
|
+
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.
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, subpipeline
|
|
471
|
+
|
|
472
|
+
@pipeline("Child")
|
|
473
|
+
def child(seed: In[str]) -> Out[str]:
|
|
474
|
+
...
|
|
475
|
+
|
|
476
|
+
@pipeline("Parent")
|
|
477
|
+
def parent(seed: In[str]) -> Out[str]:
|
|
478
|
+
child_result = subpipeline(child).named("Run child")(seed=seed)
|
|
479
|
+
return child_result.wait_for_output
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
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.
|
|
483
|
+
|
|
484
|
+
##### Dynamic arguments and runtime placeholders
|
|
485
|
+
|
|
486
|
+
Task argument values can be literals, graph inputs, task outputs, or supported dynamic data. Use `dynamic_secret("NAME")` to emit a runtime secret reference:
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
from tangle_cli.python_pipeline import dynamic_secret
|
|
490
|
+
|
|
491
|
+
call_api(api_key=dynamic_secret("OPENAI_API_KEY"))
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
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.
|
|
495
|
+
|
|
496
|
+
##### Unwrapped dict task inputs
|
|
497
|
+
|
|
498
|
+
Python-authored pipelines can mark one or more `dict[str, T]` task parameters for unwrapping:
|
|
499
|
+
|
|
500
|
+
```python
|
|
501
|
+
from cloud_pipelines import components
|
|
502
|
+
from tangle_cli.python_pipeline import Out, pipeline, task
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@task(image="python:3.12")
|
|
506
|
+
def make_greeting(name: str) -> str:
|
|
507
|
+
return f"hello {name}"
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@task(image="python:3.12", unwrap="items")
|
|
511
|
+
def join_greetings(out: components.OutputPath("Text"), items: dict[str, str], prefix: str = "joined"):
|
|
512
|
+
with open(out, "w") as fh:
|
|
513
|
+
fh.write(f"{prefix}: " + " | ".join(items[key] for key in sorted(items)))
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@pipeline("Greeting pipeline")
|
|
517
|
+
def greeting_pipeline() -> Out[str]:
|
|
518
|
+
world = make_greeting.named("world_source")(name="world")
|
|
519
|
+
tangle = make_greeting.named("tangle_source")(name="tangle")
|
|
520
|
+
joined = join_greetings.named("join")(
|
|
521
|
+
prefix="demo",
|
|
522
|
+
items={
|
|
523
|
+
"who_1": world.Output,
|
|
524
|
+
"who_2": tangle.Output,
|
|
525
|
+
"literal": "plain value",
|
|
526
|
+
},
|
|
527
|
+
)
|
|
528
|
+
return joined.out
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`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(...)`.
|
|
532
|
+
|
|
533
|
+
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.
|
|
534
|
+
|
|
535
|
+
#### Pipeline run submission and validation
|
|
321
536
|
|
|
322
537
|
`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
538
|
|
|
@@ -60,6 +60,57 @@ API-backed commands commonly accept these options. Explicit CLI options win over
|
|
|
60
60
|
| `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`. |
|
|
61
61
|
| `--log-type` | SDK progress logs: `console`, `none`, or `file`. Logs go to stderr or a temp log file so structured stdout stays parseable. |
|
|
62
62
|
| `TANGLE_VERBOSE=1` | Redacted HTTP request/response diagnostics only. This is separate from normal progress logging. |
|
|
63
|
+
| `--ca-bundle` | Global CLI flag: path to a PEM CA bundle used as the TLS trust store for every transport. Overrides `TANGLE_API_CA_BUNDLE`. Place before the subcommand. |
|
|
64
|
+
| `--verify-tls` / `--no-verify-tls` | Global CLI flag: enable or disable TLS verification for every transport. Overrides `TANGLE_API_VERIFY_TLS`. `--no-verify-tls` is local-development only. Place before the subcommand. |
|
|
65
|
+
| `TANGLE_API_CA_BUNDLE` | Path to a PEM CA bundle used to verify TLS for every transport. Use this to trust a private or corporate CA without disabling verification. |
|
|
66
|
+
| `TANGLE_API_VERIFY_TLS` | TLS verification toggle. Values `0`, `false`, or `no` (case/space-insensitive) disable verification; any other nonempty value keeps it on. |
|
|
67
|
+
|
|
68
|
+
### TLS verification
|
|
69
|
+
|
|
70
|
+
TLS certificate verification is enabled by default for all HTTP transports (schema
|
|
71
|
+
fetches, `tangle api` calls, and the programmatic clients). The effective setting is
|
|
72
|
+
resolved with the following precedence, highest to lowest:
|
|
73
|
+
|
|
74
|
+
1. An explicit `verify=` argument to the Python clients (a `bool` or a path to a CA bundle).
|
|
75
|
+
2. The global CLI flags `--ca-bundle` / `--verify-tls` / `--no-verify-tls`.
|
|
76
|
+
3. `TANGLE_API_CA_BUNDLE` — verify against the given CA bundle.
|
|
77
|
+
4. `TANGLE_API_VERIFY_TLS` — enable or disable verification.
|
|
78
|
+
5. The secure default: verification enabled against the system trust store.
|
|
79
|
+
|
|
80
|
+
The global CLI flags are true root options that apply to every command — the static
|
|
81
|
+
`tangle sdk ...` clients, the dynamic `tangle api ...` commands, and `tangle api refresh`.
|
|
82
|
+
Place them **before** the subcommand, for example `tangle --ca-bundle ca.pem api ...` or
|
|
83
|
+
`tangle --no-verify-tls sdk ...`. They are honored even by the dynamic OpenAPI schema
|
|
84
|
+
discovery that runs before command dispatch. A defaulted (absent) flag does not override
|
|
85
|
+
the environment: when a flag is not supplied, the `TANGLE_API_*` variables and the standard
|
|
86
|
+
`REQUESTS_CA_BUNDLE` / `CURL_CA_BUNDLE` handling still apply. `--ca-bundle` combined with an
|
|
87
|
+
explicit `--no-verify-tls` is contradictory and fails fast before any request; `--ca-bundle`
|
|
88
|
+
with `--verify-tls` is redundant but accepted.
|
|
89
|
+
|
|
90
|
+
If both env vars are set, `TANGLE_API_CA_BUNDLE` wins and TLS stays verified against the
|
|
91
|
+
bundle. Empty values are treated as unset. A `--ca-bundle` or `TANGLE_API_CA_BUNDLE` that
|
|
92
|
+
does not point to an existing file fails fast with an actionable error before any request is
|
|
93
|
+
made. When no Tangle-specific setting is provided, the standard `REQUESTS_CA_BUNDLE` /
|
|
94
|
+
`CURL_CA_BUNDLE` handling and any caller-supplied `requests.Session.verify` are left
|
|
95
|
+
untouched.
|
|
96
|
+
|
|
97
|
+
For a private CA, prefer `--ca-bundle` / `TANGLE_API_CA_BUNDLE` over disabling verification:
|
|
98
|
+
it keeps certificates verified against a trusted root. `--no-verify-tls` /
|
|
99
|
+
`TANGLE_API_VERIFY_TLS=0` disables verification entirely and is intended for local
|
|
100
|
+
development only — never use it against production endpoints.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Trust a private CA with the global flag (recommended for internal/self-hosted APIs)
|
|
104
|
+
uv run tangle --ca-bundle /etc/ssl/private-ca.pem \
|
|
105
|
+
api refresh --base-url https://internal.example
|
|
106
|
+
|
|
107
|
+
# Or via environment variable
|
|
108
|
+
TANGLE_API_CA_BUNDLE=/etc/ssl/private-ca.pem \
|
|
109
|
+
uv run tangle api refresh --base-url https://internal.example
|
|
110
|
+
|
|
111
|
+
# Disable verification (local development only)
|
|
112
|
+
uv run tangle --no-verify-tls api refresh --base-url https://localhost:8443
|
|
113
|
+
```
|
|
63
114
|
|
|
64
115
|
Examples for protected APIs:
|
|
65
116
|
|
|
@@ -291,7 +342,171 @@ uv run tangle sdk pipeline-runs annotations set RUN_ID key value
|
|
|
291
342
|
uv run tangle sdk pipeline-runs export RUN_ID --output pipeline.yaml
|
|
292
343
|
```
|
|
293
344
|
|
|
294
|
-
|
|
345
|
+
#### Python pipeline authoring
|
|
346
|
+
|
|
347
|
+
Python-authored pipelines live in normal `.py` files and compile with:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml
|
|
351
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml --pipeline pipeline_fn_name
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
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.
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from cloud_pipelines import components
|
|
358
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, task
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@task(image="python:3.12")
|
|
362
|
+
def write_greeting(out: components.OutputPath("Text"), who: str, greeting: str = "hello"):
|
|
363
|
+
with open(out, "w") as fh:
|
|
364
|
+
fh.write(f"{greeting} {who}")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@pipeline("Greeting pipeline", output_name="greeting_file")
|
|
368
|
+
def greeting_pipeline(who: In[str], cfg) -> Out[str]:
|
|
369
|
+
greeting = write_greeting(who=who, greeting=cfg.greeting)
|
|
370
|
+
return greeting.out
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`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.
|
|
374
|
+
|
|
375
|
+
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.
|
|
376
|
+
|
|
377
|
+
##### Conditional task execution
|
|
378
|
+
|
|
379
|
+
Pipeline inputs used as conditions are ordinary `In[str]` values; there is no special conditional input annotation. Pass the value through the reserved task-call metadata keyword `is_enabled=`:
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
@pipeline("Conditional greeting")
|
|
383
|
+
def conditional_greeting(enabled: In[str]) -> Out[str]:
|
|
384
|
+
greeting = write_greeting(who="world", is_enabled=enabled)
|
|
385
|
+
return greeting.out
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
This emits the canonical task field rather than a component argument:
|
|
389
|
+
|
|
390
|
+
```yaml
|
|
391
|
+
isEnabled:
|
|
392
|
+
graphInput:
|
|
393
|
+
inputName: enabled
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`is_enabled=` supports Python booleans (serialized as lowercase `"true"` / `"false"` strings), string constants, `In[...]` graph inputs, and previous task outputs such as `is_enabled=gate.Output`. It is container-component task metadata; component function parameters are not implicitly conditions.
|
|
397
|
+
|
|
398
|
+
If a component itself declares an input named `is_enabled`, bind that component argument separately while using the call-site keyword for task metadata:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
@task(image="python:3.12")
|
|
402
|
+
def work(is_enabled: str, message: str) -> str:
|
|
403
|
+
return message
|
|
404
|
+
|
|
405
|
+
@pipeline("Input-name collision")
|
|
406
|
+
def collision(runtime_condition: In[str]) -> Out[str]:
|
|
407
|
+
result = work.bind(is_enabled="component-input-value")(
|
|
408
|
+
message="hello",
|
|
409
|
+
is_enabled=runtime_condition,
|
|
410
|
+
)
|
|
411
|
+
return result.Output
|
|
412
|
+
```
|
|
413
|
+
|
|
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
|
+
|
|
416
|
+
##### Task images, dependencies, and image IDs
|
|
417
|
+
|
|
418
|
+
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`:
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
from tangle_cli.python_pipeline import TaskEnv, task
|
|
422
|
+
|
|
423
|
+
EVAL = TaskEnv(image="python:3.12", dependencies_from="pyproject.toml")
|
|
424
|
+
|
|
425
|
+
@task(env=EVAL)
|
|
426
|
+
def score(...):
|
|
427
|
+
...
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
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`:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml \
|
|
434
|
+
--image eval-slim=registry.example/eval-slim@sha256:...
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
An explicit `image="..."` wins over `image_id=...`. If an `image_id` has neither a registered default nor a `--image` override, compile fails.
|
|
438
|
+
|
|
439
|
+
##### Subpipelines and existing components
|
|
440
|
+
|
|
441
|
+
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.
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
from tangle_cli.python_pipeline import In, Out, pipeline, subpipeline
|
|
445
|
+
|
|
446
|
+
@pipeline("Child")
|
|
447
|
+
def child(seed: In[str]) -> Out[str]:
|
|
448
|
+
...
|
|
449
|
+
|
|
450
|
+
@pipeline("Parent")
|
|
451
|
+
def parent(seed: In[str]) -> Out[str]:
|
|
452
|
+
child_result = subpipeline(child).named("Run child")(seed=seed)
|
|
453
|
+
return child_result.wait_for_output
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
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.
|
|
457
|
+
|
|
458
|
+
##### Dynamic arguments and runtime placeholders
|
|
459
|
+
|
|
460
|
+
Task argument values can be literals, graph inputs, task outputs, or supported dynamic data. Use `dynamic_secret("NAME")` to emit a runtime secret reference:
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
from tangle_cli.python_pipeline import dynamic_secret
|
|
464
|
+
|
|
465
|
+
call_api(api_key=dynamic_secret("OPENAI_API_KEY"))
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
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.
|
|
469
|
+
|
|
470
|
+
##### Unwrapped dict task inputs
|
|
471
|
+
|
|
472
|
+
Python-authored pipelines can mark one or more `dict[str, T]` task parameters for unwrapping:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
from cloud_pipelines import components
|
|
476
|
+
from tangle_cli.python_pipeline import Out, pipeline, task
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@task(image="python:3.12")
|
|
480
|
+
def make_greeting(name: str) -> str:
|
|
481
|
+
return f"hello {name}"
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@task(image="python:3.12", unwrap="items")
|
|
485
|
+
def join_greetings(out: components.OutputPath("Text"), items: dict[str, str], prefix: str = "joined"):
|
|
486
|
+
with open(out, "w") as fh:
|
|
487
|
+
fh.write(f"{prefix}: " + " | ".join(items[key] for key in sorted(items)))
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@pipeline("Greeting pipeline")
|
|
491
|
+
def greeting_pipeline() -> Out[str]:
|
|
492
|
+
world = make_greeting.named("world_source")(name="world")
|
|
493
|
+
tangle = make_greeting.named("tangle_source")(name="tangle")
|
|
494
|
+
joined = join_greetings.named("join")(
|
|
495
|
+
prefix="demo",
|
|
496
|
+
items={
|
|
497
|
+
"who_1": world.Output,
|
|
498
|
+
"who_2": tangle.Output,
|
|
499
|
+
"literal": "plain value",
|
|
500
|
+
},
|
|
501
|
+
)
|
|
502
|
+
return joined.out
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
`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(...)`.
|
|
506
|
+
|
|
507
|
+
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.
|
|
508
|
+
|
|
509
|
+
#### Pipeline run submission and validation
|
|
295
510
|
|
|
296
511
|
`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
512
|
|
|
@@ -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.9"
|
|
18
18
|
|
|
19
19
|
__all__ = ["TangleDynamicDiscoveryClient", "__version__"]
|
|
@@ -640,10 +640,22 @@ def _argv_dispatches_dynamic_command(argv: list[str]) -> bool:
|
|
|
640
640
|
|
|
641
641
|
|
|
642
642
|
def _api_argv_tail(argv: list[str]) -> list[str] | None:
|
|
643
|
-
"""Return args after the root `api` command, or None for non-API invocations.
|
|
643
|
+
"""Return args after the root `api` command, or None for non-API invocations.
|
|
644
|
+
|
|
645
|
+
Global TLS flags (``--ca-bundle``/``--verify-tls``/``--no-verify-tls``) may
|
|
646
|
+
precede the subcommand, so they are skipped before locating `api`.
|
|
647
|
+
"""
|
|
644
648
|
|
|
645
649
|
args = list(argv[1:])
|
|
646
|
-
|
|
650
|
+
index = 0
|
|
651
|
+
while index < len(args):
|
|
652
|
+
arg = args[index]
|
|
653
|
+
if arg == "--ca-bundle" and index + 1 < len(args):
|
|
654
|
+
index += 2
|
|
655
|
+
continue
|
|
656
|
+
if arg.startswith("--ca-bundle=") or arg in {"--verify-tls", "--no-verify-tls"}:
|
|
657
|
+
index += 1
|
|
658
|
+
continue
|
|
647
659
|
if arg == "--":
|
|
648
660
|
if index + 1 < len(args) and args[index + 1] == "api":
|
|
649
661
|
return args[index + 2 :]
|
|
@@ -18,7 +18,9 @@ from .api_transport import (
|
|
|
18
18
|
_normalize_base_url,
|
|
19
19
|
_openapi_url,
|
|
20
20
|
_request_headers,
|
|
21
|
+
_VERIFY_UNSET,
|
|
21
22
|
default_base_url,
|
|
23
|
+
httpx_verify,
|
|
22
24
|
)
|
|
23
25
|
|
|
24
26
|
SUPPORTED_METHODS = {"get", "post", "put", "patch", "delete"}
|
|
@@ -122,6 +124,7 @@ def fetch_schema(
|
|
|
122
124
|
auth_header: str | None = None,
|
|
123
125
|
headers: dict[str, str] | None = None,
|
|
124
126
|
include_env_credentials: bool = True,
|
|
127
|
+
verify: Any = _VERIFY_UNSET,
|
|
125
128
|
) -> dict[str, Any]:
|
|
126
129
|
"""Fetch ``/openapi.json``, applying bearer and custom auth headers."""
|
|
127
130
|
|
|
@@ -136,6 +139,7 @@ def fetch_schema(
|
|
|
136
139
|
include_env_credentials=include_env_credentials,
|
|
137
140
|
),
|
|
138
141
|
timeout=DEFAULT_TIMEOUT_SECONDS,
|
|
142
|
+
verify=httpx_verify(verify),
|
|
139
143
|
)
|
|
140
144
|
response.raise_for_status()
|
|
141
145
|
payload = response.text
|
|
@@ -152,6 +156,7 @@ def refresh_schema(
|
|
|
152
156
|
auth_header: str | None = None,
|
|
153
157
|
headers: dict[str, str] | None = None,
|
|
154
158
|
include_env_credentials: bool = True,
|
|
159
|
+
verify: Any = _VERIFY_UNSET,
|
|
155
160
|
) -> tuple[dict[str, Any], Path]:
|
|
156
161
|
"""Fetch and cache the latest schema for a backend."""
|
|
157
162
|
|
|
@@ -163,6 +168,7 @@ def refresh_schema(
|
|
|
163
168
|
auth_header,
|
|
164
169
|
headers,
|
|
165
170
|
include_env_credentials=include_env_credentials,
|
|
171
|
+
verify=verify,
|
|
166
172
|
)
|
|
167
173
|
path = write_cached_schema(schema, base_url)
|
|
168
174
|
return schema, path
|
|
@@ -175,6 +181,7 @@ def load_or_fetch_schema(
|
|
|
175
181
|
auth_header: str | None = None,
|
|
176
182
|
headers: dict[str, str] | None = None,
|
|
177
183
|
include_env_credentials: bool = True,
|
|
184
|
+
verify: Any = _VERIFY_UNSET,
|
|
178
185
|
) -> dict[str, Any]:
|
|
179
186
|
"""Use a cached schema when available, otherwise fetch once and cache it."""
|
|
180
187
|
|
|
@@ -188,6 +195,7 @@ def load_or_fetch_schema(
|
|
|
188
195
|
auth_header,
|
|
189
196
|
headers,
|
|
190
197
|
include_env_credentials=include_env_credentials,
|
|
198
|
+
verify=verify,
|
|
191
199
|
)
|
|
192
200
|
return schema
|
|
193
201
|
|