tangle-cli 0.1.7__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.
Files changed (103) hide show
  1. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/PKG-INFO +91 -1
  2. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/README.md +90 -0
  3. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
  4. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/api_cli.py +14 -2
  5. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/api_schema.py +8 -0
  6. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/api_transport.py +126 -0
  7. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +1 -0
  8. tangle_cli-0.1.9/packages/tangle-cli/src/tangle_cli/cli.py +118 -0
  9. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/cli_options.py +24 -0
  10. tangle_cli-0.1.9/packages/tangle-cli/src/tangle_cli/client.py +1153 -0
  11. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +15 -0
  12. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +1 -0
  13. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +576 -27
  14. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +134 -4
  15. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +28 -0
  16. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipelines.py +5 -0
  17. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +1 -0
  18. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +8 -1
  19. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +36 -3
  20. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +10 -3
  21. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +19 -6
  22. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +18 -1
  23. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schema_validation.py +18 -0
  24. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +10 -1
  25. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -6
  26. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +5 -2
  27. tangle_cli-0.1.9/pyproject.toml +88 -0
  28. tangle_cli-0.1.7/pyproject.toml → tangle_cli-0.1.9/pyproject.toml.orig +1 -1
  29. tangle_cli-0.1.7/packages/tangle-cli/src/tangle_cli/cli.py +0 -57
  30. tangle_cli-0.1.7/packages/tangle-cli/src/tangle_cli/client.py +0 -684
  31. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/args_container.py +0 -0
  32. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
  33. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
  34. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_from_func.py +0 -0
  35. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_generator.py +0 -0
  36. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
  37. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
  38. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
  39. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
  40. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
  41. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
  42. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
  43. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
  44. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
  45. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
  46. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +0 -0
  47. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
  48. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +0 -0
  49. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
  50. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
  51. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
  52. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
  53. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
  54. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
  55. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
  56. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
  57. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
  58. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
  59. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
  60. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
  61. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
  62. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
  63. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
  64. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
  65. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
  66. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +0 -0
  67. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
  68. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
  69. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
  70. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
  71. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
  72. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
  73. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
  74. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
  75. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/OSS-CONVENTIONS.md +0 -0
  76. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/PORT-README.md +0 -0
  77. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/SKILL.md +0 -0
  78. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/auth-wizard.md +0 -0
  79. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/builder.md +0 -0
  80. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/debugger.md +0 -0
  81. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/reporter.md +0 -0
  82. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/researcher.md +0 -0
  83. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/reviewer.md +0 -0
  84. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/agents/scenario-builder.md +0 -0
  85. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/data-sources.md +0 -0
  86. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/event-log.md +0 -0
  87. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
  88. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
  89. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
  90. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/iterating-on-runs.md +0 -0
  91. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/knowledge-corpus.md +0 -0
  92. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/secrets.md +0 -0
  93. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/setup.md +0 -0
  94. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-0-initialize.md +0 -0
  95. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-1-analyze.md +0 -0
  96. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-2-hypothesize.md +0 -0
  97. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-3-submit.md +0 -0
  98. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-4-monitor.md +0 -0
  99. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-5-evaluate.md +0 -0
  100. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-6-synthesize.md +0 -0
  101. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/step-7-decide.md +0 -0
  102. {tangle_cli-0.1.7 → tangle_cli-0.1.9}/skills/tangent/references/tangle-tools.md +0 -0
  103. {tangle_cli-0.1.7 → 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.7
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
 
@@ -349,6 +400,45 @@ def greeting_pipeline(who: In[str], cfg) -> Out[str]:
349
400
 
350
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.
351
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
+
352
442
  ##### Task images, dependencies, and image IDs
353
443
 
354
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`:
@@ -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
 
@@ -323,6 +374,45 @@ def greeting_pipeline(who: In[str], cfg) -> Out[str]:
323
374
 
324
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.
325
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
+
326
416
  ##### Task images, dependencies, and image IDs
327
417
 
328
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`:
@@ -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.7"
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
- for index, arg in enumerate(args):
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
 
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import os
7
7
  import re
8
+ import ssl
8
9
  import sys
9
10
  import urllib.parse
10
11
  from pathlib import Path
@@ -16,6 +17,20 @@ DEFAULT_API_URL = "http://localhost:8000"
16
17
  DEFAULT_TIMEOUT_SECONDS = 30.0
17
18
  _HEADER_NAME_RE = re.compile(r"^[!#$%&'*+.^_`|~0-9A-Za-z-]+$")
18
19
  _MISSING = object()
20
+
21
+ # Canonical resolved TLS verification value: ``True``/``False`` or a CA bundle
22
+ # path. Both requests and httpx transports adapt this single contract.
23
+ VerifyValue = bool | str
24
+ VerifyArgument = bool | str | os.PathLike[str] | None
25
+ _VERIFY_UNSET = object()
26
+ _TLS_FALSE_VALUES = frozenset({"0", "false", "no"})
27
+
28
+ # Process-wide TLS override set from global CLI flags (``--ca-bundle`` /
29
+ # ``--verify-tls`` / ``--no-verify-tls``). It sits between an explicit
30
+ # ``verify=`` argument and the environment variables in :func:`resolve_verify`,
31
+ # so a single resolver serves every transport, including the schema discovery
32
+ # that runs before normal command dispatch.
33
+ _CLI_VERIFY_OVERRIDE: Any = _VERIFY_UNSET
19
34
  _SENSITIVE_HEADER_NAMES = {"authorization", "cloud-auth", "cookie", "x-api-key"}
20
35
  _SENSITIVE_KEY_RE = re.compile(
21
36
  r"(authorization|authentication|(^|[-_])auth($|[-_])|cloud[-_]?auth|cookie|x[-_]?api[-_]?key|token|secret|password|credential|pre[-_]?signed[-_]?url|signed[-_]?url)",
@@ -40,6 +55,115 @@ def tangle_verbose_enabled() -> bool:
40
55
  return value.strip().lower() in {"1", "true", "yes", "on"}
41
56
 
42
57
 
58
+ def _parse_verify_flag(raw: str) -> bool:
59
+ """Interpret ``TANGLE_API_VERIFY_TLS``.
60
+
61
+ Only the case/space-insensitive values ``0``, ``false``, and ``no`` disable
62
+ verification; any other nonempty value keeps it enabled.
63
+ """
64
+
65
+ return raw.strip().lower() not in _TLS_FALSE_VALUES
66
+
67
+
68
+ def _validate_ca_bundle(path: str, source: str) -> str:
69
+ """Return an existing CA bundle path or fail before any network request."""
70
+
71
+ candidate = Path(path).expanduser()
72
+ if not candidate.is_file():
73
+ raise SystemExit(
74
+ f"{source} points to a CA bundle that does not exist: {path!r}. "
75
+ "Provide a path to an existing PEM file, or unset it to use the "
76
+ "system trust store."
77
+ )
78
+ return str(candidate)
79
+
80
+
81
+ def _coerce_explicit_verify(value: bool | str | os.PathLike[str]) -> VerifyValue:
82
+ if isinstance(value, bool):
83
+ return value
84
+ if isinstance(value, (str, os.PathLike)):
85
+ return _validate_ca_bundle(os.fspath(value), "verify")
86
+ raise SystemExit("verify must be a bool or a path to a CA bundle file")
87
+
88
+
89
+ def configure_cli_verify(
90
+ ca_bundle: str | os.PathLike[str] | None = None,
91
+ verify_tls: bool | None = None,
92
+ ) -> None:
93
+ """Install (or clear) the process-wide TLS override from global CLI flags.
94
+
95
+ ``ca_bundle`` is a path to a PEM trust store; ``verify_tls`` is the tri-state
96
+ ``--verify-tls`` / ``--no-verify-tls`` flag where ``None`` means the flag was
97
+ not supplied. A ``--ca-bundle`` combined with an explicit
98
+ ``--no-verify-tls`` is contradictory and fails fast before any network
99
+ request. Passing neither clears any previously installed override.
100
+
101
+ The resolved value is validated here so an invalid or missing CA bundle
102
+ fails before dynamic schema discovery. It is consulted by
103
+ :func:`resolve_verify` for every transport.
104
+ """
105
+
106
+ global _CLI_VERIFY_OVERRIDE
107
+ if ca_bundle is not None:
108
+ if verify_tls is False:
109
+ raise SystemExit(
110
+ "--ca-bundle cannot be combined with --no-verify-tls: a CA "
111
+ "bundle only takes effect when verification is enabled. Drop "
112
+ "one of the two flags."
113
+ )
114
+ _CLI_VERIFY_OVERRIDE = _validate_ca_bundle(os.fspath(ca_bundle), "--ca-bundle")
115
+ return
116
+ if verify_tls is not None:
117
+ _CLI_VERIFY_OVERRIDE = bool(verify_tls)
118
+ return
119
+ _CLI_VERIFY_OVERRIDE = _VERIFY_UNSET
120
+
121
+
122
+ def resolve_verify(verify: Any = _VERIFY_UNSET) -> Any:
123
+ """Resolve the effective TLS verification setting.
124
+
125
+ Precedence, highest to lowest: an explicit ``verify`` argument, the global
126
+ CLI override (``--ca-bundle`` / ``--verify-tls`` / ``--no-verify-tls``), a
127
+ nonempty ``TANGLE_API_CA_BUNDLE``, ``TANGLE_API_VERIFY_TLS``, then a secure
128
+ enabled default. When none of these are set, ``_VERIFY_UNSET`` is returned
129
+ so callers can preserve library and caller defaults (for example requests'
130
+ ``REQUESTS_CA_BUNDLE``/``CURL_CA_BUNDLE`` handling and a caller-supplied
131
+ ``Session.verify``). Empty environment values are treated as unset.
132
+ """
133
+
134
+ if verify is not _VERIFY_UNSET and verify is not None:
135
+ return _coerce_explicit_verify(verify)
136
+ if _CLI_VERIFY_OVERRIDE is not _VERIFY_UNSET:
137
+ return _CLI_VERIFY_OVERRIDE
138
+ ca_bundle = os.environ.get("TANGLE_API_CA_BUNDLE", "")
139
+ if ca_bundle.strip():
140
+ return _validate_ca_bundle(ca_bundle.strip(), "TANGLE_API_CA_BUNDLE")
141
+ flag = os.environ.get("TANGLE_API_VERIFY_TLS", "")
142
+ if flag.strip():
143
+ return _parse_verify_flag(flag)
144
+ return _VERIFY_UNSET
145
+
146
+
147
+ def resolve_verify_default(verify: Any = _VERIFY_UNSET) -> VerifyValue:
148
+ """Like :func:`resolve_verify`, but fall back to the secure ``True`` default."""
149
+
150
+ resolved = resolve_verify(verify)
151
+ return True if resolved is _VERIFY_UNSET else resolved
152
+
153
+
154
+ def httpx_verify(verify: Any = _VERIFY_UNSET) -> bool | ssl.SSLContext:
155
+ """Adapt the resolved verify value to what httpx expects.
156
+
157
+ httpx 0.28 deprecates string CA-bundle paths, so a path is turned into an
158
+ :class:`ssl.SSLContext`; booleans pass through unchanged.
159
+ """
160
+
161
+ resolved = resolve_verify_default(verify)
162
+ if isinstance(resolved, bool):
163
+ return resolved
164
+ return ssl.create_default_context(cafile=resolved)
165
+
166
+
43
167
  def _redact_headers(headers: dict[str, Any] | None) -> dict[str, Any]:
44
168
  redacted: dict[str, Any] = {}
45
169
  for name, value in (headers or {}).items():
@@ -280,6 +404,7 @@ def request_operation(
280
404
  timeout: float = DEFAULT_TIMEOUT_SECONDS,
281
405
  allow_body_file_references: bool = False,
282
406
  include_env_credentials: bool = True,
407
+ verify: Any = _VERIFY_UNSET,
283
408
  ) -> httpx.Response:
284
409
  """Dispatch one normalized OpenAPI operation as an HTTP request.
285
410
 
@@ -306,6 +431,7 @@ def request_operation(
306
431
  content=content,
307
432
  headers=request_headers,
308
433
  timeout=timeout,
434
+ verify=httpx_verify(verify),
309
435
  )
310
436
  if tangle_verbose_enabled():
311
437
  log_http_exchange(
@@ -80,6 +80,7 @@ def artifacts_get(
80
80
  header=args.header,
81
81
  include_env_credentials=include_env_credentials_for_args(args, base_url),
82
82
  command_name="artifact commands",
83
+ logger=logger,
83
84
  )
84
85
  if require_available := getattr(client, "require_available", None):
85
86
  require_available()
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from typing import Annotated
5
+
6
+ from cyclopts import App, Parameter
7
+
8
+ from . import (
9
+ __version__,
10
+ api_cli,
11
+ artifacts_cli,
12
+ components_cli,
13
+ pipeline_runs_cli,
14
+ pipelines_cli,
15
+ published_components_cli,
16
+ quickstart,
17
+ secrets_cli,
18
+ )
19
+ from .api_transport import configure_cli_verify
20
+ from .cli_options import CaBundleOption, VerifyTlsOption
21
+
22
+
23
+ def version() -> None:
24
+ """Print the installed tangle-cli package version."""
25
+
26
+ print(__version__)
27
+
28
+
29
+ def _configure_tls_from_argv(argv: list[str]) -> None:
30
+ """Parse the global TLS flags and install the process-wide override.
31
+
32
+ Runs before the `api` app is built so the override is in place for the
33
+ dynamic schema discovery that happens during command construction, ahead of
34
+ normal Cyclopts dispatch. Only tokens before the first subcommand are
35
+ considered; flag validation and conflict detection live in
36
+ :func:`configure_cli_verify`.
37
+ """
38
+
39
+ ca_bundle: str | None = None
40
+ verify_tls: bool | None = None
41
+ index = 1
42
+ while index < len(argv):
43
+ arg = argv[index]
44
+ if arg == "--ca-bundle" and index + 1 < len(argv):
45
+ ca_bundle = argv[index + 1]
46
+ index += 2
47
+ continue
48
+ if arg.startswith("--ca-bundle="):
49
+ ca_bundle = arg.split("=", 1)[1]
50
+ index += 1
51
+ continue
52
+ if arg == "--verify-tls":
53
+ verify_tls = True
54
+ index += 1
55
+ continue
56
+ if arg == "--no-verify-tls":
57
+ verify_tls = False
58
+ index += 1
59
+ continue
60
+ break
61
+ configure_cli_verify(ca_bundle, verify_tls)
62
+
63
+
64
+ def build_sdk_app() -> App:
65
+ """Build the SDK command group."""
66
+
67
+ sdk_app = App(
68
+ name="sdk",
69
+ help="Work with local Tangle SDK resources and scaffolding.",
70
+ )
71
+ sdk_app.command(artifacts_cli.app)
72
+ sdk_app.command(components_cli.app)
73
+ sdk_app.command(pipelines_cli.app)
74
+ sdk_app.command(pipeline_runs_cli.app)
75
+ sdk_app.command(published_components_cli.app)
76
+ sdk_app.command(secrets_cli.app)
77
+ return sdk_app
78
+
79
+
80
+ def build_app(argv: list[str] | None = None) -> App:
81
+ """Build the root CLI app lazily for the current invocation.
82
+
83
+ Global TLS flags are parsed from *argv* (defaulting to ``sys.argv``) and
84
+ installed before the `api` app is built, because building it can trigger
85
+ dynamic OpenAPI schema discovery over the network.
86
+ """
87
+
88
+ _configure_tls_from_argv(sys.argv if argv is None else argv)
89
+
90
+ app = App(
91
+ help="CLI for Tangle, the open-source ML pipeline orchestration platform.",
92
+ version=__version__,
93
+ )
94
+ app.command(name="version")(version)
95
+ app.command(quickstart.app)
96
+ app.command(api_cli.build_app())
97
+ app.command(build_sdk_app())
98
+
99
+ @app.meta.default
100
+ def launcher(
101
+ *tokens: Annotated[str, Parameter(allow_leading_hyphen=True)],
102
+ ca_bundle: CaBundleOption = None,
103
+ verify_tls: VerifyTlsOption = None,
104
+ ) -> None:
105
+ """Apply global TLS options, then dispatch the requested command."""
106
+
107
+ configure_cli_verify(ca_bundle, verify_tls)
108
+ app(tokens)
109
+
110
+ return app
111
+
112
+
113
+ def main() -> None:
114
+ build_app().meta()
115
+
116
+
117
+ if __name__ == "__main__":
118
+ main()
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from pathlib import Path
5
6
  from typing import Annotated
6
7
 
7
8
  from cyclopts import Parameter
@@ -50,3 +51,26 @@ LogTypeOption = Annotated[
50
51
  str,
51
52
  Parameter(help="Log output: console, none, file."),
52
53
  ]
54
+ CaBundleOption = Annotated[
55
+ Path | None,
56
+ Parameter(
57
+ name="--ca-bundle",
58
+ help=(
59
+ "Path to a PEM CA bundle used as the TLS trust store for every "
60
+ "transport. Overrides TANGLE_API_CA_BUNDLE. Place before the "
61
+ "subcommand, e.g. `tangle --ca-bundle ca.pem api ...`."
62
+ ),
63
+ ),
64
+ ]
65
+ VerifyTlsOption = Annotated[
66
+ bool | None,
67
+ Parameter(
68
+ name="--verify-tls",
69
+ help=(
70
+ "Enable (--verify-tls) or disable (--no-verify-tls) TLS "
71
+ "certificate verification for every transport. Overrides "
72
+ "TANGLE_API_VERIFY_TLS. --no-verify-tls is for local development "
73
+ "only. Place before the subcommand."
74
+ ),
75
+ ),
76
+ ]