zenml-nightly 0.66.0.dev20240923__py3-none-any.whl → 0.66.0.dev20240925__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 (45) hide show
  1. zenml/VERSION +1 -1
  2. zenml/cli/__init__.py +7 -0
  3. zenml/cli/base.py +2 -2
  4. zenml/cli/pipeline.py +21 -0
  5. zenml/cli/utils.py +14 -11
  6. zenml/client.py +68 -3
  7. zenml/config/step_configurations.py +0 -5
  8. zenml/constants.py +3 -0
  9. zenml/enums.py +2 -0
  10. zenml/integrations/aws/flavors/sagemaker_orchestrator_flavor.py +76 -7
  11. zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +370 -115
  12. zenml/integrations/azure/orchestrators/azureml_orchestrator.py +157 -4
  13. zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +207 -18
  14. zenml/integrations/lightning/__init__.py +1 -1
  15. zenml/integrations/lightning/flavors/lightning_orchestrator_flavor.py +9 -0
  16. zenml/integrations/lightning/orchestrators/lightning_orchestrator.py +18 -17
  17. zenml/integrations/lightning/orchestrators/lightning_orchestrator_entrypoint.py +2 -6
  18. zenml/integrations/mlflow/steps/mlflow_registry.py +2 -0
  19. zenml/integrations/tensorboard/visualizers/tensorboard_visualizer.py +1 -1
  20. zenml/models/v2/base/filter.py +315 -149
  21. zenml/models/v2/base/scoped.py +5 -2
  22. zenml/models/v2/core/artifact_version.py +69 -8
  23. zenml/models/v2/core/model.py +43 -6
  24. zenml/models/v2/core/model_version.py +49 -1
  25. zenml/models/v2/core/model_version_artifact.py +18 -3
  26. zenml/models/v2/core/model_version_pipeline_run.py +18 -4
  27. zenml/models/v2/core/pipeline.py +108 -1
  28. zenml/models/v2/core/pipeline_run.py +172 -21
  29. zenml/models/v2/core/run_template.py +53 -1
  30. zenml/models/v2/core/stack.py +33 -5
  31. zenml/models/v2/core/step_run.py +7 -0
  32. zenml/new/pipelines/pipeline.py +4 -0
  33. zenml/new/pipelines/run_utils.py +4 -1
  34. zenml/orchestrators/base_orchestrator.py +41 -12
  35. zenml/stack/stack.py +11 -2
  36. zenml/utils/env_utils.py +54 -1
  37. zenml/utils/string_utils.py +50 -0
  38. zenml/zen_server/cloud_utils.py +33 -8
  39. zenml/zen_server/routers/runs_endpoints.py +89 -3
  40. zenml/zen_stores/sql_zen_store.py +1 -0
  41. {zenml_nightly-0.66.0.dev20240923.dist-info → zenml_nightly-0.66.0.dev20240925.dist-info}/METADATA +8 -1
  42. {zenml_nightly-0.66.0.dev20240923.dist-info → zenml_nightly-0.66.0.dev20240925.dist-info}/RECORD +45 -45
  43. {zenml_nightly-0.66.0.dev20240923.dist-info → zenml_nightly-0.66.0.dev20240925.dist-info}/LICENSE +0 -0
  44. {zenml_nightly-0.66.0.dev20240923.dist-info → zenml_nightly-0.66.0.dev20240925.dist-info}/WHEEL +0 -0
  45. {zenml_nightly-0.66.0.dev20240923.dist-info → zenml_nightly-0.66.0.dev20240925.dist-info}/entry_points.txt +0 -0
@@ -21,6 +21,7 @@ from typing import (
21
21
  List,
22
22
  Optional,
23
23
  Union,
24
+ cast,
24
25
  )
25
26
  from uuid import UUID
26
27
 
@@ -28,8 +29,7 @@ from pydantic import BaseModel, ConfigDict, Field
28
29
 
29
30
  from zenml.config.pipeline_configurations import PipelineConfiguration
