zenml-nightly 0.75.0.dev20250312__py3-none-any.whl → 0.75.0.dev20250313__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 (160) hide show
  1. zenml/VERSION +1 -1
  2. zenml/__init__.py +2 -0
  3. zenml/analytics/context.py +7 -0
  4. zenml/artifacts/utils.py +0 -2
  5. zenml/cli/login.py +6 -0
  6. zenml/cli/model.py +7 -15
  7. zenml/cli/secret.py +47 -44
  8. zenml/cli/service_connectors.py +0 -1
  9. zenml/cli/stack.py +0 -1
  10. zenml/cli/tag.py +3 -5
  11. zenml/cli/utils.py +25 -23
  12. zenml/cli/workspace.py +79 -5
  13. zenml/client.py +615 -348
  14. zenml/config/global_config.py +16 -3
  15. zenml/config/pipeline_configurations.py +3 -2
  16. zenml/config/pipeline_run_configuration.py +2 -1
  17. zenml/config/secret_reference_mixin.py +1 -1
  18. zenml/constants.py +1 -3
  19. zenml/enums.py +0 -7
  20. zenml/event_hub/event_hub.py +3 -1
  21. zenml/exceptions.py +0 -24
  22. zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +5 -3
  23. zenml/integrations/bitbucket/plugins/event_sources/bitbucket_webhook_event_source.py +1 -4
  24. zenml/integrations/github/plugins/event_sources/github_webhook_event_source.py +1 -4
  25. zenml/integrations/mlflow/steps/mlflow_registry.py +1 -1
  26. zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -1
  27. zenml/integrations/wandb/flavors/wandb_experiment_tracker_flavor.py +3 -3
  28. zenml/model/model.py +8 -8
  29. zenml/models/__init__.py +18 -1
  30. zenml/models/v2/base/base.py +0 -5
  31. zenml/models/v2/base/filter.py +1 -1
  32. zenml/models/v2/base/scoped.py +104 -121
  33. zenml/models/v2/core/api_key.py +1 -1
  34. zenml/models/v2/core/artifact.py +31 -18
  35. zenml/models/v2/core/artifact_version.py +42 -25
  36. zenml/models/v2/core/component.py +22 -33
  37. zenml/models/v2/core/device.py +3 -2
  38. zenml/models/v2/core/event_source.py +2 -2
  39. zenml/models/v2/core/flavor.py +19 -47
  40. zenml/models/v2/core/logs.py +1 -2
  41. zenml/models/v2/core/model.py +7 -4
  42. zenml/models/v2/core/model_version.py +36 -27
  43. zenml/models/v2/core/pipeline.py +1 -1
  44. zenml/models/v2/core/pipeline_run.py +5 -13
  45. zenml/models/v2/core/run_template.py +1 -2
  46. zenml/models/v2/core/schedule.py +0 -9
  47. zenml/models/v2/core/secret.py +93 -127
  48. zenml/models/v2/core/server_settings.py +2 -2
  49. zenml/models/v2/core/service.py +43 -12
  50. zenml/models/v2/core/service_connector.py +14 -16
  51. zenml/models/v2/core/stack.py +24 -26
  52. zenml/models/v2/core/step_run.py +3 -15
  53. zenml/models/v2/core/tag.py +41 -15
  54. zenml/models/v2/core/user.py +19 -2
  55. zenml/models/v2/misc/statistics.py +45 -0
  56. zenml/models/v2/misc/tag.py +27 -0
  57. zenml/orchestrators/cache_utils.py +1 -1
  58. zenml/orchestrators/input_utils.py +1 -0
  59. zenml/orchestrators/step_launcher.py +0 -1
  60. zenml/orchestrators/step_run_utils.py +0 -2
  61. zenml/orchestrators/step_runner.py +10 -1
  62. zenml/pipelines/build_utils.py +0 -2
  63. zenml/pipelines/pipeline_decorator.py +3 -2
  64. zenml/pipelines/pipeline_definition.py +4 -5
  65. zenml/pipelines/run_utils.py +3 -3
  66. zenml/service_connectors/service_connector.py +0 -7
  67. zenml/service_connectors/service_connector_utils.py +0 -1
  68. zenml/stack/authentication_mixin.py +1 -1
  69. zenml/stack/flavor.py +3 -14
  70. zenml/stack/stack_component.py +1 -5
  71. zenml/steps/step_context.py +19 -0
  72. zenml/utils/string_utils.py +1 -1
  73. zenml/utils/tag_utils.py +642 -0
  74. zenml/zen_server/cloud_utils.py +21 -0
  75. zenml/zen_server/exceptions.py +0 -6
  76. zenml/zen_server/rbac/endpoint_utils.py +134 -46
  77. zenml/zen_server/rbac/models.py +65 -3
  78. zenml/zen_server/rbac/rbac_interface.py +9 -0
  79. zenml/zen_server/rbac/rbac_sql_zen_store.py +15 -7
  80. zenml/zen_server/rbac/utils.py +156 -29
  81. zenml/zen_server/rbac/zenml_cloud_rbac.py +43 -11
  82. zenml/zen_server/routers/actions_endpoints.py +3 -5
  83. zenml/zen_server/routers/artifact_endpoint.py +0 -5
  84. zenml/zen_server/routers/artifact_version_endpoints.py +15 -9
  85. zenml/zen_server/routers/auth_endpoints.py +22 -7
  86. zenml/zen_server/routers/code_repositories_endpoints.py +56 -3
  87. zenml/zen_server/routers/devices_endpoints.py +0 -4
  88. zenml/zen_server/routers/event_source_endpoints.py +0 -5
  89. zenml/zen_server/routers/flavors_endpoints.py +0 -5
  90. zenml/zen_server/routers/logs_endpoints.py +0 -1
  91. zenml/zen_server/routers/model_versions_endpoints.py +102 -23
  92. zenml/zen_server/routers/models_endpoints.py +51 -68
  93. zenml/zen_server/routers/pipeline_builds_endpoints.py +58 -4
  94. zenml/zen_server/routers/pipeline_deployments_endpoints.py +58 -4
  95. zenml/zen_server/routers/pipelines_endpoints.py +73 -4
  96. zenml/zen_server/routers/plugin_endpoints.py +0 -1
  97. zenml/zen_server/routers/run_metadata_endpoints.py +99 -0
  98. zenml/zen_server/routers/run_templates_endpoints.py +66 -3
  99. zenml/zen_server/routers/runs_endpoints.py +60 -8
  100. zenml/zen_server/routers/schedule_endpoints.py +69 -6
  101. zenml/zen_server/routers/secrets_endpoints.py +40 -4
  102. zenml/zen_server/routers/server_endpoints.py +53 -1
  103. zenml/zen_server/routers/service_accounts_endpoints.py +14 -15
  104. zenml/zen_server/routers/service_connectors_endpoints.py +96 -14
  105. zenml/zen_server/routers/service_endpoints.py +20 -7
  106. zenml/zen_server/routers/stack_components_endpoints.py +68 -7
  107. zenml/zen_server/routers/stacks_endpoints.py +98 -7
  108. zenml/zen_server/routers/steps_endpoints.py +17 -11
  109. zenml/zen_server/routers/tag_resource_endpoints.py +115 -0
  110. zenml/zen_server/routers/tags_endpoints.py +6 -17
  111. zenml/zen_server/routers/triggers_endpoints.py +5 -8
  112. zenml/zen_server/routers/users_endpoints.py +47 -12
  113. zenml/zen_server/routers/workspaces_endpoints.py +56 -1285
  114. zenml/zen_server/template_execution/utils.py +5 -4
  115. zenml/zen_server/utils.py +21 -0
  116. zenml/zen_server/zen_server_api.py +4 -0
  117. zenml/zen_stores/base_zen_store.py +29 -44
  118. zenml/zen_stores/migrations/versions/1cb6477f72d6_move_artifact_save_type.py +20 -10
  119. zenml/zen_stores/migrations/versions/1f9d1cd00b90_add_unique_name_constraints.py +231 -0
  120. zenml/zen_stores/migrations/versions/288f4fb6e112_make_tags_user_scoped.py +74 -0
  121. zenml/zen_stores/migrations/versions/2e695a26fe7a_add_user_default_workspace.py +45 -0
  122. zenml/zen_stores/migrations/versions/3b1776345020_remove_workspace_from_globals.py +81 -0
  123. zenml/zen_stores/migrations/versions/41b28cae31ce_make_artifacts_workspace_scoped.py +136 -0
  124. zenml/zen_stores/migrations/versions/9e7bf0970266_adding_exclusive_attribute_to_tags.py +47 -0
  125. zenml/zen_stores/migrations/versions/b557b2871693_update_step_run_input_types.py +8 -4
  126. zenml/zen_stores/migrations/versions/cc269488e5a9_separate_run_metadata.py +12 -6
  127. zenml/zen_stores/migrations/versions/f1d723fd723b_add_secret_private_attr.py +61 -0
  128. zenml/zen_stores/migrations/versions/f76a368a25a5_add_stack_description.py +35 -0
  129. zenml/zen_stores/rest_zen_store.py +172 -171
  130. zenml/zen_stores/schemas/action_schemas.py +8 -1
  131. zenml/zen_stores/schemas/api_key_schemas.py +8 -1
  132. zenml/zen_stores/schemas/artifact_schemas.py +28 -1
  133. zenml/zen_stores/schemas/code_repository_schemas.py +8 -1
  134. zenml/zen_stores/schemas/component_schemas.py +9 -14
  135. zenml/zen_stores/schemas/event_source_schemas.py +8 -1
  136. zenml/zen_stores/schemas/flavor_schemas.py +14 -20
  137. zenml/zen_stores/schemas/model_schemas.py +3 -0
  138. zenml/zen_stores/schemas/pipeline_deployment_schemas.py +3 -1
  139. zenml/zen_stores/schemas/pipeline_run_schemas.py +0 -3
  140. zenml/zen_stores/schemas/run_template_schemas.py +8 -4
  141. zenml/zen_stores/schemas/schedule_schema.py +9 -14
  142. zenml/zen_stores/schemas/secret_schemas.py +15 -25
  143. zenml/zen_stores/schemas/service_connector_schemas.py +8 -17
  144. zenml/zen_stores/schemas/service_schemas.py +0 -1
  145. zenml/zen_stores/schemas/stack_schemas.py +12 -15
  146. zenml/zen_stores/schemas/step_run_schemas.py +7 -8
  147. zenml/zen_stores/schemas/tag_schemas.py +30 -2
  148. zenml/zen_stores/schemas/trigger_schemas.py +8 -1
  149. zenml/zen_stores/schemas/user_schemas.py +24 -2
  150. zenml/zen_stores/schemas/utils.py +16 -0
  151. zenml/zen_stores/schemas/workspace_schemas.py +7 -25
  152. zenml/zen_stores/secrets_stores/service_connector_secrets_store.py +0 -3
  153. zenml/zen_stores/sql_zen_store.py +2905 -2280
  154. zenml/zen_stores/template_utils.py +1 -1
  155. zenml/zen_stores/zen_store_interface.py +82 -58
  156. {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/METADATA +1 -1
  157. {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/RECORD +160 -147
  158. {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/LICENSE +0 -0
  159. {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/WHEEL +0 -0
  160. {zenml_nightly-0.75.0.dev20250312.dist-info → zenml_nightly-0.75.0.dev20250313.dist-info}/entry_points.txt +0 -0
@@ -34,6 +34,8 @@ from zenml.models import (
34
34
  Page,
35
35
  UserResponse,
36
36
  UserScopedResponse,
37
+ WorkspaceScopedRequest,
38
+ WorkspaceScopedResponse,
37
39
  )
38
40
  from zenml.zen_server.auth import get_auth_context
39
41
  from zenml.zen_server.rbac.models import Action, Resource, ResourceType
@@ -55,24 +57,39 @@ def dehydrate_page(page: Page[AnyResponse]) -> Page[AnyResponse]:
55
57
  Returns:
56
58
  The page with (potentially) dehydrated items.
57
59
  """
60
+ new_items = dehydrate_response_model_batch(page.items)
61
+ return page.model_copy(update={"items": new_items})
62
+
63
+
64
+ def dehydrate_response_model_batch(
65
+ batch: List[AnyResponse],
66
+ ) -> List[AnyResponse]:
67
+ """Dehydrate all items of a batch.
68
+
69
+ Args:
70
+ batch: The batch to dehydrate.
71
+
72
+ Returns:
73
+ The batch with (potentially) dehydrated items.
74
+ """
58
75
  if not server_config().rbac_enabled:
59
- return page
76
+ return batch
60
77
 
61
78
  auth_context = get_auth_context()
62
79
  assert auth_context
63
80
 
64
- resource_list = [get_subresources_for_model(item) for item in page.items]
81
+ resource_list = [get_subresources_for_model(item) for item in batch]
65
82
  resources = set.union(*resource_list) if resource_list else set()
66
83
  permissions = rbac().check_permissions(
67
84
  user=auth_context.user, resources=resources, action=Action.READ
68
85
  )
69
86
 
70
- new_items = [
87
+ new_batch = [
71
88
  dehydrate_response_model(item, permissions=permissions)
72
- for item in page.items
89
+ for item in batch
73
90
  ]
74
91
 
75
- return page.model_copy(update={"items": new_items})
92
+ return new_batch
76
93
 
77
94
 
78
95
  def dehydrate_response_model(
@@ -161,7 +178,7 @@ def _dehydrate_value(
161
178
  return value
162
179
 
163
180
 
164
- def has_permissions_for_model(model: AnyResponse, action: Action) -> bool:
181
+ def has_permissions_for_model(model: AnyModel, action: Action) -> bool:
165
182
  """If the active user has permissions to perform the action on the model.
166
183
 
167
184
  Args:
@@ -201,7 +218,7 @@ def get_permission_denied_model(model: AnyResponse) -> AnyResponse:
201
218
 
202
219
 
203
220
  def batch_verify_permissions_for_models(
204
- models: Sequence[AnyResponse],
221
+ models: Sequence[AnyModel],
205
222
  action: Action,
206
223
  ) -> None:
207
224
  """Batch permission verification for models.
@@ -229,7 +246,7 @@ def batch_verify_permissions_for_models(
229
246
  batch_verify_permissions(resources=resources, action=action)
230
247
 
231
248
 
232
- def verify_permission_for_model(model: AnyResponse, action: Action) -> None:
249
+ def verify_permission_for_model(model: AnyModel, action: Action) -> None:
233
250
  """Verifies if a user has permission to perform an action on a model.
234
251
 
235
252
  Args:
@@ -283,6 +300,7 @@ def verify_permission(
283
300
  resource_type: str,
284
301
  action: Action,
285
302
  resource_id: Optional[UUID] = None,
303
+ workspace_id: Optional[UUID] = None,
286
304
  ) -> None:
287
305
  """Verifies if a user has permission to perform an action on a resource.
288
306
 
@@ -291,20 +309,27 @@ def verify_permission(
291
309
  action on.
292
310
  action: The action the user wants to perform.
293
311
  resource_id: ID of the resource the user wants to perform the action on.
312
+ workspace_id: ID of the workspace the user wants to perform the action
313
+ on. Only used for workspace scoped resources.
294
314
  """
295
- resource = Resource(type=resource_type, id=resource_id)
315
+ resource = Resource(
316
+ type=resource_type, id=resource_id, workspace_id=workspace_id
317
+ )
296
318
  batch_verify_permissions(resources={resource}, action=action)
297
319
 
298
320
 
299
321
  def get_allowed_resource_ids(
300
322
  resource_type: str,
301
323
  action: Action = Action.READ,
324
+ workspace_id: Optional[UUID] = None,
302
325
  ) -> Optional[Set[UUID]]:
303
326
  """Get all resource IDs of a resource type that a user can access.
304
327
 
305
328
  Args:
306
329
  resource_type: The resource type.
307
330
  action: The action the user wants to perform on the resource.
331
+ workspace_id: Optional workspace ID to filter the resources by.
332
+ Required for workspace scoped resources.
308
333
 
309
334
  Returns:
310
335
  A list of resource IDs or `None` if the user has full access to the
@@ -321,7 +346,7 @@ def get_allowed_resource_ids(
321
346
  allowed_ids,
322
347
  ) = rbac().list_allowed_resource_ids(
323
348
  user=auth_context.user,
324
- resource=Resource(type=resource_type),
349
+ resource=Resource(type=resource_type, workspace_id=workspace_id),
325
350
  action=action,
326
351
  )
327
352
 
@@ -331,7 +356,7 @@ def get_allowed_resource_ids(
331
356
  return {UUID(id) for id in allowed_ids}
332
357
 
333
358
 
334
- def get_resource_for_model(model: AnyResponse) -> Optional[Resource]:
359
+ def get_resource_for_model(model: AnyModel) -> Optional[Resource]:
335
360
  """Get the resource associated with a model object.
336
361
 
337
362
  Args:
@@ -346,12 +371,26 @@ def get_resource_for_model(model: AnyResponse) -> Optional[Resource]:
346
371
  # This model is not tied to any RBAC resource type
347
372
  return None
348
373
 
349
- return Resource(type=resource_type, id=model.id)
374
+ workspace_id: Optional[UUID] = None
375
+ if isinstance(model, WorkspaceScopedResponse):
376
+ # A workspace scoped response is always scoped to a specific workspace
377
+ workspace_id = model.workspace.id
378
+ elif isinstance(model, WorkspaceScopedRequest):
379
+ # A workspace scoped request is always scoped to a specific workspace
380
+ workspace_id = model.workspace
381
+
382
+ resource_id: Optional[UUID] = None
383
+ if isinstance(model, BaseIdentifiedResponse):
384
+ resource_id = model.id
385
+
386
+ return Resource(
387
+ type=resource_type, id=resource_id, workspace_id=workspace_id
388
+ )
350
389
 
351
390
 
352
391
  def get_surrogate_permission_model_for_model(
353
- model: AnyResponse, action: str
354
- ) -> BaseIdentifiedResponse[Any, Any, Any]:
392
+ model: BaseModel, action: str
393
+ ) -> BaseModel:
355
394
  """Get a surrogate permission model for a model.
356
395
 
357
396
  In some cases a different model instead of the original model is used to
@@ -379,7 +418,7 @@ def get_surrogate_permission_model_for_model(
379
418
 
380
419
 
381
420
  def get_resource_type_for_model(
382
- model: AnyResponse,
421
+ model: AnyModel,
383
422
  ) -> Optional[ResourceType]:
384
423
  """Get the resource type associated with a model object.
385
424
 
@@ -391,64 +430,113 @@ def get_resource_type_for_model(
391
430
  is not associated with any resource type.
392
431
  """
393
432
  from zenml.models import (
433
+ ActionRequest,
394
434
  ActionResponse,
435
+ ArtifactRequest,
395
436
  ArtifactResponse,
437
+ ArtifactVersionRequest,
396
438
  ArtifactVersionResponse,
439
+ CodeRepositoryRequest,
397
440
  CodeRepositoryResponse,
441
+ ComponentRequest,
398
442
  ComponentResponse,
443
+ EventSourceRequest,
399
444
  EventSourceResponse,
445
+ FlavorRequest,
400
446
  FlavorResponse,
447
+ ModelRequest,
401
448
  ModelResponse,
449
+ ModelVersionRequest,
402
450
  ModelVersionResponse,
451
+ PipelineBuildRequest,
403
452
  PipelineBuildResponse,
453
+ PipelineDeploymentRequest,
404
454
  PipelineDeploymentResponse,
455
+ PipelineRequest,
405
456
  PipelineResponse,
457
+ PipelineRunRequest,
406
458
  PipelineRunResponse,
459
+ RunMetadataRequest,
460
+ RunTemplateRequest,
407
461
  RunTemplateResponse,
462
+ SecretRequest,
408
463
  SecretResponse,
464
+ ServiceAccountRequest,
409
465
  ServiceAccountResponse,
466
+ ServiceConnectorRequest,
410
467
  ServiceConnectorResponse,
468
+ ServiceRequest,
411
469
  ServiceResponse,
470
+ StackRequest,
412
471
  StackResponse,
472
+ TagRequest,
413
473
  TagResponse,
474
+ TriggerExecutionRequest,
414
475
  TriggerExecutionResponse,
476
+ TriggerRequest,
415
477
  TriggerResponse,
478
+ WorkspaceRequest,
479
+ WorkspaceResponse,
416
480
  )
417
481
 
418
482
  mapping: Dict[
419
483
  Any,
420
484
  ResourceType,
421
485
  ] = {
486
+ ActionRequest: ResourceType.ACTION,
422
487
  ActionResponse: ResourceType.ACTION,
488
+ ArtifactRequest: ResourceType.ARTIFACT,
489
+ ArtifactResponse: ResourceType.ARTIFACT,
490
+ ArtifactVersionRequest: ResourceType.ARTIFACT_VERSION,
491
+ ArtifactVersionResponse: ResourceType.ARTIFACT_VERSION,
492
+ CodeRepositoryRequest: ResourceType.CODE_REPOSITORY,
493
+ CodeRepositoryResponse: ResourceType.CODE_REPOSITORY,
494
+ ComponentRequest: ResourceType.STACK_COMPONENT,
495
+ ComponentResponse: ResourceType.STACK_COMPONENT,
496
+ EventSourceRequest: ResourceType.EVENT_SOURCE,
423
497
  EventSourceResponse: ResourceType.EVENT_SOURCE,
498
+ FlavorRequest: ResourceType.FLAVOR,
424
499
  FlavorResponse: ResourceType.FLAVOR,
425
- ServiceConnectorResponse: ResourceType.SERVICE_CONNECTOR,
426
- ComponentResponse: ResourceType.STACK_COMPONENT,
427
- StackResponse: ResourceType.STACK,
428
- PipelineResponse: ResourceType.PIPELINE,
429
- CodeRepositoryResponse: ResourceType.CODE_REPOSITORY,
430
- SecretResponse: ResourceType.SECRET,
500
+ ModelRequest: ResourceType.MODEL,
431
501
  ModelResponse: ResourceType.MODEL,
502
+ ModelVersionRequest: ResourceType.MODEL_VERSION,
432
503
  ModelVersionResponse: ResourceType.MODEL_VERSION,
433
- ArtifactResponse: ResourceType.ARTIFACT,
434
- ArtifactVersionResponse: ResourceType.ARTIFACT_VERSION,
435
- # WorkspaceResponse: ResourceType.WORKSPACE,
436
- # UserResponse: ResourceType.USER,
437
- PipelineDeploymentResponse: ResourceType.PIPELINE_DEPLOYMENT,
504
+ PipelineBuildRequest: ResourceType.PIPELINE_BUILD,
438
505
  PipelineBuildResponse: ResourceType.PIPELINE_BUILD,
506
+ PipelineDeploymentRequest: ResourceType.PIPELINE_DEPLOYMENT,
507
+ PipelineDeploymentResponse: ResourceType.PIPELINE_DEPLOYMENT,
508
+ PipelineRequest: ResourceType.PIPELINE,
509
+ PipelineResponse: ResourceType.PIPELINE,
510
+ PipelineRunRequest: ResourceType.PIPELINE_RUN,
439
511
  PipelineRunResponse: ResourceType.PIPELINE_RUN,
512
+ RunMetadataRequest: ResourceType.RUN_METADATA,
513
+ RunTemplateRequest: ResourceType.RUN_TEMPLATE,
440
514
  RunTemplateResponse: ResourceType.RUN_TEMPLATE,
515
+ SecretRequest: ResourceType.SECRET,
516
+ SecretResponse: ResourceType.SECRET,
517
+ ServiceAccountRequest: ResourceType.SERVICE_ACCOUNT,
518
+ ServiceAccountResponse: ResourceType.SERVICE_ACCOUNT,
519
+ ServiceConnectorRequest: ResourceType.SERVICE_CONNECTOR,
520
+ ServiceConnectorResponse: ResourceType.SERVICE_CONNECTOR,
521
+ ServiceRequest: ResourceType.SERVICE,
522
+ ServiceResponse: ResourceType.SERVICE,
523
+ StackRequest: ResourceType.STACK,
524
+ StackResponse: ResourceType.STACK,
525
+ TagRequest: ResourceType.TAG,
441
526
  TagResponse: ResourceType.TAG,
527
+ TriggerRequest: ResourceType.TRIGGER,
442
528
  TriggerResponse: ResourceType.TRIGGER,
529
+ TriggerExecutionRequest: ResourceType.TRIGGER_EXECUTION,
443
530
  TriggerExecutionResponse: ResourceType.TRIGGER_EXECUTION,
444
- ServiceAccountResponse: ResourceType.SERVICE_ACCOUNT,
445
- ServiceResponse: ResourceType.SERVICE,
531
+ WorkspaceResponse: ResourceType.WORKSPACE,
532
+ WorkspaceRequest: ResourceType.WORKSPACE,
533
+ # UserResponse: ResourceType.USER,
446
534
  }
447
535
 
448
536
  return mapping.get(type(model))
449
537
 
450
538
 
451
- def is_owned_by_authenticated_user(model: AnyResponse) -> bool:
539
+ def is_owned_by_authenticated_user(model: AnyModel) -> bool:
452
540
  """Returns whether the currently authenticated user owns the model.
453
541
 
454
542
  Args:
@@ -618,3 +706,42 @@ def update_resource_membership(
618
706
  rbac().update_resource_membership(
619
707
  user=user, resource=resource, actions=actions
620
708
  )
709
+
710
+
711
+ def delete_model_resource(model: AnyModel) -> None:
712
+ """Delete resource membership information for a model.
713
+
714
+ Args:
715
+ model: The model for which to delete the resource membership information.
716
+ """
717
+ delete_model_resources(models=[model])
718
+
719
+
720
+ def delete_model_resources(models: List[AnyModel]) -> None:
721
+ """Delete resource membership information for a list of models.
722
+
723
+ Args:
724
+ models: The models for which to delete the resource membership information.
725
+ """
726
+ if not server_config().rbac_enabled:
727
+ return
728
+
729
+ resources = set()
730
+ for model in models:
731
+ if resource := get_resource_for_model(model):
732
+ resources.add(resource)
733
+
734
+ delete_resources(resources=list(resources))
735
+
736
+
737
+ def delete_resources(resources: List[Resource]) -> None:
738
+ """Delete resource membership information for a list of resources.
739
+
740
+ Args:
741
+ resources: The resources for which to delete the resource membership
742
+ information.
743
+ """
744
+ if not server_config().rbac_enabled:
745
+ return
746
+
747
+ rbac().delete_resources(resources=resources)
@@ -13,10 +13,10 @@
13
13
  # permissions and limitations under the License.
14
14
  """Cloud RBAC implementation."""
15
15
 
16
- from typing import TYPE_CHECKING, Dict, List, Set, Tuple
16
+ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
17
17
 
18
18
  from zenml.zen_server.cloud_utils import cloud_connection
19
- from zenml.zen_server.rbac.models import Action, Resource
19
+ from zenml.zen_server.rbac.models import Action, Resource, ResourceType
20
20
  from zenml.zen_server.rbac.rbac_interface import RBACInterface
21
21
  from zenml.zen_server.utils import server_config
22
22
 
@@ -27,6 +27,7 @@ if TYPE_CHECKING:
27
27
  PERMISSIONS_ENDPOINT = "/rbac/check_permissions"
28
28
  ALLOWED_RESOURCE_IDS_ENDPOINT = "/rbac/allowed_resource_ids"
29
29
  RESOURCE_MEMBERSHIP_ENDPOINT = "/rbac/resource_members"
30
+ RESOURCES_ENDPOINT = "/rbac/resources"
30
31
 
31
32
  SERVER_SCOPE_IDENTIFIER = "server"
32
33
 
@@ -42,12 +43,7 @@ def _convert_to_cloud_resource(resource: Resource) -> str:
42
43
  Returns:
43
44
  The converted resource.
44
45
  """
45
- resource_string = f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}:{resource.type}"
46
-
47
- if resource.id:
48
- resource_string += f"/{resource.id}"
49
-
50
- return resource_string
46
+ return f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}:{resource}"
51
47
 
52
48
 
53
49
  def _convert_from_cloud_resource(cloud_resource: str) -> Resource:
@@ -62,16 +58,38 @@ def _convert_from_cloud_resource(cloud_resource: str) -> Resource:
62
58
  Returns:
63
59
  The converted resource.
64
60
  """
65
- scope, resource_type_and_id = cloud_resource.rsplit(":", maxsplit=1)
61
+ scope, workspace_resource_type_and_id = cloud_resource.split(
62
+ ":", maxsplit=1
63
+ )
66
64
 
67
65
  if scope != f"{SERVER_ID}@{SERVER_SCOPE_IDENTIFIER}":
68
66
  raise ValueError("Invalid scope for server resource.")
69
67
 
68
+ workspace_id: Optional[str] = None
69
+ if ":" in workspace_resource_type_and_id:
70
+ (
71
+ workspace_id,
72
+ resource_type_and_id,
73
+ ) = workspace_resource_type_and_id.split(":", maxsplit=1)
74
+ else:
75
+ workspace_id = None
76
+ resource_type_and_id = workspace_resource_type_and_id
77
+
78
+ resource_id: Optional[str] = None
70
79
  if "/" in resource_type_and_id:
71
80
  resource_type, resource_id = resource_type_and_id.split("/")
72
- return Resource(type=resource_type, id=resource_id)
73
81
  else:
74
- return Resource(type=resource_type_and_id)
82
+ resource_type = resource_type_and_id
83
+
84
+ if resource_type == ResourceType.WORKSPACE and workspace_id is not None:
85
+ # TODO: For now, we duplicate the workspace ID in the string
86
+ # representation when describing a workspace instance, because
87
+ # this is what is expected by the RBAC implementation.
88
+ workspace_id = None
89
+
90
+ return Resource(
91
+ type=resource_type, id=resource_id, workspace_id=workspace_id
92
+ )
75
93
 
76
94
 
77
95
  class ZenMLCloudRBAC(RBACInterface):
@@ -184,3 +202,17 @@ class ZenMLCloudRBAC(RBACInterface):
184
202
  "actions": [str(action) for action in actions],
185
203
  }
186
204
  self._connection.post(endpoint=RESOURCE_MEMBERSHIP_ENDPOINT, data=data)
205
+
206
+ def delete_resources(self, resources: List[Resource]) -> None:
207
+ """Delete resource membership information for a list of resources.
208
+
209
+ Args:
210
+ resources: The resources for which to delete the resource membership
211
+ information.
212
+ """
213
+ params = {
214
+ "resources": [
215
+ _convert_to_cloud_resource(resource) for resource in resources
216
+ ],
217
+ }
218
+ self._connection.delete(endpoint=RESOURCES_ENDPOINT, params=params)
@@ -40,6 +40,7 @@ from zenml.zen_server.rbac.endpoint_utils import (
40
40
  from zenml.zen_server.rbac.models import Action, ResourceType
41
41
  from zenml.zen_server.rbac.utils import (
42
42
  dehydrate_response_model,
43
+ delete_model_resource,
43
44
  verify_permission_for_model,
44
45
  )
45
46
  from zenml.zen_server.utils import (
@@ -58,7 +59,6 @@ router = APIRouter(
58
59
 
59
60
  @router.get(
60
61
  "",
61
- response_model=Page[ActionResponse],
62
62
  responses={401: error_response, 404: error_response, 422: error_response},
63
63
  )
64
64
  @handle_exceptions
@@ -130,7 +130,6 @@ def list_actions(
130
130
 
131
131
  @router.get(
132
132
  "/{action_id}",
133
- response_model=ActionResponse,
134
133
  responses={401: error_response, 404: error_response, 422: error_response},
135
134
  )
136
135
  @handle_exceptions
@@ -178,7 +177,6 @@ def get_action(
178
177
 
179
178
  @router.post(
180
179
  "",
181
- response_model=ActionResponse,
182
180
  responses={401: error_response, 409: error_response, 422: error_response},
183
181
  )
184
182
  @handle_exceptions
@@ -219,14 +217,12 @@ def create_action(
219
217
 
220
218
  return verify_permissions_and_create_entity(
221
219
  request_model=action,
222
- resource_type=ResourceType.ACTION,
223
220
  create_method=action_handler.create_action,
224
221
  )
225
222
 
226
223
 
227
224
  @router.put(
228
225
  "/{action_id}",
229
- response_model=ActionResponse,
230
226
  responses={401: error_response, 404: error_response, 422: error_response},
231
227
  )
232
228
  @handle_exceptions
@@ -322,3 +318,5 @@ def delete_action(
322
318
  action=action,
323
319
  force=force,
324
320
  )
321
+
322
+ delete_model_resource(action)
@@ -50,7 +50,6 @@ artifact_router = APIRouter(
50
50
 
51
51
  @artifact_router.get(
52
52
  "",
53
- response_model=Page[ArtifactResponse],
54
53
  responses={401: error_response, 404: error_response, 422: error_response},
55
54
  )
56
55
  @handle_exceptions
@@ -82,7 +81,6 @@ def list_artifacts(
82
81
 
83
82
  @artifact_router.post(
84
83
  "",
85
- response_model=ArtifactResponse,
86
84
  responses={401: error_response, 409: error_response, 422: error_response},
87
85
  )
88
86
  @handle_exceptions
@@ -100,14 +98,12 @@ def create_artifact(
100
98
  """
101
99
  return verify_permissions_and_create_entity(
102
100
  request_model=artifact,
103
- resource_type=ResourceType.ARTIFACT,
104
101
  create_method=zen_store().create_artifact,
105
102
  )
106
103
 
107
104
 
108
105
  @artifact_router.get(
109
106
  "/{artifact_id}",
110
- response_model=ArtifactResponse,
111
107
  responses={401: error_response, 404: error_response, 422: error_response},
112
108
  )
113
109
  @handle_exceptions
@@ -135,7 +131,6 @@ def get_artifact(
135
131
 
136
132
  @artifact_router.put(
137
133
  "/{artifact_id}",
138
- response_model=ArtifactResponse,
139
134
  responses={401: error_response, 404: error_response, 422: error_response},
140
135
  )
141
136
  @handle_exceptions
@@ -13,7 +13,7 @@
13
13
  # permissions and limitations under the License.
14
14
  """Endpoint definitions for artifact versions."""
15
15
 
16
- from typing import List
16
+ from typing import List, Union
17
17
  from uuid import UUID
18
18
 
19
19
  from fastapi import APIRouter, Depends, Security
@@ -46,6 +46,7 @@ from zenml.zen_server.rbac.utils import (
46
46
  from zenml.zen_server.utils import (
47
47
  handle_exceptions,
48
48
  make_dependable,
49
+ set_filter_workspace_scope,
49
50
  zen_store,
50
51
  )
51
52
 
@@ -58,7 +59,6 @@ artifact_version_router = APIRouter(
58
59
 
59
60
  @artifact_version_router.get(
60
61
  "",
61
- response_model=Page[ArtifactVersionResponse],
62
62
  responses={401: error_response, 404: error_response, 422: error_response},
63
63
  )
64
64
  @handle_exceptions
@@ -81,8 +81,14 @@ def list_artifact_versions(
81
81
  Returns:
82
82
  The artifact versions according to query filters.
83
83
  """
84
+ # A workspace scoped request must always be scoped to a specific
85
+ # workspace. This is required for the RBAC check to work.
86
+ set_filter_workspace_scope(artifact_version_filter_model)
87
+ assert isinstance(artifact_version_filter_model.workspace, UUID)
88
+
84
89
  allowed_artifact_ids = get_allowed_resource_ids(
85
- resource_type=ResourceType.ARTIFACT
90
+ resource_type=ResourceType.ARTIFACT,
91
+ workspace_id=artifact_version_filter_model.workspace,
86
92
  )
87
93
  artifact_version_filter_model.configure_rbac(
88
94
  authenticated_user_id=auth_context.user.id,
@@ -97,7 +103,6 @@ def list_artifact_versions(
97
103
 
98
104
  @artifact_version_router.post(
99
105
  "",
100
- response_model=ArtifactVersionResponse,
101
106
  responses={401: error_response, 409: error_response, 422: error_response},
102
107
  )
103
108
  @handle_exceptions
@@ -115,7 +120,6 @@ def create_artifact_version(
115
120
  """
116
121
  return verify_permissions_and_create_entity(
117
122
  request_model=artifact_version,
118
- resource_type=ResourceType.ARTIFACT_VERSION,
119
123
  create_method=zen_store().create_artifact_version,
120
124
  )
121
125
 
@@ -139,14 +143,12 @@ def batch_create_artifact_version(
139
143
  """
140
144
  return verify_permissions_and_batch_create_entity(
141
145
  batch=artifact_versions,
142
- resource_type=ResourceType.ARTIFACT_VERSION,
143
146
  create_method=zen_store().batch_create_artifact_versions,
144
147
  )
145
148
 
146
149
 
147
150
  @artifact_version_router.get(
148
151
  "/{artifact_version_id}",
149
- response_model=ArtifactVersionResponse,
150
152
  responses={401: error_response, 404: error_response, 422: error_response},
151
153
  )
152
154
  @handle_exceptions
@@ -174,7 +176,6 @@ def get_artifact_version(
174
176
 
175
177
  @artifact_version_router.put(
176
178
  "/{artifact_version_id}",
177
- response_model=ArtifactVersionResponse,
178
179
  responses={401: error_response, 404: error_response, 422: error_response},
179
180
  )
180
181
  @handle_exceptions
@@ -227,24 +228,29 @@ def delete_artifact_version(
227
228
  )
228
229
  @handle_exceptions
229
230
  def prune_artifact_versions(
231
+ workspace_name_or_id: Union[str, UUID],
230
232
  only_versions: bool = True,
231
233
  _: AuthContext = Security(authorize),
232
234
  ) -> None:
233
235
  """Prunes unused artifact versions and their artifacts.
234
236
 
235
237
  Args:
238
+ workspace_name_or_id: The workspace name or ID to prune artifact
239
+ versions for.
236
240
  only_versions: Only delete artifact versions, keeping artifacts
237
241
  """
242
+ workspace_id = zen_store().get_workspace(workspace_name_or_id).id
243
+
238
244
  verify_permissions_and_prune_entities(
239
245
  resource_type=ResourceType.ARTIFACT_VERSION,
240
246
  prune_method=zen_store().prune_artifact_versions,
241
247
  only_versions=only_versions,
248
+ workspace_id=workspace_id,
242
249
  )
243
250
 
244
251
 
245
252
  @artifact_version_router.get(
246
253
  "/{artifact_version_id}" + VISUALIZE,
247
- response_model=LoadedVisualization,
248
254
  responses={401: error_response, 404: error_response, 422: error_response},
249
255
  )
250
256
  @handle_exceptions