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
@@ -27,8 +27,9 @@ from typing import (
27
27
  )
28
28
  from uuid import UUID
29
29
 
30
- from pydantic import Field
30
+ from pydantic import Field, model_validator
31
31
 
32
+ from zenml.logger import get_logger
32
33
  from zenml.models.v2.base.base import (
33
34
  BaseDatedResponseBody,
34
35
  BaseIdentifiedResponse,
@@ -47,6 +48,9 @@ if TYPE_CHECKING:
47
48
 
48
49
  AnySchema = TypeVar("AnySchema", bound=BaseSchema)
49
50
 
51
+ logger = get_logger(__name__)
52
+
53
+
50
54
  # ---------------------- Request Models ----------------------
51
55
 
52
56
 
@@ -56,7 +60,14 @@ class UserScopedRequest(BaseRequest):
56
60
  Used as a base class for all domain models that are "owned" by a user.
57
61
  """
58
62
 
59
- user: UUID = Field(title="The id of the user that created this resource.")
63
+ user: Optional[UUID] = Field(
64
+ default=None,
65
+ title="The id of the user that created this resource. Set "
66
+ "automatically by the server.",
67
+ # This field is set automatically by the server, so the client doesn't
68
+ # need to set it and it will not be serialized.
69
+ exclude=True,
70
+ )
60
71
 
61
72
  def get_analytics_metadata(self) -> Dict[str, Any]:
62
73
  """Fetches the analytics metadata for user scoped models.
@@ -157,7 +168,6 @@ class UserScopedFilter(BaseFilter):
157
168
  ]
158
169
  CLI_EXCLUDE_FIELDS: ClassVar[List[str]] = [
159
170
  *BaseFilter.CLI_EXCLUDE_FIELDS,
160
- "user_id",
161
171
  "scope_user",
162
172
  ]
163
173
  CUSTOM_SORTING_OPTIONS: ClassVar[List[str]] = [
@@ -169,14 +179,10 @@ class UserScopedFilter(BaseFilter):
169
179
  default=None,
170
180
  description="The user to scope this query to.",
171
181
  )
172
- user_id: Optional[Union[UUID, str]] = Field(
173
- default=None,
174
- description="UUID of the user that created the entity.",
175
- union_mode="left_to_right",
176
- )
177
182
  user: Optional[Union[UUID, str]] = Field(
178
183
  default=None,
179
184
  description="Name/ID of the user that created the entity.",
185
+ union_mode="left_to_right",
180
186
  )
181
187
 
182
188
  def set_scope_user(self, user_id: UUID) -> None:
@@ -316,6 +322,18 @@ class WorkspaceScopedResponse(
316
322
  Used as a base class for all domain models that are workspace-scoped.
317
323
  """
318
324
 
325
+ # Analytics
326
+ def get_analytics_metadata(self) -> Dict[str, Any]:
327
+ """Fetches the analytics metadata for workspace scoped models.
328
+
329
+ Returns:
330
+ The analytics metadata.
331
+ """
332
+ metadata = super().get_analytics_metadata()
333
+ if self.workspace is not None:
334
+ metadata["workspace_id"] = self.workspace.id
335
+ return metadata
336
+
319
337
  # Body and metadata properties
320
338
  @property
321
339
  def workspace(self) -> "WorkspaceResponse":
@@ -327,75 +345,25 @@ class WorkspaceScopedResponse(
327
345
  return self.get_metadata().workspace
328
346
 
329
347
 
348
+ # ---------------------- Filter Models ----------------------
349
+
350
+
330
351
  class WorkspaceScopedFilter(UserScopedFilter):
331
352
  """Model to enable advanced scoping with workspace."""
332
353
 
333
354
  FILTER_EXCLUDE_FIELDS: ClassVar[List[str]] = [
334
355
  *UserScopedFilter.FILTER_EXCLUDE_FIELDS,
335
356
  "workspace",
336
- "scope_workspace",
337
- ]
338
- CLI_EXCLUDE_FIELDS: ClassVar[List[str]] = [
339
- *UserScopedFilter.CLI_EXCLUDE_FIELDS,
340
- "workspace_id",
341
- "workspace",
342
- "scope_workspace",
343
- ]
344
- CUSTOM_SORTING_OPTIONS: ClassVar[List[str]] = [
345
- *UserScopedFilter.CUSTOM_SORTING_OPTIONS,
346
- "workspace",
347
357
  ]
348
- scope_workspace: Optional[UUID] = Field(
349
- default=None,
350
- description="The workspace to scope this query to.",
351
- )
352
- workspace_id: Optional[Union[UUID, str]] = Field(
353
- default=None,
354
- description="UUID of the workspace that this entity belongs to.",
355
- union_mode="left_to_right",
356
- )
357
358
  workspace: Optional[Union[UUID, str]] = Field(
358
359
  default=None,
359
- description="Name/ID of the workspace that this entity belongs to.",
360
+ description="Name/ID of the workspace which the search is scoped to. "
361
+ "This field must always be set and is always applied in addition to "
362
+ "the other filters, regardless of the value of the "
363
+ "logical_operator field.",
364
+ union_mode="left_to_right",
360
365
  )
361
366
 
362
- def set_scope_workspace(self, workspace_id: UUID) -> None:
363
- """Set the workspace to scope this response.
364
-
365
- Args:
366
- workspace_id: The workspace to scope this response to.
367
- """
368
- self.scope_workspace = workspace_id
369
-
370
- def get_custom_filters(
371
- self, table: Type["AnySchema"]
372
- ) -> List["ColumnElement[bool]"]:
373
- """Get custom filters.
374
-
375
- Args:
376
- table: The query table.
377
-
378
- Returns:
379
- A list of custom filters.
380
- """
381
- custom_filters = super().get_custom_filters(table)
382
-
383
- from sqlmodel import and_
384
-
385
- from zenml.zen_stores.schemas import WorkspaceSchema
386
-
387
- if self.workspace:
388
- workspace_filter = and_(
389
- getattr(table, "workspace_id") == WorkspaceSchema.id,
390
- self.generate_name_or_id_query_conditions(
391
- value=self.workspace,
392
- table=WorkspaceSchema,
393
- ),
394
- )
395
- custom_filters.append(workspace_filter)
396
-
397
- return custom_filters
398
-
399
367
  def apply_filter(
400
368
  self,
401
369
  query: AnyQuery,
@@ -409,59 +377,35 @@ class WorkspaceScopedFilter(UserScopedFilter):
409
377
 
410
378
  Returns:
411
379
  The query with filter applied.
412
- """
413
- from sqlmodel import or_
414
-
415
- query = super().apply_filter(query=query, table=table)
416
-
417
- if self.scope_workspace:
418
- scope_filter = or_(
419
- getattr(table, "workspace_id") == self.scope_workspace,
420
- getattr(table, "workspace_id").is_(None),
421
- )
422
- query = query.where(scope_filter)
423
-
424
- return query
425
-
426
- def apply_sorting(
427
- self,
428
- query: AnyQuery,
429
- table: Type["AnySchema"],
430
- ) -> AnyQuery:
431
- """Apply sorting to the query.
432
380
 
433
- Args:
434
- query: The query to which to apply the sorting.
435
- table: The query table.
436
-
437
- Returns:
438
- The query with sorting applied.
381
+ Raises:
382
+ ValueError: If the workspace scope is missing from the filter.
439
383
  """
440
- from sqlmodel import asc, desc
441
-
442
- from zenml.enums import SorterOps
443
- from zenml.zen_stores.schemas import WorkspaceSchema
444
-
445
- sort_by, operand = self.sorting_params
446
-
447
- if sort_by == "workspace":
448
- column = WorkspaceSchema.name
384
+ query = super().apply_filter(query=query, table=table)
449
385
 
450
- query = query.join(
451
- WorkspaceSchema,
452
- getattr(table, "workspace_id") == WorkspaceSchema.id,
386
+ # The workspace scope must always be set and must be a UUID. If the
387
+ # client sets this to a string, the server will try to resolve it to a
388
+ # workspace ID.
389
+ #
390
+ # If not set by the client, the server will fall back to using the
391
+ # user's default workspace or even the server's default workspace, if
392
+ # they are configured. If this also fails to yield a workspace, this
393
+ # method will raise a ValueError.
394
+ #
395
+ # See: SqlZenStore._set_filter_workspace_id
396
+
397
+ if not self.workspace:
398
+ raise ValueError("Workspace scope missing from the filter.")
399
+
400
+ if not isinstance(self.workspace, UUID):
401
+ raise ValueError(
402
+ f"Workspace scope must be a UUID, got {type(self.workspace)}."
453
403
  )
454
404
 
455
- query = query.add_columns(WorkspaceSchema.name)
405
+ scope_filter = getattr(table, "workspace_id") == self.workspace
406
+ query = query.where(scope_filter)
456
407
 
457
- if operand == SorterOps.ASCENDING:
458
- query = query.order_by(asc(column))
459
- else:
460
- query = query.order_by(desc(column))
461
-
462
- return query
463
-
464
- return super().apply_sorting(query=query, table=table)
408
+ return query
465
409
 
466
410
 
467
411
  class TaggableFilter(BaseFilter):
@@ -470,16 +414,44 @@ class TaggableFilter(BaseFilter):
470
414
  tag: Optional[str] = Field(
471
415
  description="Tag to apply to the filter query.", default=None
472
416
  )
417
+ tags: Optional[List[str]] = Field(
418
+ description="Tags to apply to the filter query.", default=None
419
+ )
473
420
 
421
+ CLI_EXCLUDE_FIELDS = [
422
+ *BaseFilter.CLI_EXCLUDE_FIELDS,
423
+ ]
474
424
  FILTER_EXCLUDE_FIELDS: ClassVar[List[str]] = [
475
425
  *BaseFilter.FILTER_EXCLUDE_FIELDS,
476
426
  "tag",
427
+ "tags",
477
428
  ]
478
429
  CUSTOM_SORTING_OPTIONS: ClassVar[List[str]] = [
479
430
  *BaseFilter.CUSTOM_SORTING_OPTIONS,
480
431
  "tags",
481
432
  ]
482
433
 
434
+ @model_validator(mode="after")
435
+ def add_tag_to_tags(self) -> "TaggableFilter":
436
+ """Deprecated the tag attribute in favor of the tags attribute.
437
+
438
+ Returns:
439
+ self
440
+ """
441
+ if self.tag is not None:
442
+ logger.warning(
443
+ "The `tag` attribute is deprecated in favor of the `tags` attribute. "
444
+ "Please update your code to use the `tags` attribute instead."
445
+ )
446
+ if self.tags is not None:
447
+ self.tags.append(self.tag)
448
+ else:
449
+ self.tags = [self.tag]
450
+
451
+ self.tag = None
452
+
453
+ return self
454
+
483
455
  def apply_filter(
484
456
  self,
485
457
  query: AnyQuery,
@@ -497,7 +469,8 @@ class TaggableFilter(BaseFilter):
497
469
  from zenml.zen_stores.schemas import TagResourceSchema, TagSchema
498
470
 
499
471
  query = super().apply_filter(query=query, table=table)
500
- if self.tag:
472
+
473
+ if self.tags is not None:
501
474
  query = query.join(
502
475
  TagResourceSchema,
503
476
  TagResourceSchema.resource_id == getattr(table, "id"),
@@ -516,15 +489,25 @@ class TaggableFilter(BaseFilter):
516
489
  Returns:
517
490
  A list of custom filters.
518
491
  """
519
- from zenml.zen_stores.schemas import TagSchema
520
-
521
492
  custom_filters = super().get_custom_filters(table)
522
- if self.tag:
523
- custom_filters.append(
524
- self.generate_custom_query_conditions_for_column(
525
- value=self.tag, table=TagSchema, column="name"
493
+
494
+ if self.tags is not None:
495
+ from sqlmodel import exists, select
496
+
497
+ from zenml.zen_stores.schemas import TagResourceSchema, TagSchema
498
+
499
+ for tag in self.tags:
500
+ condition = self.generate_custom_query_conditions_for_column(
501
+ value=tag, table=TagSchema, column="name"
526
502
  )
527
- )
503
+ exists_subquery = exists(
504
+ select(TagResourceSchema)
505
+ .join(TagSchema, TagSchema.id == TagResourceSchema.tag_id) # type: ignore[arg-type]
506
+ .where(
507
+ TagResourceSchema.resource_id == table.id, condition
508
+ )
509
+ )
510
+ custom_filters.append(exists_subquery)
528
511
 
529
512
  return custom_filters
530
513
 
@@ -97,7 +97,7 @@ class APIKeyRequest(BaseRequest):
97
97
  )
98
98
 
99
99
 
100
- class APIKeyRotateRequest(BaseModel):
100
+ class APIKeyRotateRequest(BaseRequest):
101
101
  """Request model for API key rotation."""
102
102
 
103
103
  retain_period_minutes: int = Field(
@@ -25,17 +25,19 @@ from typing import (
25
25
  )
26
26
  from uuid import UUID
27
27
 
28
- from pydantic import BaseModel, Field
28
+ from pydantic import Field
29
29
 
30
30
  from zenml.constants import SORT_BY_LATEST_VERSION_KEY, STR_FIELD_MAX_LENGTH
31
- from zenml.models.v2.base.base import (
32
- BaseDatedResponseBody,
33
- BaseIdentifiedResponse,
34
- BaseRequest,
35
- BaseResponseMetadata,
36
- BaseResponseResources,
31
+ from zenml.models.v2.base.base import BaseUpdate
32
+ from zenml.models.v2.base.scoped import (
33
+ TaggableFilter,
34
+ WorkspaceScopedFilter,
35
+ WorkspaceScopedRequest,
36
+ WorkspaceScopedResponse,
37
+ WorkspaceScopedResponseBody,
38
+ WorkspaceScopedResponseMetadata,
39
+ WorkspaceScopedResponseResources,
37
40
  )
38
- from zenml.models.v2.base.scoped import TaggableFilter
39
41
  from zenml.models.v2.core.tag import TagResponse
40
42
 
41
43
  if TYPE_CHECKING:
@@ -49,7 +51,7 @@ AnyQuery = TypeVar("AnyQuery", bound=Any)
49
51
  # ------------------ Request Model ------------------
50
52
 
51
53
 
52
- class ArtifactRequest(BaseRequest):
54
+ class ArtifactRequest(WorkspaceScopedRequest):
53
55
  """Artifact request model."""
54
56
 
55
57
  name: str = Field(
@@ -70,7 +72,7 @@ class ArtifactRequest(BaseRequest):
70
72
  # ------------------ Update Model ------------------
71
73
 
72
74
 
73
- class ArtifactUpdate(BaseModel):
75
+ class ArtifactUpdate(BaseUpdate):
74
76
  """Artifact update model."""
75
77
 
76
78
  name: Optional[str] = None
@@ -82,7 +84,7 @@ class ArtifactUpdate(BaseModel):
82
84
  # ------------------ Response Model ------------------
83
85
 
84
86
 
85
- class ArtifactResponseBody(BaseDatedResponseBody):
87
+ class ArtifactResponseBody(WorkspaceScopedResponseBody):
86
88
  """Response body for artifacts."""
87
89
 
88
90
  tags: List[TagResponse] = Field(
@@ -92,7 +94,7 @@ class ArtifactResponseBody(BaseDatedResponseBody):
92
94
  latest_version_id: Optional[UUID] = None
93
95
 
94
96
 
95
- class ArtifactResponseMetadata(BaseResponseMetadata):
97
+ class ArtifactResponseMetadata(WorkspaceScopedResponseMetadata):
96
98
  """Response metadata for artifacts."""
97
99
 
98
100
  has_custom_name: bool = Field(
@@ -101,12 +103,12 @@ class ArtifactResponseMetadata(BaseResponseMetadata):
101
103
  )
102
104
 
103
105
 
104
- class ArtifactResponseResources(BaseResponseResources):
106
+ class ArtifactResponseResources(WorkspaceScopedResponseResources):
105
107
  """Class for all resource models associated with the Artifact Entity."""
106
108
 
107
109
 
108
110
  class ArtifactResponse(
109
- BaseIdentifiedResponse[
111
+ WorkspaceScopedResponse[
110
112
  ArtifactResponseBody,
111
113
  ArtifactResponseMetadata,
112
114
  ArtifactResponseResources,
@@ -176,24 +178,35 @@ class ArtifactResponse(
176
178
  """
177
179
  from zenml.client import Client
178
180
 
179
- responses = Client().list_artifact_versions(name=self.name)
181
+ responses = Client().list_artifact_versions(artifact=self.name)
180
182
  return {str(response.version): response for response in responses}
181
183
 
182
184
 
183
185
  # ------------------ Filter Model ------------------
184
186
 
185
187
 
186
- class ArtifactFilter(TaggableFilter):
188
+ class ArtifactFilter(WorkspaceScopedFilter, TaggableFilter):
187
189
  """Model to enable advanced filtering of artifacts."""
188
190
 
189
- name: Optional[str] = None
190
- has_custom_name: Optional[bool] = None
191
+ FILTER_EXCLUDE_FIELDS: ClassVar[List[str]] = [
192
+ *WorkspaceScopedFilter.FILTER_EXCLUDE_FIELDS,
193
+ *TaggableFilter.FILTER_EXCLUDE_FIELDS,
194
+ ]
191
195
 
192
196
  CUSTOM_SORTING_OPTIONS: ClassVar[List[str]] = [
197
+ *WorkspaceScopedFilter.CUSTOM_SORTING_OPTIONS,
193
198
  *TaggableFilter.CUSTOM_SORTING_OPTIONS,
194
199
  SORT_BY_LATEST_VERSION_KEY,
195
200
  ]
196
201
 
202
+ CLI_EXCLUDE_FIELDS: ClassVar[List[str]] = [
203
+ *WorkspaceScopedFilter.CLI_EXCLUDE_FIELDS,
204
+ *TaggableFilter.CLI_EXCLUDE_FIELDS,
205
+ ]
206
+
207
+ name: Optional[str] = None
208
+ has_custom_name: Optional[bool] = None
209
+
197
210
  def apply_sorting(
198
211
  self,
199
212
  query: AnyQuery,
@@ -27,7 +27,6 @@ from typing import (
27
27
  from uuid import UUID
28
28
 
29
29
  from pydantic import (
30
- BaseModel,
31
30
  ConfigDict,
32
31
  Field,
33
32
  field_validator,
@@ -36,10 +35,11 @@ from pydantic import (
36
35
 
37
36
  from zenml.config.source import Source, SourceWithValidator
38
37
  from zenml.constants import STR_FIELD_MAX_LENGTH, TEXT_FIELD_MAX_LENGTH
39
- from zenml.enums import ArtifactSaveType, ArtifactType, GenericFilterOps
38
+ from zenml.enums import ArtifactSaveType, ArtifactType
40
39
  from zenml.logger import get_logger
41
40
  from zenml.metadata.metadata_types import MetadataType
42
- from zenml.models.v2.base.filter import FilterGenerator, StrFilter
41
+ from zenml.models.v2.base.base import BaseUpdate
42
+ from zenml.models.v2.base.filter import FilterGenerator
43
43
  from zenml.models.v2.base.scoped import (
44
44
  TaggableFilter,
45
45
  WorkspaceScopedFilter,
@@ -165,7 +165,7 @@ class ArtifactVersionRequest(WorkspaceScopedRequest):
165
165
  # ------------------ Update Model ------------------
166
166
 
167
167
 
168
- class ArtifactVersionUpdate(BaseModel):
168
+ class ArtifactVersionUpdate(BaseUpdate):
169
169
  """Artifact version update model."""
170
170
 
171
171
  name: Optional[str] = None
@@ -476,7 +476,8 @@ class ArtifactVersionFilter(WorkspaceScopedFilter, TaggableFilter):
476
476
  FILTER_EXCLUDE_FIELDS: ClassVar[List[str]] = [
477
477
  *WorkspaceScopedFilter.FILTER_EXCLUDE_FIELDS,
478
478
  *TaggableFilter.FILTER_EXCLUDE_FIELDS,
479
- "name",
479
+ "artifact_id",
480
+ "artifact",
480
481
  "only_unused",
481
482
  "has_custom_name",
482
483
  "model",
@@ -484,23 +485,28 @@ class ArtifactVersionFilter(WorkspaceScopedFilter, TaggableFilter):
484
485
  "model_version_id",
485
486
  "run_metadata",
486
487
  ]
487
- CUSTOM_SORTING_OPTIONS = [
488
+ CUSTOM_SORTING_OPTIONS: ClassVar[List[str]] = [
488
489
  *WorkspaceScopedFilter.CUSTOM_SORTING_OPTIONS,
489
490
  *TaggableFilter.CUSTOM_SORTING_OPTIONS,
490
491
  ]
491
- CLI_EXCLUDE_FIELDS = [
492
+ CLI_EXCLUDE_FIELDS: ClassVar[List[str]] = [
492
493
  *WorkspaceScopedFilter.CLI_EXCLUDE_FIELDS,
493
494
  *TaggableFilter.CLI_EXCLUDE_FIELDS,
495
+ "artifact_id",
494
496
  ]
495
497
 
496
- artifact_id: Optional[Union[UUID, str]] = Field(
498
+ artifact: Optional[Union[UUID, str]] = Field(
497
499
  default=None,
498
- description="ID of the artifact to which this version belongs.",
500
+ description="The name or ID of the artifact which the search is scoped "
501
+ "to. This field must always be set and is always applied in addition "
502
+ "to the other filters, regardless of the value of the "
503
+ "logical_operator field.",
499
504
  union_mode="left_to_right",
500
505
  )
501
- name: Optional[str] = Field(
506
+ artifact_id: Optional[Union[UUID, str]] = Field(
502
507
  default=None,
503
- description="Name of the artifact to which this version belongs.",
508
+ description="[Deprecated] Use 'artifact' instead. ID of the artifact to which this version belongs.",
509
+ union_mode="left_to_right",
504
510
  )
505
511
  version: Optional[str] = Field(
506
512
  default=None,
@@ -545,10 +551,6 @@ class ArtifactVersionFilter(WorkspaceScopedFilter, TaggableFilter):
545
551
  default=None,
546
552
  description="Filter only artifacts with/without custom names.",
547
553
  )
548
- user: Optional[Union[UUID, str]] = Field(
549
- default=None,
550
- description="Name/ID of the user that created the artifact version.",
551
- )
552
554
  model: Optional[Union[UUID, str]] = Field(
553
555
  default=None,
554
556
  description="Name/ID of the model that is associated with this "
@@ -595,18 +597,15 @@ class ArtifactVersionFilter(WorkspaceScopedFilter, TaggableFilter):
595
597
  StepRunSchema,
596
598
  )
597
599
 
598
- if self.name:
599
- value, filter_operator = self._resolve_operator(self.name)
600
- filter_ = StrFilter(
601
- operation=GenericFilterOps(filter_operator),
602
- column="name",
603
- value=value,
604
- )
605
- artifact_name_filter = and_(
600
+ if self.artifact:
601
+ value, operator = self._resolve_operator(self.artifact)
602
+ artifact_filter = and_(
606
603
  ArtifactVersionSchema.artifact_id == ArtifactSchema.id,
607
- filter_.generate_query_conditions(ArtifactSchema),
604
+ self.generate_name_or_id_query_conditions(
605
+ value=self.artifact, table=ArtifactSchema
606
+ ),
608
607
  )
609
- custom_filters.append(artifact_name_filter)
608
+ custom_filters.append(artifact_filter)
610
609
 
611
610
  if self.only_unused:
612
611
  unused_filter = and_(
@@ -702,6 +701,24 @@ class ArtifactVersionFilter(WorkspaceScopedFilter, TaggableFilter):
702
701
 
703
702
  return custom_filters
704
703
 
704
+ @model_validator(mode="after")
705
+ def _migrate_artifact_id(self) -> "ArtifactVersionFilter":
706
+ """Migrate value from the deprecated artifact_id attribute.
707
+
708
+ Returns:
709
+ The filter with migrated value.
710
+ """
711
+ # Handle deprecated artifact_id field
712
+ if self.artifact_id is not None:
713
+ logger.warning(
714
+ "The 'ArtifactVersionFilter.artifact_id' field is deprecated "
715
+ "and will be removed in a future version. Please use "
716
+ "'ArtifactVersionFilter.artifact' instead."
717
+ )
718
+ self.artifact = self.artifact or self.artifact_id
719
+
720
+ return self
721
+
705
722
 
706
723
  # -------------------- Lazy Loader --------------------
707
724