prefect-client 2.18.0__py3-none-any.whl → 2.18.2__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 (49) hide show
  1. prefect/_internal/schemas/fields.py +31 -12
  2. prefect/automations.py +162 -0
  3. prefect/blocks/core.py +1 -1
  4. prefect/blocks/notifications.py +2 -2
  5. prefect/blocks/system.py +2 -3
  6. prefect/client/orchestration.py +309 -30
  7. prefect/client/schemas/objects.py +11 -8
  8. prefect/client/schemas/sorting.py +9 -0
  9. prefect/client/utilities.py +25 -3
  10. prefect/concurrency/asyncio.py +11 -5
  11. prefect/concurrency/events.py +3 -3
  12. prefect/concurrency/services.py +1 -1
  13. prefect/concurrency/sync.py +9 -5
  14. prefect/deployments/deployments.py +27 -18
  15. prefect/deployments/runner.py +34 -26
  16. prefect/engine.py +3 -1
  17. prefect/events/actions.py +2 -1
  18. prefect/events/cli/automations.py +207 -46
  19. prefect/events/clients.py +53 -20
  20. prefect/events/filters.py +31 -4
  21. prefect/events/instrument.py +40 -40
  22. prefect/events/related.py +2 -1
  23. prefect/events/schemas/automations.py +52 -7
  24. prefect/events/schemas/deployment_triggers.py +16 -228
  25. prefect/events/schemas/events.py +18 -11
  26. prefect/events/schemas/labelling.py +1 -1
  27. prefect/events/utilities.py +1 -1
  28. prefect/events/worker.py +10 -7
  29. prefect/flows.py +42 -24
  30. prefect/input/actions.py +9 -9
  31. prefect/input/run_input.py +51 -37
  32. prefect/new_flow_engine.py +444 -0
  33. prefect/new_task_engine.py +488 -0
  34. prefect/results.py +3 -2
  35. prefect/runner/runner.py +3 -2
  36. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +45 -4
  37. prefect/settings.py +47 -0
  38. prefect/states.py +25 -19
  39. prefect/tasks.py +146 -19
  40. prefect/utilities/asyncutils.py +41 -0
  41. prefect/utilities/engine.py +6 -4
  42. prefect/utilities/schema_tools/validation.py +1 -1
  43. prefect/workers/process.py +2 -1
  44. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/METADATA +1 -1
  45. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/RECORD +48 -46
  46. prefect/concurrency/common.py +0 -0
  47. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/LICENSE +0 -0
  48. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/WHEEL +0 -0
  49. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,13 @@ import anyio
16
16
  import pendulum
17
17
  import yaml
18
18
 
