tangle-cli 0.1.5__tar.gz → 0.1.6__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 (100) hide show
  1. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/PKG-INFO +1 -1
  2. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
  3. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +61 -2
  4. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipelines.py +2 -0
  5. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +24 -0
  6. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +1 -0
  7. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +1 -0
  8. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +11 -1
  9. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/pyproject.toml +1 -1
  10. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/README.md +0 -0
  11. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/api_cli.py +0 -0
  12. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/api_schema.py +0 -0
  13. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/api_transport.py +0 -0
  14. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/args_container.py +0 -0
  15. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
  16. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +0 -0
  17. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/cli.py +0 -0
  18. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +0 -0
  19. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/cli_options.py +0 -0
  20. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/client.py +0 -0
  21. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/component_from_func.py +0 -0
  22. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/component_generator.py +0 -0
  23. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
  24. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
  25. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
  26. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +0 -0
  27. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
  28. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
  29. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
  30. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
  31. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
  32. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
  33. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
  34. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +0 -0
  35. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
  36. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
  37. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +0 -0
  38. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
  39. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
  40. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +0 -0
  41. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
  42. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
  43. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +0 -0
  44. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
  45. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +0 -0
  46. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +0 -0
  47. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
  48. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
  49. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
  50. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
  51. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +0 -0
  52. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
  53. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +0 -0
  54. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
  55. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
  56. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
  57. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
  58. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
  59. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +0 -0
  60. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
  61. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
  62. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
  63. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
  64. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/schema_validation.py +0 -0
  65. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
  66. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +0 -0
  67. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -0
  68. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
  69. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +0 -0
  70. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
  71. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
  72. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/OSS-CONVENTIONS.md +0 -0
  73. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/PORT-README.md +0 -0
  74. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/SKILL.md +0 -0
  75. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/auth-wizard.md +0 -0
  76. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/builder.md +0 -0
  77. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/debugger.md +0 -0
  78. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/reporter.md +0 -0
  79. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/researcher.md +0 -0
  80. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/reviewer.md +0 -0
  81. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/agents/scenario-builder.md +0 -0
  82. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/data-sources.md +0 -0
  83. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/event-log.md +0 -0
  84. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
  85. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
  86. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
  87. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/iterating-on-runs.md +0 -0
  88. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/knowledge-corpus.md +0 -0
  89. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/secrets.md +0 -0
  90. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/setup.md +0 -0
  91. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-0-initialize.md +0 -0
  92. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-1-analyze.md +0 -0
  93. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-2-hypothesize.md +0 -0
  94. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-3-submit.md +0 -0
  95. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-4-monitor.md +0 -0
  96. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-5-evaluate.md +0 -0
  97. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-6-synthesize.md +0 -0
  98. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/step-7-decide.md +0 -0
  99. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/skills/tangent/references/tangle-tools.md +0 -0
  100. {tangle_cli-0.1.5 → tangle_cli-0.1.6}/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.5
3
+ Version: 0.1.6
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>
@@ -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.5"
17
+ __version__ = "0.1.6"
18
18
 
19
19
  __all__ = ["TangleDynamicDiscoveryClient", "__version__"]
@@ -72,6 +72,40 @@ from .schema_validation import SchemaValidationError, validate_dehydrated_pipeli
72
72
  from .utils import dump_yaml
73
73
 
74
74
 
