Skip to content

Commit 7549dbf

Browse files
authored
Merge pull request #30 from Enapter/rnovatorov/http
Extend HTTP API Support
2 parents 2de3b50 + e8a8d06 commit 7549dbf

30 files changed

+840
-49
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import argparse
2+
3+
from enapter import cli
4+
5+
from .blueprint_download_command import BlueprintDownloadCommand
6+
from .blueprint_upload_command import BlueprintUploadCommand
7+
8+
9+
class BlueprintCommand(cli.Command):
10+
11+
@staticmethod
12+
def register(parent: cli.Subparsers) -> None:
13+
parser = parent.add_parser(
14+
"blueprint", formatter_class=argparse.ArgumentDefaultsHelpFormatter
15+
)
16+
subparsers = parser.add_subparsers(dest="blueprint_command", required=True)
17+
for command in [
18+
BlueprintDownloadCommand,
19+
BlueprintUploadCommand,
20+
]:
21+
command.register(subparsers)
22+
23+
@staticmethod
24+
async def run(args: argparse.Namespace) -> None:
25+
match args.blueprint_command:
26+
case "download":
27+
await BlueprintDownloadCommand.run(args)
28+
case "upload":
29+
await BlueprintUploadCommand.run(args)
30+
case _:
31+
raise NotImplementedError(args.command_command)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import argparse
2+
import logging
3+
import pathlib
4+
5+
from enapter import cli, http
6+
7+
LOGGER = logging.getLogger(__name__)
8+
9+
10+
class BlueprintDownloadCommand(cli.Command):
11+
12+
@staticmethod
13+
def register(parent: cli.Subparsers) -> None:
14+
parser = parent.add_parser(
15+
"download", formatter_class=argparse.ArgumentDefaultsHelpFormatter
16+
)
17+
parser.add_argument("id", help="ID of the blueprint to download")
18+
parser.add_argument(
19+
"-o", "--output", type=pathlib.Path, help="Output file path", required=True
20+
)
21+
parser.add_argument(
22+
"-v",
23+
"--view",
24+
choices=["original", "compiled"],
25+
default="original",
26+
help="Blueprint view type",
27+
)
28+
29+
@staticmethod
30+
async def run(args: argparse.Namespace) -> None:
31+
async with http.api.Client(http.api.Config.from_env()) as client:
32+
content = await client.blueprints.download(
33+
args.id, view=http.api.blueprints.BlueprintView(args.view.upper())
34+
)
35+
with open(args.output, "wb") as f:
36+
f.write(content)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import argparse
2+
import json
3+
import logging
4+
import pathlib
5+
6+
from enapter import cli, http
7+
8+
LOGGER = logging.getLogger(__name__)
9+
10+
11+
class BlueprintUploadCommand(cli.Command):
12+
13+
@staticmethod
14+
def register(parent: cli.Subparsers) -> None:
15+
parser = parent.add_parser(
16+
"upload", formatter_class=argparse.ArgumentDefaultsHelpFormatter
17+
)
18+
parser.add_argument(
19+
"path", type=pathlib.Path, help="Path to a directory or a zip file"
20+
)
21+
22+
@staticmethod
23+
async def run(args: argparse.Namespace) -> None:
24+
async with http.api.Client(http.api.Config.from_env()) as client:
25+
if args.path.is_dir():
26+
blueprint = await client.blueprints.upload_directory(args.path)
27+
else:
28+
blueprint = await client.blueprints.upload_file(args.path)
29+
print(json.dumps(blueprint.to_dto()))

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from enapter import cli
44

5+
from .blueprint_command import BlueprintCommand
6+
from .command_command import CommandCommand
57
from .device_command import DeviceCommand
68
from .site_command import SiteCommand
79

@@ -13,16 +15,22 @@ def register(parent: cli.Subparsers) -> None:
1315
parser = parent.add_parser(
1416
"api", formatter_class=argparse.ArgumentDefaultsHelpFormatter
1517
)
16-
subparsers = parser.add_subparsers(dest="http_api_command", required=True)
18+
subparsers = parser.add_subparsers(dest="api_command", required=True)
1719
for command in [
20+
BlueprintCommand,
21+
CommandCommand,
1822
DeviceCommand,
1923
SiteCommand,
2024
]:
2125
command.register(subparsers)
2226

2327
@staticmethod
2428
async def run(args: argparse.Namespace) -> None:
25-
match args.http_api_command:
29+
match args.api_command:
30+
case "blueprint":
31+
await BlueprintCommand.run(args)
32+
case "command":
33+
await CommandCommand.run(args)
2634
case "device":
2735
await DeviceCommand.run(args)
2836
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

0 commit comments

Comments
 (0)