fprime-gds 3.4.4a1__py3-none-any.whl → 3.4.4a2__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 (27) hide show
  1. fprime_gds/common/data_types/event_data.py +6 -1
  2. fprime_gds/common/data_types/exceptions.py +16 -11
  3. fprime_gds/common/loaders/ch_json_loader.py +107 -0
  4. fprime_gds/common/loaders/ch_xml_loader.py +5 -5
  5. fprime_gds/common/loaders/cmd_json_loader.py +85 -0
  6. fprime_gds/common/loaders/dict_loader.py +1 -1
  7. fprime_gds/common/loaders/event_json_loader.py +108 -0
  8. fprime_gds/common/loaders/event_xml_loader.py +10 -6
  9. fprime_gds/common/loaders/json_loader.py +222 -0
  10. fprime_gds/common/loaders/xml_loader.py +31 -9
  11. fprime_gds/common/pipeline/dictionaries.py +38 -3
  12. fprime_gds/common/tools/seqgen.py +4 -4
  13. fprime_gds/common/utils/string_util.py +57 -65
  14. fprime_gds/common/zmq_transport.py +4 -2
  15. fprime_gds/executables/cli.py +21 -39
  16. fprime_gds/executables/comm.py +3 -10
  17. fprime_gds/executables/data_product_writer.py +935 -0
  18. fprime_gds/executables/utils.py +23 -11
  19. fprime_gds/flask/sequence.py +1 -1
  20. fprime_gds/flask/static/addons/commanding/command-input.js +3 -2
  21. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/METADATA +3 -2
  22. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/RECORD +27 -22
  23. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/WHEEL +1 -1
  24. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/entry_points.txt +2 -3
  25. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/LICENSE.txt +0 -0
  26. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/NOTICE.txt +0 -0
  27. {fprime_gds-3.4.4a1.dist-info → fprime_gds-3.4.4a2.dist-info}/top_level.txt +0 -0
@@ -44,7 +44,12 @@ class EventData(sys_data.SysData):
44
44
  self.display_text = event_temp.description
45
45
  elif event_temp.format_str == "":
46
46
  args_template = self.template.get_args()
47
- self.display_text = str([{args_template[index][0]:arg.val} for index, arg in enumerate(event_args)])
47
+ self.display_text = str(
48
+ [
49
+ {args_template[index][0]: arg.val}
50
+ for index, arg in enumerate(event_args)
51
+ ]
52
+ )
48
53
  else:
49
54
  self.display_text = format_string_template(
50
55
  event_temp.format_str, tuple([arg.val for arg in event_args])
@@ -2,10 +2,25 @@
2
2
  Created on Jan 9, 2015
3
3
  @author: reder
4
4
  """
5
+
5
6
  # Exception classes for all controllers modules
6
7
 
7
8
 
8
- class GseControllerException(Exception):
9
+ class FprimeGdsException(Exception):
10
+ """
11
+ Base Exception for exceptions that need to be handled at the system-level
12
+ by the GDS
13
+ """
14
+
15
+ pass
16
+
17
+
18
+ class GdsDictionaryParsingException(FprimeGdsException):
19
+ pass
20
+
21
+
22
+ # Note: Gse is the historical name for what is now called the GDS
23
+ class GseControllerException(FprimeGdsException):
9
24
  def __init__(self, val):
10
25
  self.except_msg = val
11
26
  super().__init__(val)
@@ -27,13 +42,3 @@ class GseControllerUndefinedFileException(GseControllerException):
27
42
  class GseControllerParsingException(GseControllerException):
28
43
  def __init__(self, val):
29
44
  super().__init__(f"Parsing error: {str(val)}")
30
-
31
-
32
- class GseControllerMnemonicMismatchException(GseControllerException):
33
- def __init__(self, val1, val2):
34
- super().__init__(f"ID mismatch ({str(val1)}, {str(val2)})!")
35
-
36
-
37
- class GseControllerStatusUpdateException(GseControllerException):
38
- def __init__(self, val):
39
- super().__init__(f"Bad status update mode: {str(val)}!")
@@ -0,0 +1,107 @@
1
+ """
2
+ ch_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of channels
5
+
6
+ @author thomas-bc
7
+ """
8
+
9
+ from fprime_gds.common.templates.ch_template import ChTemplate
10
+ from fprime_gds.common.loaders.json_loader import JsonLoader
11
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
12
+
13
+
14
+ class ChJsonLoader(JsonLoader):
15
+ """Class to load python based telemetry channel dictionaries"""
16
+
17
+ CHANNELS_FIELD = "telemetryChannels"
18
+
19
+ ID = "id"
20
+ NAME = "name"
21
+ DESC = "annotation"
22
+ TYPE = "type"
23
+ FMT_STR = "format"
24
+ LIMIT_FIELD = "limit"
25
+ LIMIT_LOW = "low"
26
+ LIMIT_HIGH = "high"
27
+ LIMIT_RED = "red"
28
+ LIMIT_ORANGE = "orange"
29
+ LIMIT_YELLOW = "yellow"
30
+
31
+ def construct_dicts(self, _):
32
+ """
33
+ Constructs and returns python dictionaries keyed on id and name
34
+
35
+ Args:
36
+ _: Unused argument (inherited)
37
+ Returns:
38
+ A tuple with two channel dictionaries (python type dict):
39
+ (id_dict, name_dict). The keys should be the channels' id and
40
+ name fields respectively and the values should be ChTemplate
41
+ objects.
42
+ """
43
+ id_dict = {}
44
+ name_dict = {}
45
+
46
+ if self.CHANNELS_FIELD not in self.json_dict:
47
+ raise GdsDictionaryParsingException(
48
+ f"Ground Dictionary missing '{self.CHANNELS_FIELD}' field: {str(self.json_file)}"
49
+ )
50
+
51
+ for ch_dict in self.json_dict[self.CHANNELS_FIELD]:
52
+ # Create a channel template object
53
+ ch_temp = self.construct_template_from_dict(ch_dict)
54
+
55
+ id_dict[ch_temp.get_id()] = ch_temp
56
+ name_dict[ch_temp.get_full_name()] = ch_temp
57
+
58
+ return (
59
+ dict(sorted(id_dict.items())),
60
+ dict(sorted(name_dict.items())),
61
+ self.get_versions(),
62
+ )
63
+
64
+ def construct_template_from_dict(self, channel_dict: dict) -> ChTemplate:
65
+ try:
66
+ ch_id = channel_dict[self.ID]
67
+ # The below assignment also raises a ValueError if the name does not contain a '.'
68
+ component_name, channel_name = channel_dict[self.NAME].split(".")
69
+ if not component_name or not channel_name:
70
+ raise ValueError()
71
+
72
+ type_obj = self.parse_type(channel_dict[self.TYPE])
73
+ except ValueError as e:
74
+ raise GdsDictionaryParsingException(
75
+ f"Channel dictionary entry malformed, expected name of the form '<COMP_NAME>.<CH_NAME>' in : {str(channel_dict)}"
76
+ )
77
+ except KeyError as e:
78
+ raise GdsDictionaryParsingException(
79
+ f"{str(e)} key missing from Channel dictionary entry or its associated type in the dictionary: {str(channel_dict)}"
80
+ )
81
+
82
+ format_str = JsonLoader.preprocess_format_str(channel_dict.get(self.FMT_STR))
83
+
84
+ limit_field = channel_dict.get(self.LIMIT_FIELD)
85
+ limit_low = limit_field.get(self.LIMIT_LOW) if limit_field else None
86
+ limit_high = limit_field.get(self.LIMIT_HIGH) if limit_field else None
87
+ limit_low_yellow = limit_low.get(self.LIMIT_YELLOW) if limit_low else None
88
+ limit_low_red = limit_low.get(self.LIMIT_RED) if limit_low else None
89
+ limit_low_orange = limit_low.get(self.LIMIT_ORANGE) if limit_low else None
90
+ limit_high_yellow = limit_high.get(self.LIMIT_YELLOW) if limit_high else None
91
+ limit_high_red = limit_high.get(self.LIMIT_RED) if limit_high else None
92
+ limit_high_orange = limit_high.get(self.LIMIT_ORANGE) if limit_high else None
93
+
94
+ return ChTemplate(
95
+ ch_id,
96
+ channel_name,
97
+ component_name,
98
+ type_obj,
99
+ ch_fmt_str=format_str,
100
+ ch_desc=channel_dict.get(self.DESC),
101
+ low_red=limit_low_red,
102
+ low_orange=limit_low_orange,
103
+ low_yellow=limit_low_yellow,
104
+ high_yellow=limit_high_yellow,
105
+ high_orange=limit_high_orange,
106
+ high_red=limit_high_red,
107
+ )
@@ -49,15 +49,15 @@ class ChXmlLoader(XmlLoader):
49
49
  respectively and the values are ChTemplate objects
50
50
  """
51
51
  xml_tree = self.get_xml_tree(path)
52
- versions = xml_tree.attrib.get("framework_version", "unknown"), xml_tree.attrib.get("project_version", "unknown")
52
+ versions = xml_tree.attrib.get(
53
+ "framework_version", "unknown"
54
+ ), xml_tree.attrib.get("project_version", "unknown")
53
55
 
54
56
  # Check if xml dict has channels section
55
57
  ch_section = self.get_xml_section(self.CH_SECT, xml_tree)
56
58
  if ch_section is None:
57
59
  msg = f"Xml dict did not have a {self.CH_SECT} section"
58
- raise exceptions.GseControllerParsingException(
59
- msg
60
- )
60
+ raise exceptions.GseControllerParsingException(msg)
61
61
 
62
62
  id_dict = {}
63
63
  name_dict = {}
@@ -84,7 +84,7 @@ class ChXmlLoader(XmlLoader):
84
84
  ch_desc = ch_dict[self.DESC_TAG]
85
85
 
86
86
  if self.FMT_STR_TAG in ch_dict:
87
- ch_fmt_str = ch_dict[self.FMT_STR_TAG]
87
+ ch_fmt_str = XmlLoader.preprocess_format_str(ch_dict[self.FMT_STR_TAG])
88
88
 
89
89
  # TODO we need to convert these into numbers, is this the best
90
90
  # way to do it?
@@ -0,0 +1,85 @@
1
+ """
2
+ cmd_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of commands
5
+
6
+ @author thomas-bc
7
+ """
8
+
9
+ from fprime_gds.common.templates.cmd_template import CmdTemplate
10
+ from fprime_gds.common.loaders.json_loader import JsonLoader
11
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
12
+
13
+
14
+ class CmdJsonLoader(JsonLoader):
15
+ """Class to load json based command dictionaries"""
16
+
17
+ COMMANDS_FIELD = "commands"
18
+
19
+ NAME = "name"
20
+ OPCODE = "opcode"
21
+ DESC = "annotation"
22
+ PARAMETERS = "formalParams"
23
+
24
+ def construct_dicts(self, _):
25
+ """
26
+ Constructs and returns python dictionaries keyed on id and name
27
+
28
+ Args:
29
+ _: Unused argument (inherited)
30
+ Returns:
31
+ A tuple with two command dictionaries (python type dict):
32
+ (id_dict, name_dict). The keys are the events' id and name fields
33
+ respectively and the values are CmdTemplate objects
34
+ """
35
+ id_dict = {}
36
+ name_dict = {}
37
+
38
+ if self.COMMANDS_FIELD not in self.json_dict:
39
+ raise GdsDictionaryParsingException(
40
+ f"Ground Dictionary missing '{self.COMMANDS_FIELD}' field: {str(self.json_file)}"
41
+ )
42
+
43
+ for cmd_dict in self.json_dict[self.COMMANDS_FIELD]:
44
+ cmd_temp = self.construct_template_from_dict(cmd_dict)
45
+
46
+ id_dict[cmd_temp.get_id()] = cmd_temp
47
+ name_dict[cmd_temp.get_full_name()] = cmd_temp
48
+
49
+ return (
50
+ dict(sorted(id_dict.items())),
51
+ dict(sorted(name_dict.items())),
52
+ self.get_versions(),
53
+ )
54
+
55
+ def construct_template_from_dict(self, cmd_dict: dict) -> CmdTemplate:
56
+ try:
57
+ cmd_comp, cmd_mnemonic = cmd_dict[self.NAME].split(".")
58
+ cmd_opcode = cmd_dict[self.OPCODE]
59
+ cmd_desc = cmd_dict.get(self.DESC)
60
+ except ValueError as e:
61
+ raise GdsDictionaryParsingException(
62
+ f"Command dictionary entry malformed, expected name of the form '<COMP_NAME>.<CMD_NAME>' in : {str(cmd_dict)}"
63
+ )
64
+ except KeyError as e:
65
+ raise GdsDictionaryParsingException(
66
+ f"{str(e)} key missing from Command dictionary entry: {str(cmd_dict)}"
67
+ )
68
+ # Parse Arguments
69
+ cmd_args = []
70
+ for param in cmd_dict.get(self.PARAMETERS, []):
71
+ try:
72
+ param_name = param["name"]
73
+ param_type = self.parse_type(param["type"])
74
+ except KeyError as e:
75
+ raise GdsDictionaryParsingException(
76
+ f"{str(e)} key missing from Command parameter or its associated type in the dictionary: {str(param)}"
77
+ )
78
+ cmd_args.append(
79
+ (
80
+ param_name,
81
+ param.get("annotation"),
82
+ param_type,
83
+ )
84
+ )
85
+ return CmdTemplate(cmd_opcode, cmd_mnemonic, cmd_comp, cmd_args, cmd_desc)
@@ -93,7 +93,7 @@ class DictLoader:
93
93
  return name_dict
94
94
 
95
95
  def get_versions(self):
96
- """ Get version tuple """
96
+ """Get version tuple"""
97
97
  return self.versions
98
98
 
99
99
  def construct_dicts(self, path):
@@ -0,0 +1,108 @@
1
+ """
2
+ event_json_loader.py:
3
+
4
+ Loads flight dictionary (JSON) and returns id and mnemonic based Python dictionaries of events
5
+
6
+ @author thomas-bc
7
+ """
8
+
9
+ from fprime_gds.common.templates.event_template import EventTemplate
10
+ from fprime_gds.common.utils.event_severity import EventSeverity
11
+ from fprime_gds.common.loaders.json_loader import JsonLoader
12
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
13
+
14
+
15
+ class EventJsonLoader(JsonLoader):
16
+ """Class to load json based event dictionaries"""
17
+
18
+ EVENTS_FIELD = "events"
19
+
20
+ NAME = "name"
21
+ ID = "id"
22
+ SEVERITY = "severity"
23
+ FMT_STR = "format"
24
+ DESC = "annotation"
25
+ PARAMETERS = "formalParams"
26
+
27
+ def construct_dicts(self, _):
28
+ """
29
+ Constructs and returns python dictionaries keyed on id and name
30
+
31
+ This function should not be called directly, instead, use
32
+ get_id_dict(path) and get_name_dict(path)
33
+
34
+ Args:
35
+ _: Unused argument (inherited)
36
+
37
+ Returns:
38
+ A tuple with two event dictionaries (python type dict):
39
+ (id_dict, name_dict). The keys are the events' id and name fields
40
+ respectively and the values are ChTemplate objects
41
+ """
42
+ id_dict = {}
43
+ name_dict = {}
44
+
45
+ if self.EVENTS_FIELD not in self.json_dict:
46
+ raise GdsDictionaryParsingException(
47
+ f"Ground Dictionary missing '{self.EVENTS_FIELD}' field: {str(self.json_file)}"
48
+ )
49
+
50
+ for event_dict in self.json_dict[self.EVENTS_FIELD]:
51
+ event_temp = self.construct_template_from_dict(event_dict)
52
+
53
+ id_dict[event_temp.get_id()] = event_temp
54
+ name_dict[event_temp.get_full_name()] = event_temp
55
+
56
+ return (
57
+ dict(sorted(id_dict.items())),
58
+ dict(sorted(name_dict.items())),
59
+ self.get_versions(),
60
+ )
61
+
62
+ def construct_template_from_dict(self, event_dict: dict):
63
+ try:
64
+ event_comp, event_name = event_dict[self.NAME].split(".")
65
+ event_id = event_dict[self.ID]
66
+ event_severity = EventSeverity[event_dict[self.SEVERITY]]
67
+ except ValueError as e:
68
+ raise GdsDictionaryParsingException(
69
+ f"Event dictionary entry malformed, expected name of the form '<COMP_NAME>.<EVENT_NAME>' in : {str(event_dict)}"
70
+ )
71
+ except KeyError as e:
72
+ raise GdsDictionaryParsingException(
73
+ f"{str(e)} key missing from Event dictionary entry: {str(event_dict)}"
74
+ )
75
+
76
+ event_fmt_str = JsonLoader.preprocess_format_str(
77
+ event_dict.get(self.FMT_STR, "")
78
+ )
79
+
80
+ event_desc = event_dict.get(self.DESC)
81
+
82
+ # Parse arguments
83
+ event_args = []
84
+ for arg in event_dict.get(self.PARAMETERS, []):
85
+ try:
86
+ arg_name = arg["name"]
87
+ arg_type = self.parse_type(arg["type"])
88
+ except KeyError as e:
89
+ raise GdsDictionaryParsingException(
90
+ f"{str(e)} key missing from Event parameter or its associated type in the dictionary: {str(arg)}"
91
+ )
92
+ event_args.append(
93
+ (
94
+ arg_name,
95
+ arg.get("annotation"),
96
+ arg_type,
97
+ )
98
+ )
99
+
100
+ return EventTemplate(
101
+ event_id,
102
+ event_name,
103
+ event_comp,
104
+ event_args,
105
+ event_severity,
106
+ event_fmt_str,
107
+ event_desc,
108
+ )
@@ -43,15 +43,15 @@ class EventXmlLoader(XmlLoader):
43
43
  respectively and the values are ChTemplate objects
44
44
  """
45
45
  xml_tree = self.get_xml_tree(path)
46
- versions = xml_tree.attrib.get("framework_version", "unknown"), xml_tree.attrib.get("project_version", "unknown")
46
+ versions = xml_tree.attrib.get(
47
+ "framework_version", "unknown"
48
+ ), xml_tree.attrib.get("project_version", "unknown")
47
49
 
48
50
  # Check if xml dict has events section
49
51
  event_section = self.get_xml_section(self.EVENT_SECT, xml_tree)
50
52
  if event_section is None:
51
53
  msg = f"Xml dict did not have a {self.EVENT_SECT} section"
52
- raise exceptions.GseControllerParsingException(
53
- msg
54
- )
54
+ raise exceptions.GseControllerParsingException(msg)
55
55
 
56
56
  id_dict = {}
57
57
  name_dict = {}
@@ -63,14 +63,18 @@ class EventXmlLoader(XmlLoader):
63
63
  event_name = event_dict[self.NAME_TAG]
64
64
  event_id = int(event_dict[self.ID_TAG], base=16)
65
65
  event_severity = EventSeverity[event_dict[self.SEVERITY_TAG]]
66
- event_fmt_str = event_dict[self.FMT_STR_TAG]
66
+ event_fmt_str = XmlLoader.preprocess_format_str(
67
+ event_dict[self.FMT_STR_TAG]
68
+ )
67
69
 
68
70
  event_desc = None
69
71
  if self.DESC_TAG in event_dict:
70
72
  event_desc = event_dict[self.DESC_TAG]
71
73
 
72
74
  # Parse arguments
73
- args = self.get_args_list(event, xml_tree, f"{ event_comp }::{ event_name }")
75
+ args = self.get_args_list(
76
+ event, xml_tree, f"{ event_comp }::{ event_name }"
77
+ )
74
78
 
75
79
  event_temp = EventTemplate(
76
80
  event_id,
@@ -0,0 +1,222 @@
1
+ """
2
+ json_loader.py:
3
+
4
+ Base class for all loaders that load dictionaries from json dictionaries
5
+
6
+ @author thomas-bc
7
+ """
8
+
9
+ import json
10
+ from typing import Optional
11
+
12
+ from fprime.common.models.serialize.array_type import ArrayType
13
+ from fprime.common.models.serialize.bool_type import BoolType
14
+ from fprime.common.models.serialize.enum_type import EnumType
15
+ import fprime.common.models.serialize.numerical_types as numerical_types
16
+ from fprime.common.models.serialize.serializable_type import SerializableType
17
+ from fprime.common.models.serialize.string_type import StringType
18
+ from fprime.common.models.serialize.type_base import BaseType
19
+
20
+ from fprime_gds.common.utils.string_util import preprocess_fpp_format_str
21
+ from fprime_gds.common.loaders import dict_loader
22
+ from fprime_gds.common.data_types.exceptions import GdsDictionaryParsingException
23
+
24
+
25
+ PRIMITIVE_TYPE_MAP = {
26
+ "I8": numerical_types.I8Type,
27
+ "I16": numerical_types.I16Type,
28
+ "I32": numerical_types.I32Type,
29
+ "I64": numerical_types.I64Type,
30
+ "U8": numerical_types.U8Type,
31
+ "U16": numerical_types.U16Type,
32
+ "U32": numerical_types.U32Type,
33
+ "U64": numerical_types.U64Type,
34
+ "F32": numerical_types.F32Type,
35
+ "F64": numerical_types.F64Type,
36
+ "bool": BoolType,
37
+ }
38
+
39
+
40
+ class JsonLoader(dict_loader.DictLoader):
41
+ """Class to help load JSON dictionaries"""
42
+
43
+ # Cache parsed type objects at the class level so they can be reused across subclasses
44
+ parsed_types: dict = {}
45
+
46
+ def __init__(self, json_file: str):
47
+ """
48
+ Constructor
49
+
50
+ Returns:
51
+ An initialized loader object
52
+ """
53
+ super().__init__()
54
+ self.json_file = json_file
55
+ with open(json_file, "r") as f:
56
+ self.json_dict = json.load(f)
57
+
58
+ def get_versions(self):
59
+ """
60
+ Get the framework and project versions of the dictionary
61
+
62
+ Returns:
63
+ A tuple of the framework and project versions
64
+ """
65
+ if "metadata" not in self.json_dict:
66
+ raise GdsDictionaryParsingException(
67
+ f"Dictionary has no metadata field: {self.json_file}"
68
+ )
69
+ return (
70
+ self.json_dict["metadata"].get("frameworkVersion", "unknown"),
71
+ self.json_dict["metadata"].get("projectVersion", "unknown"),
72
+ )
73
+
74
+ def parse_type(self, type_dict: dict) -> BaseType:
75
+ type_name: str = type_dict.get("name", None)
76
+
77
+ if type_name is None:
78
+ raise GdsDictionaryParsingException(
79
+ f"Dictionary entry has no `name` field: {str(type_dict)}"
80
+ )
81
+
82
+ if type_name in PRIMITIVE_TYPE_MAP:
83
+ return PRIMITIVE_TYPE_MAP[type_name]
84
+
85
+ if type_name == "string":
86
+ return StringType.construct_type(
87
+ f'String_{type_dict["size"]}', type_dict["size"]
88
+ )
89
+
90
+ # Check if type has already been parsed
91
+ if type_name in self.parsed_types:
92
+ return self.parsed_types[type_name]
93
+
94
+ # Parse new enum/array/serializable types
95
+ qualified_type = None
96
+ for type_def in self.json_dict.get("typeDefinitions", []):
97
+ if type_name == type_def.get("qualifiedName"):
98
+ qualified_type = type_def
99
+ break
100
+ else:
101
+ raise GdsDictionaryParsingException(
102
+ f"Dictionary type name has no corresponding type definition: {type_name}"
103
+ )
104
+
105
+ if qualified_type.get("kind") == "array":
106
+ return self.construct_array_type(type_name, qualified_type)
107
+
108
+ if qualified_type.get("kind") == "enum":
109
+ return self.construct_enum_type(type_name, qualified_type)
110
+
111
+ if qualified_type.get("kind") == "struct":
112
+ return self.construct_serializable_type(type_name, qualified_type)
113
+
114
+ raise GdsDictionaryParsingException(
115
+ f"Dictionary entry has unknown type {str(type_dict)}"
116
+ )
117
+
118
+ def construct_enum_type(self, type_name: str, qualified_type: dict) -> EnumType:
119
+ """
120
+ Constructs an EnumType object of the given type name and qualified type dictionary.
121
+ Caches the constructed EnumType object in the parsed_types dictionary.
122
+
123
+ Args:
124
+ type_name (str): The name of the enum type.
125
+ qualified_type (dict): A dictionary containing the qualified type information.
126
+
127
+ Returns:
128
+ EnumType: The constructed EnumType object.
129
+
130
+ """
131
+ enum_dict = {}
132
+ for member in qualified_type.get("enumeratedConstants"):
133
+ enum_dict[member["name"]] = member.get("value")
134
+ enum_type = EnumType.construct_type(
135
+ type_name,
136
+ enum_dict,
137
+ qualified_type["representationType"].get("name"),
138
+ )
139
+ self.parsed_types[type_name] = enum_type
140
+ return enum_type
141
+
142
+ def construct_array_type(self, type_name: str, qualified_type: dict) -> ArrayType:
143
+ """
144
+ Constructs an ArrayType object based on the given type name and qualified type dictionary.
145
+ Caches the constructed ArrayType object in the parsed_types dictionary.
146
+
147
+ Args:
148
+ type_name (str): The name of the array type.
149
+ qualified_type (dict): The qualified type dictionary containing information about the array type.
150
+
151
+ Returns:
152
+ ArrayType: The constructed ArrayType object.
153
+
154
+ """
155
+ array_type = ArrayType.construct_type(
156
+ type_name,
157
+ self.parse_type(qualified_type.get("elementType")),
158
+ qualified_type.get("size"),
159
+ JsonLoader.preprocess_format_str(
160
+ qualified_type["elementType"].get("format", "{}")
161
+ ),
162
+ )
163
+ self.parsed_types[type_name] = array_type
164
+ return array_type
165
+
166
+ def construct_serializable_type(
167
+ self, type_name: str, qualified_type: dict
168
+ ) -> SerializableType:
169
+ """
170
+ Constructs a SerializableType based on the given type name and qualified type dictionary.
171
+ Caches the constructed SerializableType object in the parsed_types dictionary.
172
+
173
+ Args:
174
+ type_name (str): The name of the serializable type.
175
+ qualified_type (dict): The qualified type dictionary containing information about the type.
176
+
177
+ Returns:
178
+ SerializableType: The constructed serializable type.
179
+
180
+ """
181
+ struct_members = []
182
+ for name, member_dict in qualified_type.get("members").items():
183
+ member_type_dict = member_dict["type"]
184
+ member_type_obj = self.parse_type(member_type_dict)
185
+
186
+ # For member arrays (declared inline, so we create a type on the fly)
187
+ if member_dict.get("size") is not None:
188
+ member_type_obj = ArrayType.construct_type(
189
+ f"Array_{member_type_obj.__name__}_{member_dict['size']}",
190
+ member_type_obj,
191
+ member_dict["size"],
192
+ JsonLoader.preprocess_format_str(
193
+ member_dict["type"].get("format", "{}")
194
+ ),
195
+ )
196
+ fmt_str = JsonLoader.preprocess_format_str(
197
+ member_type_obj.FORMAT if hasattr(member_type_obj, "FORMAT") else "{}"
198
+ )
199
+ description = member_type_dict.get("annotation", "")
200
+ struct_members.append((name, member_type_obj, fmt_str, description))
201
+
202
+ ser_type = SerializableType.construct_type(
203
+ type_name,
204
+ struct_members,
205
+ )
206
+ self.parsed_types[type_name] = ser_type
207
+ return ser_type
208
+
209
+ @staticmethod
210
+ def preprocess_format_str(format_str: Optional[str]) -> Optional[str]:
211
+ """Preprocess format strings before using them in Python format function
212
+ Internally, this converts FPP-style format strings to Python-style format strings
213
+
214
+ Args:
215
+ format_str (str): FPP-style format string
216
+
217
+ Returns:
218
+ str: Python-style format string
219
+ """
220
+ if format_str is None:
221
+ return None
222
+ return preprocess_fpp_format_str(format_str)