From a92dbbdf63831a699e7ceb42e8701e445908ab06 Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Wed, 2 Oct 2024 15:32:41 +0200 Subject: [PATCH 1/6] feat: add cli command action list to get pushed actions --- admyral/cli/__init__.py | 6 ++---- admyral/cli/action.py | 15 +++++++++++++++ admyral/client.py | 12 ++++++++++++ admyral/server/endpoints/action_endpoints.py | 15 +++++++++++++-- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/admyral/cli/__init__.py b/admyral/cli/__init__.py index 269075c3..8e1b49c1 100644 --- a/admyral/cli/__init__.py +++ b/admyral/cli/__init__.py @@ -1,7 +1,4 @@ -from admyral.cli.action import ( - action, - push as action_push, -) +from admyral.cli.action import action, push as action_push, list as action_list from admyral.cli.server import up, down from admyral.cli.setup import init from admyral.cli.workflow import workflow, push as workflow_push, trigger @@ -11,6 +8,7 @@ __all__ = [ "action", "action_push", + "action_list", "up", "down", "init", diff --git a/admyral/cli/action.py b/admyral/cli/action.py index 86b2c1d2..e4a08d11 100644 --- a/admyral/cli/action.py +++ b/admyral/cli/action.py @@ -38,3 +38,18 @@ def push(ctx: click.Context, action_type: str, action: str) -> None: return click.echo(f"Action {action_type} pushed successfully.") + + +@action.command( + "list", + help="List all pushed actions", +) +@click.pass_context +def list(ctx: click.Context) -> None: + """List all custom actions""" + capture(event_name="action:list") + client: AdmyralClient = ctx.obj + actions = client.list_actions() + click.echo("Actions:") + for action in actions: + click.echo(action.action_type) diff --git a/admyral/client.py b/admyral/client.py index c900f8e9..55f7e596 100644 --- a/admyral/client.py +++ b/admyral/client.py @@ -10,6 +10,7 @@ WorkflowPushRequest, WorkflowTriggerResponse, SecretMetadata, + ActionMetadata, ) from admyral.config.config import API_V1_STR @@ -192,6 +193,17 @@ def get_action(self, action_type: str) -> PythonAction | None: result = self._get(f"{API_V1_STR}/actions/{action_type}") return PythonAction.model_validate(result) if result else None + def list_actions(self) -> list[ActionMetadata]: + """ + Returns a list of action names. + + Returns: + A list of action names. + """ + + result = self._get(f"{API_V1_STR}/actions") + return [ActionMetadata.model_validate(r) for r in result] + ######################################################## # Secrets ######################################################## diff --git a/admyral/server/endpoints/action_endpoints.py b/admyral/server/endpoints/action_endpoints.py index f2075c7c..6b976c98 100644 --- a/admyral/server/endpoints/action_endpoints.py +++ b/admyral/server/endpoints/action_endpoints.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, status, Depends from admyral.server.auth import authenticate -from admyral.models import PythonAction, AuthenticatedUser +from admyral.models import PythonAction, AuthenticatedUser, ActionMetadata from admyral.server.deps import get_admyral_store @@ -13,7 +13,7 @@ async def push_action( python_action: PythonAction, authenticated_user: AuthenticatedUser = Depends(authenticate), -): +) -> None: """ Push a Python action to the store. If the action for the provided action type already exists for the user, it will be overwritten. @@ -26,6 +26,17 @@ async def push_action( ) +@router.get("", status_code=status.HTTP_200_OK) +async def list_actions() -> list[ActionMetadata]: + """ + List all stored Python actions. + + Returns: + A list of Python action objects. + """ + return await get_admyral_store().list_actions() + + @router.get("/{action_type}", status_code=status.HTTP_200_OK) async def get_action( action_type: str, authenticated_user: AuthenticatedUser = Depends(AuthenticatedUser) From fa199814ee8710fcdb73702357dd4b83a6706eb4 Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Wed, 2 Oct 2024 16:39:14 +0200 Subject: [PATCH 2/6] feat: add CLI command to delete a pushed action --- admyral/cli/__init__.py | 8 +++++++- admyral/cli/action.py | 13 ++++++++++++- admyral/client.py | 9 +++++++++ admyral/db/admyral_store.py | 9 +++++++++ admyral/db/store_interface.py | 3 +++ admyral/server/endpoints/action_endpoints.py | 11 +++++++++++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/admyral/cli/__init__.py b/admyral/cli/__init__.py index 8e1b49c1..e7598344 100644 --- a/admyral/cli/__init__.py +++ b/admyral/cli/__init__.py @@ -1,4 +1,9 @@ -from admyral.cli.action import action, push as action_push, list as action_list +from admyral.cli.action import ( + action, + push as action_push, + list as action_list, + delete as action_delete, +) from admyral.cli.server import up, down from admyral.cli.setup import init from admyral.cli.workflow import workflow, push as workflow_push, trigger @@ -9,6 +14,7 @@ "action", "action_push", "action_list", + "action_delete", "up", "down", "init", diff --git a/admyral/cli/action.py b/admyral/cli/action.py index e4a08d11..b0089d5c 100644 --- a/admyral/cli/action.py +++ b/admyral/cli/action.py @@ -50,6 +50,17 @@ def list(ctx: click.Context) -> None: capture(event_name="action:list") client: AdmyralClient = ctx.obj actions = client.list_actions() - click.echo("Actions:") + click.echo("Pushed actions:") for action in actions: click.echo(action.action_type) + + +@action.command("delete", help="Delete a pushed action") +@click.argument("action_type", type=str) +@click.pass_context +def delete(ctx: click.Context, action_type: str) -> None: + """Delete an action""" + capture(event_name="action:delete") + client: AdmyralClient = ctx.obj + client.delete_action(action_type) + click.echo(f"Action {action_type} deleted successfully.") diff --git a/admyral/client.py b/admyral/client.py index 55f7e596..b968af87 100644 --- a/admyral/client.py +++ b/admyral/client.py @@ -204,6 +204,15 @@ def list_actions(self) -> list[ActionMetadata]: result = self._get(f"{API_V1_STR}/actions") return [ActionMetadata.model_validate(r) for r in result] + def delete_action(self, action_type: str) -> None: + """ + Deletes the action with the given type. + + Args: + action_type: The action type. + """ + self._delete(f"{API_V1_STR}/actions/{action_type}") + ######################################################## # Secrets ######################################################## diff --git a/admyral/db/admyral_store.py b/admyral/db/admyral_store.py index 702b6757..3a92ea48 100644 --- a/admyral/db/admyral_store.py +++ b/admyral/db/admyral_store.py @@ -272,6 +272,15 @@ async def store_action(self, user_id: str, action: PythonAction) -> None: await db.commit() + async def delete_action(self, action_type: str) -> None: + async with self._get_async_session() as db: + await db.exec( + delete(PythonActionSchema).where( + PythonActionSchema.action_type == action_type + ) + ) + await db.commit() + ######################################################## # Pip Lockfile Cache ######################################################## diff --git a/admyral/db/store_interface.py b/admyral/db/store_interface.py index b980f401..983a76d2 100644 --- a/admyral/db/store_interface.py +++ b/admyral/db/store_interface.py @@ -63,6 +63,9 @@ async def get_action( @abstractmethod async def store_action(self, user_id: str, action: PythonAction) -> None: ... + @abstractmethod + async def delete_action(self, action_type: str) -> None: ... + ######################################################## # Pip Lockfile Cache ######################################################## diff --git a/admyral/server/endpoints/action_endpoints.py b/admyral/server/endpoints/action_endpoints.py index 6b976c98..c90d9868 100644 --- a/admyral/server/endpoints/action_endpoints.py +++ b/admyral/server/endpoints/action_endpoints.py @@ -37,6 +37,17 @@ async def list_actions() -> list[ActionMetadata]: return await get_admyral_store().list_actions() +@router.delete("/{action_type}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_action(action_type: str) -> None: + """ + Delete a Python action by its action type. + + Args: + action_type: The action type. + """ + await get_admyral_store().delete_action(action_type) + + @router.get("/{action_type}", status_code=status.HTTP_200_OK) async def get_action( action_type: str, authenticated_user: AuthenticatedUser = Depends(AuthenticatedUser) From d4d75a02eebb9b78a31362f04ed2cb4ac6bb097d Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Wed, 2 Oct 2024 16:48:02 +0200 Subject: [PATCH 3/6] docs: add new action CLI commands to doc --- docs/pages/cli.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/pages/cli.mdx b/docs/pages/cli.mdx index 10b05ded..f8884cd5 100644 --- a/docs/pages/cli.mdx +++ b/docs/pages/cli.mdx @@ -33,6 +33,20 @@ admyral action push your_custom_action -a path/to/your/action.py where `your_custom_action` is the Python function name and `file_path` is the path to the Python file where the action is defined. +List all pushed actions: + +```bash +admyral action list +``` + +Delete a pushed action: + +```bash +admyral action delete your_custom_action +``` + +where `your_custom_action` is the Python function name. + ## Secrets Management Manages secrets in Admyral. Secrets are encrypted values, such as API keys or passwords. See [Secrets Management](/secrets) for more information. From 6ce612466196186c804c0447ff39cd3e4bad2d9a Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Thu, 3 Oct 2024 20:21:45 +0200 Subject: [PATCH 4/6] fix: add authentication to new CLI action commands --- admyral/db/admyral_store.py | 8 ++++---- admyral/db/store_interface.py | 2 +- admyral/server/endpoints/action_endpoints.py | 14 ++++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/admyral/db/admyral_store.py b/admyral/db/admyral_store.py index 3a92ea48..3e50d3db 100644 --- a/admyral/db/admyral_store.py +++ b/admyral/db/admyral_store.py @@ -272,12 +272,12 @@ async def store_action(self, user_id: str, action: PythonAction) -> None: await db.commit() - async def delete_action(self, action_type: str) -> None: + async def delete_action(self, user_id: str, action_type: str) -> None: async with self._get_async_session() as db: await db.exec( - delete(PythonActionSchema).where( - PythonActionSchema.action_type == action_type - ) + delete(PythonActionSchema) + .where(PythonActionSchema.action_type == action_type) + .where(PythonActionSchema.user_id == user_id) ) await db.commit() diff --git a/admyral/db/store_interface.py b/admyral/db/store_interface.py index 983a76d2..d223eeff 100644 --- a/admyral/db/store_interface.py +++ b/admyral/db/store_interface.py @@ -64,7 +64,7 @@ async def get_action( async def store_action(self, user_id: str, action: PythonAction) -> None: ... @abstractmethod - async def delete_action(self, action_type: str) -> None: ... + async def delete_action(self, user_id: str, action_type: str) -> None: ... ######################################################## # Pip Lockfile Cache diff --git a/admyral/server/endpoints/action_endpoints.py b/admyral/server/endpoints/action_endpoints.py index c90d9868..c8d74b1d 100644 --- a/admyral/server/endpoints/action_endpoints.py +++ b/admyral/server/endpoints/action_endpoints.py @@ -27,25 +27,31 @@ async def push_action( @router.get("", status_code=status.HTTP_200_OK) -async def list_actions() -> list[ActionMetadata]: +async def list_actions( + authenticated_user: AuthenticatedUser = Depends(authenticate), +) -> list[ActionMetadata]: """ List all stored Python actions. Returns: A list of Python action objects. """ - return await get_admyral_store().list_actions() + return await get_admyral_store().list_actions(user_id=authenticated_user.user_id) @router.delete("/{action_type}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_action(action_type: str) -> None: +async def delete_action( + action_type: str, authenticated_user: AuthenticatedUser = Depends(authenticate) +) -> None: """ Delete a Python action by its action type. Args: action_type: The action type. """ - await get_admyral_store().delete_action(action_type) + await get_admyral_store().delete_action( + action_type=action_type, user_id=authenticated_user.user_id + ) @router.get("/{action_type}", status_code=status.HTTP_200_OK) From 1386adceee7c092db03f14e763ca4b2a921839cd Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Fri, 4 Oct 2024 13:41:01 +0200 Subject: [PATCH 5/6] chore: adjust descriptions of functions --- admyral/cli/action.py | 6 +++--- admyral/server/endpoints/action_endpoints.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/admyral/cli/action.py b/admyral/cli/action.py index b0089d5c..3cba8f12 100644 --- a/admyral/cli/action.py +++ b/admyral/cli/action.py @@ -42,11 +42,11 @@ def push(ctx: click.Context, action_type: str, action: str) -> None: @action.command( "list", - help="List all pushed actions", + help="List all pushed custom actions", ) @click.pass_context def list(ctx: click.Context) -> None: - """List all custom actions""" + """List all pushed custom actions""" capture(event_name="action:list") client: AdmyralClient = ctx.obj actions = client.list_actions() @@ -55,7 +55,7 @@ def list(ctx: click.Context) -> None: click.echo(action.action_type) -@action.command("delete", help="Delete a pushed action") +@action.command("delete", help="Delete a pushed custom action") @click.argument("action_type", type=str) @click.pass_context def delete(ctx: click.Context, action_type: str) -> None: diff --git a/admyral/server/endpoints/action_endpoints.py b/admyral/server/endpoints/action_endpoints.py index c8d74b1d..3552fcb7 100644 --- a/admyral/server/endpoints/action_endpoints.py +++ b/admyral/server/endpoints/action_endpoints.py @@ -31,10 +31,10 @@ async def list_actions( authenticated_user: AuthenticatedUser = Depends(authenticate), ) -> list[ActionMetadata]: """ - List all stored Python actions. + List all stored custom Python actions. Returns: - A list of Python action objects. + A list of stored custom Python action objects. """ return await get_admyral_store().list_actions(user_id=authenticated_user.user_id) @@ -44,10 +44,10 @@ async def delete_action( action_type: str, authenticated_user: AuthenticatedUser = Depends(authenticate) ) -> None: """ - Delete a Python action by its action type. + Delete a stored custom Python action by its action type. Args: - action_type: The action type. + action_type: function name of the custom Python action. """ await get_admyral_store().delete_action( action_type=action_type, user_id=authenticated_user.user_id From 0d0e8944df4fbccaf4c2fe0f1654a111cc1bade0 Mon Sep 17 00:00:00 2001 From: leon-schmid Date: Fri, 4 Oct 2024 13:43:43 +0200 Subject: [PATCH 6/6] chore: fix function description --- admyral/cli/action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admyral/cli/action.py b/admyral/cli/action.py index 3cba8f12..2ec9e064 100644 --- a/admyral/cli/action.py +++ b/admyral/cli/action.py @@ -59,7 +59,7 @@ def list(ctx: click.Context) -> None: @click.argument("action_type", type=str) @click.pass_context def delete(ctx: click.Context, action_type: str) -> None: - """Delete an action""" + """Delete a pushed custom action""" capture(event_name="action:delete") client: AdmyralClient = ctx.obj client.delete_action(action_type)