30
31
  from zenml.constants import STR_FIELD_MAX_LENGTH
31
- from zenml.enums import ExecutionStatus, GenericFilterOps
32
- from zenml.models.v2.base.filter import StrFilter
32
+ from zenml.enums import ExecutionStatus
33
33
  from zenml.models.v2.base.scoped import (
34
34
  WorkspaceScopedFilter,
35
35
  WorkspaceScopedRequest,
@@ -137,7 +137,8 @@ class PipelineRunUpdate(BaseModel):
137
137
  "configured by this pipeline run explicitly.",
138
138
  default=None,
139
139
  )
140
- # TODO: we should maybe have a different update model here, the upper three attributes should only be for internal use
140
+ # TODO: we should maybe have a different update model here, the upper
141
+ # three attributes should only be for internal use
141
142
  add_tags: Optional[List[str]] = Field(
142
143
  default=None, title="New tags to add to the pipeline run."
143
144
  )
@@ -236,6 +237,7 @@ class PipelineRunResponseMetadata(WorkspaceScopedResponseMetadata):
236
237
  description="Template used for the pipeline run.",
237
238
  )
238
239
  is_templatable: bool = Field(
240
+ default=False,
239
241
  description="Whether a template can be created from this run.",
240
242
  )
241
243
 
@@ -308,6 +310,64 @@ class PipelineRunResponse(
308
310
 
309
311
  return get_artifacts_versions_of_pipeline_run(self, only_produced=True)
310
312
 
313
+ def refresh_run_status(self) -> "PipelineRunResponse":
314
+ """Method to refresh the status of a run if it is initializing/running.
315
+
316
+ Returns:
317
+ The updated pipeline.
318
+
319
+ Raises:
320
+ ValueError: If the stack of the run response is None.
321
+ """
322
+ if self.status in [
323
+ ExecutionStatus.INITIALIZING,
324
+ ExecutionStatus.RUNNING,
325
+ ]:
326
+ # Check if the stack still accessible
327
+ if self.stack is None:
328
+ raise ValueError(
329
+ "The stack that this pipeline run response was executed on"
330
+ "has been deleted."
331
+ )
332
+
333
+ # Create the orchestrator instance
334
+ from zenml.enums import StackComponentType
335
+ from zenml.orchestrators.base_orchestrator import BaseOrchestrator
336
+ from zenml.stack.stack_component import StackComponent
337
+
338
+ # Check if the stack still accessible
339
+ orchestrator_list = self.stack.components.get(
340
+ StackComponentType.ORCHESTRATOR, []
341
+ )
342
+ if len(orchestrator_list) == 0:
343
+ raise ValueError(
344
+ "The orchestrator that this pipeline run response was "
345
+ "executed with has been deleted."
346
+ )
347
+
348
+ orchestrator = cast(
349
+ BaseOrchestrator,
350
+ StackComponent.from_model(
351
+ component_model=orchestrator_list[0]
352
+ ),
353
+ )
354
+
355
+ # Fetch the status
356
+ status = orchestrator.fetch_status(run=self)
357
+
358
+ # If it is different from the current status, update it
359
+ if status != self.status:
360
+ from zenml.client import Client
361
+ from zenml.models import PipelineRunUpdate
362
+
363
+ client = Client()
364
+ return client.zen_store.update_run(
365
+ run_id=self.id,
366
+ run_update=PipelineRunUpdate(status=status),
367
+ )
368
+
369
+ return self
370
+
311
371
  # Body and metadata properties
312
372
  @property
313
373
  def status(self) -> ExecutionStatus:
@@ -522,6 +582,11 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
522
582
  "schedule_id",
523
583
  "stack_id",
524
584
  "template_id",
585
+ "user",
586
+ "pipeline",
587
+ "stack",
588
+ "code_repository",
589
+ "model",
525
590
  "pipeline_name",
526
591
  "templatable",
527
592
  ]
@@ -538,10 +603,6 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
538
603
  description="Pipeline associated with the Pipeline Run",
539
604
  union_mode="left_to_right",
540
605
  )
