prefect-client 2.17.1__py3-none-any.whl → 2.18.1__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.
- prefect/_internal/compatibility/deprecated.py +2 -0
- prefect/_internal/pydantic/_compat.py +1 -0
- prefect/_internal/pydantic/utilities/field_validator.py +25 -10
- prefect/_internal/pydantic/utilities/model_dump.py +1 -1
- prefect/_internal/pydantic/utilities/model_validate.py +1 -1
- prefect/_internal/pydantic/utilities/model_validator.py +11 -3
- prefect/_internal/schemas/fields.py +31 -12
- prefect/_internal/schemas/validators.py +0 -6
- prefect/_version.py +97 -38
- prefect/blocks/abstract.py +34 -1
- prefect/blocks/core.py +1 -1
- prefect/blocks/notifications.py +16 -7
- prefect/blocks/system.py +2 -3
- prefect/client/base.py +10 -5
- prefect/client/orchestration.py +405 -85
- prefect/client/schemas/actions.py +4 -3
- prefect/client/schemas/objects.py +6 -5
- prefect/client/schemas/schedules.py +2 -6
- prefect/client/schemas/sorting.py +9 -0
- prefect/client/utilities.py +25 -3
- prefect/concurrency/asyncio.py +11 -5
- prefect/concurrency/events.py +3 -3
- prefect/concurrency/services.py +1 -1
- prefect/concurrency/sync.py +9 -5
- prefect/deployments/__init__.py +0 -2
- prefect/deployments/base.py +2 -144
- prefect/deployments/deployments.py +29 -20
- prefect/deployments/runner.py +36 -28
- prefect/deployments/steps/core.py +3 -3
- prefect/deprecated/packaging/serializers.py +5 -4
- prefect/engine.py +3 -1
- prefect/events/__init__.py +45 -0
- prefect/events/actions.py +250 -18
- prefect/events/cli/automations.py +201 -0
- prefect/events/clients.py +179 -21
- prefect/events/filters.py +30 -3
- prefect/events/instrument.py +40 -40
- prefect/events/related.py +2 -1
- prefect/events/schemas/automations.py +126 -8
- prefect/events/schemas/deployment_triggers.py +23 -277
- prefect/events/schemas/events.py +7 -7
- prefect/events/utilities.py +3 -1
- prefect/events/worker.py +21 -8
- prefect/exceptions.py +1 -1
- prefect/flows.py +33 -18
- prefect/input/actions.py +9 -9
- prefect/input/run_input.py +49 -37
- prefect/logging/__init__.py +2 -2
- prefect/logging/loggers.py +64 -1
- prefect/new_flow_engine.py +293 -0
- prefect/new_task_engine.py +374 -0
- prefect/results.py +32 -12
- prefect/runner/runner.py +3 -2
- prefect/serializers.py +62 -31
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +44 -3
- prefect/settings.py +32 -10
- prefect/states.py +25 -19
- prefect/tasks.py +17 -0
- prefect/types/__init__.py +90 -0
- prefect/utilities/asyncutils.py +37 -0
- prefect/utilities/engine.py +6 -4
- prefect/utilities/pydantic.py +34 -15
- prefect/utilities/schema_tools/hydration.py +88 -19
- prefect/utilities/schema_tools/validation.py +1 -1
- prefect/variables.py +4 -4
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/METADATA +1 -1
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/RECORD +71 -67
- /prefect/{concurrency/common.py → events/cli/__init__.py} +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/LICENSE +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/WHEEL +0 -0
- {prefect_client-2.17.1.dist-info → prefect_client-2.18.1.dist-info}/top_level.txt +0 -0
prefect/engine.py
CHANGED
@@ -1410,7 +1410,9 @@ def enter_task_run_engine(
|
|
1410
1410
|
task_runner=task_runner,
|
1411
1411
|
)
|
1412
1412
|
|
1413
|
-
if task.isasync and
|
1413
|
+
if task.isasync and (
|
1414
|
+
flow_run_context.flow is None or flow_run_context.flow.isasync
|
1415
|
+
):
|
1414
1416
|
# return a coro for the user to await if an async task in an async flow
|
1415
1417
|
return from_async.wait_for_call_in_loop_thread(begin_run)
|
1416
1418
|
else:
|
prefect/events/__init__.py
CHANGED
@@ -2,7 +2,9 @@ from .schemas.events import Event, ReceivedEvent
|
|
2
2
|
from .schemas.events import Resource, RelatedResource, ResourceSpecification
|
3
3
|
from .schemas.automations import (
|
4
4
|
Automation,
|
5
|
+
AutomationCore,
|
5
6
|
Posture,
|
7
|
+
TriggerTypes,
|
6
8
|
Trigger,
|
7
9
|
ResourceTrigger,
|
8
10
|
EventTrigger,
|
@@ -20,6 +22,27 @@ from .schemas.deployment_triggers import (
|
|
20
22
|
DeploymentCompoundTrigger,
|
21
23
|
DeploymentSequenceTrigger,
|
22
24
|
)
|
25
|
+
from .actions import (
|
26
|
+
ActionTypes,
|
27
|
+
Action,
|
28
|
+
DoNothing,
|
29
|
+
RunDeployment,
|
30
|
+
PauseDeployment,
|
31
|
+
ResumeDeployment,
|
32
|
+
ChangeFlowRunState,
|
33
|
+
CancelFlowRun,
|
34
|
+
SuspendFlowRun,
|
35
|
+
CallWebhook,
|
36
|
+
SendNotification,
|
37
|
+
PauseWorkPool,
|
38
|
+
ResumeWorkPool,
|
39
|
+
PauseWorkQueue,
|
40
|
+
ResumeWorkQueue,
|
41
|
+
PauseAutomation,
|
42
|
+
ResumeAutomation,
|
43
|
+
DeclareIncident,
|
44
|
+
)
|
45
|
+
from .clients import get_events_client, get_events_subscriber
|
23
46
|
from .utilities import emit_event
|
24
47
|
|
25
48
|
__all__ = [
|
@@ -29,7 +52,9 @@ __all__ = [
|
|
29
52
|
"RelatedResource",
|
30
53
|
"ResourceSpecification",
|
31
54
|
"Automation",
|
55
|
+
"AutomationCore",
|
32
56
|
"Posture",
|
57
|
+
"TriggerTypes",
|
33
58
|
"Trigger",
|
34
59
|
"ResourceTrigger",
|
35
60
|
"EventTrigger",
|
@@ -44,5 +69,25 @@ __all__ = [
|
|
44
69
|
"DeploymentMetricTrigger",
|
45
70
|
"DeploymentCompoundTrigger",
|
46
71
|
"DeploymentSequenceTrigger",
|
72
|
+
"ActionTypes",
|
73
|
+
"Action",
|
74
|
+
"DoNothing",
|
75
|
+
"RunDeployment",
|
76
|
+
"PauseDeployment",
|
77
|
+
"ResumeDeployment",
|
78
|
+
"ChangeFlowRunState",
|
79
|
+
"CancelFlowRun",
|
80
|
+
"SuspendFlowRun",
|
81
|
+
"CallWebhook",
|
82
|
+
"SendNotification",
|
83
|
+
"PauseWorkPool",
|
84
|
+
"ResumeWorkPool",
|
85
|
+
"PauseWorkQueue",
|
86
|
+
"ResumeWorkQueue",
|
87
|
+
"PauseAutomation",
|
88
|
+
"ResumeAutomation",
|
89
|
+
"DeclareIncident",
|
47
90
|
"emit_event",
|
91
|
+
"get_events_client",
|
92
|
+
"get_events_subscriber",
|
48
93
|
]
|
prefect/events/actions.py
CHANGED
@@ -1,36 +1,71 @@
|
|
1
|
+
import abc
|
1
2
|
from typing import Any, Dict, Optional, Union
|
2
3
|
from uuid import UUID
|
3
4
|
|
5
|
+
from typing_extensions import Literal, TypeAlias
|
6
|
+
|
4
7
|
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
5
8
|
|
6
9
|
if HAS_PYDANTIC_V2:
|
7
|
-
from pydantic.v1 import Field
|
10
|
+
from pydantic.v1 import Field, root_validator
|
8
11
|
else:
|
9
|
-
from pydantic import Field
|
10
|
-
|
11
|
-
from typing_extensions import Literal
|
12
|
+
from pydantic import Field, root_validator # type: ignore
|
12
13
|
|
13
14
|
from prefect._internal.schemas.bases import PrefectBaseModel
|
15
|
+
from prefect.client.schemas.objects import StateType
|
14
16
|
|
15
17
|
|
16
|
-
class Action(PrefectBaseModel):
|
18
|
+
class Action(PrefectBaseModel, abc.ABC):
|
17
19
|
"""An Action that may be performed when an Automation is triggered"""
|
18
20
|
|
19
21
|
type: str
|
20
22
|
|
23
|
+
def describe_for_cli(self) -> str:
|
24
|
+
"""A human-readable description of the action"""
|
25
|
+
return self.type.replace("-", " ").capitalize()
|
26
|
+
|
21
27
|
|
22
28
|
class DoNothing(Action):
|
23
|
-
"""Do nothing
|
29
|
+
"""Do nothing when an Automation is triggered"""
|
24
30
|
|
25
31
|
type: Literal["do-nothing"] = "do-nothing"
|
26
32
|
|
27
33
|
|
28
|
-
class
|
29
|
-
"""
|
34
|
+
class DeploymentAction(Action):
|
35
|
+
"""Base class for Actions that operate on Deployments and need to infer them from
|
36
|
+
events"""
|
37
|
+
|
38
|
+
source: Literal["selected", "inferred"] = Field(
|
39
|
+
"selected",
|
40
|
+
description=(
|
41
|
+
"Whether this Action applies to a specific selected "
|
42
|
+
"deployment (given by `deployment_id`), or to a deployment that is "
|
43
|
+
"inferred from the triggering event. If the source is 'inferred', "
|
44
|
+
"the `deployment_id` may not be set. If the source is 'selected', the "
|
45
|
+
"`deployment_id` must be set."
|
46
|
+
),
|
47
|
+
)
|
48
|
+
deployment_id: Optional[UUID] = Field(
|
49
|
+
None, description="The identifier of the deployment"
|
50
|
+
)
|
51
|
+
|
52
|
+
@root_validator
|
53
|
+
def selected_deployment_requires_id(cls, values):
|
54
|
+
wants_selected_deployment = values.get("source") == "selected"
|
55
|
+
has_deployment_id = bool(values.get("deployment_id"))
|
56
|
+
if wants_selected_deployment != has_deployment_id:
|
57
|
+
raise ValueError(
|
58
|
+
"deployment_id is "
|
59
|
+
+ ("not allowed" if has_deployment_id else "required")
|
60
|
+
)
|
61
|
+
return values
|
62
|
+
|
63
|
+
|
64
|
+
class RunDeployment(DeploymentAction):
|
65
|
+
"""Runs the given deployment with the given parameters"""
|
30
66
|
|
31
67
|
type: Literal["run-deployment"] = "run-deployment"
|
32
68
|
|
33
|
-
source: Literal["selected"] = "selected"
|
34
69
|
parameters: Optional[Dict[str, Any]] = Field(
|
35
70
|
None,
|
36
71
|
description=(
|
@@ -38,26 +73,223 @@ class RunDeployment(Action):
|
|
38
73
|
"deployment's default parameters"
|
39
74
|
),
|
40
75
|
)
|
41
|
-
deployment_id: UUID = Field(..., description="The identifier of the deployment")
|
42
76
|
job_variables: Optional[Dict[str, Any]] = Field(
|
43
77
|
None,
|
44
78
|
description=(
|
45
|
-
"
|
46
|
-
"deployment's default job variables"
|
79
|
+
"The job variables to pass to the created flow run, or None "
|
80
|
+
"to use the deployment's default job variables"
|
47
81
|
),
|
48
82
|
)
|
49
83
|
|
50
84
|
|
85
|
+
class PauseDeployment(DeploymentAction):
|
86
|
+
"""Pauses the given Deployment"""
|
87
|
+
|
88
|
+
type: Literal["pause-deployment"] = "pause-deployment"
|
89
|
+
|
90
|
+
|
91
|
+
class ResumeDeployment(DeploymentAction):
|
92
|
+
"""Resumes the given Deployment"""
|
93
|
+
|
94
|
+
type: Literal["resume-deployment"] = "resume-deployment"
|
95
|
+
|
96
|
+
|
97
|
+
class ChangeFlowRunState(Action):
|
98
|
+
"""Changes the state of a flow run associated with the trigger"""
|
99
|
+
|
100
|
+
type: Literal["change-flow-run-state"] = "change-flow-run-state"
|
101
|
+
|
102
|
+
name: Optional[str] = Field(
|
103
|
+
None,
|
104
|
+
description="The name of the state to change the flow run to",
|
105
|
+
)
|
106
|
+
state: StateType = Field(
|
107
|
+
...,
|
108
|
+
description="The type of the state to change the flow run to",
|
109
|
+
)
|
110
|
+
message: Optional[str] = Field(
|
111
|
+
None,
|
112
|
+
description="An optional message to associate with the state change",
|
113
|
+
)
|
114
|
+
|
115
|
+
|
116
|
+
class CancelFlowRun(Action):
|
117
|
+
"""Cancels a flow run associated with the trigger"""
|
118
|
+
|
119
|
+
type: Literal["cancel-flow-run"] = "cancel-flow-run"
|
120
|
+
|
121
|
+
|
122
|
+
class SuspendFlowRun(Action):
|
123
|
+
"""Suspends a flow run associated with the trigger"""
|
124
|
+
|
125
|
+
type: Literal["suspend-flow-run"] = "suspend-flow-run"
|
126
|
+
|
127
|
+
|
128
|
+
class CallWebhook(Action):
|
129
|
+
"""Call a webhook when an Automation is triggered."""
|
130
|
+
|
131
|
+
type: Literal["call-webhook"] = "call-webhook"
|
132
|
+
block_document_id: UUID = Field(
|
133
|
+
description="The identifier of the webhook block to use"
|
134
|
+
)
|
135
|
+
payload: str = Field(
|
136
|
+
default="",
|
137
|
+
description="An optional templatable payload to send when calling the webhook.",
|
138
|
+
)
|
139
|
+
|
140
|
+
|
51
141
|
class SendNotification(Action):
|
52
|
-
"""Send a notification
|
142
|
+
"""Send a notification when an Automation is triggered"""
|
53
143
|
|
54
144
|
type: Literal["send-notification"] = "send-notification"
|
55
|
-
|
56
145
|
block_document_id: UUID = Field(
|
57
|
-
|
146
|
+
description="The identifier of the notification block to use"
|
147
|
+
)
|
148
|
+
subject: str = Field("Prefect automated notification")
|
149
|
+
body: str = Field(description="The text of the notification to send")
|
150
|
+
|
151
|
+
|
152
|
+
class WorkPoolAction(Action):
|
153
|
+
"""Base class for Actions that operate on Work Pools and need to infer them from
|
154
|
+
events"""
|
155
|
+
|
156
|
+
source: Literal["selected", "inferred"] = Field(
|
157
|
+
"selected",
|
158
|
+
description=(
|
159
|
+
"Whether this Action applies to a specific selected "
|
160
|
+
"work pool (given by `work_pool_id`), or to a work pool that is "
|
161
|
+
"inferred from the triggering event. If the source is 'inferred', "
|
162
|
+
"the `work_pool_id` may not be set. If the source is 'selected', the "
|
163
|
+
"`work_pool_id` must be set."
|
164
|
+
),
|
165
|
+
)
|
166
|
+
work_pool_id: Optional[UUID] = Field(
|
167
|
+
None,
|
168
|
+
description="The identifier of the work pool to pause",
|
169
|
+
)
|
170
|
+
|
171
|
+
|
172
|
+
class PauseWorkPool(WorkPoolAction):
|
173
|
+
"""Pauses a Work Pool"""
|
174
|
+
|
175
|
+
type: Literal["pause-work-pool"] = "pause-work-pool"
|
176
|
+
|
177
|
+
|
178
|
+
class ResumeWorkPool(WorkPoolAction):
|
179
|
+
"""Resumes a Work Pool"""
|
180
|
+
|
181
|
+
type: Literal["resume-work-pool"] = "resume-work-pool"
|
182
|
+
|
183
|
+
|
184
|
+
class WorkQueueAction(Action):
|
185
|
+
"""Base class for Actions that operate on Work Queues and need to infer them from
|
186
|
+
events"""
|
187
|
+
|
188
|
+
source: Literal["selected", "inferred"] = Field(
|
189
|
+
"selected",
|
190
|
+
description=(
|
191
|
+
"Whether this Action applies to a specific selected "
|
192
|
+
"work queue (given by `work_queue_id`), or to a work queue that is "
|
193
|
+
"inferred from the triggering event. If the source is 'inferred', "
|
194
|
+
"the `work_queue_id` may not be set. If the source is 'selected', the "
|
195
|
+
"`work_queue_id` must be set."
|
196
|
+
),
|
197
|
+
)
|
198
|
+
work_queue_id: Optional[UUID] = Field(
|
199
|
+
None, description="The identifier of the work queue to pause"
|
200
|
+
)
|
201
|
+
|
202
|
+
@root_validator
|
203
|
+
def selected_work_queue_requires_id(cls, values):
|
204
|
+
wants_selected_work_queue = values.get("source") == "selected"
|
205
|
+
has_work_queue_id = bool(values.get("work_queue_id"))
|
206
|
+
if wants_selected_work_queue != has_work_queue_id:
|
207
|
+
raise ValueError(
|
208
|
+
"work_queue_id is "
|
209
|
+
+ ("not allowed" if has_work_queue_id else "required")
|
210
|
+
)
|
211
|
+
return values
|
212
|
+
|
213
|
+
|
214
|
+
class PauseWorkQueue(WorkQueueAction):
|
215
|
+
"""Pauses a Work Queue"""
|
216
|
+
|
217
|
+
type: Literal["pause-work-queue"] = "pause-work-queue"
|
218
|
+
|
219
|
+
|
220
|
+
class ResumeWorkQueue(WorkQueueAction):
|
221
|
+
"""Resumes a Work Queue"""
|
222
|
+
|
223
|
+
type: Literal["resume-work-queue"] = "resume-work-queue"
|
224
|
+
|
225
|
+
|
226
|
+
class AutomationAction(Action):
|
227
|
+
"""Base class for Actions that operate on Automations and need to infer them from
|
228
|
+
events"""
|
229
|
+
|
230
|
+
source: Literal["selected", "inferred"] = Field(
|
231
|
+
"selected",
|
232
|
+
description=(
|
233
|
+
"Whether this Action applies to a specific selected "
|
234
|
+
"automation (given by `automation_id`), or to an automation that is "
|
235
|
+
"inferred from the triggering event. If the source is 'inferred', "
|
236
|
+
"the `automation_id` may not be set. If the source is 'selected', the "
|
237
|
+
"`automation_id` must be set."
|
238
|
+
),
|
239
|
+
)
|
240
|
+
automation_id: Optional[UUID] = Field(
|
241
|
+
None, description="The identifier of the automation to act on"
|
58
242
|
)
|
59
|
-
|
60
|
-
|
243
|
+
|
244
|
+
@root_validator
|
245
|
+
def selected_automation_requires_id(cls, values):
|
246
|
+
wants_selected_automation = values.get("source") == "selected"
|
247
|
+
has_automation_id = bool(values.get("automation_id"))
|
248
|
+
if wants_selected_automation != has_automation_id:
|
249
|
+
raise ValueError(
|
250
|
+
"automation_id is "
|
251
|
+
+ ("not allowed" if has_automation_id else "required")
|
252
|
+
)
|
253
|
+
return values
|
254
|
+
|
255
|
+
|
256
|
+
class PauseAutomation(AutomationAction):
|
257
|
+
"""Pauses a Work Queue"""
|
258
|
+
|
259
|
+
type: Literal["pause-automation"] = "pause-automation"
|
260
|
+
|
261
|
+
|
262
|
+
class ResumeAutomation(AutomationAction):
|
263
|
+
"""Resumes a Work Queue"""
|
264
|
+
|
265
|
+
type: Literal["resume-automation"] = "resume-automation"
|
266
|
+
|
267
|
+
|
268
|
+
class DeclareIncident(Action):
|
269
|
+
"""Declares an incident for the triggering event. Only available on Prefect Cloud"""
|
270
|
+
|
271
|
+
type: Literal["declare-incident"] = "declare-incident"
|
61
272
|
|
62
273
|
|
63
|
-
|
274
|
+
# The actual action types that we support. It's important to update this
|
275
|
+
# Union when adding new subclasses of Action so that they are available for clients
|
276
|
+
# and in the OpenAPI docs
|
277
|
+
ActionTypes: TypeAlias = Union[
|
278
|
+
DoNothing,
|
279
|
+
RunDeployment,
|
280
|
+
PauseDeployment,
|
281
|
+
ResumeDeployment,
|
282
|
+
CancelFlowRun,
|
283
|
+
ChangeFlowRunState,
|
284
|
+
PauseWorkQueue,
|
285
|
+
ResumeWorkQueue,
|
286
|
+
SendNotification,
|
287
|
+
CallWebhook,
|
288
|
+
PauseAutomation,
|
289
|
+
ResumeAutomation,
|
290
|
+
SuspendFlowRun,
|
291
|
+
PauseWorkPool,
|
292
|
+
ResumeWorkPool,
|
293
|
+
# Prefect Cloud only
|
294
|
+
DeclareIncident,
|
295
|
+
]
|
@@ -0,0 +1,201 @@
|
|
1
|
+
"""
|
2
|
+
Command line interface for working with automations.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import functools
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
import orjson
|
9
|
+
import typer
|
10
|
+
import yaml as pyyaml
|
11
|
+
from rich.pretty import Pretty
|
12
|
+
from rich.table import Table
|
13
|
+
from rich.text import Text
|
14
|
+
|
15
|
+
from prefect.cli._types import PrefectTyper
|
16
|
+
from prefect.cli._utilities import exit_with_error, exit_with_success
|
17
|
+
from prefect.cli.root import app
|
18
|
+
from prefect.client.orchestration import get_client
|
19
|
+
|
20
|
+
automations_app = PrefectTyper(
|
21
|
+
name="automation",
|
22
|
+
help="Commands for managing automations.",
|
23
|
+
)
|
24
|
+
app.add_typer(automations_app, aliases=["automations"])
|
25
|
+
|
26
|
+
|
27
|
+
def requires_automations(func):
|
28
|
+
@functools.wraps(func)
|
29
|
+
async def wrapper(*args, **kwargs):
|
30
|
+
try:
|
31
|
+
return await func(*args, **kwargs)
|
32
|
+
except RuntimeError as exc:
|
33
|
+
if "Enable experimental" in str(exc):
|
34
|
+
exit_with_error(str(exc))
|
35
|
+
raise
|
36
|
+
|
37
|
+
return wrapper
|
38
|
+
|
39
|
+
|
40
|
+
@automations_app.command()
|
41
|
+
@requires_automations
|
42
|
+
async def ls():
|
43
|
+
"""List all automations."""
|
44
|
+
async with get_client() as client:
|
45
|
+
automations = await client.read_automations()
|
46
|
+
|
47
|
+
table = Table(title="Automations", show_lines=True)
|
48
|
+
|
49
|
+
table.add_column("Automation")
|
50
|
+
table.add_column("Enabled")
|
51
|
+
table.add_column("Trigger")
|
52
|
+
table.add_column("Actions")
|
53
|
+
|
54
|
+
for automation in automations:
|
55
|
+
identifier_column = Text()
|
56
|
+
|
57
|
+
identifier_column.append(automation.name, style="white bold")
|
58
|
+
identifier_column.append("\n")
|
59
|
+
|
60
|
+
identifier_column.append(str(automation.id), style="cyan")
|
61
|
+
identifier_column.append("\n")
|
62
|
+
|
63
|
+
if automation.description:
|
64
|
+
identifier_column.append(automation.description, style="white")
|
65
|
+
identifier_column.append("\n")
|
66
|
+
|
67
|
+
if automation.actions_on_trigger or automation.actions_on_resolve:
|
68
|
+
actions = (
|
69
|
+
[
|
70
|
+
f"(trigger) {action.describe_for_cli()}"
|
71
|
+
for action in automation.actions
|
72
|
+
]
|
73
|
+
+ [
|
74
|
+
f"(trigger) {action.describe_for_cli()}"
|
75
|
+
for action in automation.actions_on_trigger
|
76
|
+
]
|
77
|
+
+ [
|
78
|
+
f"(resolve) {action.describe_for_cli()}"
|
79
|
+
for action in automation.actions
|
80
|
+
]
|
81
|
+
+ [
|
82
|
+
f"(resolve) {action.describe_for_cli()}"
|
83
|
+
for action in automation.actions_on_resolve
|
84
|
+
]
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
actions = [action.describe_for_cli() for action in automation.actions]
|
88
|
+
|
89
|
+
table.add_row(
|
90
|
+
identifier_column,
|
91
|
+
str(automation.enabled),
|
92
|
+
automation.trigger.describe_for_cli(),
|
93
|
+
"\n".join(actions),
|
94
|
+
)
|
95
|
+
|
96
|
+
app.console.print(table)
|
97
|
+
|
98
|
+
|
99
|
+
@automations_app.command()
|
100
|
+
@requires_automations
|
101
|
+
async def inspect(id_or_name: str, yaml: bool = False, json: bool = False):
|
102
|
+
"""Inspect an automation."""
|
103
|
+
async with get_client() as client:
|
104
|
+
automation = await client.find_automation(id_or_name)
|
105
|
+
if not automation:
|
106
|
+
exit_with_error(f"Automation {id_or_name!r} not found.")
|
107
|
+
|
108
|
+
if yaml:
|
109
|
+
app.console.print(
|
110
|
+
pyyaml.dump(automation.dict(json_compatible=True), sort_keys=False)
|
111
|
+
)
|
112
|
+
elif json:
|
113
|
+
app.console.print(
|
114
|
+
orjson.dumps(
|
115
|
+
automation.dict(json_compatible=True), option=orjson.OPT_INDENT_2
|
116
|
+
).decode()
|
117
|
+
)
|
118
|
+
else:
|
119
|
+
app.console.print(Pretty(automation))
|
120
|
+
|
121
|
+
|
122
|
+
@automations_app.command(aliases=["enable"])
|
123
|
+
@requires_automations
|
124
|
+
async def resume(id_or_name: str):
|
125
|
+
"""Resume an automation."""
|
126
|
+
async with get_client() as client:
|
127
|
+
automation = await client.find_automation(id_or_name)
|
128
|
+
if not automation:
|
129
|
+
exit_with_error(f"Automation {id_or_name!r} not found.")
|
130
|
+
|
131
|
+
async with get_client() as client:
|
132
|
+
await client.resume_automation(automation.id)
|
133
|
+
|
134
|
+
exit_with_success(f"Resumed automation {automation.name!r} ({automation.id})")
|
135
|
+
|
136
|
+
|
137
|
+
@automations_app.command(aliases=["disable"])
|
138
|
+
@requires_automations
|
139
|
+
async def pause(id_or_name: str):
|
140
|
+
"""Pause an automation."""
|
141
|
+
async with get_client() as client:
|
142
|
+
automation = await client.find_automation(id_or_name)
|
143
|
+
if not automation:
|
144
|
+
exit_with_error(f"Automation {id_or_name!r} not found.")
|
145
|
+
|
146
|
+
async with get_client() as client:
|
147
|
+
await client.pause_automation(automation.id)
|
148
|
+
|
149
|
+
exit_with_success(f"Paused automation {automation.name!r} ({automation.id})")
|
150
|
+
|
151
|
+
|
152
|
+
@automations_app.command()
|
153
|
+
@requires_automations
|
154
|
+
async def delete(
|
155
|
+
name: Optional[str] = typer.Argument(None, help="An automation's name"),
|
156
|
+
id: Optional[str] = typer.Option(None, "--id", help="An automation's id"),
|
157
|
+
):
|
158
|
+
"""Delete an automation.
|
159
|
+
|
160
|
+
Arguments:
|
161
|
+
name: the name of the automation to delete
|
162
|
+
id: the id of the automation to delete
|
163
|
+
|
164
|
+
Examples:
|
165
|
+
$ prefect automation delete "my-automation"
|
166
|
+
$ prefect automation delete --id "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
167
|
+
"""
|
168
|
+
|
169
|
+
async with get_client() as client:
|
170
|
+
if not id and not name:
|
171
|
+
exit_with_error("Please provide either a name or an id.")
|
172
|
+
|
173
|
+
if id:
|
174
|
+
automation = await client.read_automation(id)
|
175
|
+
if not automation:
|
176
|
+
exit_with_error(f"Automation with id {id!r} not found.")
|
177
|
+
if not typer.confirm(
|
178
|
+
(f"Are you sure you want to delete automation with id {id!r}?"),
|
179
|
+
default=False,
|
180
|
+
):
|
181
|
+
exit_with_error("Deletion aborted.")
|
182
|
+
await client.delete_automation(id)
|
183
|
+
exit_with_success(f"Deleted automation with id {id!r}")
|
184
|
+
|
185
|
+
elif name:
|
186
|
+
automation = await client.read_automations_by_name(name=name)
|
187
|
+
if not automation:
|
188
|
+
exit_with_error(
|
189
|
+
f"Automation {name!r} not found. You can also specify an id with the `--id` flag."
|
190
|
+
)
|
191
|
+
elif len(automation) > 1:
|
192
|
+
exit_with_error(
|
193
|
+
f"Multiple automations found with name {name!r}. Please specify an id with the `--id` flag instead."
|
194
|
+
)
|
195
|
+
if not typer.confirm(
|
196
|
+
(f"Are you sure you want to delete automation with name {name!r}?"),
|
197
|
+
default=False,
|
198
|
+
):
|
199
|
+
exit_with_error("Deletion aborted.")
|
200
|
+
await client.delete_automation(automation[0].id)
|
201
|
+
exit_with_success(f"Deleted automation with name {name!r}")
|