19
+ from prefect._internal.pydantic import HAS_PYDANTIC_V2
20
+
21
+ if HAS_PYDANTIC_V2:
22
+ from pydantic.v1 import BaseModel, Field, parse_obj_as, root_validator, validator
23
+ else:
24
+ from pydantic import BaseModel, Field, parse_obj_as, root_validator, validator
25
+
19
26
  from prefect._internal.compatibility.deprecated import (
20
27
  DeprecatedInfraOverridesField,
21
28
  deprecated_callable,
@@ -23,7 +30,6 @@ from prefect._internal.compatibility.deprecated import (
23
30
  deprecated_parameter,
24
31
  handle_deprecated_infra_overrides_parameter,
25
32
  )
26
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
27
33
  from prefect._internal.schemas.validators import (
28
34
  handle_openapi_schema,
29
35
  infrastructure_must_have_capabilities,
@@ -32,16 +38,10 @@ from prefect._internal.schemas.validators import (
32
38
  validate_automation_names,
33
39
  validate_deprecated_schedule_fields,
34
40
  )
35
- from prefect.client.schemas.actions import DeploymentScheduleCreate
36
-
37
- if HAS_PYDANTIC_V2:
38
- from pydantic.v1 import BaseModel, Field, parse_obj_as, root_validator, validator
39
- else:
40
- from pydantic import BaseModel, Field, parse_obj_as, root_validator, validator
41
-
42
41
  from prefect.blocks.core import Block
43
42
  from prefect.blocks.fields import SecretDict
44
43
  from prefect.client.orchestration import PrefectClient, get_client
44
+ from prefect.client.schemas.actions import DeploymentScheduleCreate
45
45
  from prefect.client.schemas.objects import (
46
46
  FlowRun,
47
47
  MinimalDeploymentSchedule,
@@ -53,11 +53,12 @@ from prefect.deployments.schedules import (
53
53
  FlexibleScheduleList,
54
54
  )
55
55
  from prefect.deployments.steps.core import run_steps
56
- from prefect.events import DeploymentTriggerTypes
56
+ from prefect.events import DeploymentTriggerTypes, TriggerTypes
57
57
  from prefect.exceptions import (
58
58
  BlockMissingCapabilities,
59
59
  ObjectAlreadyExists,
60
60
  ObjectNotFound,
61
+ PrefectHTTPStatusError,
61
62
  )
62
63
  from prefect.filesystems import LocalFileSystem
63
64
  from prefect.flows import Flow, load_flow_from_entrypoint
@@ -609,7 +610,7 @@ class Deployment(DeprecatedInfraOverridesField, BaseModel):
609
610
  description="The parameter schema of the flow, including defaults.",
610
611
  )
611
612
  timestamp: datetime = Field(default_factory=partial(pendulum.now, "UTC"))
612
- triggers: List[DeploymentTriggerTypes] = Field(
613
+ triggers: List[Union[DeploymentTriggerTypes, TriggerTypes]] = Field(
613
614
  default_factory=list,
614
615
  description="The triggers that should cause this deployment to run.",
615
616
  )
@@ -902,14 +903,22 @@ class Deployment(DeprecatedInfraOverridesField, BaseModel):
902
903
  )
903
904
 
904
905
  if client.server_type.supports_automations():
905
- # The triggers defined in the deployment spec are, essentially,
906
- # anonymous and attempting truly sync them with cloud is not
907
- # feasible. Instead, we remove all automations that are owned
908
- # by the deployment, meaning that they were created via this
909
- # mechanism below, and then recreate them.
910
- await client.delete_resource_owned_automations(
911
- f"prefect.deployment.{deployment_id}"
912
- )
906
+ try:
907
+ # The triggers defined in the deployment spec are, essentially,
908
+ # anonymous and attempting truly sync them with cloud is not
909
+ # feasible. Instead, we remove all automations that are owned
910
+ # by the deployment, meaning that they were created via this
911
+ # mechanism below, and then recreate them.
912
+ await client.delete_resource_owned_automations(
913
+ f"prefect.deployment.{deployment_id}"
914
+ )
915
+ except PrefectHTTPStatusError as e:
916
+ if e.response.status_code == 404:
917
+ # This Prefect server does not support automations, so we can safely
918
+ # ignore this 404 and move on.
919
+ return deployment_id
920
+ raise e
921
+
913
922
  for trigger in self.triggers:
914
923
  trigger.set_deployment_id(deployment_id)
915
924
  await client.create_automation(trigger.as_automation())
@@ -42,26 +42,19 @@ from rich.console import Console
42
42
  from rich.progress import Progress, SpinnerColumn, TextColumn, track
43
43
  from rich.table import Table
44
44
 
45
- from prefect._internal.concurrency.api import create_call, from_async
46
45
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
47
- from prefect._internal.schemas.validators import (
48
- reconcile_paused_deployment,
49
- reconcile_schedules_runner,
50
- validate_automation_names,
51
- )
52
- from prefect.runner.storage import RunnerStorage
53
- from prefect.settings import (
54
- PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE,
55
- PREFECT_DEFAULT_WORK_POOL_NAME,
56
- PREFECT_UI_URL,
57
- )
58
- from prefect.utilities.collections import get_from_dict, isiterable
59
46
 
60
47
  if HAS_PYDANTIC_V2:
61
48
  from pydantic.v1 import BaseModel, Field, PrivateAttr, root_validator, validator
62
49
  else:
63
50
  from pydantic import BaseModel, Field, PrivateAttr, root_validator, validator
64
51
 
52
+ from prefect._internal.concurrency.api import create_call, from_async
53
+ from prefect._internal.schemas.validators import (
54
+ reconcile_paused_deployment,
55
+ reconcile_schedules_runner,
56
+ validate_automation_names,
57
+ )
65
58
  from prefect.client.orchestration import get_client
66
59
  from prefect.client.schemas.objects import MinimalDeploymentSchedule
67
60
  from prefect.client.schemas.schedules import (
@@ -72,13 +65,20 @@ from prefect.deployments.schedules import (
72
65
  FlexibleScheduleList,
73
66
  create_minimal_deployment_schedule,
74
67
  )
75
- from prefect.events import DeploymentTriggerTypes
68
+ from prefect.events import DeploymentTriggerTypes, TriggerTypes
76
69
  from prefect.exceptions import (
77
70
  ObjectNotFound,
78
71
  PrefectHTTPStatusError,
79
72
  )
73
+ from prefect.runner.storage import RunnerStorage
74
+ from prefect.settings import (
75
+ PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE,
76
+ PREFECT_DEFAULT_WORK_POOL_NAME,
77
+ PREFECT_UI_URL,
78
+ )
80
79
  from prefect.utilities.asyncutils import sync_compatible
81
80
  from prefect.utilities.callables import ParameterSchema, parameter_schema
81
+ from prefect.utilities.collections import get_from_dict, isiterable
82
82
  from prefect.utilities.dockerutils import (
83
83
  PushError,
84
84
  build_image,
@@ -179,7 +179,7 @@ class RunnerDeployment(BaseModel):
179
179
  "The path to the entrypoint for the workflow, relative to the `path`."
180
180
  ),
181
181
  )
182
- triggers: List[DeploymentTriggerTypes] = Field(
182
+ triggers: List[Union[DeploymentTriggerTypes, TriggerTypes]] = Field(
183
183
  default_factory=list,
184
184
  description="The triggers that should cause this deployment to run.",
185
185
  )
@@ -326,14 +326,22 @@ class RunnerDeployment(BaseModel):
326
326
  ) from exc
327
327
 
328
328
  if client.server_type.supports_automations():
329
- # The triggers defined in the deployment spec are, essentially,
330
- # anonymous and attempting truly sync them with cloud is not
331
- # feasible. Instead, we remove all automations that are owned
332
- # by the deployment, meaning that they were created via this
333
- # mechanism below, and then recreate them.
334
- await client.delete_resource_owned_automations(
335
- f"prefect.deployment.{deployment_id}"
336
- )
329
+ try:
330
+ # The triggers defined in the deployment spec are, essentially,
331
+ # anonymous and attempting truly sync them with cloud is not
332
+ # feasible. Instead, we remove all automations that are owned
333
+ # by the deployment, meaning that they were created via this
334
+ # mechanism below, and then recreate them.
335
+ await client.delete_resource_owned_automations(
336
+ f"prefect.deployment.{deployment_id}"
337
+ )
338
+ except PrefectHTTPStatusError as e:
339
+ if e.response.status_code == 404:
340
+ # This Prefect server does not support automations, so we can safely
341
+ # ignore this 404 and move on.
342
+ return deployment_id
343
+ raise e
344
+
337
345
  for trigger in self.triggers:
338
346
  trigger.set_deployment_id(deployment_id)
339
347
  await client.create_automation(trigger.as_automation())
@@ -446,7 +454,7 @@ class RunnerDeployment(BaseModel):
446
454
  schedule: Optional[SCHEDULE_TYPES] = None,
447
455
  is_schedule_active: Optional[bool] = None,
448
456
  parameters: Optional[dict] = None,
449
- triggers: Optional[List[DeploymentTriggerTypes]] = None,
457
+ triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
450
458
  description: Optional[str] = None,
451
459
  tags: Optional[List[str]] = None,
452
460
  version: Optional[str] = None,
@@ -582,7 +590,7 @@ class RunnerDeployment(BaseModel):
582
590
  schedule: Optional[SCHEDULE_TYPES] = None,
583
591
  is_schedule_active: Optional[bool] = None,
584
592
  parameters: Optional[dict] = None,
585
- triggers: Optional[List[DeploymentTriggerTypes]] = None,
593
+ triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
586
594
  description: Optional[str] = None,
587
595
  tags: Optional[List[str]] = None,
588
596
  version: Optional[str] = None,
@@ -680,7 +688,7 @@ class RunnerDeployment(BaseModel):
680
688
  schedule: Optional[SCHEDULE_TYPES] = None,
681
689
  is_schedule_active: Optional[bool] = None,
682
690
  parameters: Optional[dict] = None,
683
- triggers: Optional[List[DeploymentTriggerTypes]] = None,
691
+ triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
684
692
  description: Optional[str] = None,
685
693
  tags: Optional[List[str]] = None,
686
694
  version: Optional[str] = None,
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 flow_run_context.flow.isasync:
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/actions.py CHANGED
@@ -9,7 +9,8 @@ from prefect._internal.pydantic import HAS_PYDANTIC_V2
9
9
  if HAS_PYDANTIC_V2:
10
10
  from pydantic.v1 import Field, root_validator
11
11
  else:
12
- from pydantic import Field, root_validator
12
+ from pydantic import Field, root_validator # type: ignore
13
+
13
14
  from prefect._internal.schemas.bases import PrefectBaseModel
14
15
  from prefect.client.schemas.objects import StateType
15
16
 
@@ -3,8 +3,11 @@ Command line interface for working with automations.
3
3
  """
4
4
 
5
5
  import functools
6
+ from typing import Optional
7
+ from uuid import UUID
6
8
 
7
9
  import orjson
10
+ import typer
8
11
  import yaml as pyyaml
9
12
  from rich.pretty import Pretty
10
13
  from rich.table import Table
@@ -14,6 +17,8 @@ from prefect.cli._types import PrefectTyper
14
17
  from prefect.cli._utilities import exit_with_error, exit_with_success
15
18
  from prefect.cli.root import app
16
19
  from prefect.client.orchestration import get_client
20
+ from prefect.events.schemas.automations import Automation
21
+ from prefect.exceptions import PrefectHTTPStatusError
17
22
 
18
23
  automations_app = PrefectTyper(
19
24
  name="automation",
@@ -96,68 +101,224 @@ async def ls():
96
101
 
97
102
  @automations_app.command()
98
103
  @requires_automations
99
- async def inspect(id_or_name: str, yaml: bool = False, json: bool = False):
100
- """Inspect an automation."""
101
- async with get_client() as client:
102
- automation = await client.find_automation(id_or_name)
103
- if not automation:
104
- exit_with_error(f"Automation {id_or_name!r} not found.")
105
-
106
- if yaml:
107
- app.console.print(
108
- pyyaml.dump(automation.dict(json_compatible=True), sort_keys=False)
109
- )
110
- elif json:
111
- app.console.print(
112
- orjson.dumps(
113
- automation.dict(json_compatible=True), option=orjson.OPT_INDENT_2
114
- ).decode()
115
- )
104
+ async def inspect(
105
+ name: Optional[str] = typer.Argument(None, help="An automation's name"),
106
+ id: Optional[str] = typer.Option(None, "--id", help="An automation's id"),
107
+ yaml: bool = typer.Option(False, "--yaml", help="Output as YAML"),
108
+ json: bool = typer.Option(False, "--json", help="Output as JSON"),
109
+ ):
110
+ """
111
+ Inspect an automation.
112
+
113
+ Arguments:
114
+
115
+ name: the name of the automation to inspect
116
+
117
+ id: the id of the automation to inspect
118
+
119
+ yaml: output as YAML
120
+
121
+ json: output as JSON
122
+
123
+ Examples:
124
+
125
+ $ prefect automation inspect "my-automation"
126
+
127
+ $ prefect automation inspect --id "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
128
+
129
+ $ prefect automation inspect "my-automation" --yaml
130
+
131
+ $ prefect automation inspect "my-automation" --json
132
+ """
133
+ if not id and not name:
134
+ exit_with_error("Please provide either a name or an id.")
135
+
136
+ if name:
137
+ async with get_client() as client:
138
+ automation = await client.read_automations_by_name(name=name)
139
+ if not automation:
140
+ exit_with_error(f"Automation {name!r} not found.")
141
+
142
+ elif id:
143
+ async with get_client() as client:
144
+ try:
145
+ uuid_id = UUID(id)
146
+ automation = await client.read_automation(uuid_id)
147
+ except (PrefectHTTPStatusError, ValueError):
148
+ exit_with_error(f"Automation with id {id!r} not found.")
149
+
150
+ if yaml or json:
151
+ if isinstance(automation, list):
152
+ automation = [a.dict(json_compatible=True) for a in automation]
153
+ elif isinstance(automation, Automation):
154
+ automation = automation.dict(json_compatible=True)
155
+ if yaml:
156
+ app.console.print(pyyaml.dump(automation, sort_keys=False))
157
+ elif json:
158
+ app.console.print(
159
+ orjson.dumps(automation, option=orjson.OPT_INDENT_2).decode()
160
+ )
116
161
  else:
117
162
  app.console.print(Pretty(automation))
118
163
 
119
164
 
120
165
  @automations_app.command(aliases=["enable"])
121
166
  @requires_automations
122
- async def resume(id_or_name: str):
123
- """Resume an automation."""
124
- async with get_client() as client:
125
- automation = await client.find_automation(id_or_name)
126
- if not automation:
127
- exit_with_error(f"Automation {id_or_name!r} not found.")
128
-
129
- async with get_client() as client:
130
- await client.resume_automation(automation.id)
167
+ async def resume(
168
+ name: Optional[str] = typer.Argument(None, help="An automation's name"),
169
+ id: Optional[str] = typer.Option(None, "--id", help="An automation's id"),
170
+ ):
171
+ """
172
+ Resume an automation.
173
+
174
+ Arguments:
175
+
176
+ name: the name of the automation to resume
177
+
178
+ id: the id of the automation to resume
179
+
180
+ Examples:
181
+
182
+ $ prefect automation resume "my-automation"
183
+
184
+ $ prefect automation resume --id "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
185
+ """
186
+ if not id and not name:
187
+ exit_with_error("Please provide either a name or an id.")
188
+
189
+ if name:
190
+ async with get_client() as client:
191
+ automation = await client.read_automations_by_name(name=name)
192
+ if not automation:
193
+ exit_with_error(
194
+ f"Automation with name {name!r} not found. You can also specify an id with the `--id` flag."
195
+ )
196
+ if len(automation) > 1:
197
+ if not typer.confirm(
198
+ f"Multiple automations found with name {name!r}. Do you want to resume all of them?",
199
+ default=False,
200
+ ):
201
+ exit_with_error("Resume aborted.")
202
+
203
+ for a in automation:
204
+ await client.resume_automation(a.id)
205
+ exit_with_success(
206
+ f"Resumed automation(s) with name {name!r} and id(s) {', '.join([repr(str(a.id)) for a in automation])}."
207
+ )
131
208
 
132
- exit_with_success(f"Resumed automation {automation.name!r} ({automation.id})")
209
+ elif id:
210
+ async with get_client() as client:
211
+ try:
212
+ uuid_id = UUID(id)
213
+ automation = await client.read_automation(uuid_id)
214
+ except (PrefectHTTPStatusError, ValueError):
215
+ exit_with_error(f"Automation with id {id!r} not found.")
216
+ await client.resume_automation(automation.id)
217
+ exit_with_success(f"Resumed automation with id {str(automation.id)!r}.")
133
218
 
134
219
 
135
220
  @automations_app.command(aliases=["disable"])
136
221
  @requires_automations
137
- async def pause(id_or_name: str):
138
- """Pause an automation."""
139
- async with get_client() as client:
140
- automation = await client.find_automation(id_or_name)
141
- if not automation:
142
- exit_with_error(f"Automation {id_or_name!r} not found.")
143
-
144
- async with get_client() as client:
145
- await client.pause_automation(automation.id)
222
+ async def pause(
223
+ name: Optional[str] = typer.Argument(None, help="An automation's name"),
224
+ id: Optional[str] = typer.Option(None, "--id", help="An automation's id"),
225
+ ):
226
+ """
227
+ Pause an automation.
228
+
229
+ Arguments:
230
+
231
+ name: the name of the automation to pause
232
+
233
+ id: the id of the automation to pause
234
+
235
+ Examples:
236
+
237
+ $ prefect automation pause "my-automation"
238
+
239
+ $ prefect automation pause --id "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
240
+ """
241
+ if not id and not name:
242
+ exit_with_error("Please provide either a name or an id.")
243
+
244
+ if name:
245
+ async with get_client() as client:
246
+ automation = await client.read_automations_by_name(name=name)
247
+ if not automation:
248
+ exit_with_error(
249
+ f"Automation with name {name!r} not found. You can also specify an id with the `--id` flag."
250
+ )
251
+ if len(automation) > 1:
252
+ if not typer.confirm(
253
+ f"Multiple automations found with name {name!r}. Do you want to pause all of them?",
254
+ default=False,
255
+ ):
256
+ exit_with_error("Pause aborted.")
257
+
258
+ for a in automation:
259
+ await client.pause_automation(a.id)
260
+ exit_with_success(
261
+ f"Paused automation(s) with name {name!r} and id(s) {', '.join([repr(str(a.id)) for a in automation])}."
262
+ )
146
263
 
147
- exit_with_success(f"Paused automation {automation.name!r} ({automation.id})")
264
+ elif id:
265
+ async with get_client() as client:
266
+ try:
267
+ uuid_id = UUID(id)
268
+ automation = await client.read_automation(uuid_id)
269
+ except (PrefectHTTPStatusError, ValueError):
270
+ exit_with_error(f"Automation with id {id!r} not found.")
271
+ await client.pause_automation(automation.id)
272
+ exit_with_success(f"Paused automation with id {str(automation.id)!r}.")
148
273
 
149
274
 
150
275
  @automations_app.command()
151
276
  @requires_automations
152
- async def delete(id_or_name: str):
153
- """Delete an automation."""
154
- async with get_client() as client:
155
- automation = await client.find_automation(id_or_name)
277
+ async def delete(
278
+ name: Optional[str] = typer.Argument(None, help="An automation's name"),
279
+ id: Optional[str] = typer.Option(None, "--id", help="An automation's id"),
280
+ ):
281
+ """Delete an automation.
156
282
 
157
- if not automation:
158
- exit_with_success(f"Automation {id_or_name!r} not found.")
283
+ Arguments:
284
+ name: the name of the automation to delete
285
+ id: the id of the automation to delete
159
286
 
160
- async with get_client() as client:
161
- await client.delete_automation(automation.id)
287
+ Examples:
288
+ $ prefect automation delete "my-automation"
289
+ $ prefect automation delete --id "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
290
+ """
162
291
 
163
- exit_with_success(f"Deleted automation {automation.name!r} ({automation.id})")
292
+ async with get_client() as client:
293
+ if not id and not name:
294
+ exit_with_error("Please provide either a name or an id.")
295
+
296
+ if id:
297
+ automation = await client.read_automation(id)
298
+ if not automation:
299
+ exit_with_error(f"Automation with id {id!r} not found.")
300
+ if not typer.confirm(
301
+ (f"Are you sure you want to delete automation with id {id!r}?"),
302
+ default=False,
303
+ ):
304
+ exit_with_error("Deletion aborted.")
305
+ await client.delete_automation(id)
306
+ exit_with_success(f"Deleted automation with id {id!r}")
307
+
308
+ elif name:
309
+ automation = await client.read_automations_by_name(name=name)
310
+ if not automation:
311
+ exit_with_error(
312
+ f"Automation {name!r} not found. You can also specify an id with the `--id` flag."
313
+ )
314
+ elif len(automation) > 1:
315
+ exit_with_error(
316
+ f"Multiple automations found with name {name!r}. Please specify an id with the `--id` flag instead."
317
+ )
318
+ if not typer.confirm(
319
+ (f"Are you sure you want to delete automation with name {name!r}?"),
320
+ default=False,
321
+ ):
322
+ exit_with_error("Deletion aborted.")
323
+ await client.delete_automation(automation[0].id)
324
+ exit_with_success(f"Deleted automation with name {name!r}")