zenml-nightly 0.58.2.dev20240624__py3-none-any.whl → 0.58.2.dev20240625__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.
- zenml/VERSION +1 -1
- zenml/actions/base_action.py +177 -174
- zenml/actions/pipeline_run/pipeline_run_action.py +28 -23
- zenml/client.py +226 -55
- zenml/config/compiler.py +10 -9
- zenml/config/docker_settings.py +25 -9
- zenml/constants.py +1 -1
- zenml/event_hub/base_event_hub.py +5 -5
- zenml/event_hub/event_hub.py +15 -6
- zenml/event_sources/base_event.py +0 -11
- zenml/event_sources/base_event_source.py +7 -0
- zenml/event_sources/webhooks/base_webhook_event_source.py +1 -4
- zenml/exceptions.py +4 -0
- zenml/hooks/hook_validators.py +2 -3
- zenml/integrations/bitbucket/plugins/event_sources/bitbucket_webhook_event_source.py +3 -3
- zenml/integrations/mlflow/__init__.py +1 -1
- zenml/models/__init__.py +17 -0
- zenml/models/v2/core/action.py +276 -0
- zenml/models/v2/core/trigger.py +182 -141
- zenml/new/pipelines/pipeline.py +13 -3
- zenml/new/pipelines/pipeline_decorator.py +1 -2
- zenml/new/pipelines/run_utils.py +1 -12
- zenml/new/steps/step_decorator.py +2 -3
- zenml/pipelines/base_pipeline.py +0 -2
- zenml/pipelines/pipeline_decorator.py +1 -2
- zenml/steps/base_step.py +1 -2
- zenml/steps/step_decorator.py +1 -2
- zenml/types.py +10 -1
- zenml/utils/pipeline_docker_image_builder.py +20 -5
- zenml/zen_server/rbac/models.py +1 -0
- zenml/zen_server/rbac/utils.py +22 -1
- zenml/zen_server/routers/actions_endpoints.py +324 -0
- zenml/zen_server/routers/triggers_endpoints.py +30 -158
- zenml/zen_server/zen_server_api.py +2 -0
- zenml/zen_stores/migrations/versions/25155145c545_separate_actions_and_triggers.py +228 -0
- zenml/zen_stores/rest_zen_store.py +103 -4
- zenml/zen_stores/schemas/__init__.py +2 -0
- zenml/zen_stores/schemas/action_schemas.py +192 -0
- zenml/zen_stores/schemas/trigger_schemas.py +43 -50
- zenml/zen_stores/schemas/user_schemas.py +10 -2
- zenml/zen_stores/schemas/workspace_schemas.py +5 -0
- zenml/zen_stores/sql_zen_store.py +240 -30
- zenml/zen_stores/zen_store_interface.py +85 -0
- {zenml_nightly-0.58.2.dev20240624.dist-info → zenml_nightly-0.58.2.dev20240625.dist-info}/METADATA +1 -1
- {zenml_nightly-0.58.2.dev20240624.dist-info → zenml_nightly-0.58.2.dev20240625.dist-info}/RECORD +48 -44
- {zenml_nightly-0.58.2.dev20240624.dist-info → zenml_nightly-0.58.2.dev20240625.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.58.2.dev20240624.dist-info → zenml_nightly-0.58.2.dev20240625.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.58.2.dev20240624.dist-info → zenml_nightly-0.58.2.dev20240625.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:
|
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=
|
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
|
7482
|
-
self.
|
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
|
-
|
7487
|
-
|
7488
|
-
|
7489
|
-
|
7490
|
-
|
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
|
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:
|
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:
|
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}':
|
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
|
-
|
7591
|
-
|
7592
|
-
|
7593
|
-
|
7594
|
-
|
7595
|
-
|
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
|