Skip to content

Commit e8a8d06

Browse files
committed
http: api: implement commands
1 parent 12be0d5 commit e8a8d06

14 files changed

+455
-2
lines changed

src/enapter/cli/http/api/command.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from enapter import cli
44

55
from .blueprint_command import BlueprintCommand
6+
from .command_command import CommandCommand
67
from .device_command import DeviceCommand
78
from .site_command import SiteCommand
89

@@ -17,6 +18,7 @@ def register(parent: cli.Subparsers) -> None:
1718
subparsers = parser.add_subparsers(dest="api_command", required=True)
1819
for command in [
1920
BlueprintCommand,
21+
CommandCommand,
2022
DeviceCommand,
2123
SiteCommand,
2224
]:
@@ -27,6 +29,8 @@ async def run(args: argparse.Namespace) -> None:
2729
match args.api_command:
2830
case "blueprint":
2931
await BlueprintCommand.run(args)
32+
case "command":
33+
await CommandCommand.run(args)
3034
case "device":
3135
await DeviceCommand.run(args)
3236
case "site":
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import argparse
2+
import json
3+
4+
5+
def parse_command_arguments(arguments_string: str | None) -> dict:
6+
if arguments_string is None:
7+
return {}
8+
try:
9+
return json.loads(arguments_string)
10+
except json.JSONDecodeError as e:
11+
raise argparse.ArgumentTypeError(f"Decode JSON: {e.msg}")
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import argparse
2+
3+
from enapter import cli
4+
5+
from .command_create_execution_command import CommandCreateExecutionCommand
6+
from .command_execute_command import CommandExecuteCommand
7+
from .command_get_execution_command import CommandGetExecutionCommand
8+
from .command_list_executions_command import CommandListExecutionsCommand
9+
10+
11+
class CommandCommand(cli.Command):
12+
13+
@staticmethod
14+
def register(parent: cli.Subparsers) -> None:
15+
parser = parent.add_parser(
16+
"command", formatter_class=argparse.ArgumentDefaultsHelpFormatter
17+
)
18+
subparsers = parser.add_subparsers(dest="command_command", required=True)
19+
for command in [
20+
CommandCreateExecutionCommand,
21+
CommandExecuteCommand,
22+
CommandGetExecutionCommand,
23+
CommandListExecutionsCommand,
24+
]:
25+
command.register(subparsers)
26+
27+
@staticmethod
28+
async def run(args: argparse.Namespace) -> None:
29+
match args.command_command:
30+
case "create-execution":
31+
await CommandCreateExecutionCommand.run(args)
32+
case "execute":
33+
await CommandExecuteCommand.run(args)
34+
case "get-execution":
35+
await CommandGetExecutionCommand.run(args)
36+
case "list-executions":
37+
await CommandListExecutionsCommand.run(args)
38+
case _:
39+
raise NotImplementedError(args.command_command)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import argparse
2+
import json
3+
import logging
4+
5+
from enapter import cli, http
6+
7+
from .command_arguments import parse_command_arguments
8+
9+
LOGGER = logging.getLogger(__name__)
10+
11+
12+
class CommandCreateExecutionCommand(cli.Command):
13+
14+
@staticmethod
15+
def register(parent: cli.Subparsers) -> None:
16+
parser = parent.add_parser(
17+
"create-execution", formatter_class=argparse.ArgumentDefaultsHelpFormatter
18+
)
19+
parser.add_argument(
20+
"-d",
21+
"--device-id",
22+
required=True,
23+
help="ID or slug of the device to execute the command on",
24+
)
25+
parser.add_argument(
26+
"-a",
27+
"--arguments",
28+
type=parse_command_arguments,
29+
help="JSON string of arguments to pass to the command",
30+
)
31+
parser.add_argument("name", help="Name of the command to execute")
32+
33+
@staticmethod
34+
async def run(args: argparse.Namespace) -> None:
35+
async with http.api.Client(http.api.Config.from_env()) as client:
36+
execution = await client.commands.create_execution(
37+
device_id=args.device_id, name=args.name, arguments=args.arguments
38+
)
39+
print(json.dumps(execution.to_dto()))
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import argparse
2+
import json
3+
import logging
4+
5+
from enapter import cli, http
6+
7+
from .command_arguments import parse_command_arguments
8+
9+
LOGGER = logging.getLogger(__name__)
10+
11+
12+
class CommandExecuteCommand(cli.Command):
13+
14+
@staticmethod
15+
def register(parent: cli.Subparsers) -> None:
16+
parser = parent.add_parser(
17+
"execute", formatter_class=argparse.ArgumentDefaultsHelpFormatter
18+
)
19+
parser.add_argument(
20+
"-d",
21+
"--device-id",
22+
required=True,
23+
help="ID or slug of the device to execute the command on",
24+
)
25+
parser.add_argument(
26+
"-a",
27+
"--arguments",
28+
type=parse_command_arguments,
29+
help="JSON string of arguments to pass to the command",
30+
)
31+
parser.add_argument(
32+
"-l",
33+
"--log",
34+
action="store_true",
35+
help="Expand command execution log in the output",
36+
)
37+
parser.add_argument("name", help="Name of the command to execute")
38+
39+
@staticmethod
40+
async def run(args: argparse.Namespace) -> None:
41+
async with http.api.Client(http.api.Config.from_env()) as client:
42+
execution = await client.commands.execute(
43+
device_id=args.device_id,
44+
name=args.name,
45+
arguments=args.arguments,
46+
expand_log=args.log,
47+
)
48+
print(json.dumps(execution.to_dto()))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import argparse
2+
import json
3+
import logging
4+
5+
from enapter import cli, http
6+
7+
LOGGER = logging.getLogger(__name__)
8+
9+
10+
class CommandGetExecutionCommand(cli.Command):
11+
12+
@staticmethod
13+
def register(parent: cli.Subparsers) -> None:
14+
parser = parent.add_parser(
15+
"get-execution", formatter_class=argparse.ArgumentDefaultsHelpFormatter
16+
)
17+
parser.add_argument(
18+
"-d",
19+
"--device-id",
20+
required=True,
21+
help="ID or slug of the device to get the command execution of",
22+
)
23+
parser.add_argument(
24+
"-l",
25+
"--log",
26+
action="store_true",
27+
help="Expand command execution log in the output",
28+
)
29+
parser.add_argument(
30+
"execution_id", help="ID of the command execution to retrieve"
31+
)
32+
33+
@staticmethod
34+
async def run(args: argparse.Namespace) -> None:
35+
async with http.api.Client(http.api.Config.from_env()) as client:
36+
execution = await client.commands.get_execution(
37+
device_id=args.device_id,
38+
execution_id=args.execution_id,
39+
expand_log=args.log,
40+
)
41+
print(json.dumps(execution.to_dto()))
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import argparse
2+
import json
3+
4+
from enapter import cli, http
5+
6+
7+
class CommandListExecutionsCommand(cli.Command):
8+
9+
@staticmethod
10+
def register(parent: cli.Subparsers) -> None:
11+
parser = parent.add_parser(
12+
"list-executions", formatter_class=argparse.ArgumentDefaultsHelpFormatter
13+
)
14+
parser.add_argument(
15+
"-l",
16+
"--limit",
17+
type=int,
18+
help="Maximum number of command executions to list",
19+
default=-1,
20+
)
21+
parser.add_argument(
22+
"-o",
23+
"--order",
24+
choices=["created_at_asc", "created_at_desc"],
25+
help="Order of the listed command executions",
26+
default="created_at_asc",
27+
)
28+
parser.add_argument(
29+
"-d",
30+
"--device-id",
31+
help="ID or slug of the device to list command executions for",
32+
required=True,
33+
)
34+
35+
@staticmethod
36+
async def run(args: argparse.Namespace) -> None:
37+
if args.limit == 0:
38+
return
39+
async with http.api.Client(http.api.Config.from_env()) as client:
40+
async with client.commands.list_executions(
41+
device_id=args.device_id,
42+
order=http.api.commands.ListExecutionsOrder(args.order.upper()),
43+
) as stream:
44+
count = 0
45+
async for device in stream:
46+
print(json.dumps(device.to_dto()))
47+
count += 1
48+
if args.limit > 0 and count == args.limit:
49+
break

