Skip to content

Commit

Permalink
Update prefect automation delete (#12876)
Browse files Browse the repository at this point in the history
  • Loading branch information
serinamarie authored Apr 25, 2024
1 parent 9e02a25 commit 373f6f0
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 14 deletions.
56 changes: 47 additions & 9 deletions src/prefect/events/cli/automations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"""

import functools
from typing import Optional

import orjson
import typer
import yaml as pyyaml
from rich.pretty import Pretty
from rich.table import Table
Expand Down Expand Up @@ -149,15 +151,51 @@ async def pause(id_or_name: str):

@automations_app.command()
@requires_automations
async def delete(id_or_name: str):
"""Delete an automation."""
async with get_client() as client:
automation = await client.find_automation(id_or_name)
async def delete(
name: Optional[str] = typer.Argument(None, help="An automation's name"),
id: Optional[str] = typer.Option(None, "--id", help="An automation's id"),
):
"""Delete an automation.
if not automation:
exit_with_success(f"Automation {id_or_name!r} not found.")
Arguments:
name: the name of the automation to delete
id: the id of the automation to delete
async with get_client() as client:
await client.delete_automation(automation.id)
Examples:
$ prefect automation delete "my-automation"
$ prefect automation delete --id "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
"""

exit_with_success(f"Deleted automation {automation.name!r} ({automation.id})")
async with get_client() as client:
if not id and not name:
exit_with_error("Please provide either a name or an id.")

if id:
automation = await client.read_automation(id)
if not automation:
exit_with_error(f"Automation with id {id!r} not found.")
if not typer.confirm(
(f"Are you sure you want to delete automation with id {id!r}?"),
default=False,
):
exit_with_error("Deletion aborted.")
await client.delete_automation(id)
exit_with_success(f"Deleted automation with id {id!r}")

elif name:
automation = await client.read_automations_by_name(name=name)
if not automation:
exit_with_error(
f"Automation {name!r} not found. You can also specify an id with the `--id` flag."
)
elif len(automation) > 1:
exit_with_error(
f"Multiple automations found with name {name!r}. Please specify an id with the `--id` flag instead."
)
if not typer.confirm(
(f"Are you sure you want to delete automation with name {name!r}?"),
default=False,
):
exit_with_error("Deletion aborted.")
await client.delete_automation(automation[0].id)
exit_with_success(f"Deleted automation with name {name!r}")
103 changes: 98 additions & 5 deletions tests/events/client/cli/test_automations.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ def various_automations(read_automations: mock.AsyncMock) -> List[Automation]:
actions_on_trigger=[DoNothing()],
actions_on_resolve=[PauseAutomation(automation_id=uuid4())],
),
Automation(
id=UUID("dddddddd-dddd-dddd-dddd-dddddddddddd"),
name="A Metric one",
trigger=MetricTrigger(
metric=MetricTriggerQuery(
name=PrefectMetric.successes,
operator=MetricTriggerOperator.LT,
threshold=0.78,
)
),
actions=[CancelFlowRun()],
actions_on_trigger=[DoNothing()],
actions_on_resolve=[PauseAutomation(automation_id=uuid4())],
),
]
read_automations.return_value = automations
return automations
Expand Down Expand Up @@ -273,27 +287,106 @@ def delete_automation() -> Generator[mock.AsyncMock, None, None]:
yield m


@pytest.fixture
def read_automations_by_name() -> Generator[mock.AsyncMock, None, None]:
with mock.patch(
"prefect.client.orchestration.PrefectClient.read_automations_by_name",
autospec=True,
) as mock_read:
yield mock_read


def test_deleting_by_name(
delete_automation: mock.AsyncMock, various_automations: List[Automation]
delete_automation: mock.AsyncMock,
read_automations_by_name: mock.AsyncMock,
various_automations: List[Automation],
):
read_automations_by_name.return_value = [various_automations[0]]
invoke_and_assert(
["automations", "delete", "My First Reactive"],
prompts_and_responses=[
(
"Are you sure you want to delete automation with name 'My First Reactive'?",
"y",
)
],
expected_code=0,
expected_output_contains=["Deleted automation 'My First Reactive'"],
expected_output_contains=["Deleted automation with name 'My First Reactive'"],
)

delete_automation.assert_awaited_once_with(
mock.ANY, UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
)


def test_deleting_not_found_is_a_noop(
delete_automation: mock.AsyncMock, various_automations: List[Automation]
def test_deleting_by_name_multiple_same_name(
delete_automation: mock.AsyncMock,
read_automations_by_name: mock.AsyncMock,
various_automations: List[Automation],
):
read_automations_by_name.return_value = various_automations[:2]
invoke_and_assert(
["automations", "delete", "A Metric one"],
expected_code=1,
expected_output_contains=[
"Multiple automations found with name 'A Metric one'. Please specify an id with the `--id` flag instead."
],
)

delete_automation.assert_not_called()


def test_deleting_by_id_not_found_is_a_noop(
delete_automation: mock.AsyncMock,
various_automations: List[Automation],
read_automations_by_name: mock.AsyncMock,
):
read_automations_by_name.return_value = None
invoke_and_assert(
["automations", "delete", "Who dis?"],
expected_code=0,
expected_code=1,
expected_output_contains=["Automation 'Who dis?' not found"],
)

delete_automation.assert_not_called()


def test_deleting_by_id(
delete_automation: mock.AsyncMock,
read_automation: mock.AsyncMock,
various_automations: List[Automation],
):
read_automation.return_value = various_automations[0]
invoke_and_assert(
["automations", "delete", "--id", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"],
prompts_and_responses=[
(
"Are you sure you want to delete automation with id 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'?",
"y",
)
],
expected_code=0,
expected_output_contains=[
"Deleted automation with id 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'"
],
)

delete_automation.assert_awaited_once_with(
mock.ANY, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
)


def test_deleting_by_nonexistent_id(
delete_automation: mock.AsyncMock,
read_automation: mock.AsyncMock,
):
read_automation.return_value = None
invoke_and_assert(
["automations", "delete", "--id", "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"],
expected_code=1,
expected_output_contains=[
"Automation with id 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz' not found"
],
)

delete_automation.assert_not_called()

0 comments on commit 373f6f0

Please sign in to comment.