zenml-nightly 0.70.0.dev20241127__py3-none-any.whl → 0.70.0.dev20241129__py3-none-any.whl

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 (53) hide show
  1. zenml/VERSION +1 -1
  2. zenml/artifacts/artifact_config.py +21 -1
  3. zenml/artifacts/utils.py +5 -1
  4. zenml/cli/pipeline.py +80 -0
  5. zenml/config/compiler.py +12 -3
  6. zenml/config/pipeline_configurations.py +20 -0
  7. zenml/config/pipeline_run_configuration.py +1 -0
  8. zenml/config/step_configurations.py +21 -0
  9. zenml/enums.py +1 -0
  10. zenml/integrations/__init__.py +1 -0
  11. zenml/integrations/constants.py +1 -0
  12. zenml/integrations/feast/__init__.py +1 -1
  13. zenml/integrations/feast/feature_stores/feast_feature_store.py +13 -9
  14. zenml/integrations/kubernetes/orchestrators/kube_utils.py +46 -2
  15. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +13 -2
  16. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +3 -1
  17. zenml/integrations/kubernetes/orchestrators/manifest_utils.py +3 -2
  18. zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +3 -1
  19. zenml/integrations/modal/__init__.py +46 -0
  20. zenml/integrations/modal/flavors/__init__.py +26 -0
  21. zenml/integrations/modal/flavors/modal_step_operator_flavor.py +125 -0
  22. zenml/integrations/modal/step_operators/__init__.py +22 -0
  23. zenml/integrations/modal/step_operators/modal_step_operator.py +242 -0
  24. zenml/materializers/built_in_materializer.py +18 -1
  25. zenml/materializers/structured_string_materializer.py +8 -3
  26. zenml/model/model.py +6 -2
  27. zenml/models/v2/core/pipeline_run.py +4 -0
  28. zenml/models/v2/core/step_run.py +1 -1
  29. zenml/orchestrators/publish_utils.py +1 -1
  30. zenml/orchestrators/step_launcher.py +6 -2
  31. zenml/orchestrators/step_run_utils.py +15 -6
  32. zenml/orchestrators/step_runner.py +40 -2
  33. zenml/orchestrators/utils.py +29 -4
  34. zenml/pipelines/pipeline_decorator.py +4 -0
  35. zenml/pipelines/pipeline_definition.py +14 -3
  36. zenml/pipelines/run_utils.py +8 -3
  37. zenml/steps/base_step.py +11 -1
  38. zenml/steps/entrypoint_function_utils.py +4 -2
  39. zenml/steps/step_decorator.py +4 -0
  40. zenml/steps/utils.py +17 -5
  41. zenml/types.py +4 -0
  42. zenml/utils/string_utils.py +30 -12
  43. zenml/utils/visualization_utils.py +4 -1
  44. zenml/zen_server/template_execution/utils.py +1 -0
  45. zenml/zen_stores/schemas/artifact_schemas.py +2 -1
  46. zenml/zen_stores/schemas/pipeline_run_schemas.py +14 -3
  47. zenml/zen_stores/schemas/step_run_schemas.py +19 -0
  48. zenml/zen_stores/sql_zen_store.py +15 -11
  49. {zenml_nightly-0.70.0.dev20241127.dist-info → zenml_nightly-0.70.0.dev20241129.dist-info}/METADATA +1 -1
  50. {zenml_nightly-0.70.0.dev20241127.dist-info → zenml_nightly-0.70.0.dev20241129.dist-info}/RECORD +53 -48
  51. {zenml_nightly-0.70.0.dev20241127.dist-info → zenml_nightly-0.70.0.dev20241129.dist-info}/LICENSE +0 -0
  52. {zenml_nightly-0.70.0.dev20241127.dist-info → zenml_nightly-0.70.0.dev20241129.dist-info}/WHEEL +0 -0
  53. {zenml_nightly-0.70.0.dev20241127.dist-info → zenml_nightly-0.70.0.dev20241129.dist-info}/entry_points.txt +0 -0