src/enapter/http/api/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
from .config import Config
33
from .errors import Error, MultiError, check_error
44

5-
from . import devices, sites, blueprints # isort: skip
5+
from . import devices, sites, commands, blueprints # isort: skip
66

77
__all__ = [
88
"Client",
99
"Config",
1010
"devices",
1111
"blueprints",
1212
"sites",
13+
"commands",
1314
"Error",
1415
"MultiError",
1516
"check_error",

src/enapter/http/api/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import httpx
44

5-
from enapter.http.api import blueprints, devices, sites
5+
from enapter.http.api import blueprints, commands, devices, sites
66

77
from .config import Config
88

@@ -38,6 +38,10 @@ def devices(self) -> devices.Client:
3838
def sites(self) -> sites.Client:
3939
return sites.Client(client=self._client)
4040

41+
@property
42+
def commands(self) -> commands.Client:
43+
return commands.Client(client=self._client)
44+
4145
@property
4246
def blueprints(self) -> blueprints.Client:
4347
return blueprints.Client(client=self._client)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from .client import Client
2+
from .execution import Execution, ExecutionState, ListExecutionsOrder
3+
from .request import Request
4+
from .response import Response, ResponseState
5+
6+
__all__ = [
7+
"Client",
8+
"Execution",
9+
"ExecutionState",
10+
"ListExecutionsOrder",
11+
"Request",
12+
"Response",
13+
"ResponseState",
14+
]

0 commit comments

Comments
 (0)