fprime-gds 3.6.2a1__py3-none-any.whl → 4.0.0a1__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 (43) hide show
  1. fprime_gds/common/decoders/ch_decoder.py +1 -1
  2. fprime_gds/common/decoders/event_decoder.py +2 -1
  3. fprime_gds/common/decoders/pkt_decoder.py +1 -1
  4. fprime_gds/common/distributor/distributor.py +2 -2
  5. fprime_gds/common/encoders/ch_encoder.py +2 -2
  6. fprime_gds/common/encoders/cmd_encoder.py +2 -2
  7. fprime_gds/common/encoders/event_encoder.py +2 -2
  8. fprime_gds/common/encoders/pkt_encoder.py +2 -2
  9. fprime_gds/common/encoders/seq_writer.py +2 -2
  10. fprime_gds/common/fpy/__init__.py +0 -0
  11. fprime_gds/common/fpy/serialize_bytecode.py +229 -0
  12. fprime_gds/common/fpy/types.py +203 -0
  13. fprime_gds/common/gds_cli/base_commands.py +1 -1
  14. fprime_gds/common/handlers.py +39 -0
  15. fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
  16. fprime_gds/common/loaders/pkt_json_loader.py +121 -0
  17. fprime_gds/common/loaders/prm_json_loader.py +85 -0
  18. fprime_gds/common/pipeline/dictionaries.py +21 -4
  19. fprime_gds/common/pipeline/encoding.py +19 -0
  20. fprime_gds/common/pipeline/histories.py +4 -0
  21. fprime_gds/common/pipeline/standard.py +16 -2
  22. fprime_gds/common/templates/prm_template.py +81 -0
  23. fprime_gds/common/testing_fw/api.py +42 -0
  24. fprime_gds/common/testing_fw/pytest_integration.py +25 -2
  25. fprime_gds/common/tools/README.md +34 -0
  26. fprime_gds/common/tools/params.py +246 -0
  27. fprime_gds/common/utils/config_manager.py +6 -6
  28. fprime_gds/executables/apps.py +184 -11
  29. fprime_gds/executables/cli.py +280 -122
  30. fprime_gds/executables/comm.py +5 -2
  31. fprime_gds/executables/fprime_cli.py +3 -3
  32. fprime_gds/executables/run_deployment.py +10 -3
  33. fprime_gds/flask/static/js/vue-support/channel.js +1 -1
  34. fprime_gds/flask/static/js/vue-support/event.js +1 -1
  35. fprime_gds/plugin/definitions.py +86 -8
  36. fprime_gds/plugin/system.py +171 -58
  37. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/METADATA +17 -19
  38. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/RECORD +43 -34
  39. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/WHEEL +1 -1
  40. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/entry_points.txt +2 -0
  41. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/LICENSE.txt +0 -0
  42. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/NOTICE.txt +0 -0
  43. {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,9 @@ 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
@@ -30,7 +32,7 @@ from fprime_gds.common.transport import ThreadedTCPSocketClient
30
32
  from fprime_gds.common.utils.config_manager import ConfigManager
31
33
  from fprime_gds.executables.utils import find_app, find_dict, get_artifacts_root
32
34
  from fprime_gds.plugin.definitions import PluginType
33
- from fprime_gds.plugin.system import Plugins
35
+ from fprime_gds.plugin.system import Plugins, PluginsNotLoadedException
34
36
  from fprime_gds.common.zmq_transport import ZmqClient
35
37
 
36
38
 
@@ -81,22 +83,51 @@ class ParserBase(ABC):
81
83
  self.fill_parser(parser)
82
84
  return parser
83
85
 
86
+ @staticmethod
87
+ def safe_add_argument(parser, *flags, **keywords):
88
+ """Add an argument allowing duplicates
89
+
90
+ Add arguments to the parser (passes through *flags and **keywords) to the supplied parser. This method traps
91
+ errors to prevent duplicates from crashing the system when two plugins use the same flags.
92
+
93
+ Args:
94
+ parser: parser or argument group to add arguments to
95
+ *flags: positional arguments passed to `add_argument`
96
+ **keywords: key word arguments passed to `add_argument`
97
+ """
98
+ try:
99
+ parser.add_argument(*flags, **keywords)
100
+ except argparse.ArgumentError:
101
+ # flag has already been added, pass
102
+ pass
103
+
104
+ @classmethod
105
+ def add_arguments_from_specification(cls, parser, arguments):
106
+ """Safely add arguments to parser
107
+
108
+ In parsers and plugins, arguments are represented as a map of flag tuples to argparse keyword arguments. This
109
+ function will add arguments of that representation supplied as `arguments` to the supplied parser in a safe
110
+ collision-avoidant manner.
111
+
112
+ Args:
113
+ parser: argparse Parser or ArgumentGroup, or anything with an `add_argument` function
114
+ arguments: arguments specification
115
+
116
+ """
117
+ for flags, keywords in arguments.items():
118
+ cls.safe_add_argument(parser, *flags, **keywords)
119
+
84
120
  def fill_parser(self, parser):
85
- """ Fill supplied parser with arguments
121
+ """Fill supplied parser with arguments
86
122
 
87
123
  Fills the supplied parser with the arguments returned via the `get_arguments` method invocation. This
88
- implementation add the arguments directly to the parser.
124
+ implementation adds the arguments directly to the parser.
89
125
 
90
126
  Args:
91
127
  parser: parser to fill with arguments
92
128
 
93
129
  """
94
- for flags, keywords in self.get_arguments().items():
95
- try:
96
- parser.add_argument(*flags, **keywords)
97
- except argparse.ArgumentError:
98
- # flag has already been added, pass
99
- pass
130
+ self.add_arguments_from_specification(parser, self.get_arguments())
100
131
 
101
132
  def reproduce_cli_args(self, args_ns):
102
133
  """Reproduce the list of arguments needed on the command line"""
@@ -253,6 +284,133 @@ class DetectionParser(ParserBase):
253
284
  return args
254
285
 
255
286
 
287
+ class BareArgumentParser(ParserBase):
288
+ """Takes in the argument specification (used in plugins and get_arguments) to parse args
289
+
290
+ This parser takes in and uses a raw specification of arguments as seen in plugins and arguments to perform argument
291
+ parsing. The spec is a map of flag tuples to argparse kwargs.
292
+
293
+ Argument handling only validates using the checking_function which is a function taking in keyword arguments for
294
+ each cli argument specified. This function will be called as such: `checking_function(**args)`. Use None to skip
295
+ argument checking. checking_function should raise ValueError to indicate an error with an argument.
296
+ """
297
+
298
+ def __init__(self, specification, checking_function=None):
299
+ """Initialize this parser with the provided specification"""
300
+ self.specification = specification
301
+ self.checking_function = checking_function
302
+
303
+ def get_arguments(self):
304
+ """Raw specification is returned immediately"""
305
+ return self.specification
306
+
307
+ def handle_arguments(self, args, **kwargs):
308
+ """Handle argument calls checking function to validate"""
309
+ if self.checking_function is not None:
310
+ self.checking_function(**self.extract_arguments(args))
311
+ return args
312
+
313
+ def extract_arguments(self, args) -> Dict[str, Any]:
314
+ """Extract argument values from the args namespace into a map matching the original specification
315
+
316
+ This function extracts arguments matching the original specification and returns them as a dictionary of key-
317
+ value pairs.
318
+
319
+ Return:
320
+ filled arguments dictionary
321
+ """
322
+ expected_args = self.specification
323
+ argument_destinations = [
324
+ (
325
+ value["dest"]
326
+ if "dest" in value
327
+ else key[0].replace("--", "").replace("-", "_")
328
+ )
329
+ for key, value in expected_args.items()
330
+ ]
331
+ filled_arguments = {
332
+ destination: getattr(args, destination)
333
+ for destination in argument_destinations
334
+ }
335
+ return filled_arguments
336
+
337
+
338
+ class IndividualPluginParser(BareArgumentParser):
339
+ """Parser for an individual plugin's command line
340
+
341
+ A CLI parser for an individual plugin. This handles all the functions and arguments that apply to the parsing of a
342
+ single plugin's arguments. It also handles FEATURE plugin disable flags.
343
+ """
344
+
345
+ def __init__(self, plugin_system: Plugins, plugin_class: type):
346
+ """Initialize the plugin parser
347
+
348
+ Args:
349
+ plugin_system: Plugins object used to work with the plugin system
350
+ plugin_class: plugin class used for this specific parser
351
+ """
352
+ # Add disable flags for feature type plugins
353
+ super().__init__(plugin_class.get_arguments(), plugin_class.check_arguments)
354
+ self.disable_flag_destination = (
355
+ f"disable-{plugin_class.get_name()}".lower().replace("-", "_")
356
+ )
357
+ self.plugin_class = plugin_class
358
+ self.plugin_system = plugin_system
359
+
360
+ def get_arguments(self):
361
+ """Get the arguments for this plugin
362
+
363
+ The individual plugin parser will read the arguments from the supplied plugin class. Additionally, if the
364
+ plugin_class's plugin_type is FEATURE then this parser will add an disable flag to allow users to turn disable
365
+ the plugin feature.
366
+ """
367
+ arguments = {}
368
+ if self.plugin_class.type == PluginType.FEATURE:
369
+ arguments.update(
370
+ {
371
+ (f"--disable-{self.plugin_class.get_name()}",): {
372
+ "action": "store_true",
373
+ "default": False,
374
+ "dest": self.disable_flag_destination,
375
+ "help": f"Disable the {self.plugin_class.category} plugin '{self.plugin_class.get_name()}'",
376
+ }
377
+ }
378
+ )
379
+ arguments.update(super().get_arguments())
380
+ return arguments
381
+
382
+ def handle_arguments(self, args, **kwargs):
383
+ """Handle the given arguments for a plugin
384
+
385
+ This will process the arguments for a given plugin. Additionally, it will construct the plugin object and
386
+ supply the constructed object to the plugin system if the plugin is a selection or is enabled.
387
+
388
+ Args:
389
+ args: argparse namespace
390
+ """
391
+ arguments = super().handle_arguments(
392
+ args, **kwargs
393
+ ) # Perform argument checking first
394
+ if not getattr(args, self.disable_flag_destination, False):
395
+ # Remove the disable flag from the arguments
396
+ plugin_arguments = {
397
+ key: value
398
+ for key, value in self.extract_arguments(arguments).items()
399
+ if key != self.disable_flag_destination
400
+ }
401
+ plugin_zero_argument_class = functools.partial(
402
+ self.plugin_class.get_implementor(), **plugin_arguments
403
+ )
404
+ self.plugin_system.add_bound_class(
405
+ self.plugin_class.category, plugin_zero_argument_class
406
+ )
407
+ return arguments
408
+
409
+ def get_plugin_class(self):
410
+ """Plugin class accessor"""
411
+ return self.plugin_class
412
+
413
+
256
414
  class PluginArgumentParser(ParserBase):
257
415
  """Parser for arguments coming from plugins"""
258
416
 
@@ -262,62 +420,69 @@ class PluginArgumentParser(ParserBase):
262
420
  "communication": "ip",
263
421
  }
264
422
 
265
- def __init__(self):
266
- """Initialize the plugin information for this parser"""
423
+ def __init__(self, plugin_system: Plugins = None):
424
+ """Initialize the plugin information for this parser
425
+
426
+ This will initialize this plugin argument parser with the supplied plugin system. If not supplied this will use
427
+ the system plugin singleton, which is configured elsewhere.
428
+ """
429
+ # Accept the supplied plugin system defaulting to the global singleton
430
+ self.plugin_system = plugin_system if plugin_system else Plugins.system()
267
431
  self._plugin_map = {
268
- category: Plugins.system().get_plugins(category)
269
- for category in Plugins.system().get_categories()
432
+ category: [
433
+ IndividualPluginParser(self.plugin_system, plugin)
434
+ for plugin in self.plugin_system.get_plugins(category)
435
+ ]
436
+ for category in self.plugin_system.get_categories()
270
437
  }
271
438
 
272
- @staticmethod
273
- def safe_add_argument(parser, *flags, **keywords):
274
- """ Add an argument allowing duplicates
439
+ def fill_parser(self, parser):
440
+ """Fill supplied parser with grouped arguments
275
441
 
276
- Add arguments to the parser (passes through *flags and **keywords) to the supplied parser. This method traps
277
- errors to prevent duplicates.
442
+ Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
443
+ arguments based on the constituent parser that the argument comes from. Category specific arguments are also
444
+ added (i.e. SELECTION type selection arguments).
278
445
 
279
446
  Args:
280
- parser: parser or argument group to add arguments to
281
- *flags: positional arguments passed to `add_argument`
282
- **keywords: key word arguments passed to `add_argument`
447
+ parser: parser to fill
283
448
  """
284
- try:
285
- parser.add_argument(*flags, **keywords)
286
- except argparse.ArgumentError:
287
- # flag has already been added, pass
288
- pass
449
+ for category, plugin_parsers in self._plugin_map.items():
450
+ # Add category specific flags (selection flags, etc)
451
+ argument_group = parser.add_argument_group(
452
+ title=f"{category.title()} Plugin Options"
453
+ )
454
+ self.add_arguments_from_specification(
455
+ argument_group, self.get_category_arguments(category)
456
+ )
289
457
 
290
- def fill_parser(self, parser):
291
- """ File supplied parser with grouped arguments
458
+ # Handle the individual plugin parsers
459
+ for plugin_parser in plugin_parsers:
460
+ plugin = plugin_parser.get_plugin_class()
461
+ argument_group = parser.add_argument_group(
462
+ title=f"{category.title()} Plugin '{plugin.get_name()}' Options"
463
+ )
464
+ plugin_parser.fill_parser(argument_group)
292
465
 
293
- Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
294
- arguments based on the constituent that sources the argument.
466
+ def get_category_arguments(self, category):
467
+ """Get category arguments for a given plugin category
468
+
469
+ This function will generate category arguments for the supplied category. These arguments will follow the
470
+ standard argument specification of a dictionary of flag tuples to argparse keyword arguments.
471
+
472
+ Currently category specific arguments are just selection flags for SELECTION type plugins.
295
473
 
296
474
  Args:
297
- parser: parser to fill
475
+ category: category arguments
298
476
  """
299
- for category, plugins in self._plugin_map.items():
300
- argument_group = parser.add_argument_group(title=f"{category.title()} Plugin Options")
301
- for flags, keywords in self.get_category_arguments(category).items():
302
- self.safe_add_argument(argument_group, *flags, **keywords)
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)
477
+ plugin_type = self.plugin_system.get_category_plugin_type(category)
478
+ plugins = [
479
+ plugin_parser.get_plugin_class()
480
+ for plugin_parser in self._plugin_map[category]
481
+ ]
314
482
 
315
- def get_category_arguments(self, category):
316
- """ Get arguments for a plugin category """
317
483
  arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
318
- plugins = self._plugin_map[category]
484
+
319
485
  # Add category options: SELECTION plugins add a selection flag
320
- plugin_type = Plugins.get_category_plugin_type(category)
321
486
  if plugin_type == PluginType.SELECTION:
322
487
  arguments.update(
323
488
  {
@@ -333,81 +498,54 @@ class PluginArgumentParser(ParserBase):
333
498
  return arguments
334
499
 
335
500
  def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
336
- """Return arguments to used in plugins"""
501
+ """Return arguments to used in plugin system
502
+
503
+ This will return the command line arguments all the plugins contained within the supplied plugin system. This
504
+ will recursively return plugins from all of the IndividualPluginParser objects composing this plugin argument
505
+ parser. Arguments are returned in the standard specification form of tuple of flags mapped to a dictionary of
506
+ argparse kwarg inputs.
507
+ """
337
508
  arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
338
- for category, plugins in self._plugin_map.items():
509
+ for category, plugin_parsers in self._plugin_map.items():
339
510
  arguments.update(self.get_category_arguments(category))
340
- for plugin in plugins:
341
- # Add disable flags for feature type plugins
342
- if plugin.type == PluginType.FEATURE:
343
- arguments.update({
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())
511
+ [
512
+ arguments.update(plugin_parser.get_arguments())
513
+ for plugin_parser in plugin_parsers
514
+ ]
351
515
  return arguments
352
516
 
353
517
  def handle_arguments(self, args, **kwargs):
354
- """Handles the arguments"""
355
- for category, plugins in self._plugin_map.items():
356
- plugin_type = Plugins.get_category_plugin_type(category)
518
+ """Handle the plugin arguments
357
519
 
520
+ This will handle the plugin arguments delegating each to the IndividualPluginParser. For SELECTION plugins this
521
+ will bind a single instance of the selected plugin to its arguments. For FEATURE plugins it will bind arguments
522
+ to every enabled plugin. Bound plugins are registered with the plugin system.
523
+ """
524
+ for category, plugin_parsers in self._plugin_map.items():
525
+ plugin_type = self.plugin_system.get_category_plugin_type(category)
526
+ self.plugin_system.start_loading(category)
358
527
  # Selection plugins choose one plugin and instantiate it
359
528
  if plugin_type == PluginType.SELECTION:
360
- selection_string = getattr(args, f"{category}_selection")
361
- matching_plugins = [plugin for plugin in plugins if plugin.get_name() == selection_string]
362
- assert len(matching_plugins) == 1, "Plugin selection system failed"
363
- selection_class = matching_plugins[0].plugin_class
364
- filled_arguments = self.extract_plugin_arguments(args, selection_class)
365
- selection_instance = selection_class(**filled_arguments)
366
- setattr(args, f"{category}_selection_instance", selection_instance)
529
+ try:
530
+ self.plugin_system.get_selected_class(category)
531
+ except PluginsNotLoadedException:
532
+ selection_string = getattr(args, f"{category}_selection")
533
+ matching_plugin_parsers = [
534
+ plugin_parser
535
+ for plugin_parser in plugin_parsers
536
+ if plugin_parser.get_plugin_class().get_name()
537
+ == selection_string
538
+ ]
539
+ assert (
540
+ len(matching_plugin_parsers) == 1
541
+ ), "Plugin selection system failed"
542
+ args = matching_plugin_parsers[0].handle_arguments(args, **kwargs)
367
543
  # Feature plugins instantiate all enabled plugins
368
544
  elif plugin_type == PluginType.FEATURE:
369
- enabled_plugins = [
370
- plugin for plugin in plugins
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)
545
+ for plugin_parser in plugin_parsers:
546
+ args = plugin_parser.handle_arguments(args, **kwargs)
378
547
  return args
379
548
 
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
549
 
412
550
  class CompositeParser(ParserBase):
413
551
  """Composite parser handles parsing as a composition of multiple other parsers"""
@@ -415,7 +553,15 @@ class CompositeParser(ParserBase):
415
553
  def __init__(self, constituents, description=None):
416
554
  """Construct this parser by instantiating the sub-parsers"""
417
555
  self.given = description
418
- constructed = [constituent() for constituent in constituents]
556
+ constructed = [
557
+ constituent() if callable(constituent) else constituent
558
+ for constituent in constituents
559
+ ]
560
+ # Check to ensure everything passed in became a ParserBase after construction
561
+ for i, construct in enumerate(constructed):
562
+ assert isinstance(
563
+ construct, ParserBase
564
+ ), f"{construct.__class__.__name__} ({i}) not a ParserBase child"
419
565
  flattened = [
420
566
  item.constituents if isinstance(item, CompositeParser) else [item]
421
567
  for item in constructed
@@ -423,7 +569,7 @@ class CompositeParser(ParserBase):
423
569
  self.constituent_parsers = {*itertools.chain.from_iterable(flattened)}
424
570
 
425
571
  def fill_parser(self, parser):
426
- """ File supplied parser with grouped arguments
572
+ """File supplied parser with grouped arguments
427
573
 
428
574
  Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
429
575
  arguments based on the constituent that sources the argument.
@@ -435,7 +581,9 @@ class CompositeParser(ParserBase):
435
581
  if isinstance(constituent, (PluginArgumentParser, CompositeParser)):
436
582
  constituent.fill_parser(parser)
437
583
  else:
438
- argument_group = parser.add_argument_group(title=constituent.description)
584
+ argument_group = parser.add_argument_group(
585
+ title=constituent.description
586
+ )
439
587
  constituent.fill_parser(argument_group)
440
588
 
441
589
  @property
@@ -649,7 +797,15 @@ class DictionaryParser(DetectionParser):
649
797
  "default": None,
650
798
  "required": False,
651
799
  "type": str,
652
- "help": "Path to packet specification.",
800
+ "help": "Path to packet XML specification (should not be used if JSON packet definitions are used).",
801
+ },
802
+ ("--packet-set-name",): {
803
+ "dest": "packet_set_name",
804
+ "action": "store",
805
+ "default": None,
806
+ "required": False,
807
+ "type": str,
808
+ "help": "Name of packet set defined in the JSON dictionary.",
653
809
  },
654
810
  },
655
811
  }
@@ -730,6 +886,7 @@ class StandardPipelineParser(CompositeParser):
730
886
  "dictionary": args_ns.dictionary,
731
887
  "file_store": args_ns.files_storage_directory,
732
888
  "packet_spec": args_ns.packet_spec,
889
+ "packet_set_name": args_ns.packet_set_name,
733
890
  "logging_prefix": args_ns.logs,
734
891
  }
735
892
  pipeline = pipeline if pipeline else StandardPipeline()
@@ -753,13 +910,16 @@ class CommParser(CompositeParser):
753
910
  CommExtraParser,
754
911
  MiddleWareParser,
755
912
  LogDeployParser,
756
- PluginArgumentParser,
757
913
  ]
758
914
 
759
915
  def __init__(self):
760
916
  """Initialization"""
917
+ # Added here to ensure the call to Plugins does not interfere with the full plugin system
918
+ comm_plugin_parser_instance = PluginArgumentParser(
919
+ Plugins(["communication", "framing"])
920
+ )
761
921
  super().__init__(
762
- constituents=self.CONSTITUENTS,
922
+ constituents=self.CONSTITUENTS + [comm_plugin_parser_instance],
763
923
  description="Communications bridge application",
764
924
  )
765
925
 
@@ -867,9 +1027,7 @@ class BinaryDeployment(DetectionParser):
867
1027
  class SearchArgumentsParser(ParserBase):
868
1028
  """Parser for search arguments"""
869
1029
 
870
- DESCRIPTION = (
871
- "Searching and filtering options"
872
- )
1030
+ DESCRIPTION = "Searching and filtering options"
873
1031
 
874
1032
  def __init__(self, command_name: str) -> None:
875
1033
  self.command_name = command_name
@@ -29,6 +29,7 @@ import fprime_gds.common.logger
29
29
  import fprime_gds.executables.cli
30
30
  from fprime_gds.common.communication.updown import Downlinker, Uplinker
31
31
  from fprime_gds.common.zmq_transport import ZmqGround
32
+ from fprime_gds.plugin.system import Plugins
32
33
 
33
34
  # Uses non-standard PIP package pyserial, so test the waters before getting a hard-import crash
34
35
  try:
@@ -46,6 +47,8 @@ def main():
46
47
 
47
48
  :return: return code
48
49
  """
50
+ # comm.py supports 2 and only 2 plugin categories
51
+ Plugins.system(["communication", "framing"])
49
52
  args, _ = fprime_gds.executables.cli.ParserBase.parse_args(
50
53
  [
51
54
  fprime_gds.executables.cli.LogDeployParser,
@@ -71,11 +74,11 @@ def main():
71
74
  args.tts_addr, args.tts_port
72
75
  )
73
76
 
74
- adapter = args.communication_selection_instance
77
+ adapter = Plugins.system().get_selected_class("communication")()
75
78
 
76
79
  # Set the framing class used and pass it to the uplink and downlink component constructions giving each a separate
77
80
  # instantiation
78
- framer_instance = args.framing_selection_instance
81
+ framer_instance = Plugins.system().get_selected_class("framing")()
79
82
  LOGGER.info(
80
83
  "Starting uplinker/downlinker connecting to FSW using %s with %s",
81
84
  args.communication_selection,
@@ -14,7 +14,7 @@ from copy import deepcopy
14
14
  from typing import Callable, List, Union
15
15
 
16
16
  import argcomplete
17
- import pkg_resources
17
+ import importlib.metadata
18
18
 
19
19
  # NOTE: These modules are now only lazily loaded below as needed, due to slow
20
20
  # performance when importing them
@@ -230,7 +230,7 @@ class CommandSubparserInjector(CliSubparserInjectorBase):
230
230
  from fprime_gds.common.pipeline.dictionaries import Dictionaries
231
231
 
232
232
  dictionary = Dictionaries()
233
- dictionary.load_dictionaries(dict_path, None)
233
+ dictionary.load_dictionaries(dict_path, None, None)
234
234
  command_names = dictionary.command_name.keys()
235
235
  return [name for name in command_names if name.startswith(prefix)]
236
236
 
@@ -322,7 +322,7 @@ def create_parser():
322
322
  parser = argparse.ArgumentParser(
323
323
  description="provides utilities for interacting with the F' Ground Data System (GDS)"
324
324
  )
325
- fprime_gds_version = pkg_resources.get_distribution("fprime-gds").version
325
+ fprime_gds_version = importlib.metadata.version("fprime-gds")
326
326
  parser.add_argument("-V", "--version", action="version", version=fprime_gds_version)
327
327
 
328
328
  # Add subcommands to the parser
@@ -16,6 +16,7 @@ from fprime_gds.executables.cli import (
16
16
  PluginArgumentParser,
17
17
  )
18
18
  from fprime_gds.executables.utils import AppWrapperException, run_wrapped_application
19
+ from fprime_gds.plugin.system import Plugins
19
20
 
20
21
  BASE_MODULE_ARGUMENTS = [sys.executable, "-u", "-m"]
21
22
 
@@ -174,7 +175,7 @@ def launch_comm(parsed_args):
174
175
 
175
176
 
176
177
  def launch_plugin(plugin_class_instance):
177
- """ Launch a plugin instance """
178
+ """Launch a plugin instance"""
178
179
  plugin_name = getattr(plugin_class_instance, "get_name", lambda: cls.__name__)()
179
180
  return launch_process(
180
181
  plugin_class_instance.get_process_invocation(),
@@ -215,8 +216,14 @@ def main():
215
216
  # Launch launchers and wait for the last app to finish
216
217
  try:
217
218
  procs = [launcher(parsed_args) for launcher in launchers]
218
- _ = [launch_plugin(cls) for cls in parsed_args.gds_app_enabled_instances]
219
- _ = [instance.run() for instance in parsed_args.gds_function_enabled_instances]
219
+ _ = [
220
+ launch_plugin(cls())
221
+ for cls in Plugins.system().get_feature_classes("gds_app")
222
+ ]
223
+ _ = [
224
+ instance().run()
225
+ for instance in Plugins.system().get_feature_classes("gds_function")
226
+ ]
220
227
 
221
228
  print("[INFO] F prime is now running. CTRL-C to shutdown all components.")
222
229
  procs[-1].wait()
@@ -67,7 +67,7 @@ Vue.component("channel-table", {
67
67
  if (item.time == null || item.val == null) {
68
68
  return ["", "0x" + item.id.toString(16), template.full_name, ""];
69
69
  }
70
- return [timeToString(item.datetime || item.time), "0x" + item.id.toString(16), template.full_name,
70
+ return [timeToString(item.time || item.datetime), "0x" + item.id.toString(16), template.full_name,
71
71
  (typeof(item.display_text) !== "undefined")? item.display_text : item.val]
72
72
  },
73
73
  /**
@@ -80,7 +80,7 @@ Vue.component("event-list", {
80
80
  const msg = '<span title="' + groups[0] + '">' + command_mnemonic + '</span>'
81
81
  display_text = display_text.replace(OPREG, msg);
82
82
  }
83
- return [timeToString(item.datetime || item.time), "0x" + item.id.toString(16), template.full_name,
83
+ return [timeToString(item.time || item.datetime), "0x" + item.id.toString(16), template.full_name,
84
84
  template.severity.value.replace("EventSeverity.", ""), display_text];
85
85
  },
86
86
  /**