vss-tools 0.5.0.dev0__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 (39) hide show
  1. vspec/__init__.py +996 -0
  2. vspec/loggingconfig.py +22 -0
  3. vspec/model/__init__.py +7 -0
  4. vspec/model/constants.py +307 -0
  5. vspec/model/exceptions.py +29 -0
  6. vspec/model/vsstree.py +602 -0
  7. vspec/py.typed +0 -0
  8. vspec/utils/__init__.py +23 -0
  9. vspec/utils/idgen_utils.py +97 -0
  10. vspec/utils/stringstyle.py +25 -0
  11. vspec/utils/vss2id_val.py +165 -0
  12. vspec/vspec2vss_config.py +53 -0
  13. vspec/vspec2x.py +191 -0
  14. vspec/vss2x.py +38 -0
  15. vspec/vssexporters/__init__.py +7 -0
  16. vspec/vssexporters/vss2binary.py +147 -0
  17. vspec/vssexporters/vss2csv.py +82 -0
  18. vspec/vssexporters/vss2ddsidl.py +295 -0
  19. vspec/vssexporters/vss2franca.py +98 -0
  20. vspec/vssexporters/vss2graphql.py +143 -0
  21. vspec/vssexporters/vss2id.py +172 -0
  22. vspec/vssexporters/vss2json.py +116 -0
  23. vspec/vssexporters/vss2jsonschema.py +159 -0
  24. vspec/vssexporters/vss2protobuf.py +171 -0
  25. vspec/vssexporters/vss2yaml.py +122 -0
  26. vss_tools-0.5.0.dev0.data/scripts/vspec2csv.py +29 -0
  27. vss_tools-0.5.0.dev0.data/scripts/vspec2ddsidl.py +29 -0
  28. vss_tools-0.5.0.dev0.data/scripts/vspec2franca.py +29 -0
  29. vss_tools-0.5.0.dev0.data/scripts/vspec2graphql.py +29 -0
  30. vss_tools-0.5.0.dev0.data/scripts/vspec2id.py +28 -0
  31. vss_tools-0.5.0.dev0.data/scripts/vspec2json.py +29 -0
  32. vss_tools-0.5.0.dev0.data/scripts/vspec2jsonschema.py +29 -0
  33. vss_tools-0.5.0.dev0.data/scripts/vspec2protobuf.py +29 -0
  34. vss_tools-0.5.0.dev0.data/scripts/vspec2yaml.py +29 -0
  35. vss_tools-0.5.0.dev0.dist-info/LICENSE +373 -0
  36. vss_tools-0.5.0.dev0.dist-info/METADATA +65 -0
  37. vss_tools-0.5.0.dev0.dist-info/RECORD +39 -0
  38. vss_tools-0.5.0.dev0.dist-info/WHEEL +5 -0
  39. vss_tools-0.5.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2022 Contributors to COVESA
