zenml-nightly 0.70.0.dev20241201__py3-none-any.whl → 0.71.0.dev20241220__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 (193) hide show
  1. README.md +4 -4
  2. RELEASE_NOTES.md +112 -0
  3. zenml/VERSION +1 -1
  4. zenml/artifacts/artifact_config.py +8 -5
  5. zenml/artifacts/utils.py +3 -1
  6. zenml/cli/__init__.py +4 -4
  7. zenml/cli/base.py +1 -1
  8. zenml/cli/pipeline.py +48 -79
  9. zenml/cli/server.py +19 -19
  10. zenml/client.py +54 -2
  11. zenml/config/secret_reference_mixin.py +1 -1
  12. zenml/config/server_config.py +4 -0
  13. zenml/constants.py +10 -0
  14. zenml/image_builders/base_image_builder.py +5 -2
  15. zenml/image_builders/build_context.py +7 -16
  16. zenml/integrations/aws/__init__.py +3 -0
  17. zenml/integrations/aws/flavors/__init__.py +6 -0
  18. zenml/integrations/aws/flavors/aws_image_builder_flavor.py +146 -0
  19. zenml/integrations/aws/image_builders/__init__.py +20 -0
  20. zenml/integrations/aws/image_builders/aws_image_builder.py +307 -0
  21. zenml/integrations/gcp/orchestrators/vertex_orchestrator.py +1 -1
  22. zenml/integrations/kaniko/image_builders/kaniko_image_builder.py +2 -1
  23. zenml/integrations/kubernetes/flavors/kubernetes_orchestrator_flavor.py +11 -0
  24. zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +0 -1
  25. zenml/integrations/lightning/flavors/lightning_orchestrator_flavor.py +11 -0
  26. zenml/integrations/neptune/experiment_trackers/neptune_experiment_tracker.py +7 -5
  27. zenml/integrations/neptune/experiment_trackers/run_state.py +69 -53
  28. zenml/integrations/registry.py +2 -2
  29. zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +12 -0
  30. zenml/integrations/wandb/flavors/wandb_experiment_tracker_flavor.py +13 -5
  31. zenml/materializers/built_in_materializer.py +1 -1
  32. zenml/model/model.py +12 -16
  33. zenml/model/utils.py +3 -1
  34. zenml/models/v2/base/filter.py +26 -30
  35. zenml/models/v2/base/scoped.py +258 -5
  36. zenml/models/v2/core/artifact_version.py +15 -26
  37. zenml/models/v2/core/code_repository.py +1 -12
  38. zenml/models/v2/core/component.py +5 -46
  39. zenml/models/v2/core/flavor.py +1 -11
  40. zenml/models/v2/core/model.py +1 -57
  41. zenml/models/v2/core/model_version.py +5 -33
  42. zenml/models/v2/core/model_version_artifact.py +11 -3
  43. zenml/models/v2/core/model_version_pipeline_run.py +14 -3
  44. zenml/models/v2/core/pipeline.py +47 -55
  45. zenml/models/v2/core/pipeline_build.py +67 -12
  46. zenml/models/v2/core/pipeline_deployment.py +0 -10
  47. zenml/models/v2/core/pipeline_run.py +91 -29
  48. zenml/models/v2/core/run_template.py +21 -29
  49. zenml/models/v2/core/schedule.py +0 -10
  50. zenml/models/v2/core/secret.py +0 -14
  51. zenml/models/v2/core/service.py +9 -16
  52. zenml/models/v2/core/service_connector.py +0 -11
  53. zenml/models/v2/core/stack.py +21 -30
  54. zenml/models/v2/core/step_run.py +18 -14
  55. zenml/models/v2/core/trigger.py +19 -3
  56. zenml/orchestrators/base_orchestrator.py +13 -1
  57. zenml/orchestrators/output_utils.py +5 -1
  58. zenml/orchestrators/step_launcher.py +9 -13
  59. zenml/orchestrators/step_run_utils.py +8 -204
  60. zenml/orchestrators/utils.py +55 -27
  61. zenml/pipelines/build_utils.py +12 -0
  62. zenml/service_connectors/service_connector_utils.py +3 -9
  63. zenml/stack/stack_component.py +1 -1
  64. zenml/stack_deployments/aws_stack_deployment.py +22 -0
  65. zenml/utils/archivable.py +65 -36
  66. zenml/utils/code_utils.py +8 -4
  67. zenml/utils/docker_utils.py +9 -0
  68. zenml/zen_server/auth.py +9 -10
  69. zenml/zen_server/dashboard/assets/{404-NVXKFp-x.js → 404-Cqu3EDCm.js} +1 -1
  70. zenml/zen_server/dashboard/assets/{@reactflow-CK0KJUen.js → @reactflow-D2Y7BWwz.js} +1 -1
  71. zenml/zen_server/dashboard/assets/{AlertDialogDropdownItem-DezXKmDf.js → AlertDialogDropdownItem-BHd71pVS.js} +1 -1
  72. zenml/zen_server/dashboard/assets/{CodeSnippet-JzR8CEtw.js → CodeSnippet-DIonwetW.js} +1 -1
  73. zenml/zen_server/dashboard/assets/{CollapsibleCard-DQW_ktMO.js → CollapsibleCard-CDnC97pB.js} +1 -1
  74. zenml/zen_server/dashboard/assets/{Commands-DL2kwkRd.js → Commands-BVEXKAOj.js} +1 -1
  75. zenml/zen_server/dashboard/assets/{ComponentBadge-D_g62Wv8.js → ComponentBadge-CrRvovox.js} +1 -1
  76. zenml/zen_server/dashboard/assets/{CopyButton-LNcWaa14.js → CopyButton-B6wGAhQv.js} +1 -1
  77. zenml/zen_server/dashboard/assets/{CsvVizualization-DknpE5ej.js → CsvVizualization-CjcT7LMm.js} +5 -5
  78. zenml/zen_server/dashboard/assets/DeleteAlertDialog-D2ELtM2W.js +1 -0
  79. zenml/zen_server/dashboard/assets/{DialogItem-Bxf8FuAT.js → DialogItem-DXIMhBgU.js} +1 -1
  80. zenml/zen_server/dashboard/assets/{Error-DYflYyps.js → Error-B8uUfTpL.js} +1 -1
  81. zenml/zen_server/dashboard/assets/{ExecutionStatus-C7zyIQKZ.js → ExecutionStatus-ibAdY-dG.js} +1 -1
  82. zenml/zen_server/dashboard/assets/{Helpbox-oYSGpLqd.js → Helpbox-BfAfhKHw.js} +1 -1
  83. zenml/zen_server/dashboard/assets/{Infobox-Cx4xGoXR.js → Infobox-M_SMOu96.js} +1 -1
  84. zenml/zen_server/dashboard/assets/{InlineAvatar-DiGOWNKF.js → InlineAvatar-DBA0a0-a.js} +1 -1
  85. zenml/zen_server/dashboard/assets/{NestedCollapsible-DYbgyKxK.js → NestedCollapsible-DpgmEFKw.js} +1 -1
  86. zenml/zen_server/dashboard/assets/{Partials-03iZf8-N.js → Partials-D_ldD9if.js} +1 -1
  87. zenml/zen_server/dashboard/assets/{ProBadge-D_EB8HNo.js → ProBadge-DQbfFotM.js} +1 -1
  88. zenml/zen_server/dashboard/assets/{ProCta-DqNS4v3x.js → ProCta-Bcpb4rcY.js} +1 -1
  89. zenml/zen_server/dashboard/assets/{ProviderIcon-Bki2aw8w.js → ProviderIcon-BZpgPigN.js} +1 -1
  90. zenml/zen_server/dashboard/assets/{ProviderRadio-8f43sPD4.js → ProviderRadio-DWPnMuQ1.js} +1 -1
  91. zenml/zen_server/dashboard/assets/RunSelector-DgRGaAc6.js +1 -0
  92. zenml/zen_server/dashboard/assets/{RunsBody-07YEO7qI.js → RunsBody-KecfSkjY.js} +1 -1
  93. zenml/zen_server/dashboard/assets/{SearchField-lp1KgU4e.js → SearchField-n-ILHnaP.js} +1 -1
  94. zenml/zen_server/dashboard/assets/{SecretTooltip-CgnbyeOx.js → SecretTooltip-B8MrX5yu.js} +1 -1
  95. zenml/zen_server/dashboard/assets/{SetPassword-CpP418A2.js → SetPassword-B_IVq_wg.js} +1 -1
  96. zenml/zen_server/dashboard/assets/StackList-TWPBYnkF.js +1 -0
  97. zenml/zen_server/dashboard/assets/{Tabs-BktHkCJJ.js → Tabs-Rg857zmd.js} +1 -1
  98. zenml/zen_server/dashboard/assets/{Tick-BlMoIlJT.js → Tick-COg4A-xo.js} +1 -1
  99. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-Sc0A0pP-.js → UpdatePasswordSchemas-C6Aj3hm6.js} +1 -1
  100. zenml/zen_server/dashboard/assets/{UsageReason-YYduL4fj.js → UsageReason-BTLbx7w4.js} +1 -1
  101. zenml/zen_server/dashboard/assets/{WizardFooter-dgmizSJC.js → WizardFooter-BCAj69Vj.js} +1 -1
  102. zenml/zen_server/dashboard/assets/{all-pipeline-runs-query-D-c2G6lV.js → all-pipeline-runs-query-DMXkDrV2.js} +1 -1
  103. zenml/zen_server/dashboard/assets/code-snippets-CqONne41.js +13 -0
  104. zenml/zen_server/dashboard/assets/{create-stack-DM_JPgef.js → create-stack-HfdbhLs4.js} +1 -1
  105. zenml/zen_server/dashboard/assets/dates-3pMLCNrD.js +1 -0
  106. zenml/zen_server/dashboard/assets/delete-run-DZ4hIXff.js +1 -0
  107. zenml/zen_server/dashboard/assets/{form-schemas-K6FYKjwa.js → form-schemas-B0AVEd9b.js} +1 -1
  108. zenml/zen_server/dashboard/assets/{index-BAkC7FXi.js → index-DPqSWjug.js} +1 -1
  109. zenml/zen_server/dashboard/assets/{index-CEV4Cvaf.js → index-DScjfBRb.js} +1 -1
  110. zenml/zen_server/dashboard/assets/index-DXvT1_Um.css +1 -0
  111. zenml/zen_server/dashboard/assets/{index-CCOPpudF.js → index-FO-p0GU7.js} +5 -5
  112. zenml/zen_server/dashboard/assets/{index-B1mVPYxf.js → index-I3bKUGUj.js} +1 -1
  113. zenml/zen_server/dashboard/assets/key-icon-aH-QIa5R.js +1 -0
  114. zenml/zen_server/dashboard/assets/login-command-CkqxPtV3.js +1 -0
  115. zenml/zen_server/dashboard/assets/{login-mutation-hf-lK87O.js → login-mutation-BQeo4wTY.js} +1 -1
  116. zenml/zen_server/dashboard/assets/{not-found-BGirLjU-.js → not-found-gAJ5aDdR.js} +1 -1
  117. zenml/zen_server/dashboard/assets/page-9Y9-gig0.js +1 -0
  118. zenml/zen_server/dashboard/assets/{page-DjRJCGb3.js → page-AUwiQ14W.js} +1 -1
  119. zenml/zen_server/dashboard/assets/page-B6XU7yYT.js +2 -0
  120. zenml/zen_server/dashboard/assets/{page-C00YAkaB.js → page-BKZYc2Zv.js} +1 -1
  121. zenml/zen_server/dashboard/assets/{page-CdMWnQak.js → page-BU9FG4sR.js} +1 -1
  122. zenml/zen_server/dashboard/assets/{page-D7S3aCbF.js → page-B_Apk3xg.js} +1 -1
  123. zenml/zen_server/dashboard/assets/{page-Djikxq_S.js → page-BdowiCbr.js} +1 -1
  124. zenml/zen_server/dashboard/assets/page-Bg8OjTRe.js +1 -0
  125. zenml/zen_server/dashboard/assets/page-BxL4qD4_.js +1 -0
  126. zenml/zen_server/dashboard/assets/{page-DakHVWXF.js → page-CWxT5K5J.js} +1 -1
  127. zenml/zen_server/dashboard/assets/page-CXuQufSe.js +1 -0
  128. zenml/zen_server/dashboard/assets/{page-DLC-bNBP.js → page-CcQr8CPP.js} +1 -1
  129. zenml/zen_server/dashboard/assets/{page-CD-DcWoy.js → page-Ce4Hrjnr.js} +1 -1
  130. zenml/zen_server/dashboard/assets/page-CiYxgZP_.js +1 -0
  131. zenml/zen_server/dashboard/assets/page-Cldq1mpe.js +1 -0
  132. zenml/zen_server/dashboard/assets/{page-BDigxVpo.js → page-D4wdonLm.js} +1 -1
  133. zenml/zen_server/dashboard/assets/{page-D6uU2ax4.js → page-D8ObrbH8.js} +1 -1
  134. zenml/zen_server/dashboard/assets/{page-DXSTpqRD.js → page-DFuAUGt4.js} +1 -1
  135. zenml/zen_server/dashboard/assets/{page-CbpvrsDL.js → page-DGazBpuP.js} +1 -1
  136. zenml/zen_server/dashboard/assets/{page-COXXJj1k.js → page-DO1UcqPX.js} +1 -1
  137. zenml/zen_server/dashboard/assets/page-DRYXdL5o.js +1 -0
  138. zenml/zen_server/dashboard/assets/{page-Df-Fw0aq.js → page-DYEquBC2.js} +1 -1
  139. zenml/zen_server/dashboard/assets/page-Dk32IeZm.js +1 -0
  140. zenml/zen_server/dashboard/assets/{page-yYC9OI-E.js → page-I3nKFGie.js} +1 -1
  141. zenml/zen_server/dashboard/assets/{page-6m6yHHlE.js → page-M0w-n6vn.js} +1 -1
  142. zenml/zen_server/dashboard/assets/{page-Vcxara9U.js → page-R5dx3xGF.js} +1 -1
  143. zenml/zen_server/dashboard/assets/{page-BR68V0V1.js → page-bT5pOvcB.js} +1 -1
  144. zenml/zen_server/dashboard/assets/page-hUqK889I.js +6 -0
  145. zenml/zen_server/dashboard/assets/{page-CjGdWY13.js → page-h_Stveon.js} +1 -1
  146. zenml/zen_server/dashboard/assets/{page-D01JhjQB.js → page-r8XK5vR7.js} +1 -1
  147. zenml/zen_server/dashboard/assets/page-u_-ZXBKb.js +1 -0
  148. zenml/zen_server/dashboard/assets/page-zaMqB_ao.js +1 -0
  149. zenml/zen_server/dashboard/assets/{persist-GjC8PZoC.js → persist-AppN1B0J.js} +1 -1
  150. zenml/zen_server/dashboard/assets/{persist-Coz7ZWvz.js → persist-DAUi_3za.js} +1 -1
  151. zenml/zen_server/dashboard/assets/service-BqqeXLEe.js +2 -0
  152. zenml/zen_server/dashboard/assets/{sharedSchema-CQb14VSr.js → sharedSchema-uXN9FLLk.js} +1 -1
  153. zenml/zen_server/dashboard/assets/{stack-detail-query-OPEW-cDJ.js → stack-detail-query-XfZBiBP2.js} +1 -1
  154. zenml/zen_server/dashboard/assets/{update-server-settings-mutation-LwuQfHYn.js → update-server-settings-mutation-BWmgVJwA.js} +1 -1
  155. zenml/zen_server/dashboard/assets/{url-CkvKAnwF.js → url-BLwMbzES.js} +1 -1
  156. zenml/zen_server/dashboard/index.html +4 -4
  157. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  158. zenml/zen_server/deploy/helm/README.md +2 -2
  159. zenml/zen_server/rbac/rbac_sql_zen_store.py +173 -0
  160. zenml/zen_server/routers/auth_endpoints.py +22 -11
  161. zenml/zen_server/routers/steps_endpoints.py +7 -1
  162. zenml/zen_server/template_execution/utils.py +3 -1
  163. zenml/zen_server/utils.py +4 -3
  164. zenml/zen_stores/base_zen_store.py +10 -2
  165. zenml/zen_stores/migrations/versions/0.71.0_release.py +23 -0
  166. zenml/zen_stores/migrations/versions/26351d482b9e_add_step_run_unique_constraint.py +37 -0
  167. zenml/zen_stores/migrations/versions/a1237ba94fd8_add_model_version_producer_run_unique_.py +68 -0
  168. zenml/zen_stores/rest_zen_store.py +76 -43
  169. zenml/zen_stores/schemas/model_schemas.py +42 -6
  170. zenml/zen_stores/schemas/pipeline_deployment_schemas.py +7 -7
  171. zenml/zen_stores/schemas/pipeline_run_schemas.py +12 -6
  172. zenml/zen_stores/schemas/pipeline_schemas.py +5 -0
  173. zenml/zen_stores/schemas/step_run_schemas.py +8 -1
  174. zenml/zen_stores/sql_zen_store.py +332 -100
  175. {zenml_nightly-0.70.0.dev20241201.dist-info → zenml_nightly-0.71.0.dev20241220.dist-info}/METADATA +5 -5
  176. {zenml_nightly-0.70.0.dev20241201.dist-info → zenml_nightly-0.71.0.dev20241220.dist-info}/RECORD +179 -164
  177. zenml/zen_server/dashboard/assets/RunSelector-DkPiIiNr.js +0 -1
  178. zenml/zen_server/dashboard/assets/StackList-WvuKQusZ.js +0 -1
  179. zenml/zen_server/dashboard/assets/delete-run-CJdh1P_h.js +0 -1
  180. zenml/zen_server/dashboard/assets/index-DlGvJQPn.css +0 -1
  181. zenml/zen_server/dashboard/assets/page-0JE_-Ec1.js +0 -1
  182. zenml/zen_server/dashboard/assets/page-BRLpxOt0.js +0 -1
  183. zenml/zen_server/dashboard/assets/page-BU7huvKw.js +0 -6
  184. zenml/zen_server/dashboard/assets/page-BvqLv2Ky.js +0 -1
  185. zenml/zen_server/dashboard/assets/page-CwxrFarU.js +0 -1
  186. zenml/zen_server/dashboard/assets/page-DfbXf_8s.js +0 -1
  187. zenml/zen_server/dashboard/assets/page-Dnovpa0i.js +0 -3
  188. zenml/zen_server/dashboard/assets/page-Dot3LPmL.js +0 -1
  189. zenml/zen_server/dashboard/assets/page-Xynx4btY.js +0 -14
  190. zenml/zen_server/dashboard/assets/page-YpKAqVSa.js +0 -1
  191. {zenml_nightly-0.70.0.dev20241201.dist-info → zenml_nightly-0.71.0.dev20241220.dist-info}/LICENSE +0 -0
  192. {zenml_nightly-0.70.0.dev20241201.dist-info → zenml_nightly-0.71.0.dev20241220.dist-info}/WHEEL +0 -0
  193. {zenml_nightly-0.70.0.dev20241201.dist-info → zenml_nightly-0.71.0.dev20241220.dist-info}/entry_points.txt +0 -0