541
- pipeline_name: Optional[str] = Field(
542
- default=None,
543
- description="Name of the pipeline associated with the run",
544
- )
545
606
  workspace_id: Optional[Union[UUID, str]] = Field(
546
607
  default=None,
547
608
  description="Workspace of the Pipeline Run",
@@ -582,6 +643,11 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
582
643
  description="Template used for the pipeline run.",
583
644
  union_mode="left_to_right",
584
645
  )
646
+ model_version_id: Optional[Union[UUID, str]] = Field(
647
+ default=None,
648
+ description="Model version associated with the pipeline run.",
649
+ union_mode="left_to_right",
650
+ )
585
651
  status: Optional[str] = Field(
586
652
  default=None,
587
653
  description="Name of the Pipeline Run",
@@ -597,7 +663,37 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
597
663
  union_mode="left_to_right",
598
664
  )
599
665
  unlisted: Optional[bool] = None
600
- templatable: Optional[bool] = None
666
+ user: Optional[Union[UUID, str]] = Field(
667
+ default=None,
668
+ description="Name/ID of the user that created the run.",
669
+ )
670
+ # TODO: Remove once frontend is ready for it. This is replaced by the more
671
+ # generic `pipeline` filter below.
672
+ pipeline_name: Optional[str] = Field(
673
+ default=None,
674
+ description="Name of the pipeline associated with the run",
675
+ )
676
+ pipeline: Optional[Union[UUID, str]] = Field(
677
+ default=None,
678
+ description="Name/ID of the pipeline associated with the run.",
679
+ )
680
+ stack: Optional[Union[UUID, str]] = Field(
681
+ default=None,
682
+ description="Name/ID of the stack associated with the run.",
683
+ )
684
+ code_repository: Optional[Union[UUID, str]] = Field(
685
+ default=None,
686
+ description="Name/ID of the code repository associated with the run.",
687
+ )
688
+ model: Optional[Union[UUID, str]] = Field(
689
+ default=None,
690
+ description="Name/ID of the model associated with the run.",
691
+ )
692
+ templatable: Optional[bool] = Field(
693
+ default=None, description="Whether the run is templatable."
694
+ )
695
+
696
+ model_config = ConfigDict(protected_namespaces=())
601
697
 
