maqet 0.0.1.3__py3-none-any.whl → 0.0.5__py3-none-any.whl
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.
- maqet/__init__.py +50 -6
- maqet/__main__.py +96 -0
- maqet/__version__.py +3 -0
- maqet/api/__init__.py +35 -0
- maqet/api/decorators.py +184 -0
- maqet/api/metadata.py +147 -0
- maqet/api/registry.py +182 -0
- maqet/cli.py +71 -0
- maqet/config/__init__.py +26 -0
- maqet/config/merger.py +237 -0
- maqet/config/parser.py +198 -0
- maqet/config/validators.py +519 -0
- maqet/config_handlers.py +684 -0
- maqet/constants.py +200 -0
- maqet/exceptions.py +226 -0
- maqet/formatters.py +294 -0
- maqet/generators/__init__.py +12 -0
- maqet/generators/base_generator.py +101 -0
- maqet/generators/cli_generator.py +635 -0
- maqet/generators/python_generator.py +247 -0
- maqet/generators/rest_generator.py +58 -0
- maqet/handlers/__init__.py +12 -0
- maqet/handlers/base.py +108 -0
- maqet/handlers/init.py +147 -0
- maqet/handlers/stage.py +196 -0
- maqet/ipc/__init__.py +29 -0
- maqet/ipc/retry.py +265 -0
- maqet/ipc/runner_client.py +285 -0
- maqet/ipc/unix_socket_server.py +239 -0
- maqet/logger.py +160 -55
- maqet/machine.py +884 -0
- maqet/managers/__init__.py +7 -0
- maqet/managers/qmp_manager.py +333 -0
- maqet/managers/snapshot_coordinator.py +327 -0
- maqet/managers/vm_manager.py +683 -0
- maqet/maqet.py +1120 -0
- maqet/os_interactions.py +46 -0
- maqet/process_spawner.py +395 -0
- maqet/qemu_args.py +76 -0
- maqet/qmp/__init__.py +10 -0
- maqet/qmp/commands.py +92 -0
- maqet/qmp/keyboard.py +311 -0
- maqet/qmp/qmp.py +17 -0
- maqet/snapshot.py +473 -0
- maqet/state.py +958 -0
- maqet/storage.py +702 -162
- maqet/validation/__init__.py +9 -0
- maqet/validation/config_validator.py +170 -0
- maqet/vm_runner.py +523 -0
- maqet-0.0.5.dist-info/METADATA +237 -0
- maqet-0.0.5.dist-info/RECORD +55 -0
- {maqet-0.0.1.3.dist-info → maqet-0.0.5.dist-info}/WHEEL +1 -1
- maqet-0.0.5.dist-info/entry_points.txt +2 -0
- maqet-0.0.5.dist-info/licenses/LICENSE +21 -0
- {maqet-0.0.1.3.dist-info → maqet-0.0.5.dist-info}/top_level.txt +0 -1
- maqet/core.py +0 -395
- maqet/functions.py +0 -104
- maqet-0.0.1.3.dist-info/METADATA +0 -104
- maqet-0.0.1.3.dist-info/RECORD +0 -33
- qemu/machine/__init__.py +0 -36
- qemu/machine/console_socket.py +0 -142
- qemu/machine/machine.py +0 -954
- qemu/machine/py.typed +0 -0
- qemu/machine/qtest.py +0 -191
- qemu/qmp/__init__.py +0 -59
- qemu/qmp/error.py +0 -50
- qemu/qmp/events.py +0 -717
- qemu/qmp/legacy.py +0 -319
- qemu/qmp/message.py +0 -209
- qemu/qmp/models.py +0 -146
- qemu/qmp/protocol.py +0 -1057
- qemu/qmp/py.typed +0 -0
- qemu/qmp/qmp_client.py +0 -655
- qemu/qmp/qmp_shell.py +0 -618
- qemu/qmp/qmp_tui.py +0 -655
- qemu/qmp/util.py +0 -219
- qemu/utils/__init__.py +0 -162
- qemu/utils/accel.py +0 -84
- qemu/utils/py.typed +0 -0
- qemu/utils/qemu_ga_client.py +0 -323
- qemu/utils/qom.py +0 -273
- qemu/utils/qom_common.py +0 -175
- qemu/utils/qom_fuse.py +0 -207
maqet/formatters.py
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
"""Output formatters for CLI results.
|
2
|
+
|
3
|
+
This module implements the Strategy pattern for output formatting,
|
4
|
+
replacing the complex if-else ladder in __main__.py with composable
|
5
|
+
formatter classes.
|
6
|
+
|
7
|
+
Each formatter class implements the OutputFormatter interface and
|
8
|
+
handles a specific output format (JSON, YAML, plain text, table).
|
9
|
+
"""
|
10
|
+
|
11
|
+
import json
|
12
|
+
import sys
|
13
|
+
from abc import ABC, abstractmethod
|
14
|
+
from typing import Any
|
15
|
+
|
16
|
+
# Optional dependencies - imported with fallback
|
17
|
+
try:
|
18
|
+
import yaml
|
19
|
+
YAML_AVAILABLE = True
|
20
|
+
except ImportError:
|
21
|
+
YAML_AVAILABLE = False
|
22
|
+
|
23
|
+
try:
|
24
|
+
import tabulate
|
25
|
+
TABULATE_AVAILABLE = True
|
26
|
+
except ImportError:
|
27
|
+
TABULATE_AVAILABLE = False
|
28
|
+
|
29
|
+
|
30
|
+
class OutputFormatter(ABC):
|
31
|
+
"""Base class for output formatters.
|
32
|
+
|
33
|
+
All formatter implementations must provide a format() method
|
34
|
+
that takes a result of any type and prints it to stdout.
|
35
|
+
"""
|
36
|
+
|
37
|
+
@abstractmethod
|
38
|
+
def format(self, result: Any) -> None:
|
39
|
+
"""Format and print the result to stdout.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
result: The data to format and print
|
43
|
+
"""
|
44
|
+
pass
|
45
|
+
|
46
|
+
|
47
|
+
class AutoFormatter(OutputFormatter):
|
48
|
+
"""Auto-detect format based on data type.
|
49
|
+
|
50
|
+
This is the default formatter when no specific format is requested.
|
51
|
+
It inspects the result type and chooses an appropriate representation:
|
52
|
+
- Strings: printed as-is
|
53
|
+
- Dicts: JSON with indentation
|
54
|
+
- Lists: items printed one per line (JSON for complex items)
|
55
|
+
- Other: str() representation
|
56
|
+
"""
|
57
|
+
|
58
|
+
def format(self, result: Any) -> None:
|
59
|
+
"""Format output based on automatic type detection."""
|
60
|
+
if isinstance(result, str):
|
61
|
+
print(result)
|
62
|
+
elif isinstance(result, dict):
|
63
|
+
print(json.dumps(result, indent=2))
|
64
|
+
elif isinstance(result, list):
|
65
|
+
for item in result:
|
66
|
+
if isinstance(item, str):
|
67
|
+
print(item)
|
68
|
+
else:
|
69
|
+
print(json.dumps(item, indent=2))
|
70
|
+
else:
|
71
|
+
print(result)
|
72
|
+
|
73
|
+
|
74
|
+
class JSONFormatter(OutputFormatter):
|
75
|
+
"""JSON output format.
|
76
|
+
|
77
|
+
Outputs results as properly formatted JSON with 2-space indentation.
|
78
|
+
Non-dict/list results are wrapped in a {"result": value} structure.
|
79
|
+
"""
|
80
|
+
|
81
|
+
def format(self, result: Any) -> None:
|
82
|
+
"""Format output as JSON."""
|
83
|
+
if isinstance(result, (dict, list)):
|
84
|
+
print(json.dumps(result, indent=2))
|
85
|
+
else:
|
86
|
+
print(json.dumps({"result": str(result)}, indent=2))
|
87
|
+
|
88
|
+
|
89
|
+
class YAMLFormatter(OutputFormatter):
|
90
|
+
"""YAML output format.
|
91
|
+
|
92
|
+
Requires PyYAML to be installed. Outputs results as YAML with
|
93
|
+
block-style formatting (default_flow_style=False).
|
94
|
+
Non-dict/list results are wrapped in a {"result": value} structure.
|
95
|
+
"""
|
96
|
+
|
97
|
+
def format(self, result: Any) -> None:
|
98
|
+
"""Format output as YAML.
|
99
|
+
|
100
|
+
Raises:
|
101
|
+
SystemExit: If PyYAML is not installed
|
102
|
+
"""
|
103
|
+
if not YAML_AVAILABLE:
|
104
|
+
print("Error: PyYAML not installed. Install with: pip install PyYAML",
|
105
|
+
file=sys.stderr)
|
106
|
+
sys.exit(1)
|
107
|
+
|
108
|
+
if isinstance(result, (dict, list)):
|
109
|
+
print(yaml.dump(result, default_flow_style=False))
|
110
|
+
else:
|
111
|
+
print(yaml.dump({"result": str(result)}, default_flow_style=False))
|
112
|
+
|
113
|
+
|
114
|
+
class PlainFormatter(OutputFormatter):
|
115
|
+
"""Plain text output format.
|
116
|
+
|
117
|
+
Produces human-readable plain text output without JSON/YAML decoration:
|
118
|
+
- Dicts: key: value pairs, one per line
|
119
|
+
- Lists: items, one per line
|
120
|
+
- Other: str() representation
|
121
|
+
"""
|
122
|
+
|
123
|
+
def format(self, result: Any) -> None:
|
124
|
+
"""Format output as plain text."""
|
125
|
+
if isinstance(result, dict):
|
126
|
+
for key, value in result.items():
|
127
|
+
print(f"{key}: {value}")
|
128
|
+
elif isinstance(result, list):
|
129
|
+
for item in result:
|
130
|
+
print(item)
|
131
|
+
else:
|
132
|
+
print(result)
|
133
|
+
|
134
|
+
|
135
|
+
class TableFormatter(OutputFormatter):
|
136
|
+
"""Table format for lists of dicts using tabulate library (with fallback).
|
137
|
+
|
138
|
+
Uses the tabulate library if available for improved table formatting.
|
139
|
+
Falls back to custom formatter if tabulate is not installed.
|
140
|
+
|
141
|
+
Produces ASCII table output with headers and aligned columns.
|
142
|
+
Particularly useful for VM listings and status commands.
|
143
|
+
|
144
|
+
Example output:
|
145
|
+
name | status | pid
|
146
|
+
--------|---------|-----
|
147
|
+
myvm | running | 1234
|
148
|
+
testvm | stopped | None
|
149
|
+
"""
|
150
|
+
|
151
|
+
def __init__(self) -> None:
|
152
|
+
"""Initialize formatter and check for tabulate availability."""
|
153
|
+
self._has_tabulate = TABULATE_AVAILABLE
|
154
|
+
|
155
|
+
def format(self, result: Any) -> None:
|
156
|
+
"""Format output as ASCII table.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
result: List of dicts (or single dict) to format as table
|
160
|
+
"""
|
161
|
+
if isinstance(result, list) and result and isinstance(result[0], dict):
|
162
|
+
self._print_table(result)
|
163
|
+
elif isinstance(result, dict):
|
164
|
+
self._print_table([result])
|
165
|
+
else:
|
166
|
+
print(result)
|
167
|
+
|
168
|
+
def _print_table(self, data: list) -> None:
|
169
|
+
"""Print data as formatted table.
|
170
|
+
|
171
|
+
Uses tabulate library if available, falls back to custom formatter.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
data: List of dictionaries with consistent keys
|
175
|
+
"""
|
176
|
+
if not data:
|
177
|
+
return
|
178
|
+
|
179
|
+
if self._has_tabulate:
|
180
|
+
self._print_table_tabulate(data)
|
181
|
+
else:
|
182
|
+
self._print_table_custom(data)
|
183
|
+
|
184
|
+
def _print_table_tabulate(self, data: list) -> None:
|
185
|
+
"""Print table using tabulate library.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
data: List of dictionaries to format as table
|
189
|
+
"""
|
190
|
+
# Get all keys across all dicts (handles inconsistent keys)
|
191
|
+
all_keys = set()
|
192
|
+
for item in data:
|
193
|
+
all_keys.update(item.keys())
|
194
|
+
headers = sorted(all_keys)
|
195
|
+
|
196
|
+
# Convert to list of lists for tabulate
|
197
|
+
rows = []
|
198
|
+
for item in data:
|
199
|
+
row = [item.get(key, "") for key in headers]
|
200
|
+
rows.append(row)
|
201
|
+
|
202
|
+
# Print with pipe table format (includes | separators)
|
203
|
+
print(tabulate.tabulate(rows, headers=headers, tablefmt="pipe"))
|
204
|
+
|
205
|
+
def _print_table_custom(self, data: list) -> None:
|
206
|
+
"""Print table using custom formatter (fallback).
|
207
|
+
|
208
|
+
Args:
|
209
|
+
data: List of dictionaries with consistent keys
|
210
|
+
"""
|
211
|
+
# Get all keys across all dicts (handles inconsistent keys)
|
212
|
+
all_keys = set()
|
213
|
+
for item in data:
|
214
|
+
all_keys.update(item.keys())
|
215
|
+
headers = sorted(all_keys)
|
216
|
+
|
217
|
+
# Calculate column widths based on both headers and data
|
218
|
+
col_widths = {key: len(key) for key in headers}
|
219
|
+
for item in data:
|
220
|
+
for key in headers:
|
221
|
+
value = str(item.get(key, ""))
|
222
|
+
col_widths[key] = max(col_widths[key], len(value))
|
223
|
+
|
224
|
+
# Print header row
|
225
|
+
header_row = " | ".join(key.ljust(col_widths[key]) for key in headers)
|
226
|
+
print(header_row)
|
227
|
+
print("-" * len(header_row))
|
228
|
+
|
229
|
+
# Print data rows
|
230
|
+
for item in data:
|
231
|
+
row = " | ".join(
|
232
|
+
str(item.get(key, "")).ljust(col_widths[key]) for key in headers
|
233
|
+
)
|
234
|
+
print(row)
|
235
|
+
|
236
|
+
|
237
|
+
class FormatterFactory:
|
238
|
+
"""Factory for creating formatters.
|
239
|
+
|
240
|
+
This class manages the registry of available formatters and
|
241
|
+
provides methods to create formatter instances by name.
|
242
|
+
|
243
|
+
New formatters can be registered at runtime using register().
|
244
|
+
"""
|
245
|
+
|
246
|
+
_formatters = {
|
247
|
+
"auto": AutoFormatter,
|
248
|
+
"json": JSONFormatter,
|
249
|
+
"yaml": YAMLFormatter,
|
250
|
+
"plain": PlainFormatter,
|
251
|
+
"table": TableFormatter,
|
252
|
+
}
|
253
|
+
|
254
|
+
@classmethod
|
255
|
+
def create(cls, format_type: str) -> OutputFormatter:
|
256
|
+
"""Create formatter instance by type name.
|
257
|
+
|
258
|
+
Args:
|
259
|
+
format_type: Name of the formatter to create
|
260
|
+
(auto, json, yaml, plain, table)
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
OutputFormatter instance for the requested type
|
264
|
+
|
265
|
+
Raises:
|
266
|
+
ValueError: If format_type is not registered
|
267
|
+
"""
|
268
|
+
formatter_class = cls._formatters.get(format_type)
|
269
|
+
if not formatter_class:
|
270
|
+
raise ValueError(
|
271
|
+
f"Unknown format '{format_type}'. "
|
272
|
+
f"Valid formats: {', '.join(cls._formatters.keys())}"
|
273
|
+
)
|
274
|
+
return formatter_class()
|
275
|
+
|
276
|
+
@classmethod
|
277
|
+
def register(cls, name: str, formatter_class: type) -> None:
|
278
|
+
"""Register a new formatter type.
|
279
|
+
|
280
|
+
This allows external code to add custom formatters to the factory.
|
281
|
+
|
282
|
+
Args:
|
283
|
+
name: Name to register the formatter under
|
284
|
+
formatter_class: Class implementing OutputFormatter interface
|
285
|
+
|
286
|
+
Example:
|
287
|
+
class XMLFormatter(OutputFormatter):
|
288
|
+
def format(self, result):
|
289
|
+
# Implementation
|
290
|
+
pass
|
291
|
+
|
292
|
+
FormatterFactory.register("xml", XMLFormatter)
|
293
|
+
"""
|
294
|
+
cls._formatters[name] = formatter_class
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
MAQET Generators
|
3
|
+
|
4
|
+
Automatic generation system for CLI commands and Python APIs from
|
5
|
+
decorated methods.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .base_generator import BaseGenerator
|
9
|
+
from .cli_generator import CLIGenerator
|
10
|
+
from .python_generator import PythonAPIGenerator
|
11
|
+
|
12
|
+
__all__ = ["CLIGenerator", "PythonAPIGenerator", "BaseGenerator"]
|
@@ -0,0 +1,101 @@
|
|
1
|
+
"""
|
2
|
+
Base Generator
|
3
|
+
|
4
|
+
Common functionality for all API generators.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import inspect
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Any, Dict, List, Optional, Type
|
10
|
+
|
11
|
+
from ..api import APIMethodMetadata, APIRegistry
|
12
|
+
|
13
|
+
|
14
|
+
class BaseGenerator(ABC):
|
15
|
+
"""
|
16
|
+
Base class for all API generators.
|
17
|
+
|
18
|
+
Provides common functionality for converting method metadata
|
19
|
+
into different interface types (CLI, Python API, etc.).
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, maqet_instance: Any, registry: APIRegistry):
|
23
|
+
"""
|
24
|
+
Initialize generator.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
maqet_instance: Instance of Maqet class
|
28
|
+
registry: API registry containing method metadata
|
29
|
+
"""
|
30
|
+
self.maqet_instance = maqet_instance
|
31
|
+
self.registry = registry
|
32
|
+
|
33
|
+
def get_method_by_name(
|
34
|
+
self, method_name: str
|
35
|
+
) -> Optional[APIMethodMetadata]:
|
36
|
+
"""
|
37
|
+
Get method metadata by name.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
method_name: Method name to look up
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
Method metadata or None if not found
|
44
|
+
"""
|
45
|
+
full_name = f"{self.maqet_instance.__class__.__name__}.{method_name}"
|
46
|
+
return self.registry.get_method(full_name)
|
47
|
+
|
48
|
+
def convert_parameter_value(
|
49
|
+
self, param: inspect.Parameter, value: str
|
50
|
+
) -> Any:
|
51
|
+
"""
|
52
|
+
Convert string value to appropriate type based on parameter annotation.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
param: Parameter metadata
|
56
|
+
value: String value to convert
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Converted value
|
60
|
+
"""
|
61
|
+
if param.annotation == bool or param.annotation == "bool":
|
62
|
+
return value.lower() in ("true", "1", "yes", "on")
|
63
|
+
elif param.annotation == int or param.annotation == "int":
|
64
|
+
return int(value)
|
65
|
+
elif param.annotation == float or param.annotation == "float":
|
66
|
+
return float(value)
|
67
|
+
elif param.annotation == list or param.annotation == "list":
|
68
|
+
# Handle comma-separated lists
|
69
|
+
return [item.strip() for item in value.split(",")]
|
70
|
+
else:
|
71
|
+
# Default to string
|
72
|
+
return value
|
73
|
+
|
74
|
+
def validate_required_parameters(
|
75
|
+
self, metadata: APIMethodMetadata, provided_params: Dict[str, Any]
|
76
|
+
) -> List[str]:
|
77
|
+
"""
|
78
|
+
Validate that all required parameters are provided.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
metadata: Method metadata
|
82
|
+
provided_params: Parameters provided by user
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
List of missing parameter names
|
86
|
+
"""
|
87
|
+
missing = []
|
88
|
+
for param_name in metadata.required_parameters:
|
89
|
+
if param_name not in provided_params:
|
90
|
+
missing.append(param_name)
|
91
|
+
return missing
|
92
|
+
|
93
|
+
@abstractmethod
|
94
|
+
def generate(self) -> Any:
|
95
|
+
"""
|
96
|
+
Generate the interface (CLI parser, Python API, etc.).
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Generated interface object
|
100
|
+
"""
|
101
|
+
pass
|