@@ -40,7 +40,9 @@ if TYPE_CHECKING:
40
40
  from zenml.artifact_stores.base_artifact_store import BaseArtifactStore
41
41
 
42
42
 
43
- def get_orchestrator_run_name(pipeline_name: str) -> str:
43
+ def get_orchestrator_run_name(
44
+ pipeline_name: str, max_length: Optional[int] = None
45
+ ) -> str:
44
46
  """Gets an orchestrator run name.
45
47
 
46
48
  This run name is not the same as the ZenML run name but can instead be
@@ -48,11 +50,31 @@ def get_orchestrator_run_name(pipeline_name: str) -> str:
48
50
 
49
51
  Args:
50
52
  pipeline_name: Name of the pipeline that will run.
53
+ max_length: Maximum length of the generated name.
54
+
55
+ Raises:
56
+ ValueError: If the max length is below 8 characters.
51
57
 
52
58
  Returns:
53
59
  The orchestrator run name.
54
60
  """
55
- return f"{pipeline_name}_{random.Random().getrandbits(128):032x}"
61
+ suffix_length = 32
62
+ pipeline_name = f"{pipeline_name}_"
63
+
64
+ if max_length:
65
+ if max_length < 8:
66
+ raise ValueError(
67
+ "Maximum length for orchestrator run name must be 8 or above."
68
+ )
69
+
70
+ # Make sure we always have a certain suffix to guarantee no overlap
71
+ # with other runs
72
+ suffix_length = min(32, max(8, max_length - len(pipeline_name)))
73
+ pipeline_name = pipeline_name[: (max_length - suffix_length)]
74
+
75
+ suffix = "".join(random.choices("0123456789abcdef", k=suffix_length))
76
+
77
+ return f"{pipeline_name}{suffix}"
56
78
 
57
79
 
58
80
  def is_setting_enabled(
@@ -174,11 +196,12 @@ def get_config_environment_vars(
174
196
  return environment_vars
175
197
 
176
198
 
177
- def get_run_name(run_name_template: str) -> str:
199
+ def get_run_name(run_name_template: str, substitutions: Dict[str, str]) -> str:
178
200
  """Fill out the run name template to get a complete run name.
179
201
 
180
202
  Args:
181
203
  run_name_template: The run name template to fill out.
204
+ substitutions: The substitutions to use in the template.
182
205
 
183
206
  Raises:
184
207
  ValueError: If the run name is empty.
@@ -186,7 +209,9 @@ def get_run_name(run_name_template: str) -> str:
186
209
  Returns:
187
210
  The run name derived from the template.
188
211
  """
189
- run_name = format_name_template(run_name_template)
212
+ run_name = format_name_template(
213
+ run_name_template, substitutions=substitutions
214
+ )
190
215
 
191
216
  if run_name == "":
192
217
  raise ValueError("Empty run names are not allowed.")
@@ -55,6 +55,7 @@ def pipeline(
55
55
  on_failure: Optional["HookSpecification"] = None,
56
56
  on_success: Optional["HookSpecification"] = None,
57
57
  model: Optional["Model"] = None,
58
+ substitutions: Optional[Dict[str, str]] = None,
58
59
  ) -> Callable[["F"], "Pipeline"]: ...
59
60
 
60
61
 
@@ -71,6 +72,7 @@ def pipeline(
71
72
  on_failure: Optional["HookSpecification"] = None,
72
73
  on_success: Optional["HookSpecification"] = None,
73
74
  model: Optional["Model"] = None,
75
+ substitutions: Optional[Dict[str, str]] = None,
74
76
  ) -> Union["Pipeline", Callable[["F"], "Pipeline"]]:
75
77
  """Decorator to create a pipeline.
76
78
 
@@ -91,6 +93,7 @@ def pipeline(
91
93
  function with no arguments, or a source path to such a function
92
94
  (e.g. `module.my_function`).
93
95
  model: configuration of the model in the Model Control Plane.
96
+ substitutions: Extra placeholders to use in the name templates.
94
97
 
95
98
  Returns:
96
99
  A pipeline instance.
@@ -111,6 +114,7 @@ def pipeline(
111
114
  on_success=on_success,
112
115
  model=model,
113
116
  entrypoint=func,
117
+ substitutions=substitutions,
114
118
  )
115
119
 
116
120
  p.__doc__ = func.__doc__
@@ -135,6 +135,7 @@ class Pipeline:
135
135
  on_failure: Optional["HookSpecification"] = None,
136
136
  on_success: Optional["HookSpecification"] = None,
137
137
  model: Optional["Model"] = None,
138
+ substitutions: Optional[Dict[str, str]] = None,
138
139
  ) -> None:
139
140
  """Initializes a pipeline.
140
141
 
@@ -157,6 +158,7 @@ class Pipeline:
157
158
  be a function with no arguments, or a source path to such a
158
159
  function (e.g. `module.my_function`).
159
160
  model: configuration of the model in the Model Control Plane.
161
+ substitutions: Extra placeholders to use in the name templates.
160
162
  """
161
163
  self._invocations: Dict[str, StepInvocation] = {}
162
164
  self._run_args: Dict[str, Any] = {}
@@ -177,6 +179,7 @@ class Pipeline:
177
179
  on_failure=on_failure,
178
180
  on_success=on_success,
179
181
  model=model,
182
+ substitutions=substitutions,
180
183
  )
181
184
  self.entrypoint = entrypoint
182
185
  self._parameters: Dict[str, Any] = {}
@@ -297,6 +300,7 @@ class Pipeline:
297
300
  model: Optional["Model"] = None,
298
301
  parameters: Optional[Dict[str, Any]] = None,
299
302
  merge: bool = True,
303
+ substitutions: Optional[Dict[str, str]] = None,
300
304
  ) -> Self:
301
305
  """Configures the pipeline.
302
306
 
@@ -333,6 +337,7 @@ class Pipeline:
333
337
  method for an example.
334
338
  model: configuration of the model version in the Model Control Plane.
335
339
  parameters: input parameters for the pipeline.
340
+ substitutions: Extra placeholders to use in the name templates.
336
341
 
337
342
  Returns:
338
343
  The pipeline instance that this method was called on.
@@ -365,6 +370,7 @@ class Pipeline:
365
370
  "success_hook_source": success_hook_source,
366
371
  "model": model,
367
372
  "parameters": parameters,
373
+ "substitutions": substitutions,
368
374
  }
369
375
  )
