maqet 0.0.1.4__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.4.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.4.dist-info → maqet-0.0.5.dist-info}/top_level.txt +0 -1
- maqet/core.py +0 -411
- maqet/functions.py +0 -104
- maqet-0.0.1.4.dist-info/METADATA +0 -6
- maqet-0.0.1.4.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
@@ -0,0 +1,247 @@
|
|
1
|
+
"""
|
2
|
+
Python API Generator
|
3
|
+
|
4
|
+
Provides clean Python API access to decorated methods.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import inspect
|
8
|
+
from typing import Any, Dict, Optional
|
9
|
+
|
10
|
+
from ..api import APIMethodMetadata, APIRegistry
|
11
|
+
from .base_generator import BaseGenerator
|
12
|
+
|
13
|
+
|
14
|
+
class PythonAPIGenerator(BaseGenerator):
|
15
|
+
"""
|
16
|
+
Provides programmatic access to @api_method decorated methods.
|
17
|
+
|
18
|
+
This generator enables clean Python API usage where methods can be
|
19
|
+
called directly with proper validation and error handling.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, maqet_instance: Any, registry: APIRegistry):
|
23
|
+
"""
|
24
|
+
Initialize Python API generator.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
maqet_instance: Instance of Maqet class
|
28
|
+
registry: API registry containing method metadata
|
29
|
+
"""
|
30
|
+
super().__init__(maqet_instance, registry)
|
31
|
+
|
32
|
+
def generate(self) -> "PythonAPIInterface":
|
33
|
+
"""
|
34
|
+
Generate Python API interface.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Python API interface object
|
38
|
+
"""
|
39
|
+
return PythonAPIInterface(self.maqet_instance, self.registry)
|
40
|
+
|
41
|
+
def execute_method(self, method_name: str, *args, **kwargs) -> Any:
|
42
|
+
"""
|
43
|
+
Execute a method by name with validation.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
method_name: Name of method to execute
|
47
|
+
*args: Positional arguments
|
48
|
+
**kwargs: Keyword arguments
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Method execution result
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
ValueError: If method not found or invalid parameters
|
55
|
+
TypeError: If parameter types are incorrect
|
56
|
+
"""
|
57
|
+
# Get method metadata
|
58
|
+
metadata = self.get_method_by_name(method_name)
|
59
|
+
if not metadata:
|
60
|
+
raise ValueError(f"Method '{method_name}' not found")
|
61
|
+
|
62
|
+
# Convert positional args to kwargs for validation
|
63
|
+
combined_kwargs = self._combine_args_kwargs(metadata, args, kwargs)
|
64
|
+
|
65
|
+
# Validate parameters
|
66
|
+
self._validate_parameters(metadata, combined_kwargs)
|
67
|
+
|
68
|
+
# Get the actual method from the instance
|
69
|
+
method = getattr(self.maqet_instance, method_name)
|
70
|
+
|
71
|
+
# Execute method with original args and kwargs
|
72
|
+
return method(*args, **kwargs)
|
73
|
+
|
74
|
+
def _validate_parameters(
|
75
|
+
self, metadata: APIMethodMetadata, kwargs: Dict[str, Any]
|
76
|
+
) -> None:
|
77
|
+
"""
|
78
|
+
Validate method parameters.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
metadata: Method metadata
|
82
|
+
kwargs: Parameters provided by user
|
83
|
+
|
84
|
+
Raises:
|
85
|
+
ValueError: If required parameters missing or unknown parameters provided
|
86
|
+
"""
|
87
|
+
# Check for missing required parameters
|
88
|
+
missing = self.validate_required_parameters(metadata, kwargs)
|
89
|
+
if missing:
|
90
|
+
raise ValueError(
|
91
|
+
f"Missing required parameters: {', '.join(missing)}"
|
92
|
+
)
|
93
|
+
|
94
|
+
# Check for unknown parameters only if there's no **kwargs parameter
|
95
|
+
has_var_keyword = any(
|
96
|
+
param.kind == inspect.Parameter.VAR_KEYWORD
|
97
|
+
for param in metadata.parameters.values()
|
98
|
+
)
|
99
|
+
|
100
|
+
if not has_var_keyword:
|
101
|
+
valid_params = set(metadata.parameters.keys())
|
102
|
+
provided_params = set(kwargs.keys())
|
103
|
+
unknown = provided_params - valid_params
|
104
|
+
if unknown:
|
105
|
+
raise ValueError(f"Unknown parameters: {', '.join(unknown)}")
|
106
|
+
|
107
|
+
# Type validation could be added here if needed
|
108
|
+
|
109
|
+
def _combine_args_kwargs(
|
110
|
+
self, metadata: APIMethodMetadata, args: tuple, kwargs: Dict[str, Any]
|
111
|
+
) -> Dict[str, Any]:
|
112
|
+
"""
|
113
|
+
Combine positional and keyword arguments into a single kwargs dict for validation.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
metadata: Method metadata
|
117
|
+
args: Positional arguments
|
118
|
+
kwargs: Keyword arguments
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
Combined arguments as kwargs dict
|
122
|
+
|
123
|
+
Raises:
|
124
|
+
ValueError: If too many positional arguments provided
|
125
|
+
"""
|
126
|
+
combined = kwargs.copy()
|
127
|
+
|
128
|
+
# Get parameter names in order (excluding 'self')
|
129
|
+
param_names = list(metadata.parameters.keys())
|
130
|
+
|
131
|
+
# Map positional args to parameter names
|
132
|
+
if len(args) > len(param_names):
|
133
|
+
raise ValueError(
|
134
|
+
f"Too many positional arguments: expected {len(param_names)}, got {len(args)}"
|
135
|
+
)
|
136
|
+
|
137
|
+
for i, arg_value in enumerate(args):
|
138
|
+
param_name = param_names[i]
|
139
|
+
if param_name in combined:
|
140
|
+
raise ValueError(
|
141
|
+
f"Parameter '{param_name}' specified both positionally and as keyword argument"
|
142
|
+
)
|
143
|
+
combined[param_name] = arg_value
|
144
|
+
|
145
|
+
return combined
|
146
|
+
|
147
|
+
|
148
|
+
class PythonAPIInterface:
|
149
|
+
"""
|
150
|
+
Dynamic Python API interface that provides direct method access.
|
151
|
+
|
152
|
+
This class dynamically creates methods based on registered API methods,
|
153
|
+
allowing for clean Python usage like:
|
154
|
+
|
155
|
+
api = PythonAPIInterface(maqet_instance, registry)
|
156
|
+
api.start("myvm", detach=True)
|
157
|
+
api.stop("myvm")
|
158
|
+
"""
|
159
|
+
|
160
|
+
def __init__(self, maqet_instance: Any, registry: APIRegistry):
|
161
|
+
"""
|
162
|
+
Initialize Python API interface.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
maqet_instance: Instance of Maqet class
|
166
|
+
registry: API registry containing method metadata
|
167
|
+
"""
|
168
|
+
self.maqet_instance = maqet_instance
|
169
|
+
self.registry = registry
|
170
|
+
self.generator = PythonAPIGenerator(maqet_instance, registry)
|
171
|
+
|
172
|
+
def __getattr__(self, name: str) -> Any:
|
173
|
+
"""
|
174
|
+
Dynamically provide access to API methods.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
name: Method name
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
Callable method
|
181
|
+
|
182
|
+
Raises:
|
183
|
+
AttributeError: If method not found
|
184
|
+
"""
|
185
|
+
# Check if this is a registered API method
|
186
|
+
metadata = self.generator.get_method_by_name(name)
|
187
|
+
if metadata:
|
188
|
+
return lambda *args, **kwargs: self.generator.execute_method(
|
189
|
+
name, *args, **kwargs
|
190
|
+
)
|
191
|
+
|
192
|
+
# Check if it's a direct attribute on the instance
|
193
|
+
if hasattr(self.maqet_instance, name):
|
194
|
+
return getattr(self.maqet_instance, name)
|
195
|
+
|
196
|
+
raise AttributeError(
|
197
|
+
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
198
|
+
)
|
199
|
+
|
200
|
+
def list_methods(self) -> Dict[str, str]:
|
201
|
+
"""
|
202
|
+
List all available API methods.
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Dictionary mapping method names to descriptions
|
206
|
+
"""
|
207
|
+
methods = {}
|
208
|
+
for metadata in self.registry.get_all_methods():
|
209
|
+
if metadata.owner_class == self.maqet_instance.__class__.__name__:
|
210
|
+
methods[metadata.name] = metadata.description
|
211
|
+
return methods
|
212
|
+
|
213
|
+
def get_method_help(self, method_name: str) -> Optional[str]:
|
214
|
+
"""
|
215
|
+
Get help text for a method.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
method_name: Method name
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
Help text or None if method not found
|
222
|
+
"""
|
223
|
+
metadata = self.generator.get_method_by_name(method_name)
|
224
|
+
if not metadata:
|
225
|
+
return None
|
226
|
+
|
227
|
+
help_text = f"{metadata.name}: {metadata.description}\n\n"
|
228
|
+
|
229
|
+
# Add parameters
|
230
|
+
if metadata.parameters:
|
231
|
+
help_text += "Parameters:\n"
|
232
|
+
for param_name, param in metadata.parameters.items():
|
233
|
+
required = param.default == param.empty
|
234
|
+
default_text = (
|
235
|
+
f" (default: {param.default})"
|
236
|
+
if not required
|
237
|
+
else " (required)"
|
238
|
+
)
|
239
|
+
help_text += f" {param_name}{default_text}\n"
|
240
|
+
|
241
|
+
# Add examples
|
242
|
+
if metadata.examples:
|
243
|
+
help_text += "\nExamples:\n"
|
244
|
+
for example in metadata.examples:
|
245
|
+
help_text += f" {example}\n"
|
246
|
+
|
247
|
+
return help_text.strip()
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
REST API Generator (Placeholder)
|
3
|
+
|
4
|
+
This is a placeholder showing how the API system can be extended with new generators.
|
5
|
+
A REST API generator could be implemented here using FastAPI, Flask, or similar.
|
6
|
+
|
7
|
+
Example implementation would:
|
8
|
+
1. Inherit from BaseGenerator
|
9
|
+
2. Read method metadata from API_REGISTRY
|
10
|
+
3. Generate REST routes with proper HTTP methods:
|
11
|
+
- GET /api/vms/{vm_id}/status -> status(vm_id)
|
12
|
+
- POST /api/vms -> add(config, name)
|
13
|
+
- DELETE /api/vms/{vm_id} -> rm(vm_id)
|
14
|
+
4. Handle request/response serialization
|
15
|
+
5. Add authentication/authorization if needed
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
from maqet.generators import RestAPIGenerator
|
19
|
+
generator = RestAPIGenerator(maqet_instance, API_REGISTRY)
|
20
|
+
app = generator.generate() # Returns FastAPI/Flask app
|
21
|
+
"""
|
22
|
+
|
23
|
+
from ..api import APIRegistry
|
24
|
+
from .base_generator import BaseGenerator
|
25
|
+
|
26
|
+
|
27
|
+
class RestAPIGenerator(BaseGenerator):
|
28
|
+
"""
|
29
|
+
Placeholder REST API generator demonstrating extensibility.
|
30
|
+
|
31
|
+
A real implementation would generate REST endpoints from @api_method
|
32
|
+
decorated methods, handling routing, serialization, and validation.
|
33
|
+
"""
|
34
|
+
|
35
|
+
def generate(self):
|
36
|
+
"""
|
37
|
+
Generate REST API routes.
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
Web application instance (FastAPI, Flask, etc.)
|
41
|
+
"""
|
42
|
+
raise NotImplementedError(
|
43
|
+
"REST API generation not yet implemented. "
|
44
|
+
"This is a placeholder demonstrating the extensible architecture."
|
45
|
+
)
|
46
|
+
|
47
|
+
def run(self, host: str = "0.0.0.0", port: int = 8000):
|
48
|
+
"""
|
49
|
+
Run the REST API server.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
host: Server host
|
53
|
+
port: Server port
|
54
|
+
"""
|
55
|
+
raise NotImplementedError(
|
56
|
+
"REST API server not yet implemented. "
|
57
|
+
"This would start a web server with generated routes."
|
58
|
+
)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
MAQET handlers package.
|
3
|
+
|
4
|
+
Contains base handler classes and specific handlers for configuration
|
5
|
+
initialization and stage execution.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .base import Handler
|
9
|
+
from .init import InitHandler
|
10
|
+
from .stage import StageHandler
|
11
|
+
|
12
|
+
__all__ = ["Handler", "InitHandler", "StageHandler"]
|
maqet/handlers/base.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
import inspect
|
2
|
+
from abc import ABC
|
3
|
+
from typing import Callable
|
4
|
+
|
5
|
+
from benedict import benedict
|
6
|
+
|
7
|
+
from maqet.logger import LOG
|
8
|
+
|
9
|
+
|
10
|
+
class HandlerError(Exception):
|
11
|
+
"""
|
12
|
+
Handler error
|
13
|
+
"""
|
14
|
+
|
15
|
+
|
16
|
+
class Handler(ABC):
|
17
|
+
"""
|
18
|
+
Interface for Maqet state processors
|
19
|
+
"""
|
20
|
+
__METHODS = {}
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def method(self, function: Callable, **kwargs):
|
24
|
+
"""
|
25
|
+
Decorator to add method to handler methods
|
26
|
+
"""
|
27
|
+
name = kwargs.get('name', function.__name__)
|
28
|
+
handler_name = self.__name__
|
29
|
+
if handler_name not in self.__METHODS:
|
30
|
+
self.__METHODS[handler_name] = {}
|
31
|
+
|
32
|
+
self.__METHODS[handler_name][name] = function
|
33
|
+
|
34
|
+
# TODO: add signature check:
|
35
|
+
# method(state: dict, *args, **kwargs)
|
36
|
+
|
37
|
+
def stub(*args, **kwargs):
|
38
|
+
raise HandlerError("Handler method called outside of handler")
|
39
|
+
|
40
|
+
return stub
|
41
|
+
|
42
|
+
def __init__(self, state: dict,
|
43
|
+
argument: list | dict | str,
|
44
|
+
*args, **kwargs):
|
45
|
+
|
46
|
+
self.state = benedict(state)
|
47
|
+
self.error_fatal = kwargs.get('error_fatal', False)
|
48
|
+
|
49
|
+
self.__execute(argument)
|
50
|
+
|
51
|
+
def __execute(self, argument: list | dict | str):
|
52
|
+
if isinstance(argument, list):
|
53
|
+
LOG.debug(f"Argument {argument} - splitting into subarguments")
|
54
|
+
for subargument in argument:
|
55
|
+
self.__execute(subargument)
|
56
|
+
elif isinstance(argument, dict):
|
57
|
+
LOG.debug(f"Argument {argument} - running by key-value")
|
58
|
+
for method_name, subargument in argument.items():
|
59
|
+
self.__call_method(method_name, subargument)
|
60
|
+
elif isinstance(argument, str):
|
61
|
+
LOG.debug(f"Argument {argument} - running without argument")
|
62
|
+
self.__call_method(argument, None)
|
63
|
+
else:
|
64
|
+
self.__fail("Type check error"
|
65
|
+
f" {argument} is not list | dict | str")
|
66
|
+
|
67
|
+
@classmethod
|
68
|
+
def method_exists(self, method_name: str) -> bool:
|
69
|
+
if method_name not in self.__METHODS[self.__name__]:
|
70
|
+
LOG.debug(f"{self.__name__}::{method_name} not exists")
|
71
|
+
return False
|
72
|
+
LOG.debug(f"{self.__name__}::{method_name} exists")
|
73
|
+
return True
|
74
|
+
|
75
|
+
@classmethod
|
76
|
+
def get_methods(self) -> list:
|
77
|
+
return self.__METHODS[self.__name__].keys()
|
78
|
+
|
79
|
+
def __call_method(self,
|
80
|
+
method_name: str,
|
81
|
+
argument: list | dict | str = None):
|
82
|
+
|
83
|
+
if not self.method_exists(method_name):
|
84
|
+
self.__fail(f"Method '{method_name}' not available"
|
85
|
+
f" in {self.__class__.__name__}")
|
86
|
+
method = self.__METHODS[self.__class__.__name__].get(method_name)
|
87
|
+
|
88
|
+
LOG.debug(f"Inspecting signature for {method_name}: {inspect.signature(method)}")
|
89
|
+
LOG.debug(f"{self.__class__.__name__}::"
|
90
|
+
f"{method.__name__}({str(argument)})")
|
91
|
+
try:
|
92
|
+
if isinstance(argument, list):
|
93
|
+
method(self.state, *argument)
|
94
|
+
elif isinstance(argument, dict):
|
95
|
+
method(self.state, **argument)
|
96
|
+
elif argument is None:
|
97
|
+
method(self.state)
|
98
|
+
else:
|
99
|
+
method(self.state, argument)
|
100
|
+
except Exception as exc:
|
101
|
+
msg = f"{method_name}({argument}) execution error\n{exc}\n"
|
102
|
+
self.__fail(msg)
|
103
|
+
|
104
|
+
def __fail(self, msg: str):
|
105
|
+
if self.error_fatal:
|
106
|
+
raise HandlerError(msg)
|
107
|
+
else:
|
108
|
+
LOG.error(msg)
|
maqet/handlers/init.py
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
from benedict import benedict
|
2
|
+
|
3
|
+
from maqet.handlers.base import Handler
|
4
|
+
# from maqet.handlers.stage import PipelineHandler as run_pipeline
|
5
|
+
from maqet.handlers.stage import StageHandler
|
6
|
+
from maqet.logger import LOG
|
7
|
+
from maqet.qemu_args import Arguments
|
8
|
+
# Legacy import - Drive class no longer exists in current storage.py
|
9
|
+
# from maqet.storage import Drive
|
10
|
+
|
11
|
+
|
12
|
+
class InitHandler(Handler):
|
13
|
+
"""
|
14
|
+
Handles full config of maqet (basically yaml)
|
15
|
+
"""
|
16
|
+
|
17
|
+
|
18
|
+
@InitHandler.method
|
19
|
+
def binary(state, binary: str):
|
20
|
+
LOG.debug(f'Setting binary: {binary}')
|
21
|
+
state.binary = binary
|
22
|
+
|
23
|
+
|
24
|
+
@InitHandler.method
|
25
|
+
def arguments(state, *args):
|
26
|
+
LOG.debug(f'Setting arguments: {args}')
|
27
|
+
state['const_args'] = Arguments.parse_args(*args)
|
28
|
+
|
29
|
+
|
30
|
+
@InitHandler.method
|
31
|
+
def plain_arguments(state, *args):
|
32
|
+
LOG.debug(f'Setting plain arguments: {args}')
|
33
|
+
if 'const_args' not in state:
|
34
|
+
state['const_args'] = []
|
35
|
+
state['const_args'] += Arguments.split_args(*args)
|
36
|
+
|
37
|
+
|
38
|
+
@InitHandler.method
|
39
|
+
def storage(state, *args):
|
40
|
+
if 'storage' not in state:
|
41
|
+
state.storage = {}
|
42
|
+
|
43
|
+
if 'const_args' not in state:
|
44
|
+
state.const_args = []
|
45
|
+
|
46
|
+
for drive in args:
|
47
|
+
n = 0
|
48
|
+
name = drive.get('name', f"drive{n}")
|
49
|
+
while name in state.storage:
|
50
|
+
n += 1
|
51
|
+
name = f"drive{n}"
|
52
|
+
|
53
|
+
# Legacy code - Drive class no longer exists
|
54
|
+
# state.storage[name] = Drive(**drive)
|
55
|
+
state.storage[name] = drive # Store config dict for now
|
56
|
+
|
57
|
+
# Legacy code - commented out as Drive class no longer exists
|
58
|
+
# for name, drive in state.storage.items():
|
59
|
+
# state.const_args += drive()
|
60
|
+
|
61
|
+
|
62
|
+
@InitHandler.method
|
63
|
+
def parameters(state, **kwargs):
|
64
|
+
LOG.debug(f'Setting parameters: {kwargs}')
|
65
|
+
if len(kwargs) == 0:
|
66
|
+
return
|
67
|
+
state.parameters = kwargs
|
68
|
+
|
69
|
+
|
70
|
+
@InitHandler.method
|
71
|
+
def serial(state, *args):
|
72
|
+
LOG.debug(f'Setting serial: {args}')
|
73
|
+
state.serial = args
|
74
|
+
|
75
|
+
|
76
|
+
@InitHandler.method
|
77
|
+
def pipeline(state, **kwargs):
|
78
|
+
default_stage = {'idle': {'tasks': [
|
79
|
+
'launch',
|
80
|
+
{'wait_for_input': {'prompt': 'Press ENTER to finish'}},
|
81
|
+
]}}
|
82
|
+
|
83
|
+
state.pipeline = []
|
84
|
+
state.procedures = []
|
85
|
+
|
86
|
+
stages = kwargs.get('stages', {})
|
87
|
+
|
88
|
+
if len(state._stages_to_run) == 0:
|
89
|
+
state.pipeline.append(default_stage)
|
90
|
+
LOG.info("Stage idle (default) added to current pipeline")
|
91
|
+
return
|
92
|
+
|
93
|
+
# pre_pipeline_tasks = kwargs.get('pre_pipeline_tasks', [])
|
94
|
+
# post_pipeline_tasks = kwargs.get('post_pipeline_tasks', [])
|
95
|
+
# pre_stage_tasks = kwargs.get('pre_stage_tasks', [])
|
96
|
+
# post_stage_tasks = kwargs.get('post_stage_tasks', [])
|
97
|
+
|
98
|
+
procedures = kwargs.get('procedures', {})
|
99
|
+
state.procedures.append(procedures)
|
100
|
+
# data._stages_to_run += ['_pre_pipeline_tasks', '_post_pipeline_tasks']
|
101
|
+
|
102
|
+
for name, stage in stages.items():
|
103
|
+
current_stage = stage
|
104
|
+
current_tasks = []
|
105
|
+
|
106
|
+
# stage.tasks = pre_stage_tasks + stage.tasks + post_stage_tasks
|
107
|
+
|
108
|
+
if name not in state._stages_to_run:
|
109
|
+
continue
|
110
|
+
if 'tasks' not in stage or len(stage['tasks']) == 0:
|
111
|
+
LOG.warn(f'Stage {name} incorrect, no tasks found. Skipping')
|
112
|
+
continue
|
113
|
+
|
114
|
+
# TODO: procedure that uses another procedure
|
115
|
+
for task in stage['tasks']:
|
116
|
+
if isinstance(task, dict):
|
117
|
+
task_name = next(iter(task))
|
118
|
+
if task_name == 'procedure':
|
119
|
+
if task['procedure'] in procedures:
|
120
|
+
current_tasks += procedures[task['procedure']]
|
121
|
+
LOG.debug(f"Procedure {task['procedure']}"
|
122
|
+
" added to stage")
|
123
|
+
else:
|
124
|
+
raise Exception(f"Procedure {task['procedure']}"
|
125
|
+
" not found in procedures")
|
126
|
+
else:
|
127
|
+
current_tasks.append(task)
|
128
|
+
LOG.debug(f"Task {task_name} added to stage")
|
129
|
+
else:
|
130
|
+
current_tasks.append(task)
|
131
|
+
LOG.debug(f"Task {task} added to stage")
|
132
|
+
|
133
|
+
for task in current_tasks:
|
134
|
+
if isinstance(task, dict):
|
135
|
+
task = next(iter(task))
|
136
|
+
if not StageHandler.method_exists(task):
|
137
|
+
LOG.error(f"Task {task} not validated")
|
138
|
+
raise Exception(f"Task {task} is invalid")
|
139
|
+
LOG.debug(f"Task {task} validated")
|
140
|
+
|
141
|
+
current_stage['tasks'] = current_tasks
|
142
|
+
current_stage['arguments'] = Arguments.parse_args(
|
143
|
+
*stage.get('arguments', [])
|
144
|
+
) + stage.get('plain_arguments', [])
|
145
|
+
|
146
|
+
state.pipeline.append({name: current_stage})
|
147
|
+
LOG.info(f"Stage {name} added to current pipeline")
|