@@ -36,6 +36,7 @@ from zenml.constants import (
36
36
  DEFAULT_STACK_AND_COMPONENT_NAME,
37
37
  DEFAULT_WORKSPACE_NAME,
38
38
  ENV_ZENML_DEFAULT_WORKSPACE_NAME,
39
+ ENV_ZENML_SERVER,
39
40
  IS_DEBUG_ENV,
40
41
  )
41
42
  from zenml.enums import (
@@ -155,9 +156,16 @@ class BaseZenStore(
155
156
  TypeError: If the store type is unsupported.
156
157
  """
157
158
  if store_type == StoreType.SQL:
158
- from zenml.zen_stores.sql_zen_store import SqlZenStore
159
+ if os.environ.get(ENV_ZENML_SERVER):
160
+ from zenml.zen_server.rbac.rbac_sql_zen_store import (
161
+ RBACSqlZenStore,
162
+ )
163
+
164
+ return RBACSqlZenStore
165
+ else:
166
+ from zenml.zen_stores.sql_zen_store import SqlZenStore
159
167
 
160
- return SqlZenStore
168
+ return SqlZenStore
161
169
  elif store_type == StoreType.REST:
162
170
  from zenml.zen_stores.rest_zen_store import RestZenStore
163
171
 
@@ -0,0 +1,23 @@
1
+ """Release [0.71.0].
2
+
3
+ Revision ID: 0.71.0
4
+ Revises: cc269488e5a9
5
+ Create Date: 2024-12-04 20:44:19.316139
6
+
7
+ """
8
+
9
+ # revision identifiers, used by Alembic.
10
+ revision = "0.71.0"
11
+ down_revision = "cc269488e5a9"
12
+ branch_labels = None
13
+ depends_on = None
14
+
15
+
16
+ def upgrade() -> None:
17
+ """Upgrade database schema and/or data, creating a new revision."""
18
+ pass
19
+
20
+
21
+ def downgrade() -> None:
22
+ """Downgrade database schema and/or data back to the previous revision."""
23
+ pass
@@ -0,0 +1,37 @@
1
+ """Add step run unique constraint [26351d482b9e].
2
+
3
+ Revision ID: 26351d482b9e
4
+ Revises: 0.71.0
5
+ Create Date: 2024-12-03 11:46:57.541578
6
+
7
+ """
8
+
9
+ from alembic import op
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = "26351d482b9e"
13
+ down_revision = "0.71.0"
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ """Upgrade database schema and/or data, creating a new revision."""
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table("step_run", schema=None) as batch_op:
22
+ batch_op.create_unique_constraint(
23
+ "unique_step_name_for_pipeline_run", ["name", "pipeline_run_id"]
24
+ )
25
+
26
+ # ### end Alembic commands ###
27
+
28
+
29
+ def downgrade() -> None:
30
+ """Downgrade database schema and/or data back to the previous revision."""
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ with op.batch_alter_table("step_run", schema=None) as batch_op:
33
+ batch_op.drop_constraint(
34
+ "unique_step_name_for_pipeline_run", type_="unique"
35
+ )
36
+
37
+ # ### end Alembic commands ###
@@ -0,0 +1,68 @@
1
+ """Add model version producer run unique constraint [a1237ba94fd8].
2
+
3
+ Revision ID: a1237ba94fd8
4
+ Revises: 26351d482b9e
5
+ Create Date: 2024-12-13 10:28:55.432414
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ import sqlmodel
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "a1237ba94fd8"
15
+ down_revision = "26351d482b9e"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ """Upgrade database schema and/or data, creating a new revision."""
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("model_version", schema=None) as batch_op:
24
+ batch_op.add_column(
25
+ sa.Column(
26
+ "producer_run_id_if_numeric",
27
+ sqlmodel.sql.sqltypes.GUID(),
28
+ nullable=True,
29
+ )
30
+ )
31
+
32
+ # Set the producer_run_id_if_numeric column to the model version ID for
33
+ # existing rows
34
+ connection = op.get_bind()
35
+ metadata = sa.MetaData()
36
+ metadata.reflect(only=("model_version",), bind=connection)
37
+ model_version_table = sa.Table("model_version", metadata)
38
+
39
+ connection.execute(
40
+ model_version_table.update().values(
41
+ producer_run_id_if_numeric=model_version_table.c.id
42
+ )
43
+ )
44
+
45
+ with op.batch_alter_table("model_version", schema=None) as batch_op:
46
+ batch_op.alter_column(
47
+ "producer_run_id_if_numeric",
48
+ existing_type=sqlmodel.sql.sqltypes.GUID(),
49
+ nullable=False,
50
+ )
51
+ batch_op.create_unique_constraint(
52
+ "unique_numeric_version_for_pipeline_run",
53
+ ["model_id", "producer_run_id_if_numeric"],
54
+ )
55
+
56
+ # ### end Alembic commands ###
57
+
58
+
59
+ def downgrade() -> None:
60
+ """Downgrade database schema and/or data back to the previous revision."""
61
+ # ### commands auto generated by Alembic - please adjust! ###
62
+ with op.batch_alter_table("model_version", schema=None) as batch_op:
63
+ batch_op.drop_constraint(
64
+ "unique_numeric_version_for_pipeline_run", type_="unique"
65
+ )
66
+ batch_op.drop_column("producer_run_id_if_numeric")
67
+
68
+ # ### end Alembic commands ###
@@ -3873,13 +3873,17 @@ class RestZenStore(BaseZenStore):
3873
3873
 
3874
3874
  def get_api_token(
3875
3875
  self,
3876
+ token_type: APITokenType = APITokenType.WORKLOAD,
3877
+ expires_in: Optional[int] = None,
3876
3878
  schedule_id: Optional[UUID] = None,
3877
3879
  pipeline_run_id: Optional[UUID] = None,
3878
3880
  step_run_id: Optional[UUID] = None,
3879
3881
  ) -> str:
3880
- """Get an API token for a workload.
3882
+ """Get an API token.
3881
3883
 
3882
3884
  Args:
3885
+ token_type: The type of the token to get.
3886
+ expires_in: The time in seconds until the token expires.
3883
3887
  schedule_id: The ID of the schedule to get a token for.
3884
3888
  pipeline_run_id: The ID of the pipeline run to get a token for.
3885
3889
  step_run_id: The ID of the step run to get a token for.
@@ -3891,9 +3895,10 @@ class RestZenStore(BaseZenStore):
3891
3895
  ValueError: if the server response is not valid.
3892
3896
  """
3893
3897
  params: Dict[str, Any] = {
3894
- # Python clients may only request workload tokens.
3895
- "token_type": APITokenType.WORKLOAD.value,
3898
+ "token_type": token_type.value,
3896
3899
  }
3900
+ if expires_in:
3901
+ params["expires_in"] = expires_in
3897
3902
  if schedule_id:
3898
3903
  params["schedule_id"] = schedule_id
3899
3904
  if pipeline_run_id:
@@ -4062,7 +4067,7 @@ class RestZenStore(BaseZenStore):
4062
4067
  "you should use a service account API key to authenticate to "
4063
4068
  "the server instead of temporary CLI login credentials. For "
4064
4069
  "more information, see "
4065
- "https://docs.zenml.io/how-to/connecting-to-zenml/connect-with-a-service-account"
4070
+ "https://docs.zenml.io/how-to/project-setup-and-management/connecting-to-zenml/connect-with-a-service-account"
4066
4071
  )
4067
4072
 
4068
4073
  if api_key is not None:
@@ -4344,46 +4349,74 @@ class RestZenStore(BaseZenStore):
4344
4349
  {source_context.name: source_context.get().value}
4345
4350
  )
4346
4351
 
4347
- try:
4348
- return self._handle_response(
4349
- self.session.request(
4350
- method,
4351
- url,
4352
- params=params,
4353
- verify=self.config.verify_ssl,
4354
- timeout=timeout or self.config.http_timeout,
4355
- **kwargs,
4356
- )
4357
- )
4358
- except CredentialsNotValid:
4359
- # NOTE: CredentialsNotValid is raised only when the server
4360
- # explicitly indicates that the credentials are not valid and they
4361
- # can be thrown away.
4362
-
4363
- # We authenticate or re-authenticate here and then try the request
4364
- # again, this time with a valid API token in the header.
4365
- self.authenticate(
4366
- # If the last request was authenticated with an API token,
4367
- # we force a re-authentication to get a fresh token.
4368
- force=self._api_token is not None
4369
- )
4370
-
4371
- try:
4372
- return self._handle_response(
4373
- self.session.request(
4374
- method,
4375
- url,
4376
- params=params,
4377
- verify=self.config.verify_ssl,
4378
- timeout=self.config.http_timeout,
4379
- **kwargs,
4352
+ # If the server replies with a credentials validation (401 Unauthorized)
4353
+ # error, we (re-)authenticate and retry the request here in the
4354
+ # following cases:
4355
+ #
4356
+ # 1. initial authentication: the last request was not authenticated
4357
+ # with an API token.
4358
+ # 2. re-authentication: the last request was authenticated with an API
4359
+ # token that was rejected by the server. This is to cover the case
4360
+ # of expired tokens that can be refreshed by the client automatically
4361
+ # without user intervention from other sources (e.g. API keys).
4362
+ #
4363
+ # NOTE: it can happen that the same request is retried here for up to
4364
+ # two times: once after initial authentication and once after
4365
+ # re-authentication.
4366
+ re_authenticated = False
4367
+ while True:
4368
+ try:
4369
+ return self._handle_response(
4370
+ self.session.request(
4371
+ method,
4372
+ url,
4373
+ params=params,
4374
+ verify=self.config.verify_ssl,
4375
+ timeout=timeout or self.config.http_timeout,
4376
+ **kwargs,
4377
+ )
4380
4378
  )
4381
- )
4382
- except CredentialsNotValid as e:
4383
- raise CredentialsNotValid(
4384
- "The current credentials are no longer valid. Please log in "
4385
- "again using 'zenml login'."
4386
- ) from e
4379
+ except CredentialsNotValid as e:
4380
+ # NOTE: CredentialsNotValid is raised only when the server
4381
+ # explicitly indicates that the credentials are not valid and
4382
+ # they can be thrown away or when the request is not
4383
+ # authenticated at all.
4384
+
4385
+ if self._api_token is None:
4386
+ # The last request was not authenticated with an API
4387
+ # token at all. We authenticate here and then try the
4388
+ # request again, this time with a valid API token in the
4389
+ # header.
4390
+ logger.debug(
4391
+ f"The last request was not authenticated: {e}\n"
4392
+ "Re-authenticating and retrying..."
4393
+ )
4394
+ self.authenticate()
4395
+ elif not re_authenticated:
4396
+ # The last request was authenticated with an API token
4397
+ # that was rejected by the server. We attempt a
4398
+ # re-authentication here and then retry the request.
4399
+ logger.debug(
4400
+ "The last request was authenticated with an API token "
4401
+ f"that was rejected by the server: {e}\n"
4402
+ "Re-authenticating and retrying..."
4403
+ )
4404
+ re_authenticated = True
4405
+ self.authenticate(
4406
+ # Ignore the current token and force a re-authentication
4407
+ force=True
4408
+ )
4409
+ else:
4410
+ # The last request was made after re-authenticating but
4411
+ # still failed. Bailing out.
4412
+ logger.debug(
4413
+ f"The last request failed after re-authenticating: {e}\n"
4414
+ "Bailing out..."
4415
+ )
4416
+ raise CredentialsNotValid(
4417
+ "The current credentials are no longer valid. Please "
4418
+ "log in again using 'zenml login'."
4419
+ ) from e
4387
4420
 
4388
4421
  def get(
4389
4422
  self,
@@ -15,10 +15,16 @@
15
15
 
16
16
  from datetime import datetime
17
17
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
18
- from uuid import UUID
18
+ from uuid import UUID, uuid4
19
19
 
20
20
  from pydantic import ConfigDict
21
- from sqlalchemy import BOOLEAN, INTEGER, TEXT, Column, UniqueConstraint
21
+ from sqlalchemy import (
22
+ BOOLEAN,
23
+ INTEGER,
24
+ TEXT,
25
+ Column,
26
+ UniqueConstraint,
27
+ )
22
28
  from sqlmodel import Field, Relationship
23
29
 
24
30
  from zenml.enums import (
@@ -228,11 +234,13 @@ class ModelVersionSchema(NamedSchema, RunMetadataInterface, table=True):
228
234
 
229
235
  __tablename__ = MODEL_VERSION_TABLENAME
230
236
  __table_args__ = (
231
- # We need two unique constraints here:
237
+ # We need three unique constraints here:
232
238
  # - The first to ensure that each model version for a
233
239
  # model has a unique version number
234
240
  # - The second one to ensure that explicit names given by
235
241
  # users are unique
242
+ # - The third one to ensure that a pipeline run only produces a single
243
+ # auto-incremented version per model
236
244
  UniqueConstraint(
237
245
  "number",
238
246
  "model_id",
@@ -243,6 +251,11 @@ class ModelVersionSchema(NamedSchema, RunMetadataInterface, table=True):
243
251
  "model_id",
244
252
  name="unique_version_for_model_id",
245
253
  ),
254
+ UniqueConstraint(
255
+ "model_id",
256
+ "producer_run_id_if_numeric",
257
+ name="unique_numeric_version_for_pipeline_run",
258
+ ),
246
259
  )
247
260
 
248
261
  workspace_id: UUID = build_foreign_key_field(
@@ -312,12 +325,23 @@ class ModelVersionSchema(NamedSchema, RunMetadataInterface, table=True):
312
325
  ),
313
326
  )
314
327
  pipeline_runs: List["PipelineRunSchema"] = Relationship(
315
- back_populates="model_version"
328
+ back_populates="model_version",
316
329
  )
317
330
  step_runs: List["StepRunSchema"] = Relationship(
318
331
  back_populates="model_version"
319
332
  )
320
333
 
334
+ # We want to make sure each pipeline run only creates a single numeric
335
+ # version for each model. To solve this, we need to add a unique constraint.
336
+ # If a value of a unique constraint is NULL it is ignored and the
337
+ # remaining values in the unique constraint have to be unique. In
338
+ # our case however, we only want the unique constraint applied in
339
+ # case there is a producer run and only for numeric versions. To solve this,
340
+ # we fall back to the model version ID (which is the primary key and
341
+ # therefore unique) in case there is no producer run or the version is not
342
+ # numeric.
343
+ producer_run_id_if_numeric: UUID
344
+
321
345
  # TODO: In Pydantic v2, the `model_` is a protected namespaces for all
322
346
  # fields defined under base models. If not handled, this raises a warning.
323
347
  # It is possible to suppress this warning message with the following
@@ -328,24 +352,36 @@ class ModelVersionSchema(NamedSchema, RunMetadataInterface, table=True):
328
352
 
329
353
  @classmethod
330
354
  def from_request(
331
- cls, model_version_request: ModelVersionRequest
355
+ cls,
356
+ model_version_request: ModelVersionRequest,
357
+ model_version_number: int,
358
+ producer_run_id: Optional[UUID] = None,
332
359
  ) -> "ModelVersionSchema":
333
360
  """Convert an `ModelVersionRequest` to an `ModelVersionSchema`.
334
361
 
335
362
  Args:
336
363
  model_version_request: The request model version to convert.
364
+ model_version_number: The model version number.
365
+ producer_run_id: The ID of the producer run.
337
366
 
338
367
  Returns:
339
368
  The converted schema.
340
369
  """
370
+ id_ = uuid4()
371
+ is_numeric = str(model_version_number) == model_version_request.name
372
+
341
373
  return cls(
374
+ id=id_,
342
375
  workspace_id=model_version_request.workspace,
343
376
  user_id=model_version_request.user,
344
377
  model_id=model_version_request.model,
345
378
  name=model_version_request.name,
346
- number=model_version_request.number,
379
+ number=model_version_number,
347
380
  description=model_version_request.description,
348
381
  stage=model_version_request.stage,
382
+ producer_run_id_if_numeric=producer_run_id
383
+ if (producer_run_id and is_numeric)
384
+ else id_,
349
385
  )
350
386
 
351
387
  def to_model(
@@ -228,13 +228,6 @@ class PipelineDeploymentSchema(BaseSchema, table=True):
228
228
  Returns:
229
229
  The created `PipelineDeploymentResponse`.
230
230
  """
231
- pipeline_configuration = PipelineConfiguration.model_validate_json(
232
- self.pipeline_configuration
233
- )
234
- step_configurations = json.loads(self.step_configurations)
235
- for s, c in step_configurations.items():
236
- step_configurations[s] = Step.model_validate(c)
237
-
238
231
  body = PipelineDeploymentResponseBody(
239
232
  user=self.user.to_model() if self.user else None,
240
233
  created=self.created,
@@ -242,6 +235,13 @@ class PipelineDeploymentSchema(BaseSchema, table=True):
242
235
  )
243
236
  metadata = None
244
237
  if include_metadata:
238
+ pipeline_configuration = PipelineConfiguration.model_validate_json(
239
+ self.pipeline_configuration
240
+ )
241
+ step_configurations = json.loads(self.step_configurations)
242
+ for s, c in step_configurations.items():
243
+ step_configurations[s] = Step.model_validate(c)
244
+
245
245
  metadata = PipelineDeploymentResponseMetadata(
246
246
  workspace=self.workspace.to_model(),
247
247
  run_name_template=self.run_name_template,
@@ -298,7 +298,7 @@ class PipelineRunSchema(NamedSchema, RunMetadataInterface, table=True):
298
298
  )
299
299
 
300
300
  if self.deployment is not None:
301
- deployment = self.deployment.to_model()
301
+ deployment = self.deployment.to_model(include_metadata=True)
302
302
 
303
303
  config = deployment.pipeline_configuration
304
304
  new_substitutions = config._get_full_substitutions(self.start_time)
@@ -365,12 +365,18 @@ class PipelineRunSchema(NamedSchema, RunMetadataInterface, table=True):
365
365
  ):
366
366
  is_templatable = True
367
367
 
368
- steps = {step.name: step.to_model() for step in self.step_runs}
369
-
370
- step_substitutions = {
371
- step_name: step.config.substitutions
372
- for step_name, step in steps.items()
368
+ steps = {
369
+ step.name: step.to_model(include_metadata=True)
370
+ for step in self.step_runs
373
371
  }
372
+
373
+ step_substitutions = {}
374
+ for step_name, step in steps.items():
375
+ step_substitutions[step_name] = step.config.substitutions
376
+ # We fetch the steps hydrated before, but want them unhydrated
377
+ # in the response -> We need to reset the metadata here
378
+ step.metadata = None
379
+
374
380
  metadata = PipelineRunResponseMetadata(
375
381
  workspace=self.workspace.to_model(),
376
382
  run_metadata=self.fetch_metadata(),
@@ -156,7 +156,12 @@ class PipelineSchema(NamedSchema, table=True):
156
156
 
157
157
  resources = None
158
158
  if include_resources:
159
+ latest_run_user = self.runs[-1].user if self.runs else None
160
+
159
161
  resources = PipelineResponseResources(
162
+ latest_run_user=latest_run_user.to_model()
163
+ if latest_run_user
164
+ else None,
160
165
  tags=[t.tag.to_model() for t in self.tags],
161
166
  )
162
167
 
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
19
19
  from uuid import UUID
20
20
 
21
21
  from pydantic import ConfigDict
22
- from sqlalchemy import TEXT, Column, String
22
+ from sqlalchemy import TEXT, Column, String, UniqueConstraint
23
23
  from sqlalchemy.dialects.mysql import MEDIUMTEXT
24
24
  from sqlmodel import Field, Relationship, SQLModel
25
25
 
@@ -67,6 +67,13 @@ class StepRunSchema(NamedSchema, RunMetadataInterface, table=True):
67
67
  """SQL Model for steps of pipeline runs."""
68
68
 
69
69
  __tablename__ = "step_run"
70
+ __table_args__ = (
71
+ UniqueConstraint(
72
+ "name",
73
+ "pipeline_run_id",
74
+ name="unique_step_name_for_pipeline_run",
75
+ ),
76
+ )
70
77
 
71
78
  # Fields
72
79
  start_time: Optional[datetime] = Field(nullable=True)