602
698
  def get_custom_filters(
603
699
  self,
@@ -613,12 +709,16 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
613
709
 
614
710
  from zenml.zen_stores.schemas import (
615
711
  CodeReferenceSchema,
712
+ CodeRepositorySchema,
713
+ ModelSchema,
714
+ ModelVersionSchema,
616
715
  PipelineBuildSchema,
617
716
  PipelineDeploymentSchema,
618
717
  PipelineRunSchema,
619
718
  PipelineSchema,
620
719
  ScheduleSchema,
621
720
  StackSchema,
721
+ UserSchema,
622
722
  )
623
723
 
624
724
  if self.unlisted is not None:
@@ -628,19 +728,6 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
628
728
  unlisted_filter = PipelineRunSchema.pipeline_id.is_not(None) # type: ignore[union-attr]
629
729
  custom_filters.append(unlisted_filter)
630
730
 
631
- if self.pipeline_name is not None:
632
- value, filter_operator = self._resolve_operator(self.pipeline_name)
633
- filter_ = StrFilter(
634
- operation=GenericFilterOps(filter_operator),
635
- column="name",
636
- value=value,
637
- )
638
- pipeline_name_filter = and_(
639
- PipelineRunSchema.pipeline_id == PipelineSchema.id,
640
- filter_.generate_query_conditions(PipelineSchema),
641
- )
642
- custom_filters.append(pipeline_name_filter)
643
-
644
731
  if self.code_repository_id:
645
732
  code_repo_filter = and_(
646
733
  PipelineRunSchema.deployment_id == PipelineDeploymentSchema.id,
@@ -682,6 +769,70 @@ class PipelineRunFilter(WorkspaceScopedTaggableFilter):
682
769
  )
683
770
  custom_filters.append(run_template_filter)
684
771
 
772
+ if self.user:
773
+ user_filter = and_(
774
+ PipelineRunSchema.user_id == UserSchema.id,
775
+ self.generate_name_or_id_query_conditions(
776
+ value=self.user, table=UserSchema
777
+ ),
778
+ )
779
+ custom_filters.append(user_filter)
780
+
781
+ if self.pipeline:
782
+ pipeline_filter = and_(
783
+ PipelineRunSchema.pipeline_id == PipelineSchema.id,
784
+ self.generate_name_or_id_query_conditions(
785
+ value=self.pipeline, table=PipelineSchema
786
+ ),
787
+ )
788
+ custom_filters.append(pipeline_filter)
789
+
790
+ if self.stack:
791
+ stack_filter = and_(
792
+ PipelineRunSchema.deployment_id == PipelineDeploymentSchema.id,
793
+ PipelineDeploymentSchema.stack_id == StackSchema.id,
794
+ self.generate_name_or_id_query_conditions(
795
+ value=self.stack,
796
+ table=StackSchema,
797
+ ),
798
+ )
799
+ custom_filters.append(stack_filter)
800
+
801
+ if self.code_repository:
802
+ code_repo_filter = and_(
803
+ PipelineRunSchema.deployment_id == PipelineDeploymentSchema.id,
804
+ PipelineDeploymentSchema.code_reference_id
805
+ == CodeReferenceSchema.id,
806
+ CodeReferenceSchema.code_repository_id
807
+ == CodeRepositorySchema.id,
808
+ self.generate_name_or_id_query_conditions(
809
+ value=self.code_repository,
810
+ table=CodeRepositorySchema,
811
+ ),
812
+ )
813
+ custom_filters.append(code_repo_filter)
814
+
815
+ if self.model:
816
+ model_filter = and_(
817
+ PipelineRunSchema.model_version_id == ModelVersionSchema.id,
818
+ ModelVersionSchema.model_id == ModelSchema.id,
819
+ self.generate_name_or_id_query_conditions(
820
+ value=self.model, table=ModelSchema
821
+ ),
822
+ )
823
+ custom_filters.append(model_filter)
824
+
825
+ if self.pipeline_name:
826
+ pipeline_name_filter = and_(
827
+ PipelineRunSchema.pipeline_id == PipelineSchema.id,
828
+ self.generate_custom_query_conditions_for_column(
829
+ value=self.pipeline_name,
830
+ table=PipelineSchema,
831
+ column="name",
832
+ ),
833
+ )
834
+ custom_filters.append(pipeline_name_filter)
835
+
685
836
  if self.templatable is not None:
686
837
  if self.templatable is True:
687
838
  templatable_filter = and_(
@@ -299,7 +299,11 @@ class RunTemplateFilter(WorkspaceScopedTaggableFilter):
299
299
  *WorkspaceScopedTaggableFilter.FILTER_EXCLUDE_FIELDS,
300
300
  "code_repository_id",
301
301
  "stack_id",
302
- "build_id" "pipeline_id",
302
+ "build_id",
303
+ "pipeline_id",
304
+ "user",
305
+ "pipeline",
306
+ "stack",
303
307
  ]
304
308
 
305
309
  name: Optional[str] = Field(
@@ -336,6 +340,18 @@ class RunTemplateFilter(WorkspaceScopedTaggableFilter):
336
340
  description="Code repository associated with the template.",
337
341
  union_mode="left_to_right",
338
342
  )
343
+ user: Optional[Union[UUID, str]] = Field(
344
+ default=None,
345
+ description="Name/ID of the user that created the template.",
346
+ )
347
+ pipeline: Optional[Union[UUID, str]] = Field(
348
+ default=None,
349
+ description="Name/ID of the pipeline associated with the template.",
350
+ )
351
+ stack: Optional[Union[UUID, str]] = Field(
352
+ default=None,
353
+ description="Name/ID of the stack associated with the template.",
354
+ )
339
355
 
340
356
  def get_custom_filters(
341
357
  self,
@@ -352,7 +368,10 @@ class RunTemplateFilter(WorkspaceScopedTaggableFilter):
352
368
  from zenml.zen_stores.schemas import (
353
369
  CodeReferenceSchema,
354
370
  PipelineDeploymentSchema,
371
+ PipelineSchema,
355
372
  RunTemplateSchema,
373
+ StackSchema,
374
+ UserSchema,
356
375
  )
357
376
 
358
377
  if self.code_repository_id:
@@ -390,4 +409,37 @@ class RunTemplateFilter(WorkspaceScopedTaggableFilter):
390
409
  )
391
410
  custom_filters.append(pipeline_filter)
392
411
 
412
+ if self.user:
413
+ user_filter = and_(
414
+ RunTemplateSchema.user_id == UserSchema.id,
415
+ self.generate_name_or_id_query_conditions(
416
+ value=self.user, table=UserSchema
417
+ ),
418
+ )
419
+ custom_filters.append(user_filter)
420
+
421
+ if self.pipeline:
422
+ pipeline_filter = and_(
423
+ RunTemplateSchema.source_deployment_id
424
+ == PipelineDeploymentSchema.id,
425
+ PipelineDeploymentSchema.pipeline_id == PipelineSchema.id,
426
+ self.generate_name_or_id_query_conditions(
427
+ value=self.pipeline,
428
+ table=PipelineSchema,
429
+ ),
430
+ )
431
+ custom_filters.append(pipeline_filter)
432
+
433
+ if self.stack:
434
+ stack_filter = and_(
435
+ RunTemplateSchema.source_deployment_id
436
+ == PipelineDeploymentSchema.id,
437
+ PipelineDeploymentSchema.stack_id == StackSchema.id,
438
+ self.generate_name_or_id_query_conditions(
439
+ value=self.stack,
440
+ table=StackSchema,
441
+ ),
442
+ )
443
+ custom_filters.append(stack_filter)
444
+
393
445
  return custom_filters
@@ -318,12 +318,11 @@ class StackFilter(WorkspaceScopedFilter):
318
318
  scoping.
319
319
  """
320
320
 
321
- # `component_id` refers to a relationship through a link-table
322
- # rather than a field in the db, hence it needs to be handled
323
- # explicitly
324
321
  FILTER_EXCLUDE_FIELDS: ClassVar[List[str]] = [
325
322
  *WorkspaceScopedFilter.FILTER_EXCLUDE_FIELDS,
326
- "component_id", # This is a relationship, not a field
323
+ "component_id",
324
+ "user",
325
+ "component",
327
326
  ]
328
327
 
329
328
  name: Optional[str] = Field(
@@ -348,6 +347,13 @@ class StackFilter(WorkspaceScopedFilter):
348
347
  description="Component in the stack",
349
348
  union_mode="left_to_right",
350
349
  )
350
+ user: Optional[Union[UUID, str]] = Field(
351
+ default=None,
352
+ description="Name/ID of the user that created the stack.",
353
+ )
354
+ component: Optional[Union[UUID, str]] = Field(
355
+ default=None, description="Name/ID of a component in the stack."
356
+ )
351
357
 
352
358
  def get_custom_filters(self) -> List["ColumnElement[bool]"]:
353
359
  """Get custom filters.
@@ -357,9 +363,11 @@ class StackFilter(WorkspaceScopedFilter):
357
363
  """
358
364
  custom_filters = super().get_custom_filters()
359
365
 
360
- from zenml.zen_stores.schemas.stack_schemas import (
366
+ from zenml.zen_stores.schemas import (
367
+ StackComponentSchema,
361
368
  StackCompositionSchema,
362
369
  StackSchema,
370
+ UserSchema,
363
371
  )
364
372
 
365
373
  if self.component_id:
@@ -369,4 +377,24 @@ class StackFilter(WorkspaceScopedFilter):
369
377
  )
370
378
  custom_filters.append(component_id_filter)
371
379
 
380
+ if self.user:
381
+ user_filter = and_(
382
+ StackSchema.user_id == UserSchema.id,
383
+ self.generate_name_or_id_query_conditions(
384
+ value=self.user, table=UserSchema
385
+ ),
386
+ )
387
+ custom_filters.append(user_filter)
388
+
389
+ if self.component:
390
+ component_filter = and_(
391
+ StackCompositionSchema.stack_id == StackSchema.id,
392
+ StackCompositionSchema.component_id == StackComponentSchema.id,
393
+ self.generate_name_or_id_query_conditions(
394
+ value=self.component,
395
+ table=StackComponentSchema,
396
+ ),
397
+ )
398
+ custom_filters.append(component_filter)
399
+
372
400
  return custom_filters
@@ -536,3 +536,10 @@ class StepRunFilter(WorkspaceScopedFilter):
536
536
  description="Workspace of this step run",
537
537
  union_mode="left_to_right",
538
538
  )
539
+ model_version_id: Optional[Union[UUID, str]] = Field(
540
+ default=None,
541
+ description="Model version associated with the pipeline run.",
542
+ union_mode="left_to_right",
543
+ )
544
+
545
+ model_config = ConfigDict(protected_namespaces=())
@@ -84,6 +84,7 @@ from zenml.utils import (
84
84
  code_utils,
85
85
  dashboard_utils,
86
86
  dict_utils,
87
+ env_utils,
87
88
  pydantic_utils,
88
89
  settings_utils,
89
90
  source_utils,
@@ -1030,12 +1031,14 @@ To avoid this consider setting pipeline parameters only in one place (config or
1030
1031
 
1031
1032
  # Update with the values in code so they take precedence
1032
1033
  run_config = pydantic_utils.update_model(run_config, update=update)
1034
+ run_config = env_utils.substitute_env_variable_placeholders(run_config)
1033
1035
 
1034
1036
  deployment = Compiler().compile(
1035
1037
  pipeline=self,
1036
1038
  stack=Client().active_stack,
1037
1039
  run_configuration=run_config,
1038
1040
  )
1041
+ deployment = env_utils.substitute_env_variable_placeholders(deployment)
1039
1042
 
1040
1043
  return deployment, run_config.schedule, run_config.build
1041
1044
 
@@ -1252,6 +1255,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
1252
1255
  if config_path:
1253
1256
  with open(config_path, "r") as f:
1254
1257
  _from_config_file = yaml.load(f, Loader=yaml.SafeLoader)
1258
+
1255
1259
  _from_config_file = dict_utils.remove_none_values(
1256
1260
  {k: v for k, v in _from_config_file.items() if k in matcher}
1257
1261
  )
@@ -138,7 +138,10 @@ def deploy_pipeline(
138
138
  previous_value = constants.SHOULD_PREVENT_PIPELINE_EXECUTION
139
139
  constants.SHOULD_PREVENT_PIPELINE_EXECUTION = True
140
140
  try:
141
- stack.deploy_pipeline(deployment=deployment)
141
+ stack.deploy_pipeline(
142
+ deployment=deployment,
143
+ placeholder_run=placeholder_run,
144
+ )
142
145
  except Exception as e:
143
146
  if (
144
147
  placeholder_run
@@ -14,12 +14,14 @@
14
14
  """Base orchestrator class."""
15
15
 
16
16
  from abc import ABC, abstractmethod
17
- from typing import TYPE_CHECKING, Any, Dict, Optional, Type, cast
17
+ from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Type, cast
18
18
 
19
19
  from pydantic import model_validator
20
20
 
21
- from zenml.enums import StackComponentType
21
+ from zenml.enums import ExecutionStatus, StackComponentType
22
22
  from zenml.logger import get_logger
23
+ from zenml.metadata.metadata_types import MetadataType
24
+ from zenml.orchestrators.publish_utils import publish_pipeline_run_metadata
23
25
  from zenml.orchestrators.step_launcher import StepLauncher
24
26
  from zenml.orchestrators.utils import get_config_environment_vars
25
27
  from zenml.stack import Flavor, Stack, StackComponent, StackComponentConfig
@@ -27,7 +29,7 @@ from zenml.utils.pydantic_utils import before_validator_handler
27
29
 
28
30
  if TYPE_CHECKING:
29
31
  from zenml.config.step_configurations import Step
30
- from zenml.models import PipelineDeploymentResponse
32
+ from zenml.models import PipelineDeploymentResponse, PipelineRunResponse
31
33
 
32
34
  logger = get_logger(__name__)
33
35
 
@@ -124,7 +126,7 @@ class BaseOrchestrator(StackComponent, ABC):
124
126
  deployment: "PipelineDeploymentResponse",
125
127
  stack: "Stack",
126
128
  environment: Dict[str, str],
127
- ) -> Any:
129
+ ) -> Optional[Iterator[Dict[str, MetadataType]]]:
128
130
  """The method needs to be implemented by the respective orchestrator.
129
131
 
130
132
  Depending on the type of orchestrator you'll have to perform slightly
@@ -169,29 +171,41 @@ class BaseOrchestrator(StackComponent, ABC):
169
171
  self,
170
172
  deployment: "PipelineDeploymentResponse",
171
173
  stack: "Stack",
174
+ placeholder_run: Optional["PipelineRunResponse"] = None,
172
175
  ) -> Any:
173
176
  """Runs a pipeline on a stack.
174
177
 
175
178
  Args:
176
179
  deployment: The pipeline deployment.
177
180
  stack: The stack on which to run the pipeline.
178
-
179
- Returns:
180
- Orchestrator-specific return value.
181
+ placeholder_run: An optional placeholder run for the deployment.
182
+ This will be deleted in case the pipeline deployment failed.
181
183
  """
182
184
  self._prepare_run(deployment=deployment)
183
185
 
184
186
  environment = get_config_environment_vars(deployment=deployment)
185
187
 
186
188
  try:
187
- result = self.prepare_or_run_pipeline(
188
- deployment=deployment, stack=stack, environment=environment
189
- )
189
+ if metadata_iterator := self.prepare_or_run_pipeline(
190
+ deployment=deployment,
191
+ stack=stack,
192
+ environment=environment,
193
+ ):
194
+ for metadata_dict in metadata_iterator:
195
+ try:
196
+ if placeholder_run:
197
+ publish_pipeline_run_metadata(
198
+ pipeline_run_id=placeholder_run.id,
199
+ pipeline_run_metadata={self.id: metadata_dict},
200
+ )
201
+ except Exception as e:
202
+ logger.debug(
203
+ "Something went went wrong trying to publish the"
204
+ f"run metadata: {e}"
205
+ )
190
206
  finally:
191
207
  self._cleanup_run()
192
208
 
193
- return result
194
-
195
209
  def run_step(self, step: "Step") -> None:
196
210
  """Runs the given step.
197
211
 
@@ -239,6 +253,21 @@ class BaseOrchestrator(StackComponent, ABC):
239
253
  """Cleans up the active run."""
240
254
  self._active_deployment = None
241
255
 
256
+ def fetch_status(self, run: "PipelineRunResponse") -> ExecutionStatus:
257
+ """Refreshes the status of a specific pipeline run.
258
+
259
+ Args:
260
+ run: A pipeline run response to fetch its status.
261
+
262
+ Raises:
263
+ NotImplementedError: If any orchestrator inheriting from the base
264
+ class does not implement this logic.
265
+ """
266
+ raise NotImplementedError(
267
+ "The fetch status functionality is not implemented for the "
268
+ f"'{self.__class__.__name__}' orchestrator."
269
+ )
270
+
242
271
 
243
272
  class BaseOrchestratorFlavor(Flavor):
244
273
  """Base orchestrator flavor class."""
zenml/stack/stack.py CHANGED
@@ -62,7 +62,11 @@ if TYPE_CHECKING:
62
62
  from zenml.image_builders import BaseImageBuilder
63
63
  from zenml.model_deployers import BaseModelDeployer
64
64
  from zenml.model_registries import BaseModelRegistry
65
- from zenml.models import PipelineDeploymentBase, PipelineDeploymentResponse
65
+ from zenml.models import (
66
+ PipelineDeploymentBase,
67
+ PipelineDeploymentResponse,
68
+ PipelineRunResponse,
69
+ )
66
70
  from zenml.orchestrators import BaseOrchestrator
67
71
  from zenml.stack import StackComponent
68
72
  from zenml.step_operators import BaseStepOperator
@@ -826,16 +830,21 @@ class Stack:
826
830
  def deploy_pipeline(
827
831
  self,
828
832
  deployment: "PipelineDeploymentResponse",
833
+ placeholder_run: Optional["PipelineRunResponse"] = None,
829
834
  ) -> Any:
830
835
  """Deploys a pipeline on this stack.
831
836
 
832
837
  Args:
833
838
  deployment: The pipeline deployment.
839
+ placeholder_run: An optional placeholder run for the deployment.
840
+ This will be deleted in case the pipeline deployment failed.
834
841
 
835
842
  Returns:
836
843
  The return value of the call to `orchestrator.run_pipeline(...)`.
837
844
  """
838
- return self.orchestrator.run(deployment=deployment, stack=self)
845
+ return self.orchestrator.run(
846
+ deployment=deployment, stack=self, placeholder_run=placeholder_run
847
+ )
839
848
 
840
849
  def _get_active_components_for_step(
841
850
  self, step_config: "StepConfiguration"
zenml/utils/env_utils.py CHANGED
@@ -14,7 +14,16 @@
14
14
  """Utility functions for handling environment variables."""
15
15
 
16
16
  import os
17
- from typing import Dict, List, Optional, cast
17
+ import re
18
+ from typing import Any, Dict, List, Match, Optional, TypeVar, cast
19
+
20
+ from zenml.logger import get_logger
21
+ from zenml.utils import string_utils
22
+
23
+ logger = get_logger(__name__)
24
+
25
+ V = TypeVar("V", bound=Any)
26
+ ENV_VARIABLE_PLACEHOLDER_PATTERN = re.compile(pattern=r"\$\{([a-zA-Z0-9_]+)\}")
18
27
 
19
28
  ENV_VAR_CHUNK_SUFFIX = "_CHUNK_"
20
29
 
@@ -99,3 +108,47 @@ def reconstruct_environment_variables(
99
108
  # Remove the chunk environment variables
100
109
  for key in chunk_keys:
101
110
  env.pop(key)
111
+
112
+
113
+ def substitute_env_variable_placeholders(
114
+ value: V, raise_when_missing: bool = True
115
+ ) -> V:
116
+ """Substitute environment variable placeholders in an object.
117
+
118
+ Args:
119
+ value: The object in which to substitute the placeholders.
120
+ raise_when_missing: If True, an exception will be raised when an
121
+ environment variable is missing. Otherwise, a warning will be logged
122
+ instead.
123
+
124
+ Returns:
125
+ The object with placeholders substituted.
126
+ """
127
+
128
+ def _replace_with_env_variable_value(match: Match[str]) -> str:
129
+ key = match.group(1)
130
+ if key in os.environ:
131
+ return os.environ[key]
132
+ else:
133
+ if raise_when_missing:
134
+ raise KeyError(
135
+ "Unable to substitute environment variable placeholder "
136
+ f"'{key}' because the environment variable is not set."
137
+ )
138
+ else:
139
+ logger.warning(
140
+ "Unable to substitute environment variable placeholder %s "
141
+ "because the environment variable is not set, using an "
142
+ "empty string instead.",
143
+ key,
144
+ )
145
+ return ""
146
+
147
+ def _substitution_func(v: str) -> str:
148
+ return ENV_VARIABLE_PLACEHOLDER_PATTERN.sub(
149
+ _replace_with_env_variable_value, v
150
+ )
151
+
152
+ return string_utils.substitute_string(
153
+ value=value, substitution_func=_substitution_func
154
+ )