Skip to content

Commit

Permalink
Merge branch 'COVESA:master' into add_vss2samm
Browse files Browse the repository at this point in the history
  • Loading branch information
Kostadin-Ivanov authored Sep 5, 2024
2 parents b185813 + 4346fd6 commit d0802d5
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 15 deletions.
30 changes: 22 additions & 8 deletions src/vss_tools/vspec/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,21 @@ def as_dict(
self,
with_extra_attributes: bool = True,
exclude_fields: list[str] = EXPORT_EXCLUDE_ATTRIBUTES,
extended_attributes: tuple[str, ...] = (),
) -> dict[str, Any]:
excludes = exclude_fields.copy()
if not with_extra_attributes:
excludes.extend(self.get_extra_attributes())
data = {
k: v for k, v in self.model_dump(mode="json", exclude_none=True, exclude=set(excludes)).items() if v != []
}
for extra_attribute in self.get_extra_attributes():
if extra_attribute not in extended_attributes:
excludes.append(extra_attribute)
data = {}
for k, v in self.model_dump(mode="json", exclude_none=False, exclude=set(excludes)).items():
if k not in extended_attributes:
if v == []:
continue
if v is None:
continue
data[k] = v
return data


Expand All @@ -108,7 +116,9 @@ class VSSData(VSSRaw):

@field_validator("constUID")
@classmethod
def check_const_uid_format(cls, v: str) -> str:
def check_const_uid_format(cls, v: str | None) -> str | None:
if v is None:
return v
pattern = r"^0x[0-9A-Fa-f]{8}$"
assert bool(re.match(pattern, v)), f"'{v}' is not a valid 'constUID'"
return v
Expand All @@ -126,6 +136,8 @@ def fill_instances(cls, v: Any) -> list[str]:
and even of a list of lists.
Normalizing it to be a list
"""
if v is None:
return []
if not (isinstance(v, str) or isinstance(v, list)):
assert False, f"'{v}' is not a valid 'instances' content"
if isinstance(v, str):
Expand Down Expand Up @@ -245,7 +257,9 @@ def check_datatype(self) -> Self:

@field_validator("unit")
@classmethod
def check_valid_unit(cls, v: str) -> str:
def check_valid_unit(cls, v: str | None) -> str | None:
if v is None:
return v
assert v in dynamic_units, f"'{v}' is not a valid unit"
return v

Expand Down Expand Up @@ -298,12 +312,12 @@ def resolve_vss_raw(model: VSSRaw) -> VSSData:
Resolves a raw model to the actual node that
it should be validated to
"""
model = VSSData(**model.model_dump(exclude_none=True))
model = VSSData(**model.model_dump())
cls = TYPE_CLASS_MAP.get(model.type)
if not cls:
log.warning(f"No class mapping for type='{model.type.value}'")
raise ModelException()
model = cls(**model.model_dump(exclude_none=True))
model = cls(**model.model_dump())
return model # type: ignore


Expand Down
4 changes: 2 additions & 2 deletions src/vss_tools/vspec/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def get_extra_attributes(self, allowed: tuple[str, ...]) -> list[list[str]]:
log.warning(f"Attributes, violations={len(violations)}")
return violations

def as_flat_dict(self, with_extra_attributes: bool) -> dict[str, Any]:
def as_flat_dict(self, with_extra_attributes: bool, extended_attributes: tuple[str, ...] = ()) -> dict[str, Any]:
"""
Generates a flat dict and whether to include
user attributes or not
Expand All @@ -316,7 +316,7 @@ def as_flat_dict(self, with_extra_attributes: bool) -> dict[str, Any]:
node: VSSNode
for node in PreOrderIter(self):
key = node.get_fqn()
data[key] = node.data.as_dict(with_extra_attributes)
data[key] = node.data.as_dict(with_extra_attributes, extended_attributes=extended_attributes)
if node.uuid:
data[key]["uuid"] = node.uuid
return data
Expand Down
6 changes: 3 additions & 3 deletions src/vss_tools/vspec/vssexporters/vss2json.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
from vss_tools.vspec.tree import VSSNode


def get_data(node: VSSNode, with_extra_attributes: bool = True):
data = node.data.as_dict(with_extra_attributes)
def get_data(node: VSSNode, with_extra_attributes: bool = True, extended_attributes: tuple[str, ...] = ()):
data = node.data.as_dict(with_extra_attributes, extended_attributes=extended_attributes)
if node.uuid:
data["uuid"] = node.uuid
if len(node.children) > 0:
Expand Down Expand Up @@ -85,7 +85,7 @@ def cli(
if pretty:
indent = 2

signals_data = {tree.name: get_data(tree, extend_all_attributes)}
signals_data = {tree.name: get_data(tree, extend_all_attributes, extended_attributes)}

if datatype_tree:
types_data: dict[str, Any] = {datatype_tree.name: get_data(datatype_tree, extend_all_attributes)}
Expand Down
4 changes: 2 additions & 2 deletions src/vss_tools/vspec/vssexporters/vss2yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ def cli(
expand=expand,
)
log.info("Generating YAML output...")
tree_data = tree.as_flat_dict(extend_all_attributes)
tree_data = tree.as_flat_dict(extend_all_attributes, extended_attributes)

if datatype_tree:
datatype_tree_data = datatype_tree.as_flat_dict(extend_all_attributes)
datatype_tree_data = datatype_tree.as_flat_dict(extend_all_attributes, extended_attributes)
if not types_output:
log.info("Adding custom data types to signal dictionary")
tree_data["ComplexDataTypes"] = datatype_tree_data
Expand Down
5 changes: 5 additions & 0 deletions tests/vspec/test_null/expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Vehicle:
bar: null
description: Vehicle
foo: null
type: branch
5 changes: 5 additions & 0 deletions tests/vspec/test_null/test.vspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Vehicle:
description: Vehicle
type: branch
foo: null
bar: null
37 changes: 37 additions & 0 deletions tests/vspec/test_null/test_null.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2022 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0

import subprocess
from pathlib import Path

import yaml

HERE = Path(__file__).resolve().parent


def run_exporter(exporter, tmp_path):
vspec = HERE / "test.vspec"
output = tmp_path / f"out.{exporter}"
expected = HERE / f"expected.{exporter}"
if not expected.exists():
return
cmd = f"vspec export {exporter} --vspec {vspec} --output {output} -e foo -e bar"
subprocess.run(cmd.split(), check=True)
with open(output) as f:
out_content = yaml.safe_load(f)
assert "foo" in out_content["Vehicle"]
assert "bar" in out_content["Vehicle"]
assert out_content["Vehicle"]["foo"] is None
assert out_content["Vehicle"]["bar"] is None


def test_null(tmp_path):
exporters = ["yaml"]

for exporter in exporters:
run_exporter(exporter, tmp_path)

0 comments on commit d0802d5

Please sign in to comment.