zenml-nightly 0.58.2.dev20240623__py3-none-any.whl → 0.58.2.dev20240626__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 (54) hide show
  1. zenml/VERSION +1 -1
  2. zenml/actions/base_action.py +177 -174
  3. zenml/actions/pipeline_run/pipeline_run_action.py +28 -23
  4. zenml/artifact_stores/base_artifact_store.py +7 -1
  5. zenml/artifacts/utils.py +13 -10
  6. zenml/cli/service_connectors.py +1 -0
  7. zenml/client.py +234 -58
  8. zenml/config/compiler.py +10 -9
  9. zenml/config/docker_settings.py +25 -9
  10. zenml/constants.py +1 -1
  11. zenml/event_hub/base_event_hub.py +5 -5
  12. zenml/event_hub/event_hub.py +15 -6
  13. zenml/event_sources/base_event.py +0 -11
  14. zenml/event_sources/base_event_source.py +7 -0
  15. zenml/event_sources/webhooks/base_webhook_event_source.py +1 -4
  16. zenml/exceptions.py +4 -0
  17. zenml/hooks/hook_validators.py +2 -3
  18. zenml/integrations/bitbucket/plugins/event_sources/bitbucket_webhook_event_source.py +3 -3
  19. zenml/integrations/mlflow/__init__.py +1 -1
  20. zenml/integrations/s3/artifact_stores/s3_artifact_store.py +76 -3
  21. zenml/logging/step_logging.py +54 -51
  22. zenml/models/__init__.py +17 -0
  23. zenml/models/v2/core/action.py +276 -0
  24. zenml/models/v2/core/trigger.py +182 -141
  25. zenml/new/pipelines/pipeline.py +13 -3
  26. zenml/new/pipelines/pipeline_decorator.py +1 -2
  27. zenml/new/pipelines/run_utils.py +1 -12
  28. zenml/new/steps/step_decorator.py +2 -3
  29. zenml/pipelines/base_pipeline.py +0 -2
  30. zenml/pipelines/pipeline_decorator.py +1 -2
  31. zenml/stack/stack_component.py +4 -0
  32. zenml/steps/base_step.py +1 -2
  33. zenml/steps/step_decorator.py +1 -2
  34. zenml/types.py +10 -1
  35. zenml/utils/pipeline_docker_image_builder.py +20 -5
  36. zenml/zen_server/rbac/models.py +1 -0
  37. zenml/zen_server/rbac/utils.py +22 -1
  38. zenml/zen_server/routers/actions_endpoints.py +324 -0
  39. zenml/zen_server/routers/triggers_endpoints.py +30 -158
  40. zenml/zen_server/zen_server_api.py +2 -0
  41. zenml/zen_stores/migrations/versions/25155145c545_separate_actions_and_triggers.py +228 -0
  42. zenml/zen_stores/rest_zen_store.py +103 -4
  43. zenml/zen_stores/schemas/__init__.py +2 -0
  44. zenml/zen_stores/schemas/action_schemas.py +192 -0
  45. zenml/zen_stores/schemas/trigger_schemas.py +43 -50
  46. zenml/zen_stores/schemas/user_schemas.py +10 -2
  47. zenml/zen_stores/schemas/workspace_schemas.py +5 -0
  48. zenml/zen_stores/sql_zen_store.py +240 -30
  49. zenml/zen_stores/zen_store_interface.py +85 -0
  50. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.58.2.dev20240626.dist-info}/METADATA +2 -2
  51. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.58.2.dev20240626.dist-info}/RECORD +54 -50
  52. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.58.2.dev20240626.dist-info}/LICENSE +0 -0
  53. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.58.2.dev20240626.dist-info}/WHEEL +0 -0
  54. {zenml_nightly-0.58.2.dev20240623.dist-info → zenml_nightly-0.58.2.dev20240626.dist-info}/entry_points.txt +0 -0
@@ -117,6 +117,7 @@ from zenml.enums import (
117
117
  TaggableResourceTypes,
118
118
  )
