fprime-gds 3.6.1__py3-none-any.whl → 4.0.0__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.
- fprime_gds/common/communication/adapters/ip.py +14 -9
- fprime_gds/common/communication/adapters/uart.py +34 -25
- fprime_gds/common/communication/ccsds/__init__.py +0 -0
- fprime_gds/common/communication/ccsds/apid.py +19 -0
- fprime_gds/common/communication/ccsds/chain.py +106 -0
- fprime_gds/common/communication/ccsds/space_data_link.py +196 -0
- fprime_gds/common/communication/ccsds/space_packet.py +129 -0
- fprime_gds/common/communication/framing.py +27 -32
- fprime_gds/common/decoders/ch_decoder.py +1 -1
- fprime_gds/common/decoders/event_decoder.py +9 -2
- fprime_gds/common/decoders/pkt_decoder.py +1 -1
- fprime_gds/common/distributor/distributor.py +6 -3
- fprime_gds/common/encoders/ch_encoder.py +2 -2
- fprime_gds/common/encoders/cmd_encoder.py +2 -2
- fprime_gds/common/encoders/event_encoder.py +2 -2
- fprime_gds/common/encoders/pkt_encoder.py +2 -2
- fprime_gds/common/encoders/seq_writer.py +2 -2
- fprime_gds/common/fpy/README.md +56 -0
- fprime_gds/common/fpy/SPEC.md +69 -0
- fprime_gds/common/fpy/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/directives.py +490 -0
- fprime_gds/common/fpy/codegen.py +1687 -0
- fprime_gds/common/fpy/grammar.lark +88 -0
- fprime_gds/common/fpy/main.py +40 -0
- fprime_gds/common/fpy/parser.py +239 -0
- fprime_gds/common/gds_cli/base_commands.py +1 -1
- fprime_gds/common/handlers.py +39 -0
- fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
- fprime_gds/common/loaders/json_loader.py +15 -0
- fprime_gds/common/loaders/pkt_json_loader.py +125 -0
- fprime_gds/common/loaders/prm_json_loader.py +85 -0
- fprime_gds/common/logger/__init__.py +2 -2
- fprime_gds/common/pipeline/dictionaries.py +60 -41
- fprime_gds/common/pipeline/encoding.py +19 -0
- fprime_gds/common/pipeline/histories.py +4 -0
- fprime_gds/common/pipeline/standard.py +16 -2
- fprime_gds/common/templates/cmd_template.py +8 -0
- fprime_gds/common/templates/prm_template.py +81 -0
- fprime_gds/common/testing_fw/api.py +148 -1
- fprime_gds/common/testing_fw/pytest_integration.py +37 -3
- fprime_gds/common/tools/README.md +34 -0
- fprime_gds/common/tools/params.py +246 -0
- fprime_gds/common/utils/config_manager.py +6 -6
- fprime_gds/common/utils/data_desc_type.py +6 -1
- fprime_gds/executables/apps.py +189 -11
- fprime_gds/executables/cli.py +468 -127
- fprime_gds/executables/comm.py +5 -2
- fprime_gds/executables/data_product_writer.py +164 -165
- fprime_gds/executables/fprime_cli.py +3 -3
- fprime_gds/executables/run_deployment.py +13 -5
- fprime_gds/flask/app.py +3 -0
- fprime_gds/flask/resource.py +5 -2
- fprime_gds/flask/static/addons/chart-display/addon.js +8 -3
- fprime_gds/flask/static/js/datastore.js +1 -0
- fprime_gds/flask/static/js/vue-support/channel.js +1 -1
- fprime_gds/flask/static/js/vue-support/event.js +1 -1
- fprime_gds/plugin/definitions.py +86 -8
- fprime_gds/plugin/system.py +172 -58
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/METADATA +23 -21
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/RECORD +66 -50
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/entry_points.txt +2 -0
- fprime_gds/common/loaders/ch_py_loader.py +0 -79
- fprime_gds/common/loaders/cmd_py_loader.py +0 -66
- fprime_gds/common/loaders/event_py_loader.py +0 -75
- fprime_gds/common/loaders/python_loader.py +0 -132
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.1.dist-info → fprime_gds-4.0.0.dist-info}/top_level.txt +0 -0
fprime_gds/executables/cli.py
CHANGED
@@ -11,12 +11,17 @@ code that they are importing.
|
|
11
11
|
import argparse
|
12
12
|
import datetime
|
13
13
|
import errno
|
14
|
+
import functools
|
14
15
|
import getpass
|
16
|
+
import inspect
|
15
17
|
import itertools
|
16
18
|
import os
|
17
19
|
import platform
|
18
20
|
import re
|
19
21
|
import sys
|
22
|
+
|
23
|
+
import yaml
|
24
|
+
|
20
25
|
from abc import ABC, abstractmethod
|
21
26
|
from pathlib import Path
|
22
27
|
from typing import Any, Dict, List, Tuple
|
@@ -30,7 +35,7 @@ from fprime_gds.common.transport import ThreadedTCPSocketClient
|
|
30
35
|
from fprime_gds.common.utils.config_manager import ConfigManager
|
31
36
|
from fprime_gds.executables.utils import find_app, find_dict, get_artifacts_root
|
32
37
|
from fprime_gds.plugin.definitions import PluginType
|
33
|
-
from fprime_gds.plugin.system import Plugins
|
38
|
+
from fprime_gds.plugin.system import Plugins, PluginsNotLoadedException
|
34
39
|
from fprime_gds.common.zmq_transport import ZmqClient
|
35
40
|
|
36
41
|
|
@@ -81,22 +86,51 @@ class ParserBase(ABC):
|
|
81
86
|
self.fill_parser(parser)
|
82
87
|
return parser
|
83
88
|
|
89
|
+
@staticmethod
|
90
|
+
def safe_add_argument(parser, *flags, **keywords):
|
91
|
+
"""Add an argument allowing duplicates
|
92
|
+
|
93
|
+
Add arguments to the parser (passes through *flags and **keywords) to the supplied parser. This method traps
|
94
|
+
errors to prevent duplicates from crashing the system when two plugins use the same flags.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
parser: parser or argument group to add arguments to
|
98
|
+
*flags: positional arguments passed to `add_argument`
|
99
|
+
**keywords: key word arguments passed to `add_argument`
|
100
|
+
"""
|
101
|
+
try:
|
102
|
+
parser.add_argument(*flags, **keywords)
|
103
|
+
except argparse.ArgumentError:
|
104
|
+
# flag has already been added, pass
|
105
|
+
pass
|
106
|
+
|
107
|
+
@classmethod
|
108
|
+
def add_arguments_from_specification(cls, parser, arguments):
|
109
|
+
"""Safely add arguments to parser
|
110
|
+
|
111
|
+
In parsers and plugins, arguments are represented as a map of flag tuples to argparse keyword arguments. This
|
112
|
+
function will add arguments of that representation supplied as `arguments` to the supplied parser in a safe
|
113
|
+
collision-avoidant manner.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
parser: argparse Parser or ArgumentGroup, or anything with an `add_argument` function
|
117
|
+
arguments: arguments specification
|
118
|
+
|
119
|
+
"""
|
120
|
+
for flags, keywords in arguments.items():
|
121
|
+
cls.safe_add_argument(parser, *flags, **keywords)
|
122
|
+
|
84
123
|
def fill_parser(self, parser):
|
85
|
-
"""
|
124
|
+
"""Fill supplied parser with arguments
|
86
125
|
|
87
126
|
Fills the supplied parser with the arguments returned via the `get_arguments` method invocation. This
|
88
|
-
implementation
|
127
|
+
implementation adds the arguments directly to the parser.
|
89
128
|
|
90
129
|
Args:
|
91
130
|
parser: parser to fill with arguments
|
92
131
|
|
93
132
|
"""
|
94
|
-
|
95
|
-
try:
|
96
|
-
parser.add_argument(*flags, **keywords)
|
97
|
-
except argparse.ArgumentError:
|
98
|
-
# flag has already been added, pass
|
99
|
-
pass
|
133
|
+
self.add_arguments_from_specification(parser, self.get_arguments())
|
100
134
|
|
101
135
|
def reproduce_cli_args(self, args_ns):
|
102
136
|
"""Reproduce the list of arguments needed on the command line"""
|
@@ -154,8 +188,34 @@ class ParserBase(ABC):
|
|
154
188
|
Returns: namespace with processed results of arguments.
|
155
189
|
"""
|
156
190
|
|
157
|
-
@
|
191
|
+
@classmethod
|
192
|
+
def parse_known_args(
|
193
|
+
cls,
|
194
|
+
parser_classes,
|
195
|
+
description="No tool description provided",
|
196
|
+
arguments=None,
|
197
|
+
**kwargs,
|
198
|
+
):
|
199
|
+
"""Parse and post-process arguments
|
200
|
+
|
201
|
+
Create a parser for the given application using the description provided. This will then add all specified
|
202
|
+
ParserBase subclasses' get_parser output as parent parses for the created parser. Then all of the handle
|
203
|
+
arguments methods will be called, and the final namespace will be returned. This will allow unknown arguments
|
204
|
+
which are returned as the last tuple result.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
parser_classes: a list of ParserBase subclasses that will be used to
|
208
|
+
description: description passed ot the argument parser
|
209
|
+
arguments: arguments to process, None to use command line input
|
210
|
+
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
211
|
+
"""
|
212
|
+
return cls._parse_args(
|
213
|
+
parser_classes, description, arguments, use_parse_known=True, **kwargs
|
214
|
+
)
|
215
|
+
|
216
|
+
@classmethod
|
158
217
|
def parse_args(
|
218
|
+
cls,
|
159
219
|
parser_classes,
|
160
220
|
description="No tool description provided",
|
161
221
|
arguments=None,
|
@@ -163,20 +223,52 @@ class ParserBase(ABC):
|
|
163
223
|
):
|
164
224
|
"""Parse and post-process arguments
|
165
225
|
|
226
|
+
Create a parser for the given application using the description provided. This will then add all specified
|
227
|
+
ParserBase subclasses' get_parser output as parent parses for the created parser. Then all of the handle
|
228
|
+
arguments methods will be called, and the final namespace will be returned. This does not allow unknown
|
229
|
+
arguments.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
parser_classes: a list of ParserBase subclasses that will be used to
|
233
|
+
description: description passed ot the argument parser
|
234
|
+
arguments: arguments to process, None to use command line input
|
235
|
+
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
236
|
+
"""
|
237
|
+
return cls._parse_args(parser_classes, description, arguments, **kwargs)
|
238
|
+
|
239
|
+
@staticmethod
|
240
|
+
def _parse_args(
|
241
|
+
parser_classes,
|
242
|
+
description="No tool description provided",
|
243
|
+
arguments=None,
|
244
|
+
use_parse_known=False,
|
245
|
+
**kwargs,
|
246
|
+
):
|
247
|
+
"""Parse and post-process arguments helper
|
248
|
+
|
166
249
|
Create a parser for the given application using the description provided. This will then add all specified
|
167
250
|
ParserBase subclasses' get_parser output as parent parses for the created parser. Then all of the handle
|
168
251
|
arguments methods will be called, and the final namespace will be returned.
|
169
252
|
|
253
|
+
This takes a function that will take in a parser and return the parsing function to call on arguments.
|
254
|
+
|
170
255
|
Args:
|
256
|
+
parse_function_processor: takes a parser, returns the parse function to call
|
171
257
|
parser_classes: a list of ParserBase subclasses that will be used to
|
172
258
|
description: description passed ot the argument parser
|
173
259
|
arguments: arguments to process, None to use command line input
|
260
|
+
use_parse_known: use parse_known_arguments from argparse
|
261
|
+
|
174
262
|
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
175
263
|
"""
|
176
264
|
composition = CompositeParser(parser_classes, description)
|
177
265
|
parser = composition.get_parser()
|
178
266
|
try:
|
179
|
-
|
267
|
+
if use_parse_known:
|
268
|
+
args_ns, *unknowns = parser.parse_known_args(arguments)
|
269
|
+
else:
|
270
|
+
args_ns = parser.parse_args(arguments)
|
271
|
+
unknowns = []
|
180
272
|
args_ns = composition.handle_arguments(args_ns, **kwargs)
|
181
273
|
except ValueError as ver:
|
182
274
|
print(f"[ERROR] Failed to parse arguments: {ver}", file=sys.stderr)
|
@@ -185,7 +277,7 @@ class ParserBase(ABC):
|
|
185
277
|
except Exception as exc:
|
186
278
|
print(f"[ERROR] {exc}", file=sys.stderr)
|
187
279
|
sys.exit(-1)
|
188
|
-
return args_ns, parser
|
280
|
+
return args_ns, parser, *unknowns
|
189
281
|
|
190
282
|
@staticmethod
|
191
283
|
def find_in(token, deploy, is_file=True):
|
@@ -205,6 +297,119 @@ class ParserBase(ABC):
|
|
205
297
|
return None
|
206
298
|
|
207
299
|
|
300
|
+
class ConfigDrivenParser(ParserBase):
|
301
|
+
"""Parser that allows options from configuration and command line
|
302
|
+
|
303
|
+
This parser reads a configuration file (if supplied) and uses the values to drive the inputs to arguments. Command
|
304
|
+
line arguments will still take precedence over the configured values.
|
305
|
+
"""
|
306
|
+
|
307
|
+
DEFAULT_CONFIGURATION_PATH = Path("fprime-gds.yml")
|
308
|
+
|
309
|
+
@classmethod
|
310
|
+
def set_default_configuration(cls, path: Path):
|
311
|
+
"""Set path for (global) default configuration file
|
312
|
+
|
313
|
+
Set the path for default configuration file. If unset, will use 'fprime-gds.yml'. Set to None to disable default
|
314
|
+
configuration.
|
315
|
+
"""
|
316
|
+
cls.DEFAULT_CONFIGURATION_PATH = path
|
317
|
+
|
318
|
+
@classmethod
|
319
|
+
def parse_args(
|
320
|
+
cls,
|
321
|
+
parser_classes,
|
322
|
+
description="No tool description provided",
|
323
|
+
arguments=None,
|
324
|
+
**kwargs,
|
325
|
+
):
|
326
|
+
"""Parse and post-process arguments using inputs and config
|
327
|
+
|
328
|
+
Parse the arguments in two stages: first parse the configuration data, ignoring unknown inputs, then parse the
|
329
|
+
full argument set with the supplied configuration to fill in additional options.
|
330
|
+
|
331
|
+
Args:
|
332
|
+
parser_classes: a list of ParserBase subclasses that will be used to
|
333
|
+
description: description passed ot the argument parser
|
334
|
+
arguments: arguments to process, None to use command line input
|
335
|
+
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
336
|
+
"""
|
337
|
+
arguments = sys.argv[1:] if arguments is None else arguments
|
338
|
+
|
339
|
+
# Help should spill all the arguments, so delegate to the normal parsing flow including
|
340
|
+
# this and supplied parsers
|
341
|
+
if "-h" in arguments or "--help" in arguments:
|
342
|
+
parsers = [ConfigDrivenParser] + parser_classes
|
343
|
+
ParserBase.parse_args(parsers, description, arguments, **kwargs)
|
344
|
+
sys.exit(0)
|
345
|
+
|
346
|
+
# Custom flow involving parsing the arguments of this parser first, then passing the configured values
|
347
|
+
# as part of the argument source
|
348
|
+
ns_config, _, remaining = ParserBase.parse_known_args(
|
349
|
+
[ConfigDrivenParser], description, arguments, **kwargs
|
350
|
+
)
|
351
|
+
config_options = ns_config.config_values.get("command-line-options", {})
|
352
|
+
config_args = cls.flatten_options(config_options)
|
353
|
+
# Argparse allows repeated (overridden) arguments, thus the CLI override is accomplished by providing
|
354
|
+
# remaining arguments after the configured ones
|
355
|
+
ns_full, parser = ParserBase.parse_args(
|
356
|
+
parser_classes, description, config_args + remaining, **kwargs
|
357
|
+
)
|
358
|
+
ns_final = argparse.Namespace(**vars(ns_config), **vars(ns_full))
|
359
|
+
return ns_final, parser
|
360
|
+
|
361
|
+
@staticmethod
|
362
|
+
def flatten_options(configured_options):
|
363
|
+
"""Flatten options down to arguments"""
|
364
|
+
flattened = []
|
365
|
+
for option, value in configured_options.items():
|
366
|
+
flattened.append(f"--{option}")
|
367
|
+
if value is not None:
|
368
|
+
flattened.extend(
|
369
|
+
value if isinstance(value, (list, tuple)) else [f"{value}"]
|
370
|
+
)
|
371
|
+
return flattened
|
372
|
+
|
373
|
+
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
374
|
+
"""Arguments needed for config processing"""
|
375
|
+
return {
|
376
|
+
("-c", "--config"): {
|
377
|
+
"dest": "config",
|
378
|
+
"required": False,
|
379
|
+
"default": self.DEFAULT_CONFIGURATION_PATH,
|
380
|
+
"type": Path,
|
381
|
+
"help": f"Argument configuration file path.",
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
def handle_arguments(self, args, **kwargs):
|
386
|
+
"""Handle the arguments
|
387
|
+
|
388
|
+
Loads the configuration file specified and fills in the `config_values` attribute of the namespace with the
|
389
|
+
loaded configuration dictionary.
|
390
|
+
"""
|
391
|
+
args.config_values = {}
|
392
|
+
# Specified but non-existent config file is a hard error
|
393
|
+
if (
|
394
|
+
"-c" in sys.argv[1:] or "--config" in sys.argv[1:]
|
395
|
+
) and not args.config.exists():
|
396
|
+
raise ValueError(
|
397
|
+
f"Specified configuration file '{args.config}' does not exist"
|
398
|
+
)
|
399
|
+
# Read configuration if the file was set and exists
|
400
|
+
if args.config is not None and args.config.exists():
|
401
|
+
print(f"[INFO] Reading command-line configuration from: {args.config}")
|
402
|
+
with open(args.config, "r") as file_handle:
|
403
|
+
try:
|
404
|
+
loaded = yaml.safe_load(file_handle)
|
405
|
+
args.config_values = loaded if loaded is not None else {}
|
406
|
+
except Exception as exc:
|
407
|
+
raise ValueError(
|
408
|
+
f"Malformed configuration {args.config}: {exc}", exc
|
409
|
+
)
|
410
|
+
return args
|
411
|
+
|
412
|
+
|
208
413
|
class DetectionParser(ParserBase):
|
209
414
|
"""Parser that detects items from a root/directory or deployment"""
|
210
415
|
|
@@ -253,71 +458,207 @@ class DetectionParser(ParserBase):
|
|
253
458
|
return args
|
254
459
|
|
255
460
|
|
461
|
+
class BareArgumentParser(ParserBase):
|
462
|
+
"""Takes in the argument specification (used in plugins and get_arguments) to parse args
|
463
|
+
|
464
|
+
This parser takes in and uses a raw specification of arguments as seen in plugins and arguments to perform argument
|
465
|
+
parsing. The spec is a map of flag tuples to argparse kwargs.
|
466
|
+
|
467
|
+
Argument handling only validates using the checking_function which is a function taking in keyword arguments for
|
468
|
+
each cli argument specified. This function will be called as such: `checking_function(**args)`. Use None to skip
|
469
|
+
argument checking. checking_function should raise ValueError to indicate an error with an argument.
|
470
|
+
"""
|
471
|
+
|
472
|
+
def __init__(self, specification, checking_function=None):
|
473
|
+
"""Initialize this parser with the provided specification"""
|
474
|
+
self.specification = specification
|
475
|
+
self.checking_function = checking_function
|
476
|
+
|
477
|
+
def get_arguments(self):
|
478
|
+
"""Raw specification is returned immediately"""
|
479
|
+
return self.specification
|
480
|
+
|
481
|
+
def handle_arguments(self, args, **kwargs):
|
482
|
+
"""Handle argument calls checking function to validate"""
|
483
|
+
if self.checking_function is not None:
|
484
|
+
self.checking_function(**self.extract_arguments(args))
|
485
|
+
return args
|
486
|
+
|
487
|
+
def extract_arguments(self, args) -> Dict[str, Any]:
|
488
|
+
"""Extract argument values from the args namespace into a map matching the original specification
|
489
|
+
|
490
|
+
This function extracts arguments matching the original specification and returns them as a dictionary of key-
|
491
|
+
value pairs.
|
492
|
+
|
493
|
+
Return:
|
494
|
+
filled arguments dictionary
|
495
|
+
"""
|
496
|
+
expected_args = self.specification
|
497
|
+
argument_destinations = [
|
498
|
+
(
|
499
|
+
value["dest"]
|
500
|
+
if "dest" in value
|
501
|
+
else key[0].replace("--", "").replace("-", "_")
|
502
|
+
)
|
503
|
+
for key, value in expected_args.items()
|
504
|
+
]
|
505
|
+
filled_arguments = {
|
506
|
+
destination: getattr(args, destination)
|
507
|
+
for destination in argument_destinations
|
508
|
+
}
|
509
|
+
return filled_arguments
|
510
|
+
|
511
|
+
|
512
|
+
class IndividualPluginParser(BareArgumentParser):
|
513
|
+
"""Parser for an individual plugin's command line
|
514
|
+
|
515
|
+
A CLI parser for an individual plugin. This handles all the functions and arguments that apply to the parsing of a
|
516
|
+
single plugin's arguments. It also handles FEATURE plugin disable flags.
|
517
|
+
"""
|
518
|
+
|
519
|
+
def __init__(self, plugin_system: Plugins, plugin_class: type):
|
520
|
+
"""Initialize the plugin parser
|
521
|
+
|
522
|
+
Args:
|
523
|
+
plugin_system: Plugins object used to work with the plugin system
|
524
|
+
plugin_class: plugin class used for this specific parser
|
525
|
+
"""
|
526
|
+
# Add disable flags for feature type plugins
|
527
|
+
super().__init__(plugin_class.get_arguments(), plugin_class.check_arguments)
|
528
|
+
self.disable_flag_destination = (
|
529
|
+
f"disable-{plugin_class.get_name()}".lower().replace("-", "_")
|
530
|
+
)
|
531
|
+
self.plugin_class = plugin_class
|
532
|
+
self.plugin_system = plugin_system
|
533
|
+
|
534
|
+
def get_arguments(self):
|
535
|
+
"""Get the arguments for this plugin
|
536
|
+
|
537
|
+
The individual plugin parser will read the arguments from the supplied plugin class. Additionally, if the
|
538
|
+
plugin_class's plugin_type is FEATURE then this parser will add an disable flag to allow users to turn disable
|
539
|
+
the plugin feature.
|
540
|
+
"""
|
541
|
+
arguments = {}
|
542
|
+
if self.plugin_class.type == PluginType.FEATURE:
|
543
|
+
arguments.update(
|
544
|
+
{
|
545
|
+
(f"--disable-{self.plugin_class.get_name()}",): {
|
546
|
+
"action": "store_true",
|
547
|
+
"default": False,
|
548
|
+
"dest": self.disable_flag_destination,
|
549
|
+
"help": f"Disable the {self.plugin_class.category} plugin '{self.plugin_class.get_name()}'",
|
550
|
+
}
|
551
|
+
}
|
552
|
+
)
|
553
|
+
arguments.update(super().get_arguments())
|
554
|
+
return arguments
|
555
|
+
|
556
|
+
def handle_arguments(self, args, **kwargs):
|
557
|
+
"""Handle the given arguments for a plugin
|
558
|
+
|
559
|
+
This will process the arguments for a given plugin. Additionally, it will construct the plugin object and
|
560
|
+
supply the constructed object to the plugin system if the plugin is a selection or is enabled.
|
561
|
+
|
562
|
+
Args:
|
563
|
+
args: argparse namespace
|
564
|
+
"""
|
565
|
+
arguments = super().handle_arguments(
|
566
|
+
args, **kwargs
|
567
|
+
) # Perform argument checking first
|
568
|
+
if not getattr(args, self.disable_flag_destination, False):
|
569
|
+
# Remove the disable flag from the arguments
|
570
|
+
plugin_arguments = {
|
571
|
+
key: value
|
572
|
+
for key, value in self.extract_arguments(arguments).items()
|
573
|
+
if key != self.disable_flag_destination
|
574
|
+
}
|
575
|
+
|
576
|
+
plugin_zero_argument_class = functools.partial(
|
577
|
+
self.plugin_class.get_implementor(), **plugin_arguments
|
578
|
+
)
|
579
|
+
self.plugin_system.add_bound_class(
|
580
|
+
self.plugin_class.category, plugin_zero_argument_class
|
581
|
+
)
|
582
|
+
return arguments
|
583
|
+
|
584
|
+
def get_plugin_class(self):
|
585
|
+
"""Plugin class accessor"""
|
586
|
+
return self.plugin_class
|
587
|
+
|
588
|
+
|
256
589
|
class PluginArgumentParser(ParserBase):
|
257
590
|
"""Parser for arguments coming from plugins"""
|
258
591
|
|
259
592
|
DESCRIPTION = "Plugin options"
|
593
|
+
# Defaults:
|
260
594
|
FPRIME_CHOICES = {
|
261
|
-
"framing": "
|
595
|
+
"framing": "space-packet-space-data-link",
|
262
596
|
"communication": "ip",
|
263
597
|
}
|
264
598
|
|
265
|
-
def __init__(self):
|
266
|
-
"""Initialize the plugin information for this parser
|
599
|
+
def __init__(self, plugin_system: Plugins = None):
|
600
|
+
"""Initialize the plugin information for this parser
|
601
|
+
|
602
|
+
This will initialize this plugin argument parser with the supplied plugin system. If not supplied this will use
|
603
|
+
the system plugin singleton, which is configured elsewhere.
|
604
|
+
"""
|
605
|
+
# Accept the supplied plugin system defaulting to the global singleton
|
606
|
+
self.plugin_system = plugin_system if plugin_system else Plugins.system()
|
267
607
|
self._plugin_map = {
|
268
|
-
category:
|
269
|
-
|
608
|
+
category: [
|
609
|
+
IndividualPluginParser(self.plugin_system, plugin)
|
610
|
+
for plugin in self.plugin_system.get_plugins(category)
|
611
|
+
]
|
612
|
+
for category in self.plugin_system.get_categories()
|
270
613
|
}
|
271
614
|
|
272
|
-
|
273
|
-
|
274
|
-
""" Add an argument allowing duplicates
|
615
|
+
def fill_parser(self, parser):
|
616
|
+
"""Fill supplied parser with grouped arguments
|
275
617
|
|
276
|
-
|
277
|
-
|
618
|
+
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
619
|
+
arguments based on the constituent parser that the argument comes from. Category specific arguments are also
|
620
|
+
added (i.e. SELECTION type selection arguments).
|
278
621
|
|
279
622
|
Args:
|
280
|
-
parser: parser
|
281
|
-
*flags: positional arguments passed to `add_argument`
|
282
|
-
**keywords: key word arguments passed to `add_argument`
|
623
|
+
parser: parser to fill
|
283
624
|
"""
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
625
|
+
for category, plugin_parsers in self._plugin_map.items():
|
626
|
+
# Add category specific flags (selection flags, etc)
|
627
|
+
argument_group = parser.add_argument_group(
|
628
|
+
title=f"{category.title()} Plugin Options"
|
629
|
+
)
|
630
|
+
self.add_arguments_from_specification(
|
631
|
+
argument_group, self.get_category_arguments(category)
|
632
|
+
)
|
289
633
|
|
290
|
-
|
291
|
-
|
634
|
+
# Handle the individual plugin parsers
|
635
|
+
for plugin_parser in plugin_parsers:
|
636
|
+
plugin = plugin_parser.get_plugin_class()
|
637
|
+
argument_group = parser.add_argument_group(
|
638
|
+
title=f"{category.title()} Plugin '{plugin.get_name()}' Options"
|
639
|
+
)
|
640
|
+
plugin_parser.fill_parser(argument_group)
|
292
641
|
|
293
|
-
|
294
|
-
|
642
|
+
def get_category_arguments(self, category):
|
643
|
+
"""Get category arguments for a given plugin category
|
644
|
+
|
645
|
+
This function will generate category arguments for the supplied category. These arguments will follow the
|
646
|
+
standard argument specification of a dictionary of flag tuples to argparse keyword arguments.
|
647
|
+
|
648
|
+
Currently category specific arguments are just selection flags for SELECTION type plugins.
|
295
649
|
|
296
650
|
Args:
|
297
|
-
|
651
|
+
category: category arguments
|
298
652
|
"""
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
for plugin in plugins:
|
305
|
-
argument_group = parser.add_argument_group(title=f"{category.title()} Plugin '{plugin.get_name()}' Options")
|
306
|
-
if plugin.type == PluginType.FEATURE:
|
307
|
-
self.safe_add_argument(argument_group,
|
308
|
-
f"--disable-{plugin.get_name()}",
|
309
|
-
action="store_true",
|
310
|
-
default=False,
|
311
|
-
help=f"Disable the {category} plugin '{plugin.get_name()}'")
|
312
|
-
for flags, keywords in plugin.get_arguments().items():
|
313
|
-
self.safe_add_argument(argument_group, *flags, **keywords)
|
653
|
+
plugin_type = self.plugin_system.get_category_plugin_type(category)
|
654
|
+
plugins = [
|
655
|
+
plugin_parser.get_plugin_class()
|
656
|
+
for plugin_parser in self._plugin_map[category]
|
657
|
+
]
|
314
658
|
|
315
|
-
def get_category_arguments(self, category):
|
316
|
-
""" Get arguments for a plugin category """
|
317
659
|
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
318
|
-
|
660
|
+
|
319
661
|
# Add category options: SELECTION plugins add a selection flag
|
320
|
-
plugin_type = Plugins.get_category_plugin_type(category)
|
321
662
|
if plugin_type == PluginType.SELECTION:
|
322
663
|
arguments.update(
|
323
664
|
{
|
@@ -333,81 +674,54 @@ class PluginArgumentParser(ParserBase):
|
|
333
674
|
return arguments
|
334
675
|
|
335
676
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
336
|
-
"""Return arguments to used in
|
677
|
+
"""Return arguments to used in plugin system
|
678
|
+
|
679
|
+
This will return the command line arguments all the plugins contained within the supplied plugin system. This
|
680
|
+
will recursively return plugins from all of the IndividualPluginParser objects composing this plugin argument
|
681
|
+
parser. Arguments are returned in the standard specification form of tuple of flags mapped to a dictionary of
|
682
|
+
argparse kwarg inputs.
|
683
|
+
"""
|
337
684
|
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
338
|
-
for category,
|
685
|
+
for category, plugin_parsers in self._plugin_map.items():
|
339
686
|
arguments.update(self.get_category_arguments(category))
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
(f"--disable-{plugin.get_name()}", ): {
|
345
|
-
"action": "store_true",
|
346
|
-
"default": False,
|
347
|
-
"help": f"Disable the {category} plugin '{plugin.get_name()}'"
|
348
|
-
}
|
349
|
-
})
|
350
|
-
arguments.update(plugin.get_arguments())
|
687
|
+
[
|
688
|
+
arguments.update(plugin_parser.get_arguments())
|
689
|
+
for plugin_parser in plugin_parsers
|
690
|
+
]
|
351
691
|
return arguments
|
352
692
|
|
353
693
|
def handle_arguments(self, args, **kwargs):
|
354
|
-
"""
|
355
|
-
for category, plugins in self._plugin_map.items():
|
356
|
-
plugin_type = Plugins.get_category_plugin_type(category)
|
694
|
+
"""Handle the plugin arguments
|
357
695
|
|
696
|
+
This will handle the plugin arguments delegating each to the IndividualPluginParser. For SELECTION plugins this
|
697
|
+
will bind a single instance of the selected plugin to its arguments. For FEATURE plugins it will bind arguments
|
698
|
+
to every enabled plugin. Bound plugins are registered with the plugin system.
|
699
|
+
"""
|
700
|
+
for category, plugin_parsers in self._plugin_map.items():
|
701
|
+
plugin_type = self.plugin_system.get_category_plugin_type(category)
|
702
|
+
self.plugin_system.start_loading(category)
|
358
703
|
# Selection plugins choose one plugin and instantiate it
|
359
704
|
if plugin_type == PluginType.SELECTION:
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
705
|
+
try:
|
706
|
+
self.plugin_system.get_selected_class(category)
|
707
|
+
except PluginsNotLoadedException:
|
708
|
+
selection_string = getattr(args, f"{category}_selection")
|
709
|
+
matching_plugin_parsers = [
|
710
|
+
plugin_parser
|
711
|
+
for plugin_parser in plugin_parsers
|
712
|
+
if plugin_parser.get_plugin_class().get_name()
|
713
|
+
== selection_string
|
714
|
+
]
|
715
|
+
assert (
|
716
|
+
len(matching_plugin_parsers) == 1
|
717
|
+
), "Plugin selection system failed"
|
718
|
+
args = matching_plugin_parsers[0].handle_arguments(args, **kwargs)
|
367
719
|
# Feature plugins instantiate all enabled plugins
|
368
720
|
elif plugin_type == PluginType.FEATURE:
|
369
|
-
|
370
|
-
|
371
|
-
if not getattr(args, f"disable_{plugin.get_name().replace('-', '_')}", False)
|
372
|
-
]
|
373
|
-
plugin_instantiations = [
|
374
|
-
plugin.plugin_class(**self.extract_plugin_arguments(args, plugin))
|
375
|
-
for plugin in enabled_plugins
|
376
|
-
]
|
377
|
-
setattr(args, f"{category}_enabled_instances", plugin_instantiations)
|
721
|
+
for plugin_parser in plugin_parsers:
|
722
|
+
args = plugin_parser.handle_arguments(args, **kwargs)
|
378
723
|
return args
|
379
724
|
|
380
|
-
@staticmethod
|
381
|
-
def extract_plugin_arguments(args, plugin) -> Dict[str, Any]:
|
382
|
-
"""Extract plugin argument values from the args namespace into a map
|
383
|
-
|
384
|
-
Plugin arguments will be supplied to the `__init__` function of the plugin via a keyword argument dictionary.
|
385
|
-
This function maps from the argument namespace from parsing back into that dictionary.
|
386
|
-
|
387
|
-
Args:
|
388
|
-
args: argument namespace from argparse
|
389
|
-
plugin: plugin to extract arguments for
|
390
|
-
Return:
|
391
|
-
filled arguments dictionary
|
392
|
-
"""
|
393
|
-
expected_args = plugin.get_arguments()
|
394
|
-
argument_destinations = [
|
395
|
-
(
|
396
|
-
value["dest"]
|
397
|
-
if "dest" in value
|
398
|
-
else key[0].replace("--", "").replace("-", "_")
|
399
|
-
)
|
400
|
-
for key, value in expected_args.items()
|
401
|
-
]
|
402
|
-
filled_arguments = {
|
403
|
-
destination: getattr(args, destination)
|
404
|
-
for destination in argument_destinations
|
405
|
-
}
|
406
|
-
# Check arguments or yield a Value error
|
407
|
-
if hasattr(plugin, "check_arguments"):
|
408
|
-
plugin.check_arguments(**filled_arguments)
|
409
|
-
return filled_arguments
|
410
|
-
|
411
725
|
|
412
726
|
class CompositeParser(ParserBase):
|
413
727
|
"""Composite parser handles parsing as a composition of multiple other parsers"""
|
@@ -415,7 +729,15 @@ class CompositeParser(ParserBase):
|
|
415
729
|
def __init__(self, constituents, description=None):
|
416
730
|
"""Construct this parser by instantiating the sub-parsers"""
|
417
731
|
self.given = description
|
418
|
-
constructed = [
|
732
|
+
constructed = [
|
733
|
+
constituent() if callable(constituent) else constituent
|
734
|
+
for constituent in constituents
|
735
|
+
]
|
736
|
+
# Check to ensure everything passed in became a ParserBase after construction
|
737
|
+
for i, construct in enumerate(constructed):
|
738
|
+
assert isinstance(
|
739
|
+
construct, ParserBase
|
740
|
+
), f"{construct.__class__.__name__} ({i}) not a ParserBase child"
|
419
741
|
flattened = [
|
420
742
|
item.constituents if isinstance(item, CompositeParser) else [item]
|
421
743
|
for item in constructed
|
@@ -423,7 +745,7 @@ class CompositeParser(ParserBase):
|
|
423
745
|
self.constituent_parsers = {*itertools.chain.from_iterable(flattened)}
|
424
746
|
|
425
747
|
def fill_parser(self, parser):
|
426
|
-
"""
|
748
|
+
"""File supplied parser with grouped arguments
|
427
749
|
|
428
750
|
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
429
751
|
arguments based on the constituent that sources the argument.
|
@@ -435,7 +757,9 @@ class CompositeParser(ParserBase):
|
|
435
757
|
if isinstance(constituent, (PluginArgumentParser, CompositeParser)):
|
436
758
|
constituent.fill_parser(parser)
|
437
759
|
else:
|
438
|
-
argument_group = parser.add_argument_group(
|
760
|
+
argument_group = parser.add_argument_group(
|
761
|
+
title=constituent.description
|
762
|
+
)
|
439
763
|
constituent.fill_parser(argument_group)
|
440
764
|
|
441
765
|
@property
|
@@ -520,6 +844,13 @@ class LogDeployParser(ParserBase):
|
|
520
844
|
"default": False,
|
521
845
|
"help": "Log to standard out along with log output files",
|
522
846
|
},
|
847
|
+
("--log-level-gds",): {
|
848
|
+
"action": "store",
|
849
|
+
"dest": "log_level_gds",
|
850
|
+
"choices": ["DEBUG", "INFO", "WARNING", "ERROR"],
|
851
|
+
"default": "INFO",
|
852
|
+
"help": "Set the logging level of GDS processes [default: %(default)s]",
|
853
|
+
},
|
523
854
|
}
|
524
855
|
|
525
856
|
def handle_arguments(self, args, **kwargs):
|
@@ -547,7 +878,7 @@ class LogDeployParser(ParserBase):
|
|
547
878
|
raise
|
548
879
|
# Setup the basic python logging
|
549
880
|
fprime_gds.common.logger.configure_py_log(
|
550
|
-
args.logs, mirror_to_stdout=args.log_to_stdout
|
881
|
+
args.logs, mirror_to_stdout=args.log_to_stdout, log_level=args.log_level_gds
|
551
882
|
)
|
552
883
|
return args
|
553
884
|
|
@@ -649,7 +980,15 @@ class DictionaryParser(DetectionParser):
|
|
649
980
|
"default": None,
|
650
981
|
"required": False,
|
651
982
|
"type": str,
|
652
|
-
"help": "Path to packet specification.",
|
983
|
+
"help": "Path to packet XML specification (should not be used if JSON packet definitions are used).",
|
984
|
+
},
|
985
|
+
("--packet-set-name",): {
|
986
|
+
"dest": "packet_set_name",
|
987
|
+
"action": "store",
|
988
|
+
"default": None,
|
989
|
+
"required": False,
|
990
|
+
"type": str,
|
991
|
+
"help": "Name of packet set defined in the JSON dictionary.",
|
653
992
|
},
|
654
993
|
},
|
655
994
|
}
|
@@ -730,6 +1069,7 @@ class StandardPipelineParser(CompositeParser):
|
|
730
1069
|
"dictionary": args_ns.dictionary,
|
731
1070
|
"file_store": args_ns.files_storage_directory,
|
732
1071
|
"packet_spec": args_ns.packet_spec,
|
1072
|
+
"packet_set_name": args_ns.packet_set_name,
|
733
1073
|
"logging_prefix": args_ns.logs,
|
734
1074
|
}
|
735
1075
|
pipeline = pipeline if pipeline else StandardPipeline()
|
@@ -753,13 +1093,16 @@ class CommParser(CompositeParser):
|
|
753
1093
|
CommExtraParser,
|
754
1094
|
MiddleWareParser,
|
755
1095
|
LogDeployParser,
|
756
|
-
PluginArgumentParser,
|
757
1096
|
]
|
758
1097
|
|
759
1098
|
def __init__(self):
|
760
1099
|
"""Initialization"""
|
1100
|
+
# Added here to ensure the call to Plugins does not interfere with the full plugin system
|
1101
|
+
comm_plugin_parser_instance = PluginArgumentParser(
|
1102
|
+
Plugins(["communication", "framing"])
|
1103
|
+
)
|
761
1104
|
super().__init__(
|
762
|
-
constituents=self.CONSTITUENTS,
|
1105
|
+
constituents=self.CONSTITUENTS + [comm_plugin_parser_instance],
|
763
1106
|
description="Communications bridge application",
|
764
1107
|
)
|
765
1108
|
|
@@ -867,9 +1210,7 @@ class BinaryDeployment(DetectionParser):
|
|
867
1210
|
class SearchArgumentsParser(ParserBase):
|
868
1211
|
"""Parser for search arguments"""
|
869
1212
|
|
870
|
-
DESCRIPTION =
|
871
|
-
"Searching and filtering options"
|
872
|
-
)
|
1213
|
+
DESCRIPTION = "Searching and filtering options"
|
873
1214
|
|
874
1215
|
def __init__(self, command_name: str) -> None:
|
875
1216
|
self.command_name = command_name
|