zenml-nightly 0.80.2.dev20250415__py3-none-any.whl → 0.80.2.dev20250416__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 (40) hide show
  1. zenml/VERSION +1 -1
  2. zenml/cli/utils.py +13 -11
  3. zenml/config/compiler.py +1 -0
  4. zenml/config/global_config.py +1 -1
  5. zenml/config/pipeline_configurations.py +1 -0
  6. zenml/config/pipeline_run_configuration.py +1 -0
  7. zenml/constants.py +4 -1
  8. zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +47 -5
  9. zenml/integrations/gcp/vertex_custom_job_parameters.py +15 -1
  10. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +0 -1
  11. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +0 -1
  12. zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +0 -3
  13. zenml/logging/step_logging.py +41 -21
  14. zenml/login/credentials_store.py +31 -0
  15. zenml/models/v2/base/base.py +8 -4
  16. zenml/models/v2/base/filter.py +1 -1
  17. zenml/models/v2/core/pipeline_run.py +19 -0
  18. zenml/orchestrators/step_launcher.py +2 -3
  19. zenml/orchestrators/step_runner.py +2 -2
  20. zenml/orchestrators/utils.py +2 -5
  21. zenml/pipelines/pipeline_context.py +1 -0
  22. zenml/pipelines/pipeline_decorator.py +4 -0
  23. zenml/pipelines/pipeline_definition.py +83 -22
  24. zenml/pipelines/run_utils.py +4 -0
  25. zenml/steps/utils.py +1 -1
  26. zenml/zen_server/auth.py +44 -64
  27. zenml/zen_server/download_utils.py +26 -29
  28. zenml/zen_server/jwt.py +0 -14
  29. zenml/zen_server/routers/auth_endpoints.py +5 -36
  30. zenml/zen_server/routers/pipeline_deployments_endpoints.py +63 -26
  31. zenml/zen_server/routers/runs_endpoints.py +57 -0
  32. zenml/zen_server/template_execution/utils.py +1 -1
  33. zenml/zen_stores/rest_zen_store.py +16 -13
  34. zenml/zen_stores/schemas/pipeline_run_schemas.py +1 -0
  35. zenml/zen_stores/sql_zen_store.py +18 -0
  36. {zenml_nightly-0.80.2.dev20250415.dist-info → zenml_nightly-0.80.2.dev20250416.dist-info}/METADATA +2 -1
  37. {zenml_nightly-0.80.2.dev20250415.dist-info → zenml_nightly-0.80.2.dev20250416.dist-info}/RECORD +40 -40
  38. {zenml_nightly-0.80.2.dev20250415.dist-info → zenml_nightly-0.80.2.dev20250416.dist-info}/LICENSE +0 -0
  39. {zenml_nightly-0.80.2.dev20250415.dist-info → zenml_nightly-0.80.2.dev20250416.dist-info}/WHEEL +0 -0
  40. {zenml_nightly-0.80.2.dev20250415.dist-info → zenml_nightly-0.80.2.dev20250416.dist-info}/entry_points.txt +0 -0
@@ -477,7 +477,6 @@ def api_token(
477
477
  expires_in: Optional[int] = None,
478
478
  schedule_id: Optional[UUID] = None,
479
479
  pipeline_run_id: Optional[UUID] = None,
480
- step_run_id: Optional[UUID] = None,
481
480
  auth_context: AuthContext = Security(authorize),
482
481
  ) -> str:
483
482
  """Generate an API token for the current user.
@@ -506,7 +505,6 @@ def api_token(
506
505
  schedule_id: The ID of the schedule to scope the workload API token to.
507
506
  pipeline_run_id: The ID of the pipeline run to scope the workload API
508
507
  token to.
509
- step_run_id: The ID of the step run to scope the workload API token to.
510
508
  auth_context: The authentication context.
511
509
 
512
510
  Returns:
@@ -522,10 +520,10 @@ def api_token(
522
520
  raise AuthorizationException("Not authenticated.")
523
521
 
524
522
  if token_type == APITokenType.GENERIC:
525
- if schedule_id or pipeline_run_id or step_run_id:
523
+ if schedule_id or pipeline_run_id:
526
524
  raise ValueError(
527
- "Generic API tokens cannot be scoped to a schedule, pipeline "
528
- "run or step run."
525
+ "Generic API tokens cannot be scoped to a schedule or pipeline "
526
+ "run."
529
527
  )
530
528
 
531
529
  config = server_config()
@@ -549,12 +547,10 @@ def api_token(
549
547
 
550
548
  schedule_id = schedule_id or token.schedule_id
551
549
  pipeline_run_id = pipeline_run_id or token.pipeline_run_id
552
- step_run_id = step_run_id or token.step_run_id
553
550
 
554
- if not pipeline_run_id and not schedule_id and not step_run_id:
551
+ if not pipeline_run_id and not schedule_id:
555
552
  raise ValueError(
556
- "Workload API tokens must be scoped to a schedule, pipeline run "
557
- "or step run."
553
+ "Workload API tokens must be scoped to a schedule or pipeline run."
558
554
  )
559
555
 
560
556
  if schedule_id and token.schedule_id and schedule_id != token.schedule_id:
@@ -575,13 +571,6 @@ def api_token(
575
571
  f"pipeline run {token.pipeline_run_id}."
576
572
  )
577
573
 
578
- if step_run_id and token.step_run_id and step_run_id != token.step_run_id:
579
- raise AuthorizationException(
580
- f"Unable to scope API token to step run {step_run_id}. The "
581
- f"token used to authorize this request is already scoped to "
582
- f"step run {token.step_run_id}."
583
- )
584
-
585
574
  project_id: Optional[UUID] = None
586
575
 
587
576
  if schedule_id:
@@ -623,25 +612,6 @@ def api_token(
623
612
  "for security reasons."
624
613
  )
625
614
 
626
- if step_run_id:
627
- # The step run must exist and the step must not be concluded
628
- try:
629
- step_run = zen_store().get_run_step(step_run_id, hydrate=False)
630
- except KeyError:
631
- raise ValueError(
632
- f"Step run {step_run_id} does not exist and API tokens cannot "
633
- "be generated for non-existent step runs for security reasons."
634
- )
635
-
636
- project_id = step_run.project.id
637
-
638
- if step_run.status.is_finished:
639
- raise ValueError(
640
- f"The execution of step run {step_run_id} has already "
641
- "concluded and API tokens can no longer be generated for it "
642
- "for security reasons."
643
- )
644
-
645
615
  assert project_id is not None
646
616
  verify_permission(
647
617
  resource_type=ResourceType.PIPELINE_RUN,
@@ -656,7 +626,6 @@ def api_token(
656
626
  device=auth_context.device,
657
627
  schedule_id=schedule_id,
658
628
  pipeline_run_id=pipeline_run_id,
659
- step_run_id=step_run_id,
660
629
  # Don't include the access token as a cookie in the response
661
630
  response=None,
662
631
  # Never expire the token
@@ -19,11 +19,13 @@ from uuid import UUID
19
19
  from fastapi import APIRouter, Depends, Security
20
20
 
21
21
  from zenml.constants import API, PIPELINE_DEPLOYMENTS, VERSION_1
22
+ from zenml.logging.step_logging import fetch_logs
22
23
  from zenml.models import (
23
24
  Page,
24
25
  PipelineDeploymentFilter,
25
26
  PipelineDeploymentRequest,
26
27
  PipelineDeploymentResponse,
28
+ PipelineRunFilter,
27
29
  )
28
30
  from zenml.zen_server.auth import AuthContext, authorize
29
31
  from zenml.zen_server.exceptions import error_response
@@ -179,33 +181,68 @@ def delete_deployment(
179
181
  )
180
182
 
181
183
 
182
- if server_config().workload_manager_enabled:
184
+ @router.get(
185
+ "/{deployment_id}/logs",
186
+ responses={
187
+ 401: error_response,
188
+ 404: error_response,
189
+ 422: error_response,
190
+ },
191
+ )
192
+ @handle_exceptions
193
+ def deployment_logs(
194
+ deployment_id: UUID,
195
+ offset: int = 0,
196
+ length: int = 1024 * 1024 * 16, # Default to 16MiB of data
197
+ _: AuthContext = Security(authorize),
198
+ ) -> str:
199
+ """Get deployment logs.
200
+
201
+ Args:
202
+ deployment_id: ID of the deployment.
203
+ offset: The offset from which to start reading.
204
+ length: The amount of bytes that should be read.
205
+
206
+ Returns:
207
+ The deployment logs.
208
+
209
+ Raises:
210
+ KeyError: If no logs are available for the deployment.
211
+ """
212
+ store = zen_store()
183
213
 
184
- @router.get(
185
- "/{deployment_id}/logs",
186
- responses={
187
- 401: error_response,
188
- 404: error_response,
189
- 422: error_response,
190
- },
214
+ deployment = verify_permissions_and_get_entity(
215
+ id=deployment_id,
216
+ get_method=store.get_deployment,
217
+ hydrate=True,
191
218
  )
192
- @handle_exceptions
193
- def deployment_logs(
194
- deployment_id: UUID,
195
- _: AuthContext = Security(authorize),
196
- ) -> str:
197
- """Get deployment logs.
198
-
199
- Args:
200
- deployment_id: ID of the deployment.
201
-
202
- Returns:
203
- The deployment logs.
204
- """
205
- deployment = verify_permissions_and_get_entity(
206
- id=deployment_id,
207
- get_method=zen_store().get_deployment,
208
- hydrate=True,
209
- )
210
219
 
220
+ if deployment.template_id and server_config().workload_manager_enabled:
211
221
  return workload_manager().get_logs(workload_id=deployment.id)
222
+
223
+ # Get the last pipeline run for this deployment
224
+ pipeline_runs = store.list_runs(
225
+ runs_filter_model=PipelineRunFilter(
226
+ project=deployment.project.id,
227
+ sort_by="asc:created",
228
+ size=1,
229
+ deployment_id=deployment.id,
230
+ )
231
+ )
232
+
233
+ if len(pipeline_runs.items) == 0:
234
+ return ""
235
+
236
+ run = pipeline_runs.items[0]
237
+
238
+ logs = run.logs
239
+ if logs is None:
240
+ raise KeyError("No logs available for this deployment")
241
+
242
+ return fetch_logs(
243
+ zen_store=store,
244
+ artifact_store_id=logs.artifact_store_id,
245
+ logs_uri=logs.uri,
246
+ offset=offset,
247
+ length=length,
248
+ )
@@ -29,6 +29,7 @@ from zenml.constants import (
29
29
  )
30
30
  from zenml.enums import ExecutionStatus, StackComponentType
31
31
  from zenml.logger import get_logger
32
+ from zenml.logging.step_logging import fetch_logs
32
33
  from zenml.models import (
33
34
  Page,
34
35
  PipelineRunFilter,
@@ -55,6 +56,8 @@ from zenml.zen_server.routers.projects_endpoints import workspace_router
55
56
  from zenml.zen_server.utils import (
56
57
  handle_exceptions,
57
58
  make_dependable,
59
+ server_config,
60
+ workload_manager,
58
61
  zen_store,
59
62
  )
60
63
 
@@ -375,3 +378,57 @@ def refresh_run_status(
375
378
  f"The stack, the run '{run.id}' was executed on, is deleted."
376
379
  )
377
380
  run.refresh_run_status()
381
+
382
+
383
+ @router.get(
384
+ "/{run_id}/logs",
385
+ responses={
386
+ 401: error_response,
387
+ 404: error_response,
388
+ 422: error_response,
389
+ },
390
+ )
391
+ @handle_exceptions
392
+ def run_logs(
393
+ run_id: UUID,
394
+ offset: int = 0,
395
+ length: int = 1024 * 1024 * 16, # Default to 16MiB of data
396
+ _: AuthContext = Security(authorize),
397
+ ) -> str:
398
+ """Get pipeline run logs.
399
+
400
+ Args:
401
+ run_id: ID of the pipeline run.
402
+ offset: The offset from which to start reading.
403
+ length: The amount of bytes that should be read.
404
+
405
+ Returns:
406
+ The pipeline run logs.
407
+
408
+ Raises:
409
+ KeyError: If no logs are available for the pipeline run.
410
+ """
411
+ store = zen_store()
412
+
413
+ run = verify_permissions_and_get_entity(
414
+ id=run_id,
415
+ get_method=store.get_run,
416
+ hydrate=True,
417
+ )
418
+
419
+ if run.deployment_id:
420
+ deployment = store.get_deployment(run.deployment_id)
421
+ if deployment.template_id and server_config().workload_manager_enabled:
422
+ return workload_manager().get_logs(workload_id=deployment.id)
423
+
424
+ logs = run.logs
425
+ if logs is None:
426
+ raise KeyError("No logs available for this pipeline run")
427
+
428
+ return fetch_logs(
429
+ zen_store=store,
430
+ artifact_store_id=logs.artifact_store_id,
431
+ logs_uri=logs.uri,
432
+ offset=offset,
433
+ length=length,
434
+ )
@@ -373,7 +373,7 @@ def deployment_request_from_template(
373
373
  )
374
374
 
375
375
  step_config_dict_base = pipeline_configuration.model_dump(
376
- exclude={"name", "parameters", "tags"}
376
+ exclude={"name", "parameters", "tags", "enable_pipeline_logs"}
377
377
  )
378
378
  steps = {}
379
379
  for invocation_id, step in deployment.step_configurations.items():
@@ -4074,15 +4074,6 @@ class RestZenStore(BaseZenStore):
4074
4074
  # Check if username and password are configured
4075
4075
  username, password = credentials_store.get_password(self.url)
4076
4076
 
4077
- api_key_hint = (
4078
- "\nHint: If you're getting this error in an automated, "
4079
- "non-interactive workload like a pipeline run or a CI/CD job, "
4080
- "you should use a service account API key to authenticate to "
4081
- "the server instead of temporary CLI login credentials. For "
4082
- "more information, see "
4083
- "https://docs.zenml.io/how-to/project-setup-and-management/connecting-to-zenml/connect-with-a-service-account"
4084
- )
4085
-
4086
4077
  if api_key is not None:
4087
4078
  # An API key is configured. Use it as a password to
4088
4079
  # authenticate.
@@ -4119,14 +4110,12 @@ class RestZenStore(BaseZenStore):
4119
4110
  "You need to be logged in to ZenML Pro in order to "
4120
4111
  f"access the ZenML Pro server '{self.url}'. Please run "
4121
4112
  "'zenml login' to log in or choose a different server."
4122
- + api_key_hint
4123
4113
  )
4124
4114
 
4125
4115
  elif pro_token.expired:
4126
4116
  raise CredentialsNotValid(
4127
4117
  "Your ZenML Pro login session has expired. "
4128
4118
  "Please log in again using 'zenml login'."
4129
- + api_key_hint
4130
4119
  )
4131
4120
 
4132
4121
  data = {
@@ -4140,13 +4129,12 @@ class RestZenStore(BaseZenStore):
4140
4129
  raise CredentialsNotValid(
4141
4130
  "No valid credentials found. Please run 'zenml login "
4142
4131
  f"--url {self.url}' to connect to the current server."
4143
- + api_key_hint
4144
4132
  )
4145
4133
  elif token.expired:
4146
4134
  raise CredentialsNotValid(
4147
4135
  "Your authentication to the current server has expired. "
4148
4136
  "Please log in again using 'zenml login --url "
4149
- f"{self.url}'." + api_key_hint
4137
+ f"{self.url}'."
4150
4138
  )
4151
4139
 
4152
4140
  response = self._handle_response(
@@ -4405,6 +4393,7 @@ class RestZenStore(BaseZenStore):
4405
4393
  # explicitly indicates that the credentials are not valid and
4406
4394
  # they can be thrown away or when the request is not
4407
4395
  # authenticated at all.
4396
+ credentials_store = get_credentials_store()
4408
4397
 
4409
4398
  if self._api_token is None:
4410
4399
  # The last request was not authenticated with an API
@@ -4416,6 +4405,20 @@ class RestZenStore(BaseZenStore):
4416
4405
  "Re-authenticating and retrying..."
4417
4406
  )
4418
4407
  self.authenticate()
4408
+ elif not credentials_store.can_login(self.url):
4409
+ # The request failed either because we're not
4410
+ # authenticated or our current credentials are not valid
4411
+ # anymore.
4412
+ logger.error(
4413
+ "The current token is no longer valid, and "
4414
+ "it is not possible to generate a new token using the "
4415
+ "configured credentials. Please run "
4416
+ f"`zenml login --url {self.url}` to re-authenticate to "
4417
+ "the server or authenticate using an API key. See "
4418
+ "https://docs.zenml.io/how-to/project-setup-and-management/connecting-to-zenml/connect-with-a-service-account "
4419
+ "for more information."
4420
+ )
4421
+ raise e
4419
4422
  elif not re_authenticated:
4420
4423
  # The last request was authenticated with an API token
4421
4424
  # that was rejected by the server. We attempt a
@@ -412,6 +412,7 @@ class PipelineRunSchema(NamedSchema, RunMetadataInterface, table=True):
412
412
  resources = PipelineRunResponseResources(
413
413
  model_version=model_version,
414
414
  tags=[tag.to_model() for tag in self.tags],
415
+ logs=self.logs.to_model() if self.logs else None,
415
416
  )
416
417
 
417
418
  return PipelineRunResponse(
@@ -5022,6 +5022,24 @@ class SqlZenStore(BaseZenStore):
5022
5022
  new_run = PipelineRunSchema.from_request(pipeline_run)
5023
5023
 
5024
5024
  session.add(new_run)
5025
+
5026
+ # Add logs entry for the run if exists
5027
+ if pipeline_run.logs is not None:
5028
+ self._get_reference_schema_by_id(
5029
+ resource=pipeline_run,
5030
+ reference_schema=StackComponentSchema,
5031
+ reference_id=pipeline_run.logs.artifact_store_id,
5032
+ session=session,
5033
+ reference_type="logs artifact store",
5034
+ )
5035
+
5036
+ log_entry = LogsSchema(
5037
+ uri=pipeline_run.logs.uri,
5038
+ pipeline_run_id=new_run.id,
5039
+ artifact_store_id=pipeline_run.logs.artifact_store_id,
5040
+ )
5041
+ session.add(log_entry)
5042
+
5025
5043
  try:
5026
5044
  session.commit()
5027
5045
  except IntegrityError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: zenml-nightly
3
- Version: 0.80.2.dev20250415
3
+ Version: 0.80.2.dev20250416
4
4
  Summary: ZenML: Write production-ready ML code.
5
5
  License: Apache-2.0
6
6
  Keywords: machine learning,production,pipeline,mlops,devops
@@ -67,6 +67,7 @@ Requires-Dist: gitpython (>=3.1.18,<4.0.0)
67
67
  Requires-Dist: google-cloud-aiplatform (>=1.34.0) ; extra == "vertex"
68
68
  Requires-Dist: google-cloud-artifact-registry (>=1.11.3) ; extra == "connectors-gcp"
69
69
  Requires-Dist: google-cloud-container (>=2.21.0) ; extra == "connectors-gcp"
70
+ Requires-Dist: google-cloud-pipeline-components (>=2.19.0) ; extra == "vertex"
70
71
  Requires-Dist: google-cloud-secret-manager (>=2.12.5) ; extra == "secrets-gcp"
71
72
  Requires-Dist: google-cloud-storage (>=2.9.0) ; extra == "connectors-gcp"
72
73
  Requires-Dist: hvac (>=0.11.2) ; extra == "secrets-hashicorp"