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
@@ -13,21 +13,17 @@
13
13
  # permissions and limitations under the License.
14
14
  """High-level helper functions to write endpoints with RBAC."""
15
15
 
16
- from typing import Any, Callable, List, TypeVar, Union
16
+ from typing import Any, Callable, List, Optional, Tuple, TypeVar, Union
17
17
  from uuid import UUID
18
18
 
19
- from pydantic import BaseModel
20
-
21
- from zenml.constants import (
22
- REQUIRES_CUSTOM_RESOURCE_REPORTING,
23
- )
24
- from zenml.exceptions import IllegalOperationError
25
19
  from zenml.models import (
26
20
  BaseFilter,
27
21
  BaseIdentifiedResponse,
28
22
  BaseRequest,
23
+ BaseUpdate,
29
24
  Page,
30
25
  UserScopedRequest,
26
+ WorkspaceScopedFilter,
31
27
  )
32
28
  from zenml.zen_server.auth import get_auth_context
33
29
  from zenml.zen_server.feature_gate.endpoint_utils import (
@@ -36,36 +32,42 @@ from zenml.zen_server.feature_gate.endpoint_utils import (
36
32
  )
37
33
  from zenml.zen_server.rbac.models import Action, ResourceType
38
34
  from zenml.zen_server.rbac.utils import (
35
+ batch_verify_permissions_for_models,
39
36
  dehydrate_page,
40
37
  dehydrate_response_model,
38
+ dehydrate_response_model_batch,
39
+ delete_model_resource,
41
40
  get_allowed_resource_ids,
41
+ get_resource_type_for_model,
42
42
  verify_permission,
43
43
  verify_permission_for_model,
44
44
  )
45
- from zenml.zen_server.utils import server_config
45
+ from zenml.zen_server.utils import server_config, set_filter_workspace_scope
46
46
 
47
47
  AnyRequest = TypeVar("AnyRequest", bound=BaseRequest)
48
48
  AnyResponse = TypeVar("AnyResponse", bound=BaseIdentifiedResponse) # type: ignore[type-arg]
49
+ AnyOtherResponse = TypeVar("AnyOtherResponse", bound=BaseIdentifiedResponse) # type: ignore[type-arg]
49
50
  AnyFilter = TypeVar("AnyFilter", bound=BaseFilter)
50
- AnyUpdate = TypeVar("AnyUpdate", bound=BaseModel)
51
+ AnyUpdate = TypeVar("AnyUpdate", bound=BaseUpdate)
51
52
  UUIDOrStr = TypeVar("UUIDOrStr", UUID, Union[UUID, str])
52
53
 
53
54
 
54
55
  def verify_permissions_and_create_entity(
55
56
  request_model: AnyRequest,
56
- resource_type: ResourceType,
57
57
  create_method: Callable[[AnyRequest], AnyResponse],
58
+ surrogate_models: Optional[List[AnyOtherResponse]] = None,
59
+ skip_entitlements: bool = False,
58
60
  ) -> AnyResponse:
59
61
  """Verify permissions and create the entity if authorized.
60
62
 
61
63
  Args:
62
64
  request_model: The entity request model.
63
- resource_type: The resource type of the entity to create.
64
65
  create_method: The method to create the entity.
65
-
66
- Raises:
67
- IllegalOperationError: If the request model has a different owner then
68
- the currently authenticated user.
66
+ surrogate_models: Optional list of surrogate models to verify
67
+ UPDATE permissions for instead of verifying CREATE permissions for
68
+ the request model.
69
+ skip_entitlements: Whether to skip the entitlement check and usage
70
+ increment.
69
71
 
70
72
  Returns:
71
73
  A model of the created entity.
@@ -74,43 +76,47 @@ def verify_permissions_and_create_entity(
74
76
  auth_context = get_auth_context()
75
77
  assert auth_context
76
78
 
77
- if request_model.user != auth_context.user.id:
78
- raise IllegalOperationError(
79
- f"Not allowed to create resource '{resource_type}' for a "
80
- "different user."
81
- )
82
- verify_permission(resource_type=resource_type, action=Action.CREATE)
79
+ # Ignore the user field set in the request model, if any, and set it to
80
+ # the current user's ID instead. This is just a precaution, given that
81
+ # the SQLZenStore also does this same validation on all request models.
82
+ request_model.user = auth_context.user.id
83
83
 
84
- needs_usage_increment = (
85
- resource_type in server_config().reportable_resources
86
- and resource_type not in REQUIRES_CUSTOM_RESOURCE_REPORTING
87
- )
88
- if needs_usage_increment:
89
- check_entitlement(resource_type)
84
+ if surrogate_models:
85
+ batch_verify_permissions_for_models(
86
+ models=surrogate_models, action=Action.UPDATE
87
+ )
88
+ else:
89
+ verify_permission_for_model(model=request_model, action=Action.CREATE)
90
+
91
+ resource_type = get_resource_type_for_model(request_model)
92
+
93
+ if resource_type:
94
+ needs_usage_increment = (
95
+ not skip_entitlements
96
+ and resource_type in server_config().reportable_resources
97
+ )
98
+ if needs_usage_increment:
99
+ check_entitlement(resource_type)
90
100
 
91
101
  created = create_method(request_model)
92
102
 
93
- if needs_usage_increment:
103
+ if resource_type and needs_usage_increment:
94
104
  report_usage(resource_type, resource_id=created.id)
95
105
 
96
- return created
106
+ return dehydrate_response_model(created)
97
107
 
98
108
 
99
109
  def verify_permissions_and_batch_create_entity(
100
110
  batch: List[AnyRequest],
101
- resource_type: ResourceType,
102
111
  create_method: Callable[[List[AnyRequest]], List[AnyResponse]],
103
112
  ) -> List[AnyResponse]:
104
113
  """Verify permissions and create a batch of entities if authorized.
105
114
 
106
115
  Args:
107
116
  batch: The batch to create.
108
- resource_type: The resource type of the entities to create.
109
117
  create_method: The method to create the entities.
110
118
 
111
119
  Raises:
112
- IllegalOperationError: If the request model has a different owner then
113
- the currently authenticated user.
114
120
  RuntimeError: If the resource type is usage-tracked.
115
121
 
116
122
  Returns:
@@ -119,23 +125,73 @@ def verify_permissions_and_batch_create_entity(
119
125
  auth_context = get_auth_context()
120
126
  assert auth_context
121
127
 
128
+ resource_types = set()
122
129
  for request_model in batch:
130
+ resource_type = get_resource_type_for_model(request_model)
131
+ if resource_type:
132
+ resource_types.add(resource_type)
133
+
123
134
  if isinstance(request_model, UserScopedRequest):
124
- if request_model.user != auth_context.user.id:
125
- raise IllegalOperationError(
126
- f"Not allowed to create resource '{resource_type}' for a "
127
- "different user."
128
- )
135
+ # Ignore the user field set in the request model, if any, and set it
136
+ # to the current user's ID instead. This is just a precaution, given
137
+ # that the SQLZenStore also does this same validation on all request
138
+ # models.
139
+ request_model.user = auth_context.user.id
129
140
 
130
- verify_permission(resource_type=resource_type, action=Action.CREATE)
141
+ batch_verify_permissions_for_models(models=batch, action=Action.CREATE)
131
142
 
132
- if resource_type in server_config().reportable_resources:
143
+ if resource_types & set(server_config().reportable_resources):
133
144
  raise RuntimeError(
134
- "Batch requests are currently not possible with usage-tracked features."
145
+ "Batch requests are currently not possible with usage-tracked "
146
+ "features."
135
147
  )
136
148
 
137
149
  created = create_method(batch)
138
- return created
150
+ return dehydrate_response_model_batch(created)
151
+
152
+
153
+ def verify_permissions_and_get_or_create_entity(
154
+ request_model: AnyRequest,
155
+ get_or_create_method: Callable[
156
+ [AnyRequest, Optional[Callable[[], None]]], Tuple[AnyResponse, bool]
157
+ ],
158
+ ) -> Tuple[AnyResponse, bool]:
159
+ """Verify permissions and create the entity if authorized.
160
+
161
+ Args:
162
+ request_model: The entity request model.
163
+ get_or_create_method: The method to get or create the entity.
164
+
165
+ Returns:
166
+ The entity and a boolean indicating whether the entity was created.
167
+ """
168
+ if isinstance(request_model, UserScopedRequest):
169
+ auth_context = get_auth_context()
170
+ assert auth_context
171
+
172
+ # Ignore the user field set in the request model, if any, and set it to
173
+ # the current user's ID instead. This is just a precaution, given that
174
+ # the SQLZenStore also does this same validation on all request models.
175
+ request_model.user = auth_context.user.id
176
+
177
+ resource_type = get_resource_type_for_model(request_model)
178
+ needs_usage_increment = (
179
+ resource_type and resource_type in server_config().reportable_resources
180
+ )
181
+
182
+ def _pre_creation_hook() -> None:
183
+ verify_permission_for_model(model=request_model, action=Action.CREATE)
184
+ if resource_type and needs_usage_increment:
185
+ check_entitlement(resource_type=resource_type)
186
+
187
+ model, created = get_or_create_method(request_model, _pre_creation_hook)
188
+
189
+ if not created:
190
+ verify_permission_for_model(model=model, action=Action.READ)
191
+ elif resource_type and needs_usage_increment:
192
+ report_usage(resource_type, resource_id=model.id)
193
+
194
+ return dehydrate_response_model(model), created
139
195
 
140
196
 
141
197
  def verify_permissions_and_get_entity(
@@ -174,11 +230,30 @@ def verify_permissions_and_list_entities(
174
230
 
175
231
  Returns:
176
232
  A page of entity models.
233
+
234
+ Raises:
235
+ ValueError: If the filter's workspace scope is not set or is not a UUID.
177
236
  """
178
237
  auth_context = get_auth_context()
179
238
  assert auth_context
180
239
 
181
- allowed_ids = get_allowed_resource_ids(resource_type=resource_type)
240
+ workspace_id: Optional[UUID] = None
241
+ if isinstance(filter_model, WorkspaceScopedFilter):
242
+ # A workspace scoped filter must always be scoped to a specific
243
+ # workspace. This is required for the RBAC check to work.
244
+ set_filter_workspace_scope(filter_model)
245
+ if not filter_model.workspace or not isinstance(
246
+ filter_model.workspace, UUID
247
+ ):
248
+ raise ValueError(
249
+ "Workspace scope must be a UUID, got "
250
+ f"{type(filter_model.workspace)}."
251
+ )
252
+ workspace_id = filter_model.workspace
253
+
254
+ allowed_ids = get_allowed_resource_ids(
255
+ resource_type=resource_type, workspace_id=workspace_id
256
+ )
182
257
  filter_model.configure_rbac(
183
258
  authenticated_user_id=auth_context.user.id, id=allowed_ids
184
259
  )
@@ -191,6 +266,7 @@ def verify_permissions_and_update_entity(
191
266
  update_model: AnyUpdate,
192
267
  get_method: Callable[[UUIDOrStr, bool], AnyResponse],
193
268
  update_method: Callable[[UUIDOrStr, AnyUpdate], AnyResponse],
269
+ **update_method_kwargs: Any,
194
270
  ) -> AnyResponse:
195
271
  """Verify permissions and update an entity.
196
272
 
@@ -199,6 +275,7 @@ def verify_permissions_and_update_entity(
199
275
  update_model: The entity update model.
200
276
  get_method: The method to fetch the entity.
201
277
  update_method: The method to update the entity.
278
+ update_method_kwargs: Keyword arguments to pass to the update method.
202
279
 
203
280
  Returns:
204
281
  A model of the updated entity.
@@ -206,7 +283,9 @@ def verify_permissions_and_update_entity(
206
283
  # We don't need the hydrated version here
207
284
  model = get_method(id, False)
208
285
  verify_permission_for_model(model, action=Action.UPDATE)
209
- updated_model = update_method(model.id, update_model)
286
+ updated_model = update_method(
287
+ model.id, update_model, **update_method_kwargs
288
+ )
210
289
  return dehydrate_response_model(updated_model)
211
290
 
212
291
 
@@ -214,6 +293,7 @@ def verify_permissions_and_delete_entity(
214
293
  id: UUIDOrStr,
215
294
  get_method: Callable[[UUIDOrStr, bool], AnyResponse],
216
295
  delete_method: Callable[[UUIDOrStr], None],
296
+ **delete_method_kwargs: Any,
217
297
  ) -> AnyResponse:
218
298
  """Verify permissions and delete an entity.
219
299
 
@@ -221,6 +301,7 @@ def verify_permissions_and_delete_entity(
221
301
  id: The ID of the entity to delete.
222
302
  get_method: The method to fetch the entity.
223
303
  delete_method: The method to delete the entity.
304
+ delete_method_kwargs: Keyword arguments to pass to the delete method.
224
305
 
225
306
  Returns:
226
307
  The deleted entity.
@@ -228,7 +309,8 @@ def verify_permissions_and_delete_entity(
228
309
  # We don't need the hydrated version here
229
310
  model = get_method(id, False)
230
311
  verify_permission_for_model(model, action=Action.DELETE)
231
- delete_method(model.id)
312
+ delete_method(model.id, **delete_method_kwargs)
313
+ delete_model_resource(model)
232
314
 
233
315
  return model
234
316
 
@@ -236,6 +318,7 @@ def verify_permissions_and_delete_entity(
236
318
  def verify_permissions_and_prune_entities(
237
319
  resource_type: ResourceType,
238
320
  prune_method: Callable[..., None],
321
+ workspace_id: Optional[UUID] = None,
239
322
  **kwargs: Any,
240
323
  ) -> None:
241
324
  """Verify permissions and prune entities of certain type.
@@ -243,7 +326,12 @@ def verify_permissions_and_prune_entities(
243
326
  Args:
244
327
  resource_type: The resource type of the entities to prune.
245
328
  prune_method: The method to prune the entities.
329
+ workspace_id: The workspace ID to prune the entities for.
246
330
  kwargs: Keyword arguments to pass to the prune method.
247
331
  """
248
- verify_permission(resource_type=resource_type, action=Action.PRUNE)
332
+ verify_permission(
333
+ resource_type=resource_type,
334
+ action=Action.PRUNE,
335
+ workspace_id=workspace_id,
336
+ )
249
337
  prune_method(**kwargs)
@@ -16,7 +16,11 @@
16
16
  from typing import Optional
17
17
  from uuid import UUID
18
18
 
19
- from pydantic import BaseModel, ConfigDict
19
+ from pydantic import (
20
+ BaseModel,
21
+ ConfigDict,
22
+ model_validator,
23
+ )
20
24
 
21
25
  from zenml.utils.enum_utils import StrEnum
22
26
 
@@ -69,9 +73,28 @@ class ResourceType(StrEnum):
69
73
  TAG = "tag"
70
74
  TRIGGER = "trigger"
71
75
  TRIGGER_EXECUTION = "trigger_execution"
76
+ WORKSPACE = "workspace"
72
77
  # Deactivated for now
73
78
  # USER = "user"
74
- # WORKSPACE = "workspace"
79
+
80
+ def is_workspace_scoped(self) -> bool:
81
+ """Check if a resource type is workspace scoped.
82
+
83
+ Returns:
84
+ Whether the resource type is workspace scoped.
85
+ """
86
+ return self not in [
87
+ self.FLAVOR,
88
+ self.SECRET,
89
+ self.SERVICE_CONNECTOR,
90
+ self.STACK,
91
+ self.STACK_COMPONENT,
92
+ self.TAG,
93
+ self.SERVICE_ACCOUNT,
94
+ self.WORKSPACE,
95
+ # Deactivated for now
96
+ # self.USER,
97
+ ]
75
98
 
76
99
 
77
100
  class Resource(BaseModel):
@@ -79,6 +102,7 @@ class Resource(BaseModel):
79
102
 
80
103
  type: str
81
104
  id: Optional[UUID] = None
105
+ workspace_id: Optional[UUID] = None
82
106
 
83
107
  def __str__(self) -> str:
84
108
  """Convert to a string.
@@ -86,10 +110,48 @@ class Resource(BaseModel):
86
110
  Returns:
87
111
  Resource string representation.
88
112
  """
89
- representation = self.type
113
+ workspace_id = self.workspace_id
114
+ if self.type == ResourceType.WORKSPACE and self.id:
115
+ # TODO: For now, we duplicate the workspace ID in the string
116
+ # representation when describing a workspace instance, because
117
+ # this is what is expected by the RBAC implementation.
118
+ workspace_id = self.id
119
+
120
+ if workspace_id:
121
+ representation = f"{workspace_id}:"
122
+ else:
123
+ representation = ""
124
+ representation += self.type
90
125
  if self.id:
91
126
  representation += f"/{self.id}"
92
127
 
93
128
  return representation
94
129
 
130
+ @model_validator(mode="after")
131
+ def validate_workspace_id(self) -> "Resource":
132
+ """Validate that workspace_id is set in combination with workspace-scoped resource types.
133
+
134
+ Raises:
135
+ ValueError: If workspace_id is not set for a workspace-scoped
136
+ resource or set for an unscoped resource.
137
+
138
+ Returns:
139
+ The validated resource.
140
+ """
141
+ resource_type = ResourceType(self.type)
142
+
143
+ if resource_type.is_workspace_scoped() and not self.workspace_id:
144
+ raise ValueError(
145
+ "workspace_id must be set for workspace-scoped resource type "
146
+ f"'{self.type}'"
147
+ )
148
+
149
+ if not resource_type.is_workspace_scoped() and self.workspace_id:
150
+ raise ValueError(
151
+ "workspace_id must not be set for global resource type "
152
+ f"'{self.type}'"
153
+ )
154
+
155
+ return self
156
+
95
157
  model_config = ConfigDict(frozen=True)
@@ -73,3 +73,12 @@ class RBACInterface(ABC):
73
73
  actions: The actions that the user should be able to perform on the
74
74
  resource.
75
75
  """
76
+
77
+ @abstractmethod
78
+ def delete_resources(self, resources: List[Resource]) -> None:
79
+ """Delete resource membership information for a list of resources.
80
+
81
+ Args:
82
+ resources: The resources for which to delete the resource membership
83
+ information.
84
+ """
@@ -63,7 +63,9 @@ class RBACSqlZenStore(SqlZenStore):
63
63
 
64
64
  try:
65
65
  verify_permission(
66
- resource_type=ResourceType.MODEL, action=Action.CREATE
66
+ resource_type=ResourceType.MODEL,
67
+ action=Action.CREATE,
68
+ workspace_id=model_request.workspace,
67
69
  )
68
70
  check_entitlement(resource_type=ResourceType.MODEL)
69
71
  except Exception as e:
@@ -76,7 +78,10 @@ class RBACSqlZenStore(SqlZenStore):
76
78
  )
77
79
  else:
78
80
  try:
79
- model_response = self.get_model(model_request.name)
81
+ model_response = self.get_model_by_name_or_id(
82
+ model_name_or_id=model_request.name,
83
+ workspace=model_request.workspace,
84
+ )
80
85
  created = False
81
86
  except KeyError:
82
87
  # The model does not exist. We now raise the error that
@@ -145,17 +150,20 @@ class RBACSqlZenStore(SqlZenStore):
145
150
 
146
151
  try:
147
152
  verify_permission(
148
- resource_type=ResourceType.MODEL_VERSION, action=Action.CREATE
153
+ resource_type=ResourceType.MODEL_VERSION,
154
+ action=Action.CREATE,
155
+ workspace_id=model_version_request.workspace,
149
156
  )
150
157
  except Exception as e:
151
158
  allow_creation = False
152
159
  error = e
153
160
 
154
161
  if allow_creation:
155
- created, model_version_response = (
156
- super()._get_or_create_model_version(
157
- model_version_request, producer_run_id=producer_run_id
158
- )
162
+ (
163
+ created,
164
+ model_version_response,
165
+ ) = super()._get_or_create_model_version(
166
+ model_version_request, producer_run_id=producer_run_id
159
167
  )
160
168
  else:
161
169
  try: