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.
Files changed (83) hide show
  1. maqet/__init__.py +50 -6
  2. maqet/__main__.py +96 -0
  3. maqet/__version__.py +3 -0
  4. maqet/api/__init__.py +35 -0
  5. maqet/api/decorators.py +184 -0
  6. maqet/api/metadata.py +147 -0
  7. maqet/api/registry.py +182 -0
  8. maqet/cli.py +71 -0
  9. maqet/config/__init__.py +26 -0
  10. maqet/config/merger.py +237 -0
  11. maqet/config/parser.py +198 -0
  12. maqet/config/validators.py +519 -0
  13. maqet/config_handlers.py +684 -0
  14. maqet/constants.py +200 -0
  15. maqet/exceptions.py +226 -0
  16. maqet/formatters.py +294 -0
  17. maqet/generators/__init__.py +12 -0
  18. maqet/generators/base_generator.py +101 -0
  19. maqet/generators/cli_generator.py +635 -0
  20. maqet/generators/python_generator.py +247 -0
  21. maqet/generators/rest_generator.py +58 -0
  22. maqet/handlers/__init__.py +12 -0
  23. maqet/handlers/base.py +108 -0
  24. maqet/handlers/init.py +147 -0
  25. maqet/handlers/stage.py +196 -0
  26. maqet/ipc/__init__.py +29 -0
  27. maqet/ipc/retry.py +265 -0
  28. maqet/ipc/runner_client.py +285 -0
  29. maqet/ipc/unix_socket_server.py +239 -0
  30. maqet/logger.py +160 -55
  31. maqet/machine.py +884 -0
  32. maqet/managers/__init__.py +7 -0
  33. maqet/managers/qmp_manager.py +333 -0
  34. maqet/managers/snapshot_coordinator.py +327 -0
  35. maqet/managers/vm_manager.py +683 -0
  36. maqet/maqet.py +1120 -0
  37. maqet/os_interactions.py +46 -0
  38. maqet/process_spawner.py +395 -0
  39. maqet/qemu_args.py +76 -0
  40. maqet/qmp/__init__.py +10 -0
  41. maqet/qmp/commands.py +92 -0
  42. maqet/qmp/keyboard.py +311 -0
  43. maqet/qmp/qmp.py +17 -0
  44. maqet/snapshot.py +473 -0
  45. maqet/state.py +958 -0
  46. maqet/storage.py +702 -162
  47. maqet/validation/__init__.py +9 -0
  48. maqet/validation/config_validator.py +170 -0
  49. maqet/vm_runner.py +523 -0
  50. maqet-0.0.5.dist-info/METADATA +237 -0
  51. maqet-0.0.5.dist-info/RECORD +55 -0
  52. {maqet-0.0.1.3.dist-info → maqet-0.0.5.dist-info}/WHEEL +1 -1
  53. maqet-0.0.5.dist-info/entry_points.txt +2 -0
  54. maqet-0.0.5.dist-info/licenses/LICENSE +21 -0
  55. {maqet-0.0.1.3.dist-info → maqet-0.0.5.dist-info}/top_level.txt +0 -1
  56. maqet/core.py +0 -395
  57. maqet/functions.py +0 -104
  58. maqet-0.0.1.3.dist-info/METADATA +0 -104
  59. maqet-0.0.1.3.dist-info/RECORD +0 -33
  60. qemu/machine/__init__.py +0 -36
  61. qemu/machine/console_socket.py +0 -142
  62. qemu/machine/machine.py +0 -954
  63. qemu/machine/py.typed +0 -0
  64. qemu/machine/qtest.py +0 -191
  65. qemu/qmp/__init__.py +0 -59
  66. qemu/qmp/error.py +0 -50
  67. qemu/qmp/events.py +0 -717
  68. qemu/qmp/legacy.py +0 -319
  69. qemu/qmp/message.py +0 -209
  70. qemu/qmp/models.py +0 -146
  71. qemu/qmp/protocol.py +0 -1057
  72. qemu/qmp/py.typed +0 -0
  73. qemu/qmp/qmp_client.py +0 -655
  74. qemu/qmp/qmp_shell.py +0 -618
  75. qemu/qmp/qmp_tui.py +0 -655
  76. qemu/qmp/util.py +0 -219
  77. qemu/utils/__init__.py +0 -162
  78. qemu/utils/accel.py +0 -84
  79. qemu/utils/py.typed +0 -0
  80. qemu/utils/qemu_ga_client.py +0 -323
  81. qemu/utils/qom.py +0 -273
  82. qemu/utils/qom_common.py +0 -175
  83. 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