370
376
  if not self.__suppress_warnings_flag__:
@@ -577,6 +583,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
577
583
  config_path: Optional[str] = None,
578
584
  unlisted: bool = False,
579
585
  prevent_build_reuse: bool = False,
586
+ skip_schedule_registration: bool = False,
580
587
  ) -> PipelineDeploymentResponse:
581
588
  """Create a pipeline deployment.
582
589
 
@@ -603,6 +610,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
603
610
  to any pipeline).
604
611
  prevent_build_reuse: DEPRECATED: Use
605
612
  `DockerSettings.prevent_build_reuse` instead.
613
+ skip_schedule_registration: Whether to skip schedule registration.
606
614
 
607
615
  Returns:
608
616
  The pipeline deployment.
@@ -643,7 +651,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
643
651
  stack.validate()
644
652
 
645
653
  schedule_id = None
646
- if schedule:
654
+ if schedule and not skip_schedule_registration:
647
655
  if not stack.orchestrator.config.is_schedulable:
648
656
  raise ValueError(
649
657
  f"Stack {stack.name} does not support scheduling. "
@@ -656,7 +664,8 @@ To avoid this consider setting pipeline parameters only in one place (config or
656
664
  schedule_name = schedule.name
657
665
  else:
658
666
  schedule_name = format_name_template(
659
- deployment.run_name_template
667
+ deployment.run_name_template,
668
+ substitutions=deployment.pipeline_configuration.substitutions,
660
669
  )
661
670
  components = Client().active_stack_model.components
662
671
  orchestrator = components[StackComponentType.ORCHESTRATOR][0]
@@ -1438,7 +1447,9 @@ To avoid this consider setting pipeline parameters only in one place (config or
1438
1447
  The created run template.
1439
1448
  """