75
+ IMAGE_IDS: dict[str, str] = {}
76
+ """Logical image-id registry used by ``@task(image_id=...)``.
77
+
78
+ OSS ships this empty. Downstream distributions may register environment-specific
79
+ image defaults without hard-coding their image literals into the open-source
80
+ package; CLI ``--image ID=REF`` overrides still win over registered defaults.
81
+ """
82
+
83
+
84
+ def register_image_id(image_id: str, image: str) -> None:
85
+ """Register a default image reference for ``@task(image_id=...)``."""
86
+ if not image_id:
87
+ raise ValueError("image_id must be a non-empty string")
88
+ if not image:
89
+ raise ValueError("image must be a non-empty string")
90
+ IMAGE_IDS[image_id] = image
91
+
92
+
93
+ def get_image_id(image_id: str) -> str | None:
94
+ """Return the registered default image for ``image_id``, if any."""
95
+ return IMAGE_IDS.get(image_id)
96
+
97
+
98
+ def resolve_image_id(
99
+ image_id: str,
100
+ image_overrides: Mapping[str, str] | None = None,
101
+ ) -> str | None:
102
+ """Resolve ``image_id`` using compile-time overrides, then registered defaults."""
103
+ overrides = image_overrides or {}
104
+ if image_id in overrides:
105
+ return overrides[image_id]
106
+ return get_image_id(image_id)
107
+
108
+
75
109
  @dataclass
76
110
  class CompileResult:
77
111
  """Outcome of a :func:`compile_pipeline` call.
@@ -102,6 +136,7 @@ def compile_pipeline(
102
136
  *,
103
137
  pipeline_name: str | None = None,
104
138
  emit_components_sidecar: bool = True,
139
+ image_overrides: Mapping[str, str] | None = None,
105
140
  ) -> CompileResult:
106
141
  """Compile ``script`` to a single pipeline YAML at ``output``.
107
142
 