4
+ #
5
+ # This program and the accompanying materials are made available under the
6
+ # terms of the Mozilla Public License 2.0 which is available at
7
+ # https://www.mozilla.org/en-US/MPL/2.0/
8
+ #
9
+ # SPDX-License-Identifier: MPL-2.0
10
+
11
+ import argparse
12
+ from vspec.model.vsstree import VSSNode
13
+ from vspec.utils.stringstyle import camel_back
14
+ from vspec.model.constants import VSSDataType
15
+ from vspec import VSpecError
16
+ from typing import Dict
17
+ from typing import Optional
18
+ from vspec.vss2x import Vss2X
19
+ from vspec.vspec2vss_config import Vspec2VssConfig
20
+
21
+ from graphql import (
22
+ GraphQLSchema,
23
+ GraphQLObjectType,
24
+ GraphQLField,
25
+ GraphQLArgument,
26
+ GraphQLNonNull,
27
+ GraphQLString,
28
+ GraphQLList,
29
+ print_schema,
30
+ GraphQLInt,
31
+ GraphQLFloat,
32
+ GraphQLBoolean,
33
+ )
34
+
35
+ GRAPHQL_TYPE_MAPPING = {
36
+ VSSDataType.INT8: GraphQLInt,
37
+ VSSDataType.INT8_ARRAY: GraphQLList(GraphQLInt),
38
+ VSSDataType.UINT8: GraphQLInt,
39
+ VSSDataType.UINT8_ARRAY: GraphQLList(GraphQLInt),
40
+ VSSDataType.INT16: GraphQLInt,
41
+ VSSDataType.INT16_ARRAY: GraphQLList(GraphQLInt),
42
+ VSSDataType.UINT16: GraphQLInt,
43
+ VSSDataType.UINT16_ARRAY: GraphQLList(GraphQLInt),
44
+ VSSDataType.INT32: GraphQLInt,
45
+ VSSDataType.INT32_ARRAY: GraphQLList(GraphQLInt),
46
+ VSSDataType.UINT32: GraphQLFloat,
47
+ VSSDataType.UINT32_ARRAY: GraphQLList(GraphQLFloat),
48
+ VSSDataType.INT64: GraphQLFloat,
49
+ VSSDataType.INT64_ARRAY: GraphQLList(GraphQLFloat),
50
+ VSSDataType.UINT64: GraphQLFloat,
51
+ VSSDataType.UINT64_ARRAY: GraphQLList(GraphQLFloat),
52
+ VSSDataType.FLOAT: GraphQLFloat,
53
+ VSSDataType.FLOAT_ARRAY: GraphQLList(GraphQLFloat),
54
+ VSSDataType.DOUBLE: GraphQLFloat,
55
+ VSSDataType.DOUBLE_ARRAY: GraphQLList(GraphQLFloat),
56
+ VSSDataType.BOOLEAN: GraphQLBoolean,
57
+ VSSDataType.BOOLEAN_ARRAY: GraphQLList(GraphQLBoolean),
58
+ VSSDataType.STRING: GraphQLString,
59
+ VSSDataType.STRING_ARRAY: GraphQLList(GraphQLString),
60
+ }
61
+
62
+
63
+ def get_schema_from_tree(root_node: VSSNode, additional_leaf_fields: list) -> str:
64
+ """Takes a VSSNode and additional fields for the leafs. Returns a graphql schema as string."""
65
+ args = dict(
66
+ id=GraphQLArgument(
67
+ GraphQLNonNull(GraphQLString),
68
+ description="VIN of the vehicle that you want to request data for.",
69
+ ),
70
+ after=GraphQLArgument(
71
+ GraphQLString,
72
+ description=(
73
+ "Filter data to only provide information that was sent "
74
+ "from the vehicle after that timestamp."
75
+ ),
76
+ ),
77
+ )
78
+
79
+ root_query = GraphQLObjectType(
80
+ "Query",
81
+ lambda: {"vehicle": GraphQLField(to_gql_type(root_node, additional_leaf_fields), args)},
82
+ )
83
+ return print_schema(GraphQLSchema(root_query))
84
+
85
+
86
+ def to_gql_type(node: VSSNode, additional_leaf_fields: list) -> GraphQLObjectType:
87
+ return GraphQLObjectType(
88
+ name=node.qualified_name("_"),
89
+ fields=leaf_fields(node, additional_leaf_fields) if hasattr(
90
+ node, "datatype") else branch_fields(node, additional_leaf_fields),
91
+ description=node.description,
92
+ )
93
+
94
+
95
+ def leaf_fields(node: VSSNode, additional_leaf_fields: list) -> Dict[str, GraphQLField]:
96
+ field_dict = {}
97
+ if node.datatype is not None:
98
+ field_dict["value"] = field(node, "Value: ", GRAPHQL_TYPE_MAPPING[node.datatype])
99
+ field_dict["timestamp"] = field(node, "Timestamp: ")
100
+ if additional_leaf_fields:
101
+ for additional_field in additional_leaf_fields:
102
+ if len(additional_field) == 2:
103
+ field_dict[additional_field[0]] = field(node, f" {str(additional_field[1])}: ")
104
+ else:
105
+ raise VSpecError("", "", "Incorrect format of graphql field specification")
106
+ if node.has_unit():
107
+ field_dict["unit"] = field(node, "Unit of ")
108
+ return field_dict
109
+
110
+
111
+ def branch_fields(node: VSSNode, additional_leaf_fields: list) -> Dict[str, GraphQLField]:
112
+ # we only consider nodes that either have children or are leave with a datatype
113
+ valid = (c for c in node.children if len(c.children) or hasattr(c, "datatype"))
114
+ return {camel_back(c.name): field(c, type=to_gql_type(c, additional_leaf_fields)) for c in valid}
115
+
116
+
117
+ def field(node: VSSNode, description_prefix="", type=GraphQLString) -> GraphQLField:
118
+ return GraphQLField(
119
+ type,
120
+ deprecation_reason=node.deprecation or None,
121
+ description=f"{description_prefix}{node.description}",
122
+ )
123
+
124
+
125
+ class Vss2Graphql(Vss2X):
126
+
127
+ def __init__(self, vspec2vss_config: Vspec2VssConfig):
128
+ vspec2vss_config.no_expand_option_supported = False
129
+ vspec2vss_config.type_tree_supported = False
130
+ vspec2vss_config.uuid_supported = False
131
+
132
+ def add_arguments(self, parser: argparse.ArgumentParser):
133
+ parser.add_argument('--gqlfield', action='append', nargs=2,
134
+ help=" Add additional fields to the nodes in the graphql schema. "
135
+ "use: <field_name> <description>")
136
+
137
+ def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
138
+ data_type_root: Optional[VSSNode] = None) -> None:
139
+ print("Generating graphql output...")
140
+ outfile = open(config.output_file, 'w')
141
+ outfile.write(get_schema_from_tree(signal_root, config.gqlfield))
142
+ outfile.write("\n")
143
+ outfile.close()
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2023 Contributors to COVESA
4
+ #
5
+ # This program and the accompanying materials are made available under the
6
+ # terms of the Mozilla Public License 2.0 which is available at
7
+ # https://www.mozilla.org/en-US/MPL/2.0/
8
+ #
9
+ # SPDX-License-Identifier: MPL-2.0
10
+ #
11
+ # Generate IDs of 4bytes size, 3 bytes incremental value + 1 byte for layer id.
12
+
13
+ import argparse
14
+ import logging
15
+ import os
16
+ import sys
17
+ from typing import Dict, Tuple, Optional
18
+
19
+ import yaml
20
+
21
+ from vspec import load_tree
22
+ from vspec.model.constants import VSSTreeType
23
+ from vspec.model.vsstree import VSSNode
24
+ from vspec.utils import vss2id_val
25
+ from vspec.utils.idgen_utils import (fnv1_32_hash, get_all_keys_values,
26
+ get_node_identifier_bytes)
27
+ from vspec.vss2x import Vss2X
28
+ from vspec.vspec2vss_config import Vspec2VssConfig
29
+
30
+
31
+ def generate_split_id(
32
+ node: VSSNode, id_counter: int, strict_mode: bool
33
+ ) -> Tuple[str, int]:
34
+
35
+ """Generates static UIDs using 4-byte FNV-1 hash.
36
+
37
+ @param node: VSSNode that we want to generate a static UID for
38
+ @param id_counter: consecutive numbers counter for amount of nodes
39
+ @param strict_mode: strict mode means case sensitivity for static UID generation
40
+ @return: tuple of hashed string and id counter
41
+ """
42
+
43
+ if hasattr(node, "fka") and node.fka:
44
+ name = node.fka[0] if isinstance(node.fka, list) else node.fka
45
+ else:
46
+ name = node.qualified_name()
47
+ identifier = get_node_identifier_bytes(
48
+ name,
49
+ node.data_type_str,
50
+ node.type.value,
51
+ node.get_unit(),
52
+ node.allowed,
53
+ node.min,
54
+ node.max,
55
+ strict_mode,
56
+ )
57
+ hashed_str = format(fnv1_32_hash(identifier), "08X")
58
+
59
+ return hashed_str, id_counter + 1
60
+
61
+
62
+ def export_node(yaml_dict, node, id_counter, strict_mode: bool) -> Tuple[int, int]:
63
+ """Recursive function to export the full tree to a dict
64
+
65
+ @param yaml_dict: the to be exported dict
66
+ @param node: parent node of the tree
67
+ @param id_counter: counter for amount of ids
68
+ @param strict_mode: strict mode means case sensitivity for static UID generation
69
+ @return: id_counter, id_counter
70
+ """
71
+ node_id, id_counter = generate_split_id(node, id_counter, strict_mode)
72
+
73
+ # check for hash duplicates
74
+ for key, value in get_all_keys_values(yaml_dict):
75
+ if not isinstance(value, dict) and key == "staticUID":
76
+ if node_id == value[2:]:
77
+ logging.fatal(
78
+ f"There is a small chance that the result of FNV-1 "
79
+ f"hashes are the same in this case the hash of node "
80
+ f"'{node.qualified_name()}' is the same as another hash."
81
+ f"Can you please update it."
82
+ )
83
+ # We could add handling of duplicates here
84
+ sys.exit(-1)
85
+
86
+ node_path = node.qualified_name()
87
+
88
+ yaml_dict[node_path] = {"staticUID": f"0x{node_id}"}
89
+ yaml_dict[node_path]["description"] = node.description
90
+ yaml_dict[node_path]["type"] = str(node.type.value)
91
+ if node.unit:
92
+ yaml_dict[node_path]["unit"] = str(node.unit.value)
93
+ if node.is_signal() or node.is_property():
94
+ yaml_dict[node_path]["datatype"] = node.data_type_str
95
+ if node.allowed:
96
+ yaml_dict[node_path]["allowed"] = node.allowed
97
+ if node.min:
98
+ yaml_dict[node_path]["min"] = node.min
99
+ if node.max:
100
+ yaml_dict[node_path]["max"] = node.max
101
+
102
+ if node.fka:
103
+ yaml_dict[node_path]["fka"] = node.fka
104
+ elif "fka" in node.extended_attributes.keys():
105
+ yaml_dict[node_path]["fka"] = node.extended_attributes["fka"]
106
+
107
+ if node.deprecation:
108
+ yaml_dict[node_path]["deprecation"] = node.deprecation
109
+
110
+ for child in node.children:
111
+ id_counter, id_counter = export_node(yaml_dict, child, id_counter, strict_mode)
112
+
113
+ return id_counter, id_counter
114
+
115
+
116
+ class Vss2Id(Vss2X):
117
+
118
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
119
+ """Adds command line arguments to a pre-existing argument parser
120
+
121
+ @param parser: the pre-existing argument parser
122
+ """
123
+ parser.add_argument(
124
+ "--validate-static-uid", type=str, default="", help="Path to validation file."
125
+ )
126
+ parser.add_argument(
127
+ "--only-validate-no-export",
128
+ action="store_true",
129
+ default=False,
130
+ help="For pytests and pipelines you can skip the export of the vspec file.",
131
+ )
132
+ parser.add_argument(
133
+ "--strict-mode",
134
+ action="store_true",
135
+ help="Strict mode means that the generation of static UIDs is case-sensitive.",
136
+ )
137
+
138
+ def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
139
+ data_type_root: Optional[VSSNode] = None) -> None:
140
+
141
+ """Main export function used to generate the output id vspec.
142
+
143
+ @param config: Command line arguments it was run with
144
+ @param signal_root: root of the signal tree
145
+ @param print_uuid: Not used here but needed by main script
146
+ """
147
+ logging.info("Generating YAML output...")
148
+
149
+ id_counter: int = 0
150
+ signals_yaml_dict: Dict[str, str] = {} # Use str for ID values
151
+ id_counter, _ = export_node(
152
+ signals_yaml_dict, signal_root, id_counter, config.strict_mode
153
+ )
154
+
155
+ if config.validate_static_uid:
156
+ logging.info(
157
+ f"Now validating nodes, static UIDs, types, units and description with "
158
+ f"file '{config.validate_static_uid}'"
159
+ )
160
+ if os.path.isabs(config.validate_static_uid):
161
+ other_path = config.validate_static_uid
162
+ else:
163
+ other_path = os.path.join(os.getcwd(), config.validate_static_uid)
164
+
165
+ validation_tree = load_tree(
166
+ other_path, ["."], tree_type=VSSTreeType.SIGNAL_TREE
167
+ )
168
+ vss2id_val.validate_static_uids(signals_yaml_dict, validation_tree, config)
169
+
170
+ if not config.only_validate_no_export:
171
+ with open(config.output_file, "w") as f:
172
+ yaml.dump(signals_yaml_dict, f)
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2022 Contributors to COVESA
4
+ #
5
+ # This program and the accompanying materials are made available under the
6
+ # terms of the Mozilla Public License 2.0 which is available at
7
+ # https://www.mozilla.org/en-US/MPL/2.0/
8
+ #
9
+ # SPDX-License-Identifier: MPL-2.0
10
+
11
+ # Convert vspec tree to JSON
12
+
13
+ from vspec.model.vsstree import VSSNode
14
+ import argparse
15
+ import json
16
+ import logging
17
+ from typing import Dict, Any
18
+ from typing import Optional
19
+ from vspec.vss2x import Vss2X
20
+ from vspec.vspec2vss_config import Vspec2VssConfig
21
+
22
+
23
+ def export_node(json_dict, node, config, print_uuid):
24
+
25
+ json_dict[node.name] = {}
26
+
27
+ if node.is_signal() or node.is_property():
28
+ json_dict[node.name]["datatype"] = node.data_type_str
29
+
30
+ json_dict[node.name]["type"] = str(node.type.value)
31
+
32
+ # many optional attributes are initilized to "" in vsstree.py
33
+ if node.min != "":
34
+ json_dict[node.name]["min"] = node.min
35
+ if node.max != "":
36
+ json_dict[node.name]["max"] = node.max
37
+ if node.allowed != "":
38
+ json_dict[node.name]["allowed"] = node.allowed
39
+ if node.default != "":
40
+ json_dict[node.name]["default"] = node.default
41
+ if node.deprecation != "":
42
+ json_dict[node.name]["deprecation"] = node.deprecation
43
+
44
+ # in case of unit or aggregate, the attribute will be missing
45
+ try:
46
+ json_dict[node.name]["unit"] = str(node.unit.value)
47
+ except AttributeError:
48
+ pass
49
+ try:
50
+ json_dict[node.name]["aggregate"] = node.aggregate
51
+ except AttributeError:
52
+ pass
53
+
54
+ json_dict[node.name]["description"] = node.description
55
+ if node.comment != "":
56
+ json_dict[node.name]["comment"] = node.comment
57
+
58
+ if print_uuid:
59
+ json_dict[node.name]["uuid"] = node.uuid
60
+
61
+ for k, v in node.extended_attributes.items():
62
+ if not config.json_all_extended_attributes and k not in VSSNode.whitelisted_extended_attributes:
63
+ continue
64
+ json_dict[node.name][k] = v
65
+
66
+ # Include instance information if we run tool in "no-expand" mode
67
+ if config.no_expand and node.instances is not None:
68
+ json_dict[node.name]["instances"] = node.instances
69
+
70
+ # Might be better to not generate child dict, if there are no children
71
+ # if node.type == VSSType.BRANCH and len(node.children) != 0:
72
+ # json_dict[node.name]["children"]={}
73
+
74
+ # But old JSON code always generates children, so lets do so to
75
+ if node.is_branch() or node.is_struct():
76
+ json_dict[node.name]["children"] = {}
77
+
78
+ for child in node.children:
79
+ export_node(json_dict[node.name]["children"],
80
+ child, config, print_uuid)
81
+
82
+
83
+ class Vss2Json(Vss2X):
84
+
85
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
86
+ parser.add_argument('--json-all-extended-attributes', action='store_true',
87
+ help="Generate all extended attributes found in the model "
88
+ "(default is generating only those given by the -e/--extended-attributes parameter).")
89
+ parser.add_argument('--json-pretty', action='store_true',
90
+ help=" Pretty print JSON output.")
91
+
92
+ def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
93
+ data_type_root: Optional[VSSNode] = None) -> None:
94
+ logging.info("Generating JSON output...")
95
+ indent = None
96
+ if config.json_pretty:
97
+ logging.info("Serializing pretty JSON...")
98
+ indent = 2
99
+ else:
100
+ logging.info("Serializing compact JSON...")
101
+
102
+ signals_json_dict: Dict[str, Any] = {}
103
+ export_node(signals_json_dict, signal_root, config, vspec2vss_config.generate_uuid)
104
+
105
+ if data_type_root is not None:
106
+ data_types_json_dict: Dict[str, Any] = {}
107
+ export_node(data_types_json_dict, data_type_root, config, vspec2vss_config.generate_uuid)
108
+ if config.types_output_file is None:
109
+ logging.info("Adding custom data types to signal dictionary")
110
+ signals_json_dict["ComplexDataTypes"] = data_types_json_dict
111
+ else:
112
+ with open(config.types_output_file, 'w') as f:
113
+ json.dump(data_types_json_dict, f, indent=indent, sort_keys=True)
114
+
115
+ with open(config.output_file, 'w') as f:
116
+ json.dump(signals_json_dict, f, indent=indent, sort_keys=True)
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2023 Contributors to COVESA
4
+ #
5
+ # This program and the accompanying materials are made available under the
6
+ # terms of the Mozilla Public License 2.0 which is available at
7
+ # https://www.mozilla.org/en-US/MPL/2.0/
8
+ #
9
+ # SPDX-License-Identifier: MPL-2.0
10
+ #
11
+ # Convert vspec tree compatible JSON schema
12
+
13
+ import argparse
14
+ import json
15
+ import logging
16
+ from typing import Dict, Any
17
+ from typing import Optional
18
+ from vspec.model.vsstree import VSSNode
19
+ from vspec.vss2x import Vss2X
20
+ from vspec.vspec2vss_config import Vspec2VssConfig
21
+
22
+ type_map = {
23
+ "int8": "integer",
24
+ "uint8": "integer",
25
+ "int16": "integer",
26
+ "uint16": "integer",
27
+ "int32": "integer",
28
+ "uint32": "integer",
29
+ "int64": "integer",
30
+ "uint64": "integer",
31
+ "boolean": "boolean",
32
+ "float": "number",
33
+ "double": "number",
34
+ "string": "string",
35
+ "int8[]": "array",
36
+ "uint8[]": "array",
37
+ "int16[]": "array",
38
+ "uint16[]": "array",
39
+ "int32[]": "array",
40
+ "uint32[]": "array",
41
+ "int64[]": "array",
42
+ "uint64[]": "array",
43
+ "boolean[]": "array",
44
+ "float[]": "array",
45
+ "double[]": "array",
46
+ "string[]": "array"
47
+ }
48
+
49
+
50
+ def export_node(json_dict, node, config, print_uuid):
51
+ """Preparing nodes for JSON schema output."""
52
+ # keyword with X- sign are left for extensions and they are not part of official JSON schema
53
+ json_dict[node.name] = {
54
+ "description": node.description,
55
+ }
56
+
57
+ if node.is_signal() or node.is_property():
58
+ json_dict[node.name]["type"] = type_map[node.data_type_str]
59
+
60
+ # many optional attributes are initialized to "" in vsstree.py
61
+ if node.min != "":
62
+ json_dict[node.name]["minimum"] = node.min
63
+ if node.max != "":
64
+ json_dict[node.name]["maximum"] = node.max
65
+ if node.allowed != "":
66
+ json_dict[node.name]["enum"] = node.allowed
67
+ if node.default != "":
68
+ json_dict[node.name]["default"] = node.default
69
+ if node.is_struct():
70
+ # change type to object
71
+ json_dict[node.type]["type"] = "object"
72
+
73
+ if config.jsonschema_all_extended_attributes:
74
+ if node.type != "":
75
+ json_dict[node.name]["x-VSStype"] = str(node.type.value)
76
+ if node.data_type_str != "":
77
+ json_dict[node.name]["x-datatype"] = node.data_type_str
78
+ if node.deprecation != "":
79
+ json_dict[node.name]["x-deprecation"] = node.deprecation
80
+
81
+ # in case of unit or aggregate, the attribute will be missing
82
+ try:
83
+ json_dict[node.name]["x-unit"] = str(node.unit.value)
84
+ except AttributeError:
85
+ pass
86
+ try:
87
+ json_dict[node.name]["x-aggregate"] = node.aggregate
88
+ if node.aggregate:
89
+ # change type to object
90
+ json_dict[node.type]["type"] = "object"
91
+ except AttributeError:
92
+ pass
93
+
94
+ if node.comment != "":
95
+ json_dict[node.name]["x-comment"] = node.comment
96
+
97
+ if print_uuid:
98
+ json_dict[node.name]["x-uuid"] = node.uuid
99
+
100
+ for k, v in node.extended_attributes.items():
101
+ json_dict[node.name][k] = v
102
+
103
+ # Generate child nodes
104
+ if node.is_branch() or node.is_struct():
105
+ if config.jsonschema_disallow_additional_properties:
106
+ json_dict[node.name]["additionalProperties"] = False
107
+ json_dict[node.name]["properties"] = {}
108
+ if config.jsonschema_require_all_properties:
109
+ json_dict[node.name]["required"] = [child.name for child in node.children]
110
+ for child in node.children:
111
+ export_node(json_dict[node.name]["properties"], child, config, print_uuid)
112
+
113
+
114
+ class Vss2JsonSchema(Vss2X):
115
+
116
+ def add_arguments(self, parser: argparse.ArgumentParser):
117
+ """Check for input arguments."""
118
+ parser.add_argument('--jsonschema-all-extended-attributes', action='store_true',
119
+ help="Generate all extended attributes found in the model."
120
+ "Should not be used with strict mode JSON Schema validators.")
121
+ parser.add_argument("--jsonschema-disallow-additional-properties", action='store_true',
122
+ help="Do not allow properties not defined in VSS tree")
123
+ parser.add_argument("--jsonschema-require-all-properties", action='store_true',
124
+ help="Require all elements defined in VSS tree for a valid object")
125
+ parser.add_argument('--jsonschema-pretty', action='store_true',
126
+ help=" Pretty print JSON Schema output.")
127
+
128
+ def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
129
+ data_type_root: Optional[VSSNode] = None) -> None:
130
+ """Export function for generating JSON schema file."""
131
+ logging.info("Generating JSON schema...")
132
+ indent = None
133
+ if config.jsonschema_pretty:
134
+ logging.info("Serializing pretty JSON schema...")
135
+ indent = 2
136
+
137
+ signals_json_schema: Dict[str, Any] = {}
138
+ export_node(signals_json_schema, signal_root, config, vspec2vss_config.generate_uuid)
139
+
140
+ # Add data types to the schema
141
+ if data_type_root is not None:
142
+ data_types_json_schema: Dict[str, Any] = {}
143
+ export_node(data_types_json_schema, data_type_root, config, vspec2vss_config.generate_uuid)
144
+ if config.jsonschema_all_extended_attributes:
145
+ signals_json_schema["x-ComplexDataTypes"] = data_types_json_schema
146
+
147
+ # VSS models only have one root, so there should only be one
148
+ # key in the dict
149
+ assert (len(signals_json_schema.keys()) == 1)
150
+ top_node_name = list(signals_json_schema.keys())[0]
151
+ signals_json_schema = signals_json_schema.pop(top_node_name)
152
+
153
+ json_schema = {
154
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
155
+ "title": top_node_name,
156
+ "type": "object",
157
+ **signals_json_schema}
158
+ with open(config.output_file, 'w', encoding="utf-8") as output_file:
159
+ json.dump(json_schema, output_file, indent=indent, sort_keys=False)