119
119
  from zenml.exceptions import (
120
+ ActionExistsError,
120
121
  AuthorizationException,
121
122
  BackupSecretsStoreNotConfiguredError,
122
123
  EntityExistsError,
@@ -130,6 +131,10 @@ from zenml.exceptions import (
130
131
  from zenml.io import fileio
131
132
  from zenml.logger import get_console_handler, get_logger, get_logging_level
132
133
  from zenml.models import (
134
+ ActionFilter,
135
+ ActionRequest,
136
+ ActionResponse,
137
+ ActionUpdate,
133
138
  APIKeyFilter,
134
139
  APIKeyInternalResponse,
135
140
  APIKeyInternalUpdate,
@@ -289,6 +294,7 @@ from zenml.zen_stores.migrations.alembic import (
289
294
  )
290
295
  from zenml.zen_stores.migrations.utils import MigrationUtils
291
296
  from zenml.zen_stores.schemas import (
297
+ ActionSchema,
292
298
  APIKeySchema,
293
299
  ArtifactSchema,
294
300
  ArtifactVersionSchema,
@@ -1739,6 +1745,205 @@ class SqlZenStore(BaseZenStore):
1739
1745
 
1740
1746
  self.activate_server(request)
1741
1747
 
1748
+ # -------------------- Actions --------------------
1749
+
1750
+ def _fail_if_action_with_name_exists(
1751
+ self, action_name: str, workspace_id: UUID, session: Session
1752
+ ) -> None:
1753
+ """Raise an exception if an action with same name exists.
1754
+
1755
+ Args:
1756
+ action_name: The name of the action.
1757
+ workspace_id: Workspace ID of the action.
1758
+ session: DB Session.
1759
+
1760
+ Raises:
1761
+ ActionExistsError: If an action with the given name already exists.
1762
+ """
1763
+ existing_domain_action = session.exec(
1764
+ select(ActionSchema)
1765
+ .where(ActionSchema.name == action_name)
1766
+ .where(ActionSchema.workspace_id == workspace_id)
1767
+ ).first()
1768
+ if existing_domain_action is not None:
1769
+ workspace = self._get_workspace_schema(
1770
+ workspace_name_or_id=workspace_id, session=session
1771
+ )
1772
+ raise ActionExistsError(
1773
+ f"Unable to register action with name "
1774
+ f"'{action_name}': Found an existing action with "
1775
+ f"the same name in the active workspace, '{workspace.name}'."
1776
+ )
1777
+
1778
+ def create_action(self, action: ActionRequest) -> ActionResponse:
1779
+ """Create an action.
1780
+
1781
+ Args:
1782
+ action: The action to create.
1783
+
1784
+ Returns:
1785
+ The created action.
1786
+ """
1787
+ with Session(self.engine) as session:
1788
+ self._fail_if_action_with_name_exists(
1789
+ action_name=action.name,
1790
+ workspace_id=action.workspace,
1791
+ session=session,
1792
+ )
1793
+
1794
+ # Verify that the given service account exists
1795
+ self._get_account_schema(
1796
+ account_name_or_id=action.service_account_id,
1797
+ session=session,
1798
+ service_account=True,
1799
+ )
1800
+
1801
+ new_action = ActionSchema.from_request(action)
1802
+ session.add(new_action)
1803
+ session.commit()
1804
+ session.refresh(new_action)
1805
+
1806
+ return new_action.to_model(
1807
+ include_metadata=True, include_resources=True
1808
+ )
1809
+
1810
+ def _get_action(
1811
+ self,
1812
+ action_id: UUID,
1813
+ session: Session,
1814
+ ) -> ActionSchema:
1815
+ """Get an action by ID.
1816
+
1817
+ Args:
1818
+ action_id: The ID of the action to get.
1819
+ session: The DB session.
1820
+
1821
+ Returns:
1822
+ The action schema.
1823
+ """
1824
+ return self._get_schema_by_name_or_id(
1825
+ object_name_or_id=action_id,
1826
+ schema_class=ActionSchema,
1827
+ schema_name="action",
1828
+ session=session,
1829
+ )
1830
+
1831
+ def get_action(
1832
+ self,
1833
+ action_id: UUID,
1834
+ hydrate: bool = True,
1835
+ ) -> ActionResponse:
1836
+ """Get an action by ID.
1837
+
1838
+ Args:
1839
+ action_id: The ID of the action to get.
1840
+ hydrate: Flag deciding whether to hydrate the output model(s)
1841
+ by including metadata fields in the response.
1842
+
1843
+ Returns:
1844
+ The action.
1845
+ """
1846
+ with Session(self.engine) as session:
1847
+ action = self._get_action(action_id=action_id, session=session)
1848
+
1849
+ return action.to_model(
1850
+ include_metadata=hydrate, include_resources=hydrate
1851
+ )
1852
+
1853
+ def list_actions(
1854
+ self,
1855
+ action_filter_model: ActionFilter,
1856
+ hydrate: bool = False,
1857
+ ) -> Page[ActionResponse]:
1858
+ """List all actions matching the given filter criteria.
1859
+
1860
+ Args:
1861
+ action_filter_model: All filter parameters including pagination
1862
+ params.
1863
+ hydrate: Flag deciding whether to hydrate the output model(s)
1864
+ by including metadata fields in the response.
1865
+
1866
+ Returns:
1867
+ A page of actions matching the filter criteria.
1868
+ """
1869
+ with Session(self.engine) as session:
1870
+ query = select(ActionSchema)
1871
+ return self.filter_and_paginate(
1872
+ session=session,
1873
+ query=query,
1874
+ table=ActionSchema,
1875
+ filter_model=action_filter_model,
1876
+ hydrate=hydrate,
1877
+ )
1878
+
1879
+ def update_action(
1880
+ self,
1881
+ action_id: UUID,
1882
+ action_update: ActionUpdate,
1883
+ ) -> ActionResponse:
1884
+ """Update an existing action.
1885
+
1886
+ Args:
1887
+ action_id: The ID of the action to update.
1888
+ action_update: The update to be applied to the action.
1889
+
1890
+ Returns:
1891
+ The updated action.
1892
+ """
1893
+ with Session(self.engine) as session:
1894
+ action = self._get_action(session=session, action_id=action_id)
1895
+
1896
+ if action_update.service_account_id:
1897
+ # Verify that the given service account exists
1898
+ self._get_account_schema(
1899
+ account_name_or_id=action_update.service_account_id,
1900
+ session=session,
1901
+ service_account=True,
1902
+ )
1903
+
1904
+ # In case of a renaming update, make sure no action already exists
1905
+ # with that name
1906
+ if action_update.name:
1907
+ if action.name != action_update.name:
1908
+ self._fail_if_action_with_name_exists(
1909
+ action_name=action_update.name,
1910
+ workspace_id=action.workspace.id,
1911
+ session=session,
1912
+ )
1913
+
1914
+ action.update(action_update=action_update)
1915
+ session.add(action)
1916
+ session.commit()
1917
+
1918
+ session.refresh(action)
1919
+
1920
+ return action.to_model(
1921
+ include_metadata=True, include_resources=True
1922
+ )
1923
+
1924
+ def delete_action(self, action_id: UUID) -> None:
1925
+ """Delete an action.
1926
+
1927
+ Args:
1928
+ action_id: The ID of the action to delete.
1929
+
1930
+ Raises:
1931
+ IllegalOperationError: If the action can't be deleted
1932
+ because it's used by triggers.
1933
+ """
1934
+ with Session(self.engine) as session:
1935
+ action = self._get_action(action_id=action_id, session=session)
1936
+
1937
+ # Prevent deletion of action if it is used by a trigger
1938
+ if action.triggers:
1939
+ raise IllegalOperationError(
1940
+ f"Unable to delete action with ID `{action_id}` "
1941
+ f"as it is used by {len(action.triggers)} triggers."
1942
+ )
1943
+
1944
+ session.delete(action)
1945
+ session.commit()
1946
+
1742
1947
  # ------------------------- API Keys -------------------------
1743
1948
 
1744
1949
  def _get_api_key(
@@ -4183,11 +4388,9 @@ class SqlZenStore(BaseZenStore):
4183
4388
  event_source: The event_source to create.
4184
4389
  session: The Session
4185
4390
 
4186
- Returns:
4187
- None
4188
-
4189
4391
  Raises:
4190
- EventSourceExistsError: In case the event source already exists
4392
+ EventSourceExistsError: If an event source with the given name
4393
+ already exists.
4191
4394
  """
4192
4395
  existing_domain_event_source = session.exec(
4193
4396
  select(EventSourceSchema)
@@ -4203,7 +4406,6 @@ class SqlZenStore(BaseZenStore):
4203
4406
  f"'{event_source.name}': Found an existing event source with "
4204
4407
  f"the same name in the active workspace, '{workspace.name}'."
4205
4408
  )
4206
- return None
4207
4409
 
4208
4410
  def create_event_source(
4209
4411
  self, event_source: EventSourceRequest
@@ -4269,7 +4471,7 @@ class SqlZenStore(BaseZenStore):
4269
4471
  with Session(self.engine) as session:
4270
4472
  return self._get_event_source(
4271
4473
  event_source_id=event_source_id, session=session
4272
- ).to_model(include_metadata=hydrate, include_resources=True)
4474
+ ).to_model(include_metadata=hydrate, include_resources=hydrate)
4273
4475
 
4274
4476
  def list_event_sources(
4275
4477
  self,
@@ -4333,6 +4535,8 @@ class SqlZenStore(BaseZenStore):
4333
4535
 
4334
4536
  Raises:
4335
4537
  KeyError: if the event_source doesn't exist.
4538
+ IllegalOperationError: If the event source can't be deleted
4539
+ because it's used by triggers.
4336
4540
  """
4337
4541
  with Session(self.engine) as session:
4338
4542
  event_source = self._get_event_source(
@@ -4343,12 +4547,17 @@ class SqlZenStore(BaseZenStore):
4343
4547
  f"Unable to delete event_source with ID `{event_source_id}`: "
4344
4548
  f"No event_source with this ID found."
4345
4549
  )
4550
+
4551
+ # Prevent deletion of event source if it is used by a trigger
4552
+ if event_source.triggers:
4553
+ raise IllegalOperationError(
4554
+ f"Unable to delete event_source with ID `{event_source_id}`"
4555
+ f" as it is used by {len(event_source.triggers)} triggers."
4556
+ )
4557
+
4346
4558
  session.delete(event_source)
4347
4559
  session.commit()
4348
4560
 
4349
- # TODO: catch and throw proper error if it can't be deleted due to
4350
- # not-null constraints on triggers
4351
-
4352
4561
  # ----------------------------- Pipeline runs -----------------------------
4353
4562
 
4354
4563
  def create_run(
@@ -7478,19 +7687,19 @@ class SqlZenStore(BaseZenStore):
7478
7687
  The newly created trigger.
7479
7688
  """
7480
7689
  with Session(self.engine) as session:
7481
- # Verify that the given event_source exists
7482
- self._get_event_source(
7483
- event_source_id=trigger.event_source_id, session=session
7484
- )
7690
+ # Verify that the given action exists
7691
+ self._get_action(action_id=trigger.action_id, session=session)
7485
7692
 
7486
- # Verify that the given service account exists
7487
- self._get_account_schema(
7488
- account_name_or_id=trigger.service_account_id,
7489
- session=session,
7490
- service_account=True,
7491
- )
7693
+ if trigger.event_source_id:
7694
+ # Verify that the given event_source exists
7695
+ self._get_event_source(
7696
+ event_source_id=trigger.event_source_id, session=session
7697
+ )
7492
7698
 
7493
- # Verify that the trigger won't validate Unique
7699
+ # Verify that the action exists
7700
+ self._get_action(action_id=trigger.action_id, session=session)
7701
+
7702
+ # Verify that the trigger name is unique
7494
7703
  self._fail_if_trigger_with_name_exists(
7495
7704
  trigger_name=trigger.name,
7496
7705
  workspace_id=trigger.workspace,
@@ -7520,7 +7729,7 @@ class SqlZenStore(BaseZenStore):
7520
7729
  The trigger with the given ID.
7521
7730
 
7522
7731
  Raises:
7523
- KeyError: if the trigger doesn't exist.
7732
+ KeyError: If the trigger doesn't exist.
7524
7733
  """
7525
7734
  with Session(self.engine) as session:
7526
7735
  trigger = session.exec(
@@ -7573,7 +7782,8 @@ class SqlZenStore(BaseZenStore):
7573
7782
  The updated trigger.
7574
7783
 
7575
7784
  Raises:
7576
- KeyError: if the trigger doesn't exist.
7785
+ KeyError: If the trigger doesn't exist.
7786
+ ValueError: If both a schedule and an event source are provided.
7577
7787
  """
7578
7788
  with Session(self.engine) as session:
7579
7789
  # Check if trigger with the domain key (name, workspace, owner)
@@ -7583,16 +7793,16 @@ class SqlZenStore(BaseZenStore):
7583
7793
  ).first()
7584
7794
  if existing_trigger is None:
7585
7795
  raise KeyError(
7586
- f"Unable to update trigger with id '{trigger_id}': Found no"
7587
- f"existing trigger with this id."
7796
+ f"Unable to update trigger with id '{trigger_id}': No "
7797
+ f"existing trigger with this id exists."
7588
7798
  )
7589
7799
 
7590
- if trigger_update.service_account_id:
7591
- # Verify that the given service account exists
7592
- self._get_account_schema(
7593
- account_name_or_id=trigger_update.service_account_id,
7594
- session=session,
7595
- service_account=True,
7800
+ # Verify that either a schedule or an event source is provided, not
7801
+ # both
7802
+ if existing_trigger.event_source and trigger_update.schedule:
7803
+ raise ValueError(
7804
+ "Unable to update trigger: A trigger cannot have both a "
7805
+ "schedule and an event source."
7596
7806
  )
7597
7807
 
7598
7808
  # In case of a renaming update, make sure no trigger already exists
@@ -19,6 +19,10 @@ from uuid import UUID
19
19
 
20
20
  from zenml.config.pipeline_run_configuration import PipelineRunConfiguration
21
21
  from zenml.models import (
22
+ ActionFilter,
23
+ ActionRequest,
24
+ ActionResponse,
25
+ ActionUpdate,
22
26
  APIKeyFilter,
23
27
  APIKeyRequest,
24
28
  APIKeyResponse,
@@ -262,6 +266,87 @@ class ZenStoreInterface(ABC):
262
266
  The updated server settings.
263
267
  """
264
268
 
269
+ # -------------------- Actions --------------------
270
+
271
+ @abstractmethod
272
+ def create_action(self, action: ActionRequest) -> ActionResponse:
273
+ """Create an action.
274
+
275
+ Args:
276
+ action: The action to create.
277
+
278
+ Returns:
279
+ The created action.
280
+ """
281
+
282
+ @abstractmethod
283
+ def get_action(
284
+ self,
285
+ action_id: UUID,
286
+ hydrate: bool = True,
287
+ ) -> ActionResponse:
288
+ """Get an action by ID.
289
+
290
+ Args:
291
+ action_id: The ID of the action to get.
292
+ hydrate: Flag deciding whether to hydrate the output model(s)
293
+ by including metadata fields in the response.
294
+
295
+ Returns:
296
+ The action.
297
+
298
+ Raises:
299
+ KeyError: If the action doesn't exist.
300
+ """
301
+
302
+ @abstractmethod
303
+ def list_actions(
304
+ self,
305
+ action_filter_model: ActionFilter,
306
+ hydrate: bool = False,
307
+ ) -> Page[ActionResponse]:
308
+ """List all actions matching the given filter criteria.
309
+
310
+ Args:
311
+ action_filter_model: All filter parameters including pagination
312
+ params.
313
+ hydrate: Flag deciding whether to hydrate the output model(s)
314
+ by including metadata fields in the response.
315
+
316
+ Returns:
317
+ A list of all actions matching the filter criteria.
318
+ """
319
+
320
+ @abstractmethod
321
+ def update_action(
322
+ self,
323
+ action_id: UUID,
324
+ action_update: ActionUpdate,
325
+ ) -> ActionResponse:
326
+ """Update an existing action.
327
+
328
+ Args:
329
+ action_id: The ID of the action to update.
330
+ action_update: The update to be applied to the action.
331
+
332
+ Returns:
333
+ The updated action.
334
+
335
+ Raises:
336
+ KeyError: If the action doesn't exist.
337
+ """
338
+
339
+ @abstractmethod
340
+ def delete_action(self, action_id: UUID) -> None:
341
+ """Delete an action.
342
+
343
+ Args:
344
+ action_id: The ID of the action to delete.
345
+
346
+ Raises:
347
+ KeyError: If the action doesn't exist.
348
+ """
349
+
265
350
  # -------------------- API Keys --------------------
266
351
 
267
352
  @abstractmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zenml-nightly
3
- Version: 0.58.2.dev20240623
3
+ Version: 0.58.2.dev20240626
4
4
  Summary: ZenML: Write production-ready ML code.
5
5
  Home-page: https://zenml.io
6
6
  License: Apache-2.0
@@ -78,7 +78,7 @@ Requires-Dist: mkdocs (>=1.2.3,<2.0.0) ; extra == "dev"
78
78
  Requires-Dist: mkdocs-awesome-pages-plugin (>=2.6.1,<3.0.0) ; extra == "dev"
79
79
  Requires-Dist: mkdocs-material (>=8.1.7,<9.0.0) ; extra == "dev"
80
80
  Requires-Dist: mkdocstrings (>=0.17.0,<0.18.0) ; extra == "dev"
81
- Requires-Dist: mlstacks (==0.8.0) ; extra == "mlstacks"
81
+ Requires-Dist: mlstacks (==0.9.0) ; extra == "mlstacks"
82
82
  Requires-Dist: mypy (==1.7.1) ; extra == "dev"
83
83
  Requires-Dist: numpy (<2.0.0)
84
84
  Requires-Dist: orjson (>=3.10.0,<3.11.0) ; extra == "server"