Skip to content

Commit

Permalink
Add support for handling of RegularExpression definitions for string …
Browse files Browse the repository at this point in the history
…based properties

Signed-off-by: Kostadin Ivanov (BD/TBC-BG) <[email protected]>
  • Loading branch information
Kostadin-Ivanov committed Oct 31, 2024
1 parent 2f1a984 commit 8dd0b9c
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/vss_tools/exporters/samm/helpers/samm_concepts.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SammConcepts(Enum):
DESCRIPTION = "description"
ENTITY = "Entity"
EVENTS = "events"
VALUE = "value"
EXAMPLE_VALUE = "exampleValue"
NAME = "name"
OPERATIONS = "operations"
Expand Down Expand Up @@ -92,6 +93,7 @@ class SammCConcepts(Enum):
MIN_VALUE = "minValue"
QUANTIFIABLE = "Quantifiable"
RANGE_CONSTRAINT = "RangeConstraint"
REG_EXP_CONSTRAINT = "RegularExpressionConstraint"
SINGLE_ENTITY = "SingleEntity"
STATE = "State"
TIMESTAMP = "Timestamp"
Expand Down
19 changes: 18 additions & 1 deletion src/vss_tools/exporters/samm/helpers/ttl_builder_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,14 @@ def add_node_leaf_constraint(graph: Graph, node_char_name: str, node_char_uri: U
constraint_name = str_to_uc_first_camel_case(vss_node.ttl_name + "Constraint")
constraint_node_uri = get_vspec_uri(constraint_name)

__add_node_tuple(graph, constraint_node_uri, RDF.type, SammCConcepts.RANGE_CONSTRAINT.uri)
# Default Constraint URI is for Range (min/max) constraints
constraint_uri = SammCConcepts.RANGE_CONSTRAINT.uri

if hasattr(vss_node.data, "pattern") and vss_node.data.pattern is not None:
# Pattern property is used for Regular Expression constraints of STRING based data nodes
constraint_uri = SammCConcepts.REG_EXP_CONSTRAINT.uri

__add_node_tuple(graph, constraint_node_uri, RDF.type, constraint_uri)
__add_node_tuple(graph, constraint_node_uri, SammConcepts.NAME.uri, Literal(constraint_name))

# Workaround since doubles are serialized as scientific numbers
Expand All @@ -354,6 +361,16 @@ def add_node_leaf_constraint(graph: Graph, node_char_name: str, node_char_uri: U
Literal(vss_node.data.min, datatype=data_type), # type: ignore
)

if vss_node.data.pattern is not None: # type: ignore
__add_node_tuple(
graph,
constraint_node_uri,
SammConcepts.VALUE.uri,
Literal(vss_node.data.pattern, datatype=data_type), # type: ignore
)

# Set the RegExp value for constraint_node_uri

base_c_name = str_to_uc_first_camel_case(vss_node.ttl_name + "BaseCharacteristic")
base_c_uri = get_vspec_uri(base_c_name)

Expand Down
14 changes: 4 additions & 10 deletions src/vss_tools/exporters/samm/helpers/vss_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from rdflib import URIRef
from vss_tools import log
from vss_tools.datatypes import Datatypes
from vss_tools.model import NodeType, VSSDataBranch
from vss_tools.model import NodeType, VSSDataBranch, VSSDataDatatype
from vss_tools.tree import VSSNode

from ..config import config as cfg
Expand Down Expand Up @@ -260,15 +260,9 @@ def get_node_description(vss_node: VSSNode) -> str:


def has_constraints(vss_node: VSSNode) -> bool:
return (
hasattr(vss_node.data, "type")
and vss_node.data.type in [NodeType.ACTUATOR, NodeType.SENSOR]
and (
hasattr(vss_node.data, "max")
and vss_node.data.max is not None
or hasattr(vss_node.data, "min")
and vss_node.data.min is not None
)
return bool(
isinstance(vss_node.data, VSSDataDatatype)
and (vss_node.data.max is not None or vss_node.data.min is not None or vss_node.data.pattern is not None)
)


Expand Down
40 changes: 40 additions & 0 deletions src/vss_tools/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ class VSSDataDatatype(VSSData):
arraysize: int | None = None
min: int | float | None = None
max: int | float | None = None
# Field, used to allow definition of Regular Expression constraints
# for string based property nodes.
# Example: VSS - VehicleIdentification.VIN property
pattern: str | None = None
unit: str | None = None
allowed: list[str | int | float | bool] | None = None
default: list[str | int | float | bool] | str | int | float | bool | None = None
Expand Down Expand Up @@ -277,6 +281,42 @@ def check_datatype_matching_allowed_unit_datatypes(self) -> Self:
), f"'{self.datatype}' is not allowed for unit '{self.unit}'"
return self

@model_validator(mode="after")
def check_datatype_pattern(self) -> Self:
"""
Checks that regular expression datatype 'pattern' field is:
1. string only
2. defined only for string typed nodes i.e., STRING and STRING_ARRAY
3. if default value is provided it is matching the specified pattern
"""
if self.pattern:
# Datatypes.TUPLE[0] is the string name of the type.
allowed_for = f"Allowed types: '{[Datatypes.STRING[0], Datatypes.STRING_ARRAY[0]]}'"
assert Datatypes.get_type(self.datatype) in [
Datatypes.STRING,
Datatypes.STRING_ARRAY,
], f"Field 'pattern' is not allowed for type: '{self.datatype}'. {allowed_for}"

def check_value_match(value_to_check: Any, value_type: str, reg_exp: str) -> None:
check_values = [value_to_check]

if type(value_to_check) is list:
check_values = value_to_check

for def_val in check_values:
assert re.match(
reg_exp, def_val
), f"Specified '{value_type}' value: '{def_val}' must match defined pattern: '{self.pattern}'"

if self.default:
check_value_match(self.default, "default", self.pattern)

if self.allowed:
check_value_match(self.allowed, "allowed", self.pattern)

return self


class VSSDataProperty(VSSDataDatatype):
pass
Expand Down

0 comments on commit 8dd0b9c

Please sign in to comment.