@@ -132,6 +167,9 @@ def compile_pipeline(
132
167
  compilation fails with a :class:`CompileError` (a valid
133
168
  dehydrated pipeline cannot be produced without the sidecar).
134
169
  Pipelines that use only ``ref(url=...)`` are unaffected.
170
+ image_overrides: Compile-time image-id overrides from ``--image
171
+ ID=REF``. They apply only to ``@task(image_id=...)`` refs that do
172
+ not also set an explicit ``image=``.
135
173
 
136
174
  Returns:
137
175
  A :class:`CompileResult`. ``components_path`` is the sidecar path
@@ -143,6 +181,7 @@ def compile_pipeline(
143
181
  unreachable ``@task`` source/dependency files).
144
182
  """
145
183
  overrides = dict(overrides or {})
184
+ image_overrides = dict(image_overrides or {})
146
185
 
147
186
  # 1. Validate the script path.
148
187
  script_path = Path(script).resolve()
@@ -197,6 +236,7 @@ def compile_pipeline(
197
236
  root_overrides=overrides,
198
237
  emit_components_sidecar=emit_components_sidecar,
199
238
  source_dirs=purge_dirs,
239
+ image_overrides=image_overrides,
200
240
  )
201
241
 
202
242
  # 5. Compile the root (and, recursively, all children) into in-memory
@@ -436,7 +476,11 @@ def _compile_pipeline_fn(
436
476
  )
437
477
  if task_refs:
438
478
  components_path = output_path.with_name(output_path.stem + ".components.yaml")
439
- components_entries = _build_local_from_python_components(task_refs, components_yaml_dir=components_path.parent)
479
+ components_entries = _build_local_from_python_components(
480
+ task_refs,
481
+ components_yaml_dir=components_path.parent,
482
+ image_overrides=ctx.image_overrides,
483
+ )
440
484
  _rewrite_task_componentref_urls(
441
485
  body_dict=body_dict,
442
486
  task_refs=task_refs,
@@ -1427,7 +1471,10 @@ def _relocate_relative_local_url(url: str, source_dir: Path, sidecar_dir: Path)
1427
1471
 
1428
1472
 
1429
1473
  def _build_local_from_python_components(
1430
- task_refs: list[tuple[str, CallableRef]], *, components_yaml_dir: Path
1474
+ task_refs: list[tuple[str, CallableRef]],
1475
+ *,
1476
+ components_yaml_dir: Path,
1477
+ image_overrides: Mapping[str, str] | None = None,
1431
1478
  ) -> dict[str, Any]:
1432
1479
  """Build the ``<stem>.components.yaml`` content for @task refs.
1433
1480
 
@@ -1492,6 +1539,16 @@ def _build_local_from_python_components(
1492
1539
  local_from_python: dict[str, Any] = {}
1493
1540
  if ref._task_image is not None:
1494
1541
  local_from_python["image"] = ref._task_image
1542
+ elif ref._task_image_id is not None:
1543
+ resolved_image = resolve_image_id(ref._task_image_id, image_overrides)
1544
+ if resolved_image is None:
1545
+ raise CompileError(
1546
+ f"@task image_id={ref._task_image_id!r} on function "
1547
+ f"{ref._task_function_name!r} did not resolve to an image. "
1548
+ f"Pass --image {ref._task_image_id}=IMAGE to `tangle sdk pipelines compile`, "
1549
+ f"or register a default with register_image_id({ref._task_image_id!r}, IMAGE)."
1550
+ )
1551
+ local_from_python["image"] = resolved_image
1495
1552
  # Always pin the function name. Without it the hydrator defaults
1496
1553
  # to the file stem and extracts the wrong symbol.
1497
1554
  assert ref._task_function_name is not None
@@ -2233,6 +2290,7 @@ class PipelineCompiler(TangleCliHandler):
2233
2290
  overrides: Mapping[str, str] | None = None,
2234
2291
  pipeline_name: str | None = None,
2235
2292
  emit_components_sidecar: bool = True,
2293
+ image_overrides: Mapping[str, str] | None = None,
2236
2294
  ) -> CompileResult:
2237
2295
  """Compile ``script`` to a single dehydrated pipeline YAML at ``output``.
2238
2296
 
@@ -2251,6 +2309,7 @@ class PipelineCompiler(TangleCliHandler):
2251
2309
  overrides,
2252
2310
  pipeline_name=pipeline_name,
2253
2311
  emit_components_sidecar=emit_components_sidecar,
2312
+ image_overrides=image_overrides,
2254
2313
  )
2255
2314
  self.log.info(f"wrote {result.pipeline_path}")
2256
2315
  if result.components_path is not None:
@@ -258,6 +258,7 @@ def compile_pipeline_file(
258
258
  overrides: Mapping[str, str] | None = None,
259
259
  pipeline_name: str | None = None,
260
260
  emit_components_sidecar: bool = True,
261
+ image_overrides: Mapping[str, str] | None = None,
261
262
  logger: Any | None = None,
262
263
  ) -> CompileResult:
263
264
  """Compile a Python-authored pipeline to a dehydrated YAML bundle.
@@ -284,6 +285,7 @@ def compile_pipeline_file(
284
285
  overrides=dict(overrides) if overrides else None,
285
286
  pipeline_name=pipeline_name,
286
287
  emit_components_sidecar=emit_components_sidecar,
288
+ image_overrides=dict(image_overrides) if image_overrides else None,
287
289
  )
288
290
  except (CompileError, SchemaValidationError) as exc:
289
291
  raise PipelineValidationError(str(exc)) from exc
@@ -138,6 +138,18 @@ def _parse_overrides(values: list[str] | None) -> dict[str, str]:
138
138
  return parsed
139
139
 
140
140
 
141
+ def _parse_image_overrides(values: list[str] | None) -> dict[str, str]:
142
+ parsed: dict[str, str] = {}
143
+ for value in values or []:
144
+ if "=" not in value:
145
+ raise SystemExit("--image entries must use ID=REF syntax")
146
+ image_id, image_ref = value.split("=", 1)
147
+ if not image_id or not image_ref:
148
+ raise SystemExit("--image entries must use ID=REF syntax")
149
+ parsed[image_id] = image_ref
150
+ return parsed
151
+
152
+
141
153
  @app.command(name="hydrate")
142
154
  def pipelines_hydrate(
143
155
  pipeline_path: pathlib.Path,
@@ -266,6 +278,17 @@ def pipelines_compile(
266
278
  negative_iterable=(),
267
279
  ),
268
280
  ] = None,
281
+ image: Annotated[
282
+ list[str] | None,
283
+ Parameter(
284
+ name="--image",
285
+ help=(
286
+ "Compile-time image-id override as ID=REF for @task(image_id=ID). "
287
+ "Repeat for multiple IDs."
288
+ ),
289
+ negative_iterable=(),
290
+ ),
291
+ ] = None,
269
292
  log_type: LogTypeOption = "console",
270
293
  ) -> None:
271
294
  """Compile a Python-authored pipeline to a dehydrated YAML bundle."""
@@ -276,6 +299,7 @@ def pipelines_compile(
276
299
  pipeline_path,
277
300
  output,
278
301
  overrides=_parse_overrides(override),
302
+ image_overrides=_parse_image_overrides(image),
279
303
  pipeline_name=pipeline,
280
304
  logger=logger,
281
305
  )
@@ -252,6 +252,7 @@ class CompileContext:
252
252
  subgraph_dir: Path
253
253
  root_overrides: dict[str, str] = field(default_factory=dict)
254
254
  emit_components_sidecar: bool = True
255
+ image_overrides: dict[str, str] = field(default_factory=dict)
255
256
  max_depth: int = 32
256
257
  # Compiled CHILD artifacts keyed by compile key (Decision M dedup).
257
258
  # The root is NOT stored here; it is returned directly.
@@ -49,6 +49,7 @@ class CallableRef:
49
49
  _task_source_path: Path | None = None
50
50
  _task_function_name: str | None = None
51
51
  _task_image: str | None = None
52
+ _task_image_id: str | None = None
52
53
  _task_dependencies_from: Path | None = None
53
54
  _task_mode: str | None = None
54
55
  _task_resolve_root: Path | None = None
@@ -65,6 +65,7 @@ def task(
65
65
  *,
66
66
  env: TaskEnv | None = None,
67
67
  image: str | None = None,
68
+ image_id: str | None = None,
68
69
  dependencies_from: str | Path | None = None,
69
70
  mode: str | None = None,
70
71
  resolve_root: str | Path | None = None,
@@ -95,6 +96,10 @@ def task(
95
96
  written verbatim into the emitted
96
97
  ``components.yaml#local_from_python.image`` field. Overrides
97
98
  ``env.image`` when both are given.
99
+ image_id: Logical image identifier resolved at compile time via
100
+ registered defaults or ``tangle sdk pipelines compile --image
101
+ ID=REF`` overrides. Ignored when explicit ``image=`` supplies
102
+ an image; otherwise it also overrides ``env.image``.
98
103
  dependencies_from: Path to a ``pyproject.toml`` (or any file
99
104
  the hydrator understands) that declares pip
100
105
  dependencies. Resolved relative to the caller's source
@@ -138,7 +143,11 @@ def task(
138
143
  # overrides the corresponding ``env`` field; otherwise the env value
139
144
  # (if any) is used. This mirrors YAML anchor semantics: start from the
140
145
  # declared-once defaults, override locally where needed.
141
- effective_image = image if image is not None else (env.image if env else None)
146
+ effective_image = (
147
+ image
148
+ if image is not None
149
+ else (None if image_id is not None else (env.image if env else None))
150
+ )
142
151
  effective_deps_raw = (
143
152
  dependencies_from
144
153
  if dependencies_from is not None
@@ -202,6 +211,7 @@ def task(
202
211
  _task_source_path=source_path,
203
212
  _task_function_name=function_name,
204
213
  _task_image=effective_image,
214
+ _task_image_id=image_id,
205
215
  _task_dependencies_from=deps_path,
206
216
  _task_mode=mode,
207
217
  _task_resolve_root=resolve_root_path,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tangle-cli"
3
- version = "0.1.5"
3
+ version = "0.1.6"
4
4
  description = "CLI for Tangle, the open-source ML pipeline orchestration platform"
5
5
  readme = "README.md"
6
6
  authors = [
File without changes