1440
1449
  self._prepare_if_possible()
1441
- deployment = self._create_deployment(**self._run_args)
1450
+ deployment = self._create_deployment(
1451
+ **self._run_args, skip_schedule_registration=True
1452
+ )
1442
1453
 
1443
1454
  return Client().create_run_template(
1444
1455
  name=name, deployment_id=deployment.id, **kwargs
@@ -66,16 +66,21 @@ def create_placeholder_run(
66
66
 
67
67
  if deployment.schedule:
68
68
  return None
69
-
69
+ start_time = datetime.utcnow()
70
70
  run_request = PipelineRunRequest(
71
- name=get_run_name(run_name_template=deployment.run_name_template),
71
+ name=get_run_name(
72
+ run_name_template=deployment.run_name_template,
73
+ substitutions=deployment.pipeline_configuration._get_full_substitutions(
74
+ start_time
75
+ ),
76
+ ),
72
77
  # We set the start time on the placeholder run already to
73
78
  # make it consistent with the {time} placeholder in the
74
79
  # run name. This means the placeholder run will usually
75
80
  # have longer durations than scheduled runs, as for them
76
81
  # the start_time is only set once the first step starts
77
82
  # running.
78
- start_time=datetime.utcnow(),
83
+ start_time=start_time,
79
84
  orchestrator_run_id=None,
80
85
  user=deployment.user.id,
81
86
  workspace=deployment.workspace.id,
zenml/steps/base_step.py CHANGED
@@ -116,6 +116,7 @@ class BaseStep:
116
116
  on_success: Optional["HookSpecification"] = None,
117
117
  model: Optional["Model"] = None,
118
118
  retry: Optional[StepRetryConfig] = None,
119
+ substitutions: Optional[Dict[str, str]] = None,
119
120
  ) -> None:
120
121
  """Initializes a step.
121
122
 
@@ -144,11 +145,13 @@ class BaseStep:
144
145
  function (e.g. `module.my_function`).
145
146
  model: configuration of the model version in the Model Control Plane.
146
147
  retry: Configuration for retrying the step in case of failure.
148
+ substitutions: Extra placeholders to use in the name template.
147
149
  """
148
150
  from zenml.config.step_configurations import PartialStepConfiguration
149
151
 
150
152
  self.entrypoint_definition = validate_entrypoint_function(
151
- self.entrypoint, reserved_arguments=["after", "id"]
153
+ self.entrypoint,
154
+ reserved_arguments=["after", "id"],
152
155
  )
153
156
 
154
157
  name = name or self.__class__.__name__
@@ -203,6 +206,7 @@ class BaseStep:
203
206
  on_success=on_success,
204
207
  model=model,
205
208
  retry=retry,
209
+ substitutions=substitutions,
206
210
  )
207
211
 
208
212
  notebook_utils.try_to_save_notebook_cell_code(self.source_object)
@@ -595,6 +599,7 @@ class BaseStep:
595
599
  model: Optional["Model"] = None,
596
600
  merge: bool = True,
597
601
  retry: Optional[StepRetryConfig] = None,
602
+ substitutions: Optional[Dict[str, str]] = None,
598
603
  ) -> T:
599
604
  """Configures the step.
600
605
 
@@ -637,6 +642,7 @@ class BaseStep:
637
642
  overwrite all existing ones. See the general description of this
638
643
  method for an example.
639
644
  retry: Configuration for retrying the step in case of failure.
645
+ substitutions: Extra placeholders to use in the name template.
640
646
 
641
647
  Returns:
642
648
  The step instance that this method was called on.
@@ -701,6 +707,7 @@ class BaseStep:
701
707
  "success_hook_source": success_hook_source,
702
708
  "model": model,
703
709
  "retry": retry,
710
+ "substitutions": substitutions,
704
711
  }
705
712
  )
706
713
  config = StepConfigurationUpdate(**values)
@@ -725,6 +732,7 @@ class BaseStep:
725
732
  on_success: Optional["HookSpecification"] = None,
726
733
  model: Optional["Model"] = None,
727
734
  merge: bool = True,
735
+ substitutions: Optional[Dict[str, str]] = None,
728
736
  ) -> "BaseStep":
729
737
  """Copies the step and applies the given configurations.
