acex-cli 0.1.0__tar.gz
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.
- acex_cli-0.1.0/PKG-INFO +12 -0
- acex_cli-0.1.0/cli/__init__.py +8 -0
- acex_cli-0.1.0/cli/commands/__init__.py +7 -0
- acex_cli-0.1.0/cli/commands/assets.py +37 -0
- acex_cli-0.1.0/cli/commands/logical_nodes.py +34 -0
- acex_cli-0.1.0/cli/commands/nodes.py +106 -0
- acex_cli-0.1.0/cli/main.py +36 -0
- acex_cli-0.1.0/cli/utils.py +43 -0
- acex_cli-0.1.0/pyproject.toml +24 -0
acex_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: acex-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI components for acex framework
|
|
5
|
+
Author: Johan Lahti
|
|
6
|
+
Author-email: johan.lahti@acebit.se
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Requires-Dist: acex-core
|
|
12
|
+
Requires-Dist: typer[all]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asset management commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
@app.command("list")
|
|
14
|
+
def list_assets():
|
|
15
|
+
"""List all assets"""
|
|
16
|
+
console.print("Listing assets...", style="green")
|
|
17
|
+
# Implementation would go here
|
|
18
|
+
|
|
19
|
+
@app.command("create")
|
|
20
|
+
def create_asset(
|
|
21
|
+
name: str = typer.Argument(..., help="Asset name"),
|
|
22
|
+
description: Optional[str] = typer.Option(None, "--description", "-d", help="Asset description")
|
|
23
|
+
):
|
|
24
|
+
"""Create a new asset"""
|
|
25
|
+
# Import only when needed
|
|
26
|
+
from ...models import Asset
|
|
27
|
+
|
|
28
|
+
console.print(f"Creating asset: {name}", style="green")
|
|
29
|
+
if description:
|
|
30
|
+
console.print(f"Description: {description}")
|
|
31
|
+
# Implementation would go here
|
|
32
|
+
|
|
33
|
+
@app.command("show")
|
|
34
|
+
def show_asset(asset_id: int = typer.Argument(..., help="Asset ID")):
|
|
35
|
+
"""Show asset details"""
|
|
36
|
+
console.print(f"Showing asset {asset_id}", style="green")
|
|
37
|
+
# Implementation would go here
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logical Node management commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
@app.command("list")
|
|
14
|
+
def list_logical_nodes():
|
|
15
|
+
"""List all logical nodes"""
|
|
16
|
+
console.print("Listing logical nodes...", style="green")
|
|
17
|
+
# Implementation would go here
|
|
18
|
+
|
|
19
|
+
@app.command("create")
|
|
20
|
+
def create_logical_node(
|
|
21
|
+
name: str = typer.Argument(..., help="Logical node name"),
|
|
22
|
+
node_id: Optional[int] = typer.Option(None, "--node-id", "-n", help="Parent node ID")
|
|
23
|
+
):
|
|
24
|
+
"""Create a new logical node"""
|
|
25
|
+
console.print(f"Creating logical node: {name}", style="green")
|
|
26
|
+
if node_id:
|
|
27
|
+
console.print(f"Node ID: {node_id}")
|
|
28
|
+
# Implementation would go here
|
|
29
|
+
|
|
30
|
+
@app.command("show")
|
|
31
|
+
def show_logical_node(logical_node_id: int = typer.Argument(..., help="Logical node ID")):
|
|
32
|
+
"""Show logical node details"""
|
|
33
|
+
console.print(f"Showing logical node {logical_node_id}", style="green")
|
|
34
|
+
# Implementation would go here
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Node management commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
def build_node_filters(name=None, hostname=None, node_type=None, active=None, tag=None):
|
|
14
|
+
"""
|
|
15
|
+
Convert CLI arguments to filter expressions compatible with the project's filter system.
|
|
16
|
+
Returns a dict that can be passed to database plugins or converted to FilterExpressions.
|
|
17
|
+
"""
|
|
18
|
+
filters = {}
|
|
19
|
+
|
|
20
|
+
# Basic string filters (support regex patterns /pattern/)
|
|
21
|
+
if name is not None:
|
|
22
|
+
filters['name'] = name
|
|
23
|
+
if hostname is not None:
|
|
24
|
+
filters['hostname'] = hostname
|
|
25
|
+
if node_type is not None:
|
|
26
|
+
filters['type'] = node_type
|
|
27
|
+
|
|
28
|
+
# Boolean filters
|
|
29
|
+
if active is not None:
|
|
30
|
+
filters['active'] = active
|
|
31
|
+
|
|
32
|
+
# List filters (for tags, can be expanded to other list fields)
|
|
33
|
+
if tag is not None and len(tag) > 0:
|
|
34
|
+
# For multiple tags, we might want AND or OR logic
|
|
35
|
+
# This structure allows the database plugin to handle it appropriately
|
|
36
|
+
filters['tags'] = {'in': tag} # SQL-style "IN" operation
|
|
37
|
+
|
|
38
|
+
return filters
|
|
39
|
+
|
|
40
|
+
@app.command("list")
|
|
41
|
+
def list_nodes(
|
|
42
|
+
# Basic filters
|
|
43
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by node name (supports regex /pattern/)"),
|
|
44
|
+
hostname: Optional[str] = typer.Option(None, "--hostname", "-h", help="Filter by hostname (supports regex /pattern/)"),
|
|
45
|
+
node_type: Optional[str] = typer.Option(None, "--type", "-t", help="Filter by node type"),
|
|
46
|
+
|
|
47
|
+
# Status filters
|
|
48
|
+
active: Optional[bool] = typer.Option(None, "--active", help="Filter by active status"),
|
|
49
|
+
|
|
50
|
+
# Advanced filters
|
|
51
|
+
tag: Optional[List[str]] = typer.Option(None, "--tag", help="Filter by tags (can be used multiple times)"),
|
|
52
|
+
|
|
53
|
+
# Query modifiers
|
|
54
|
+
limit: Optional[int] = typer.Option(None, "--limit", "-l", help="Limit number of results"),
|
|
55
|
+
sort_by: Optional[str] = typer.Option("name", "--sort", "-s", help="Sort by field (name, hostname, type)"),
|
|
56
|
+
output_format: Optional[str] = typer.Option("table", "--format", "-f", help="Output format (table, json)")
|
|
57
|
+
):
|
|
58
|
+
"""List nodes with flexible filtering options
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
acex nodes list --name "router.*" --active
|
|
62
|
+
acex nodes list --type switch --limit 10
|
|
63
|
+
acex nodes list --tag production --tag core --format json
|
|
64
|
+
"""
|
|
65
|
+
console.print("Listing nodes...", style="green")
|
|
66
|
+
|
|
67
|
+
# Build filters using helper function
|
|
68
|
+
filters = build_node_filters(
|
|
69
|
+
name=name,
|
|
70
|
+
hostname=hostname,
|
|
71
|
+
node_type=node_type,
|
|
72
|
+
active=active,
|
|
73
|
+
tag=tag
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Apply query modifiers
|
|
77
|
+
query_options = {
|
|
78
|
+
'limit': limit,
|
|
79
|
+
'sort_by': sort_by,
|
|
80
|
+
'output_format': output_format
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if filters:
|
|
84
|
+
console.print(f"Active filters: {filters}", style="dim")
|
|
85
|
+
if any(v is not None for v in query_options.values()):
|
|
86
|
+
console.print(f"Query options: {query_options}", style="dim")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
console.print("Found X nodes matching criteria", style="green")
|
|
90
|
+
|
|
91
|
+
@app.command("create")
|
|
92
|
+
def create_node(
|
|
93
|
+
name: str = typer.Argument(..., help="Node name"),
|
|
94
|
+
hostname: Optional[str] = typer.Option(None, "--hostname", "-h", help="Node hostname")
|
|
95
|
+
):
|
|
96
|
+
"""Create a new node"""
|
|
97
|
+
console.print(f"Creating node: {name}", style="green")
|
|
98
|
+
if hostname:
|
|
99
|
+
console.print(f"Hostname: {hostname}")
|
|
100
|
+
# Implementation would go here
|
|
101
|
+
|
|
102
|
+
@app.command("show")
|
|
103
|
+
def show_node(node_id: int = typer.Argument(..., help="Node ID")):
|
|
104
|
+
"""Show node details"""
|
|
105
|
+
console.print(f"Showing node {node_id}", style="green")
|
|
106
|
+
# Implementation would go here
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI application
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from .commands import assets, nodes, logical_nodes
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(
|
|
9
|
+
name="acex",
|
|
10
|
+
help="ACEX - Extendable Automation & Control Ecosystem CLI",
|
|
11
|
+
add_completion=False
|
|
12
|
+
)
|
|
13
|
+
state = {"verbose": False}
|
|
14
|
+
|
|
15
|
+
app.add_typer(assets.app, name="assets", help="Manage assets")
|
|
16
|
+
app.add_typer(nodes.app, name="nodes", help="Manage nodes")
|
|
17
|
+
app.add_typer(logical_nodes.app, name="logical-nodes", help="Manage logical nodes")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def hello():
|
|
22
|
+
"""Test command"""
|
|
23
|
+
typer.echo(f"ACEX!")
|
|
24
|
+
|
|
25
|
+
# Override the main callback to load commands when needed
|
|
26
|
+
@app.callback(invoke_without_command=True)
|
|
27
|
+
def main(ctx: typer.Context, verbose: bool = False):
|
|
28
|
+
state["verbose"] = verbose
|
|
29
|
+
"""Main callback - loads commands only when subcommands are used"""
|
|
30
|
+
if ctx.invoked_subcommand is None:
|
|
31
|
+
# No subcommand specified, show help
|
|
32
|
+
print(ctx.get_help())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
app()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI utilities and helpers
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Any, Dict
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from rich import print as rprint
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
def print_success(message: str):
|
|
13
|
+
"""Print success message"""
|
|
14
|
+
console.print(f"✅ {message}", style="green")
|
|
15
|
+
|
|
16
|
+
def print_error(message: str):
|
|
17
|
+
"""Print error message"""
|
|
18
|
+
console.print(f"❌ {message}", style="red")
|
|
19
|
+
|
|
20
|
+
def print_warning(message: str):
|
|
21
|
+
"""Print warning message"""
|
|
22
|
+
console.print(f"⚠️ {message}", style="yellow")
|
|
23
|
+
|
|
24
|
+
def print_info(message: str):
|
|
25
|
+
"""Print info message"""
|
|
26
|
+
console.print(f"ℹ️ {message}", style="blue")
|
|
27
|
+
|
|
28
|
+
def create_table(title: str, columns: list) -> Table:
|
|
29
|
+
"""Create a Rich table with standard formatting"""
|
|
30
|
+
table = Table(title=title, show_header=True, header_style="bold magenta")
|
|
31
|
+
for column in columns:
|
|
32
|
+
table.add_column(column)
|
|
33
|
+
return table
|
|
34
|
+
|
|
35
|
+
def display_model_data(data: Any, title: str = "Data"):
|
|
36
|
+
"""Display model data in a formatted way"""
|
|
37
|
+
if hasattr(data, 'dict'):
|
|
38
|
+
# SQLModel/Pydantic model
|
|
39
|
+
rprint({title: data.dict()})
|
|
40
|
+
elif isinstance(data, dict):
|
|
41
|
+
rprint({title: data})
|
|
42
|
+
else:
|
|
43
|
+
rprint({title: str(data)})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "acex-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI components for acex framework"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Johan Lahti", email = "johan.lahti@acebit.se"}
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.13"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"acex-core",
|
|
11
|
+
"typer[all]"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
acex = "acex.cli.cli.main:app"
|
|
16
|
+
|
|
17
|
+
[tool.poetry]
|
|
18
|
+
packages = [
|
|
19
|
+
{ include = "cli", to = "acex/cli" },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|