730
738
 
@@ -756,6 +764,7 @@ class BaseStep:
756
764
  configurations. If `False` the given configurations will
757
765
  overwrite all existing ones. See the general description of this
758
766
  method for an example.
767
+ substitutions: Extra placeholders for the step name.
759
768
 
760
769
  Returns:
761
770
  The copied step instance.
@@ -776,6 +785,7 @@ class BaseStep:
776
785
  on_success=on_success,
777
786
  model=model,
778
787
  merge=merge,
788
+ substitutions=substitutions,
779
789
  )
780
790
  return step_copy
781
791
 
@@ -211,7 +211,8 @@ class EntrypointFunctionDefinition(NamedTuple):
211
211
 
212
212
 
213
213
  def validate_entrypoint_function(
214
- func: Callable[..., Any], reserved_arguments: Sequence[str] = ()
214
+ func: Callable[..., Any],
215
+ reserved_arguments: Sequence[str] = (),
215
216
  ) -> EntrypointFunctionDefinition:
216
217
  """Validates a step entrypoint function.
217
218
 
@@ -258,7 +259,8 @@ def validate_entrypoint_function(
258
259
  inputs[key] = parameter
259
260
 
260
261
  outputs = parse_return_type_annotations(
261
- func=func, enforce_type_annotations=ENFORCE_TYPE_ANNOTATIONS
262
+ func=func,
263
+ enforce_type_annotations=ENFORCE_TYPE_ANNOTATIONS,
262
264
  )
263
265
 
264
266
  return EntrypointFunctionDefinition(
@@ -73,6 +73,7 @@ def step(
73
73
  on_success: Optional["HookSpecification"] = None,
74
74
  model: Optional["Model"] = None,
75
75
  retry: Optional["StepRetryConfig"] = None,
76
+ substitutions: Optional[Dict[str, str]] = None,
76
77
  ) -> Callable[["F"], "BaseStep"]: ...
77
78
 
78
79
 
@@ -93,6 +94,7 @@ def step(
93
94
  on_success: Optional["HookSpecification"] = None,
94
95
  model: Optional["Model"] = None,
95
96
  retry: Optional["StepRetryConfig"] = None,
97
+ substitutions: Optional[Dict[str, str]] = None,
96
98
  ) -> Union["BaseStep", Callable[["F"], "BaseStep"]]:
97
99
  """Decorator to create a ZenML step.
98
100
 
@@ -124,6 +126,7 @@ def step(
124
126
  (e.g. `module.my_function`).
125
127
  model: configuration of the model in the Model Control Plane.
126
128
  retry: configuration of step retry in case of step failure.
129
+ substitutions: Extra placeholders for the step name.
127
130
 
128
131
  Returns:
129
132
  The step instance.
@@ -157,6 +160,7 @@ def step(
157
160
  on_success=on_success,
158
161
  model=model,
159
162
  retry=retry,
163
+ substitutions=substitutions,
160
164
  )
161
165
 
162
166
  return step_instance
zenml/steps/utils.py CHANGED
@@ -18,7 +18,15 @@ import ast
18
18
  import contextlib
19
19
  import inspect
20
20
  import textwrap
21
- from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Union
21
+ from typing import (
22
+ TYPE_CHECKING,
23
+ Any,
24
+ Callable,
25
+ Dict,
26
+ Optional,
27
+ Tuple,
28
+ Union,
29
+ )
22
30
  from uuid import UUID
23
31
 
24
32
  from pydantic import BaseModel
@@ -94,7 +102,8 @@ def get_args(obj: Any) -> Tuple[Any, ...]:
94
102
 
95
103
 
96
104
  def parse_return_type_annotations(
97
- func: Callable[..., Any], enforce_type_annotations: bool = False
105
+ func: Callable[..., Any],
106
+ enforce_type_annotations: bool = False,
98
107
  ) -> Dict[str, OutputSignature]:
99
108
  """Parse the return type annotation of a step function.
100
109
 
@@ -228,9 +237,12 @@ def get_artifact_config_from_annotation_metadata(
228
237
 
229
238
  error_message = (
230
239
  "Artifact annotation should only contain two elements: the artifact "
231
- "type, and either an output name or an `ArtifactConfig`, e.g.: "
232
- "`Annotated[int, 'output_name']` or "
233
- "`Annotated[int, ArtifactConfig(name='output_name'), ...]`."
240
+ "type, and one of the following: { a static or dynamic name || "
241
+ "an `ArtifactConfig` }, e.g.: "
242
+ "`Annotated[int, 'output_name']` || "
243
+ "`Annotated[int, 'output_{placeholder}']` || "
244
+ "`Annotated[int, ArtifactConfig(name='output_{placeholder}')]` ||"
245
+ "`Annotated[int, ArtifactConfig(name='output_name')]`."
234
246
  )
235
247
 
236
248
  if len(metadata) > 2:
zenml/types.py CHANGED
@@ -33,3 +33,7 @@ class MarkdownString(str):
33
33
 
34
34
  class CSVString(str):
35
35
  """Special string class to indicate a CSV string."""
36
+
37
+
38
+ class JSONString(str):
39
+ """Special string class to indicate a JSON string."""
@@ -14,10 +14,10 @@
14
14
  """Utils for strings."""
15
15
 
16
16
  import base64
17
- import datetime
18
17
  import functools
19
18
  import random
20
19
  import string
20
+ from datetime import datetime
21
21
  from typing import Any, Callable, Dict, TypeVar, cast
22
22
 
23
23
  from pydantic import BaseModel
@@ -147,7 +147,7 @@ def validate_name(model: BaseModel) -> None:
147
147
 
148
148
  def format_name_template(
149
149
  name_template: str,
150
- **kwargs: str,
150
+ substitutions: Dict[str, str],
151
151
  ) -> str:
152
152
  """Formats a name template with the given arguments.
153
153
 
@@ -157,20 +157,38 @@ def format_name_template(
157
157
 
158
158
  Args:
159
159
  name_template: The name template to format.
160
- **kwargs: The arguments to replace in the template.
160
+ substitutions: A dictionary of substitutions to use in the template.
161
161
 
162
162
  Returns:
163
163
  The formatted name template.
164
+
165
+ Raises:
166
+ KeyError: If a key in template is missing in the kwargs.
164
167
  """
165
- kwargs["date"] = kwargs.get(
166
- "date",
167
- datetime.datetime.now(datetime.timezone.utc).strftime("%Y_%m_%d"),
168
- )
169
- kwargs["time"] = kwargs.get(
170
- "time",
171
- datetime.datetime.now(datetime.timezone.utc).strftime("%H_%M_%S_%f"),
172
- )
173
- return name_template.format(**kwargs)
168
+ if ("date" not in substitutions and "{date}" in name_template) or (
169
+ "time" not in substitutions and "{time}" in name_template
170
+ ):
171
+ from zenml import get_step_context
172
+
173
+ try:
174
+ pr = get_step_context().pipeline_run
175
+ start_time = pr.start_time
176
+ substitutions.update(pr.config.substitutions)
177
+ except RuntimeError:
178
+ start_time = None
179
+
180
+ if start_time is None:
181
+ start_time = datetime.utcnow()
182
+ substitutions.setdefault("date", start_time.strftime("%Y_%m_%d"))
183
+ substitutions.setdefault("time", start_time.strftime("%H_%M_%S_%f"))
184
+
185
+ try:
186
+ return name_template.format(**substitutions)
187
+ except KeyError as e:
188
+ raise KeyError(
189
+ f"Could not format the name template `{name_template}`. "
190
+ f"Missing key: {e}"
191
+ )
174
192
 
175
193
 
176
194
  def substitute_string(value: V, substitution_func: Callable[[str], str]) -> V:
@@ -13,9 +13,10 @@
13
13
  # permissions and limitations under the License.
14
14
  """Utility functions for dashboard visualizations."""
15
15
 
16
+ import json
16
17
  from typing import TYPE_CHECKING, Optional
17
18
 
18
- from IPython.core.display import HTML, Image, Markdown, display
19
+ from IPython.core.display import HTML, JSON, Image, Markdown, display
19
20
 
20
21
  from zenml.artifacts.utils import load_artifact_visualization
21
22
  from zenml.enums import VisualizationType
@@ -63,6 +64,8 @@ def visualize_artifact(
63
64
  assert isinstance(visualization.value, str)
64
65
  table = format_csv_visualization_as_html(visualization.value)
65
66
  display(HTML(table))
67
+ elif visualization.type == VisualizationType.JSON:
68
+ display(JSON(json.loads(visualization.value)))
66
69
  else:
67
70
  display(visualization.value)
68
71
 
@@ -363,6 +363,7 @@ def deployment_request_from_template(
363
363
  "external_input_artifacts",
364
364
  "model_artifacts_or_metadata",
365
365
  "client_lazy_loaders",
366
+ "substitutions",
366
367
  "outputs",
367
368
  }
368
369
  ),
@@ -352,8 +352,9 @@ class ArtifactVersionSchema(BaseSchema, table=True):
352
352
  producer_step_run_id = step_run.original_step_run_id
353
353
 
354
354
  # Create the body of the model
355
+ artifact = self.artifact.to_model()
355
356
  body = ArtifactVersionResponseBody(
356
- artifact=self.artifact.to_model(),
357
+ artifact=artifact,
357
358
  version=self.version or str(self.version_number),
358
359
  user=self.user.to_model() if self.user else None,
359
360
  uri=self.uri,
@@ -284,6 +284,10 @@ class PipelineRunSchema(NamedSchema, table=True):
284
284
  deployment = self.deployment.to_model()
285
285
 
286
286
  config = deployment.pipeline_configuration
287
+ new_substitutions = config._get_full_substitutions(self.start_time)
288
+ config = config.model_copy(
289
+ update={"substitutions": new_substitutions}
290
+ )
287
291
  client_environment = deployment.client_environment
288
292
 
289
293
  stack = deployment.stack
@@ -323,9 +327,11 @@ class PipelineRunSchema(NamedSchema, table=True):
323
327
  build=build,
324
328
  schedule=schedule,
325
329
  code_reference=code_reference,
326
- trigger_execution=self.trigger_execution.to_model()
327
- if self.trigger_execution
328
- else None,
330
+ trigger_execution=(
331
+ self.trigger_execution.to_model()
332
+ if self.trigger_execution
333
+ else None
334
+ ),
329
335
  created=self.created,
330
336
  updated=self.updated,
331
337
  deployment_id=self.deployment_id,
@@ -344,6 +350,10 @@ class PipelineRunSchema(NamedSchema, table=True):
344
350
 
345
351
  steps = {step.name: step.to_model() for step in self.step_runs}
346
352
 
353
+ steps_substitutions = {
354
+ step_name: step.config.substitutions
355
+ for step_name, step in steps.items()
356
+ }
347
357
  metadata = PipelineRunResponseMetadata(
348
358
  workspace=self.workspace.to_model(),
349
359
  run_metadata=run_metadata,
@@ -361,6 +371,7 @@ class PipelineRunSchema(NamedSchema, table=True):
361
371
  if self.deployment
362
372
  else None,
363
373
  is_templatable=is_templatable,
374
+ steps_substitutions=steps_substitutions,
364
375
  )
365
376
 
366
377
  resources = None
@@ -23,6 +23,7 @@ from sqlalchemy import TEXT, Column, String
23
23
  from sqlalchemy.dialects.mysql import MEDIUMTEXT
24
24
  from sqlmodel import Field, Relationship, SQLModel
25
25
 
26
+ from zenml.config.pipeline_configurations import PipelineConfiguration
26
27
  from zenml.config.step_configurations import Step
27
28
  from zenml.constants import MEDIUMTEXT_MAX_LENGTH
28
29
  from zenml.enums import (
@@ -163,6 +164,9 @@ class StepRunSchema(NamedSchema, table=True):
163
164
  "primaryjoin": "StepRunParentsSchema.child_id == StepRunSchema.id",
164
165
  },
165
166
  )
167
+ pipeline_run: "PipelineRunSchema" = Relationship(
168
+ back_populates="step_runs"
169
+ )
166
170
  model_version: "ModelVersionSchema" = Relationship(
167
171
  back_populates="step_runs",
168
172
  )
@@ -248,6 +252,21 @@ class StepRunSchema(NamedSchema, table=True):
248
252
  full_step_config = Step.model_validate(
249
253
  step_configuration[self.name]
250
254
  )
255
+ new_substitutions = (
256
+ full_step_config.config._get_full_substitutions(
257
+ PipelineConfiguration.model_validate_json(
258
+ self.deployment.pipeline_configuration
259
+ ),
260
+ self.pipeline_run.start_time,
261
+ )
262
+ )
263
+ full_step_config = full_step_config.model_copy(
264
+ update={
265
+ "config": full_step_config.config.model_copy(
266
+ update={"substitutions": new_substitutions}
267
+ )
268
+ }
269
+ )
251
270
  elif not self.step_configuration:
252
271
  raise ValueError(
253
272
  f"Unable to load the configuration for step `{self.name}` from the"
@@ -2726,7 +2726,9 @@ class SqlZenStore(BaseZenStore):
2726
2726
  # -------------------- Artifact Versions --------------------
2727
2727
 
2728
2728
  def _get_or_create_artifact_for_name(
2729
- self, name: str, has_custom_name: bool
2729
+ self,
2730
+ name: str,
2731
+ has_custom_name: bool,
2730
2732
  ) -> ArtifactSchema:
2731
2733
  """Get or create an artifact with a specific name.
2732
2734
 
@@ -2747,7 +2749,8 @@ class SqlZenStore(BaseZenStore):
2747
2749
  try:
2748
2750
  with session.begin_nested():
2749
2751
  artifact_request = ArtifactRequest(
2750
- name=name, has_custom_name=has_custom_name
2752
+ name=name,
2753
+ has_custom_name=has_custom_name,
2751
2754
  )
2752
2755
  artifact = ArtifactSchema.from_request(
2753
2756
  artifact_request
@@ -8182,12 +8185,12 @@ class SqlZenStore(BaseZenStore):
8182
8185
  )
8183
8186
 
8184
8187
  # Save output artifact IDs into the database.
8185
- for output_name, artifact_version_ids in step_run.outputs.items():
8188
+ for name, artifact_version_ids in step_run.outputs.items():
8186
8189
  for artifact_version_id in artifact_version_ids:
8187
8190
  self._set_run_step_output_artifact(
8188
8191
  step_run_id=step_schema.id,
8189
8192
  artifact_version_id=artifact_version_id,
8190
- name=output_name,
8193
+ name=name,
8191
8194
  session=session,
8192
8195
  )
8193
8196
 
@@ -8291,13 +8294,14 @@ class SqlZenStore(BaseZenStore):
8291
8294
  session.add(existing_step_run)
8292
8295
 
8293
8296
  # Update the artifacts.
8294
- for name, artifact_version_id in step_run_update.outputs.items():
8295
- self._set_run_step_output_artifact(
8296
- step_run_id=step_run_id,
8297
- artifact_version_id=artifact_version_id,
8298
- name=name,
8299
- session=session,
8300
- )
8297
+ for name, artifact_version_ids in step_run_update.outputs.items():
8298
+ for artifact_version_id in artifact_version_ids:
8299
+ self._set_run_step_output_artifact(
8300
+ step_run_id=step_run_id,
8301
+ artifact_version_id=artifact_version_id,
8302
+ name=name,
8303
+ session=session,
8304
+ )
8301
8305
 
8302
8306
  # Update loaded artifacts.
8303
8307
  for (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zenml-nightly
3
- Version: 0.70.0.dev20241127
3
+ Version: 0.70.0.dev20241129
4
4
  Summary: ZenML: Write production-ready ML code.
5
5
  Home-page: https://zenml.io
6
6
  License: Apache-2.0