From 1a7fd6c1ed95fd8dc141ad5f70356241a373185f Mon Sep 17 00:00:00 2001 From: Neal Kruis Date: Fri, 11 Apr 2025 11:36:32 -0600 Subject: [PATCH 1/2] Add data element value constraint validation checks. Also some formatting. --- examples/fan_spec/schema/RS0003.schema.yaml | 73 +++++----- examples/fan_spec/schema/RS0005.schema.yaml | 29 ++-- examples/fan_spec/schema/RS0006.schema.yaml | 51 ++++--- examples/fan_spec/schema/RS0007.schema.yaml | 32 ++--- lattice/schema.py | 142 ++++++++++++++++---- 5 files changed, 218 insertions(+), 109 deletions(-) diff --git a/examples/fan_spec/schema/RS0003.schema.yaml b/examples/fan_spec/schema/RS0003.schema.yaml index 021fde4..4eb35f3 100644 --- a/examples/fan_spec/schema/RS0003.schema.yaml +++ b/examples/fan_spec/schema/RS0003.schema.yaml @@ -74,7 +74,7 @@ RS0003: metadata: Description: "Metadata data group" Data Type: "{Metadata}" - Constraints: "schema=RS0003" + Constraints: schema_name="RS0003" Required: True description: Description: "Data group describing product and rating information" @@ -156,17 +156,23 @@ Performance: Description: "Type of performance map" Data Type: "" Required: True - Notes: ["Determines which performance map data group is used for `performance_map`", - "If `operation_speed_control_type` is `DISCRETE` performance map data is provided at individual impeller speeds", - "If `operation_speed_control_type` is `CONTINUOUS` performance map data is provided over a range of impeller speeds"] + Notes: + [ + "Determines which performance map data group is used for `performance_map`", + "If `operation_speed_control_type` is `DISCRETE` performance map data is provided at individual impeller speeds", + "If `operation_speed_control_type` is `CONTINUOUS` performance map data is provided over a range of impeller speeds", + ] installation_speed_control_type: Description: "Type of fan impeller speed control" Data Type: "" Required: True - Notes: ["If `operation_speed_control_type` is `DISCRETE` and `installation_speed_control_type` is `FIXED`, impeller speed shall be restricted to a single discrete speed", - "If `operation_speed_control_type` is `DISCRETE` and `installation_speed_control_type` is `VARIABLE`, impeller speed shall be restricted to a set of two or more discrete speeds", - "If `operation_speed_control_type` is `CONTINUOUS` and `installation_speed_control_type` is `FIXED`, impeller speed shall be restricted to a single speed (which may be interpolated from `impeller_speed` values provided in the performance map)", - "If `operation_speed_control_type` is `CONTINUOUS` and `installation_speed_control_type` is `VARIABLE`, impeller speed shall be unrestricted within operational limits"] + Notes: + [ + "If `operation_speed_control_type` is `DISCRETE` and `installation_speed_control_type` is `FIXED`, impeller speed shall be restricted to a single discrete speed", + "If `operation_speed_control_type` is `DISCRETE` and `installation_speed_control_type` is `VARIABLE`, impeller speed shall be restricted to a set of two or more discrete speeds", + "If `operation_speed_control_type` is `CONTINUOUS` and `installation_speed_control_type` is `FIXED`, impeller speed shall be restricted to a single speed (which may be interpolated from `impeller_speed` values provided in the performance map)", + "If `operation_speed_control_type` is `CONTINUOUS` and `installation_speed_control_type` is `VARIABLE`, impeller speed shall be unrestricted within operational limits", + ] motor_representation: Description: "The corresponding Standard 205 motor representation" Notes: "If the fan assembly is packaged with a motor, a motor representation shall be provided" @@ -204,8 +210,11 @@ AssemblyComponent: Data Type: "Numeric" Constraints: ">=0.0" Units: "Pa" - Notes: ["Corresponds to additional pressure difference at `nominal_standard_air_volumetric_flow_rate`", - "If unknown, a value of 75 Pa shall be used"] + Notes: + [ + "Corresponds to additional pressure difference at `nominal_standard_air_volumetric_flow_rate`", + "If unknown, a value of 75 Pa shall be used", + ] Required: True Scalable: True @@ -216,8 +225,8 @@ SystemCurve: Description: "Volumetric air flow rate through an air distribution system at standard air conditions" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[2..]" + - ">=0.0" + - "[2..]" Units: "m3/s" Required: True Scalable: True @@ -225,8 +234,8 @@ SystemCurve: Description: "Static pressure difference of an air distribution system" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[2..]" + - ">=0.0" + - "[2..]" Units: "Pa" Required: True Scalable: True @@ -252,8 +261,8 @@ GridVariablesContinuous: Description: "Volumetric air flow rate through fan assembly at standard air conditions" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "m3/s" Required: True Scalable: True @@ -261,8 +270,8 @@ GridVariablesContinuous: Description: "External static pressure across fan assembly at dry coil conditions" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "Pa" Notes: "Any static pressure deduction (or addition) for wet coil is specified by `wet_pressure_difference` in 'assembly_components' data group" Required: True @@ -275,16 +284,16 @@ LookupVariablesContinuous: Description: "Rotational speed of fan impeller" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "rev/s" Required: True shaft_power: Description: "Mechanical shaft power input to fan assembly" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "W" Notes: "Does not include the mechanical efficiency of any mechanical drive used to modify rotational speed between the motor and impeller" Required: True @@ -316,8 +325,8 @@ GridVariablesDiscrete: Description: "Number indicating discrete speed of fan impeller in rank order (with 1 being the lowest speed)" Data Type: "[Integer]" Constraints: - - ">=0" - - "[1..]" + - ">=0" + - "[1..]" Units: "-" Notes: "Data shall be provided for all allowable discrete speeds or settings" Required: True @@ -325,8 +334,8 @@ GridVariablesDiscrete: Description: "External static pressure across fan assembly at dry coil conditions" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "Pa" Notes: "Any static pressure deduction (or addition) for wet coil is specified by `wet_pressure_difference` in 'assembly_components' data group" Required: True @@ -339,8 +348,8 @@ LookupVariablesDiscrete: Description: "Volumetric air flow rate through fan assembly at standard air conditions" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "m3/s" Required: True Scalable: True @@ -348,8 +357,8 @@ LookupVariablesDiscrete: Description: "Mechanical shaft power input to fan assembly" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "W" Notes: "Does not include the mechanical efficiency of any mechanical drive used to modify rotational speed between the motor and impeller" Required: True @@ -358,8 +367,8 @@ LookupVariablesDiscrete: Description: "Rotational speed of fan impeller" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "rev/s" Required: True operation_state: diff --git a/examples/fan_spec/schema/RS0005.schema.yaml b/examples/fan_spec/schema/RS0005.schema.yaml index dd5ed6f..d74f0e0 100644 --- a/examples/fan_spec/schema/RS0005.schema.yaml +++ b/examples/fan_spec/schema/RS0005.schema.yaml @@ -16,7 +16,7 @@ RS0005: metadata: Description: "Metadata data group" Data Type: "{Metadata}" - Constraints: "schema=RS0005" + Constraints: schema_name="RS0005" Required: True description: Description: "Data group describing product and rating information" @@ -87,8 +87,11 @@ Performance: performance_map: Description: "Data group describing motor performance when operating" Data Type: "{PerformanceMap}" - Notes: ["If no performance map is defined, the motor shall be assumed to transfer all electric power directly to mechanical shaft power", - "***Informative note:*** This field may be omitted for motor-driven equipment where motor efficiencies are incorporated into their performance data"] + Notes: + [ + "If no performance map is defined, the motor shall be assumed to transfer all electric power directly to mechanical shaft power", + "***Informative note:*** This field may be omitted for motor-driven equipment where motor efficiencies are incorporated into their performance data", + ] PerformanceMap: Object Type: "Data Group" @@ -111,8 +114,8 @@ GridVariables: Description: "Delivered rotational shaft power" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "W" Required: True Scalable: True @@ -120,8 +123,8 @@ GridVariables: Description: "Rotational speed of shaft" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "rev/s" Required: True @@ -133,9 +136,9 @@ LookupVariables: Description: "Efficiency of motor" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "<=1.0" - - "[1..]" + - ">=0.0" + - "<=1.0" + - "[1..]" Units: "-" Notes: "Defined as the ratio of mechanical shaft power to electrical input power of the motor" Required: True @@ -143,9 +146,9 @@ LookupVariables: Description: "Power factor of the motor" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "<=1.0" - - "[1..]" + - ">=0.0" + - "<=1.0" + - "[1..]" Units: "-" Required: True operation_state: diff --git a/examples/fan_spec/schema/RS0006.schema.yaml b/examples/fan_spec/schema/RS0006.schema.yaml index b158942..8db37c7 100644 --- a/examples/fan_spec/schema/RS0006.schema.yaml +++ b/examples/fan_spec/schema/RS0006.schema.yaml @@ -18,13 +18,19 @@ CoolingMethod: ACTIVE_AIR_COOLED: Description: "Drive is cooled using forced air convection within the surrounding environment" Display Text: "Active Air Cooled" - Notes: ["Electrical power required for the active cooling system shall be included in the efficiency of the drive", - "All drive efficiency losses are assumed to be added as heat to the surrounding environment"] + Notes: + [ + "Electrical power required for the active cooling system shall be included in the efficiency of the drive", + "All drive efficiency losses are assumed to be added as heat to the surrounding environment", + ] ACTIVE_LIQUID_COOLED: Description: "Drive is cooled using forced liquid convection, transferring heat to the liquid" Display Text: "Active Liquid Cooled" - Notes: ["Any liquid pumping power shall be modeled external to the drive by the application software", - "All drive efficiency losses are assumed to be added as heat to the liquid stream"] + Notes: + [ + "Any liquid pumping power shall be modeled external to the drive by the application software", + "All drive efficiency losses are assumed to be added as heat to the liquid stream", + ] # Data Groups RS0006: @@ -34,7 +40,7 @@ RS0006: metadata: Description: "Metadata data group" Data Type: "{Metadata}" - Constraints: "schema=RS0006" + Constraints: schema_name="RS0006" Required: True description: Description: "Data group describing product and rating information" @@ -54,13 +60,13 @@ Description: ProductInformation: Object Type: "Data Group" Data Elements: - manufacturer: - Description: "Manufacturer name" - Data Type: "String" - model_number: - Description: "Model number" - Data Type: "Pattern" - Notes: "Pattern shall match all model numbers that can be represented by the representation" + manufacturer: + Description: "Manufacturer name" + Data Type: "String" + model_number: + Description: "Model number" + Data Type: "Pattern" + Notes: "Pattern shall match all model numbers that can be represented by the representation" Performance: Object Type: "Data Group" @@ -113,8 +119,8 @@ GridVariables: Description: "Power delivered to the motor" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "W" Required: True Scalable: True @@ -122,8 +128,8 @@ GridVariables: Description: "Frequency delivered to the motor" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "Hz" Required: True @@ -135,12 +141,15 @@ LookupVariables: Description: "Efficiency of drive" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "<=1.0" - - "[1..]" + - ">=0.0" + - "<=1.0" + - "[1..]" Units: "-" - Notes: ["Defined as the ratio of electrical output power (to the motor) to electrical input power (to the drive)", - "Input power shall include any power required to provide active air cooling for the drive"] + Notes: + [ + "Defined as the ratio of electrical output power (to the motor) to electrical input power (to the drive)", + "Input power shall include any power required to provide active air cooling for the drive", + ] Required: True operation_state: Description: "The operation state at the operating conditions" diff --git a/examples/fan_spec/schema/RS0007.schema.yaml b/examples/fan_spec/schema/RS0007.schema.yaml index 0c8a165..dd6b712 100644 --- a/examples/fan_spec/schema/RS0007.schema.yaml +++ b/examples/fan_spec/schema/RS0007.schema.yaml @@ -35,7 +35,7 @@ RS0007: metadata: Description: "Metadata data group" Data Type: "{Metadata}" - Constraints: "schema=RS0007" + Constraints: schema_name="RS0007" Required: True description: Description: "Data group describing product and rating information" @@ -55,16 +55,16 @@ Description: ProductInformation: Object Type: "Data Group" Data Elements: - manufacturer: - Description: "Manufacturer name" - Data Type: "String" - model_number: - Description: "Model number" - Data Type: "Pattern" - Notes: "Pattern shall match all model numbers that can be represented by the representation" - drive_type: - Description: "Type of mechanical drive" - Data Type: "" + manufacturer: + Description: "Manufacturer name" + Data Type: "String" + model_number: + Description: "Model number" + Data Type: "Pattern" + Notes: "Pattern shall match all model numbers that can be represented by the representation" + drive_type: + Description: "Type of mechanical drive" + Data Type: "" Performance: Object Type: "Data Group" @@ -105,8 +105,8 @@ GridVariables: Description: "Output shaft power" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "[1..]" + - ">=0.0" + - "[1..]" Units: "W" Required: True Scalable: True @@ -119,9 +119,9 @@ LookupVariables: Description: "Efficiency of drive" Data Type: "[Numeric]" Constraints: - - ">=0.0" - - "<=1.0" - - "[1..]" + - ">=0.0" + - "<=1.0" + - "[1..]" Units: "-" Notes: "Defined as the ratio of output shaft power to input shaft power" Required: True diff --git a/lattice/schema.py b/lattice/schema.py index bfdd8ff..7f3bf69 100644 --- a/lattice/schema.py +++ b/lattice/schema.py @@ -20,8 +20,14 @@ def __init__(self, pattern_string: str) -> None: def __str__(self): return self.pattern.pattern - def match(self, test_string: str, anchored: bool = False) -> Union[re.Match[str], None]: - return self.pattern.match(test_string) if not anchored else self.anchored_pattern.match(test_string) + def match( + self, test_string: str, anchored: bool = False + ) -> Union[re.Match[str], None]: + return ( + self.pattern.match(test_string) + if not anchored + else self.anchored_pattern.match(test_string) + ) def anchored(self): return self.anchored_pattern.pattern @@ -65,7 +71,9 @@ class IntegerType(DataType): class NumericType(DataType): pattern = RegularExpressionPattern("(Numeric)") - value_pattern = RegularExpressionPattern("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)") + value_pattern = RegularExpressionPattern( + "([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)" + ) class BooleanType(DataType): @@ -94,7 +102,11 @@ def __init__(self, text, parent_data_element): self.data_group = None # only valid once resolve() is called def resolve(self): - self.data_group = self.parent_data_element.parent_data_group.parent_schema.get_data_group(self.data_group_name) + self.data_group = ( + self.parent_data_element.parent_data_group.parent_schema.get_data_group( + self.data_group_name + ) + ) class EnumerationType(DataType): @@ -111,7 +123,9 @@ class ReferenceType(DataType): class ArrayType(DataType): - pattern = RegularExpressionPattern(rf"\[({_type_base_names}|{DataGroupType.pattern}|{EnumerationType.pattern})\]") + pattern = RegularExpressionPattern( + rf"\[({_type_base_names}|{DataGroupType.pattern}|{EnumerationType.pattern})\]" + ) _value_pattern = RegularExpressionPattern( @@ -130,6 +144,9 @@ def __init__(self, text: str, parent_data_element: DataElement): self.text = text self.parent_data_element = parent_data_element + def resolve(self): + pass + class RangeConstraint(Constraint): pattern = RegularExpressionPattern(f"(>|>=|<=|<)({NumericType.value_pattern})") @@ -140,7 +157,9 @@ class MultipleConstraint(Constraint): class SetConstraint(Constraint): - pattern = RegularExpressionPattern(rf"\[{NumericType.value_pattern}(, ?{NumericType.value_pattern})*\]") + pattern = RegularExpressionPattern( + rf"\[{NumericType.value_pattern}(, ?{NumericType.value_pattern})*\]" + ) class SelectorConstraint(Constraint): @@ -157,7 +176,9 @@ def __init__(self, text: str, parent_data_element: DataElement): try: re.compile(text) except re.error: - raise Exception(f"Invalid regular expression: {text}") # pylint:disable=W0707 + raise Exception( + f"Invalid regular expression: {text}" + ) # pylint:disable=W0707 class DataElementValueConstraint(Constraint): @@ -165,11 +186,39 @@ class DataElementValueConstraint(Constraint): def __init__(self, text: str, parent_data_element: DataElement): super().__init__(text, parent_data_element) - self.pattern = parent_data_element.parent_data_group.parent_schema.schema_patterns.data_element_value_constraint + self.pattern = ( + parent_data_element.parent_data_group.parent_schema.schema_patterns.data_element_value_constraint + ) match = self.pattern.match(self.text) assert match is not None + # parent data element must be a data group + if not isinstance(self.parent_data_element.data_type, DataGroupType): + raise Exception( + f"Data Element Value Constraint must be a Data Group Type, not {type(self.parent_data_element)}" + ) + self.data_element_name = match.group(1) # TODO: Named groups? - self.data_element_value = match.group(5) # TODO: Named groups? + self.data_element_value = match.group(5) + self.data_element: DataElement + + def resolve(self): + assert isinstance(self.parent_data_element.data_type, DataGroupType) + if ( + self.data_element_name + not in self.parent_data_element.data_type.data_group.data_elements + ): + raise Exception( + f"Data Element Value Constraint '{self.data_element_name}' not found in Data Group '{self.parent_data_element.data_type.data_group_name}'" + ) + + self.data_element = self.parent_data_element.data_type.data_group.data_elements[ + self.data_element_name + ] + match = self.data_element.data_type.value_pattern.match(self.data_element_value) + if match is None: + raise Exception( + f"Data Element Value Constraint '{self.data_element_value}' does not match the value pattern of '{self.data_element.name}'" + ) class ArrayLengthLimitsConstraint(Constraint): @@ -197,7 +246,9 @@ def _constraint_factory(text: str, parent_data_element: DataElement) -> Constrai if number_of_matches == 1: return match_type(text, parent_data_element) if number_of_matches == 0: - raise Exception(f"No matching constraint for {text} in element {parent_data_element.name}.") + raise Exception( + f"No matching constraint for {text} in element {parent_data_element.name}." + ) raise Exception(f"Multiple matches found for constraint, {text}") @@ -207,7 +258,9 @@ def _constraint_factory(text: str, parent_data_element: DataElement) -> Constrai class DataElement: pattern = _data_element_names - def __init__(self, name: str, data_element_dictionary: dict, parent_data_group: DataGroup): + def __init__( + self, name: str, data_element_dictionary: dict, parent_data_group: DataGroup + ): self.name = name self.dictionary = data_element_dictionary self.parent_data_group = parent_data_group @@ -219,7 +272,9 @@ def __init__(self, name: str, data_element_dictionary: dict, parent_data_group: elif attribute == "Units": self.units = self.dictionary[attribute] elif attribute == "Data Type": - self.data_type = self.get_data_type(parent_data_group, self.dictionary[attribute]) + self.data_type = self.get_data_type( + parent_data_group, self.dictionary[attribute] + ) elif attribute == "Constraints": self.set_constraints(self.dictionary[attribute]) elif attribute == "Required": @@ -245,11 +300,20 @@ def __init__(self, name: str, data_element_dictionary: dict, parent_data_group: f"Data Element={self.name}" ) - def get_data_type(self, parent_data_group: DataGroup, attribute_str: str): + def get_data_type( + self, parent_data_group: DataGroup, attribute_str: str + ) -> DataType: + """ + Returns the data type from the attribute string. + """ try: - return self.parent_data_group.parent_schema.data_type_factory(attribute_str, self) + return self.parent_data_group.parent_schema.data_type_factory( + attribute_str, self + ) except RuntimeError: - for reference_schema in self.parent_data_group.parent_schema.reference_schemas.values(): + for ( + reference_schema + ) in self.parent_data_group.parent_schema.reference_schemas.values(): try: return reference_schema.data_type_factory(attribute_str, self) except RuntimeError: @@ -264,6 +328,8 @@ def set_constraints(self, constraints_input: Union[str, List[str]]) -> None: def resolve(self): self.data_type.resolve() + for constraint in self.constraints: + constraint.resolve() class FundamentalDataType: @@ -302,8 +368,10 @@ def __init__(self, name: str, data_group_dictionary: dict, parent_schema: Schema self.name = name self.dictionary = data_group_dictionary self.parent_schema = parent_schema - self.data_elements = {} - self.id_data_element: Union[DataElement, None] = None # data element containing unique id for this data group + self.data_elements: dict[str, DataElement] = {} + self.id_data_element: Union[DataElement, None] = ( + None # data element containing unique id for this data group + ) for data_element in self.dictionary["Data Elements"]: self.data_elements[data_element] = DataElement( data_element, self.dictionary["Data Elements"][data_element], self @@ -317,7 +385,9 @@ def resolve(self): class Enumerator: pattern = EnumerationType.value_pattern - def __init__(self, name: str, enumerator_dictionary: dict, parent_enumeration: Enumeration): + def __init__( + self, name: str, enumerator_dictionary: dict, parent_enumeration: Enumeration + ): self.name = name self.dictionary = enumerator_dictionary self.parent_enumeration = parent_enumeration @@ -330,11 +400,15 @@ def __init__(self, name: str, enumeration_dictionary: dict, parent_schema: Schem self.parent_schema = parent_schema self.enumerators = {} for enumerator in self.dictionary["Enumerators"]: - self.enumerators[enumerator] = Enumerator(enumerator, self.dictionary["Enumerators"][enumerator], self) + self.enumerators[enumerator] = Enumerator( + enumerator, self.dictionary["Enumerators"][enumerator], self + ) class DataGroupTemplate: - def __init__(self, name: str, data_group_template_dictionary: dict, parent_schema: Schema): + def __init__( + self, name: str, data_group_template_dictionary: dict, parent_schema: Schema + ): self.name = name self.dictionary = data_group_template_dictionary self.parent_schema = parent_schema @@ -382,7 +456,9 @@ def __init__(self, schema=None): single_type = rf"({base_types}|{re_string_types}|{self.data_group_types}|{self.enumeration_types}|{references})" alternatives = rf"\(({single_type})(,\s*{single_type})+\)" arrays = ArrayType.pattern - self.data_types = RegularExpressionPattern(f"({single_type})|({alternatives})|({arrays})") + self.data_types = RegularExpressionPattern( + f"({single_type})|({alternatives})|({arrays})" + ) # Values self.values = RegularExpressionPattern( @@ -417,7 +493,9 @@ def __init__(self, schema=None): class Schema: - def __init__(self, file_path: pathlib.Path, parent_schema: Schema | None = None): # noqa: PLR0912 + def __init__( + self, file_path: pathlib.Path, parent_schema: Schema | None = None + ): # noqa: PLR0912 self.file_path = file_path.absolute() self.source_dictionary = load(self.file_path) self.name = get_file_basename(self.file_path, depth=2) @@ -460,9 +538,13 @@ def __init__(self, file_path: pathlib.Path, parent_schema: Schema | None = None) for object_name in self.source_dictionary: object_type = self.source_dictionary[object_name]["Object Type"] if object_type == "Data Group": - self.data_groups[object_name] = DataGroup(object_name, self.source_dictionary[object_name], self) + self.data_groups[object_name] = DataGroup( + object_name, self.source_dictionary[object_name], self + ) elif object_type == "Enumeration": - self.enumerations[object_name] = Enumeration(object_name, self.source_dictionary[object_name], self) + self.enumerations[object_name] = Enumeration( + object_name, self.source_dictionary[object_name], self + ) elif object_type == "Data Type": self.data_types[object_name] = FundamentalDataType( object_name, self.source_dictionary[object_name], self @@ -511,7 +593,9 @@ def set_reference_schemas(self): parent_directory = self.file_path.parent for reference in self.source_dictionary["Schema"]["References"]: if reference == "core": - raise Exception(f"Illegal reference schema name, {reference}. This name is reserved.") + raise Exception( + f"Illegal reference schema name, {reference}. This name is reserved." + ) self.set_reference_schema( reference, pathlib.Path(parent_directory, f"{reference}.schema.yaml"), @@ -547,11 +631,15 @@ def get_data_group(self, data_group_name: str) -> DataGroup: matching_schemas.append(reference_schema) if len(matching_schemas) == 0: - raise Exception(f'Data Group "{data_group_name}" not found in "{self.file_path}" or its referenced schemas') + raise Exception( + f'Data Group "{data_group_name}" not found in "{self.file_path}" or its referenced schemas' + ) return matching_schemas[0].data_groups[data_group_name] - def data_type_factory(self, text: str, parent_data_element: DataElement) -> DataType: + def data_type_factory( + self, text: str, parent_data_element: DataElement + ) -> DataType: number_of_matches = 0 for data_type in self._data_type_list: if data_type.pattern.match(text): From 84924768511644b62098d1fa728d87858f95ae6e Mon Sep 17 00:00:00 2001 From: Neal Kruis Date: Fri, 11 Apr 2025 11:47:21 -0600 Subject: [PATCH 2/2] Reformat with Ruff. --- lattice/schema.py | 113 +++++++++++----------------------------------- 1 file changed, 27 insertions(+), 86 deletions(-) diff --git a/lattice/schema.py b/lattice/schema.py index 7f3bf69..91b0179 100644 --- a/lattice/schema.py +++ b/lattice/schema.py @@ -20,14 +20,8 @@ def __init__(self, pattern_string: str) -> None: def __str__(self): return self.pattern.pattern - def match( - self, test_string: str, anchored: bool = False - ) -> Union[re.Match[str], None]: - return ( - self.pattern.match(test_string) - if not anchored - else self.anchored_pattern.match(test_string) - ) + def match(self, test_string: str, anchored: bool = False) -> Union[re.Match[str], None]: + return self.pattern.match(test_string) if not anchored else self.anchored_pattern.match(test_string) def anchored(self): return self.anchored_pattern.pattern @@ -71,9 +65,7 @@ class IntegerType(DataType): class NumericType(DataType): pattern = RegularExpressionPattern("(Numeric)") - value_pattern = RegularExpressionPattern( - "([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)" - ) + value_pattern = RegularExpressionPattern("([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)") class BooleanType(DataType): @@ -102,11 +94,7 @@ def __init__(self, text, parent_data_element): self.data_group = None # only valid once resolve() is called def resolve(self): - self.data_group = ( - self.parent_data_element.parent_data_group.parent_schema.get_data_group( - self.data_group_name - ) - ) + self.data_group = self.parent_data_element.parent_data_group.parent_schema.get_data_group(self.data_group_name) class EnumerationType(DataType): @@ -123,9 +111,7 @@ class ReferenceType(DataType): class ArrayType(DataType): - pattern = RegularExpressionPattern( - rf"\[({_type_base_names}|{DataGroupType.pattern}|{EnumerationType.pattern})\]" - ) + pattern = RegularExpressionPattern(rf"\[({_type_base_names}|{DataGroupType.pattern}|{EnumerationType.pattern})\]") _value_pattern = RegularExpressionPattern( @@ -157,9 +143,7 @@ class MultipleConstraint(Constraint): class SetConstraint(Constraint): - pattern = RegularExpressionPattern( - rf"\[{NumericType.value_pattern}(, ?{NumericType.value_pattern})*\]" - ) + pattern = RegularExpressionPattern(rf"\[{NumericType.value_pattern}(, ?{NumericType.value_pattern})*\]") class SelectorConstraint(Constraint): @@ -176,9 +160,7 @@ def __init__(self, text: str, parent_data_element: DataElement): try: re.compile(text) except re.error: - raise Exception( - f"Invalid regular expression: {text}" - ) # pylint:disable=W0707 + raise Exception(f"Invalid regular expression: {text}") # pylint:disable=W0707 class DataElementValueConstraint(Constraint): @@ -186,9 +168,7 @@ class DataElementValueConstraint(Constraint): def __init__(self, text: str, parent_data_element: DataElement): super().__init__(text, parent_data_element) - self.pattern = ( - parent_data_element.parent_data_group.parent_schema.schema_patterns.data_element_value_constraint - ) + self.pattern = parent_data_element.parent_data_group.parent_schema.schema_patterns.data_element_value_constraint match = self.pattern.match(self.text) assert match is not None # parent data element must be a data group @@ -203,17 +183,12 @@ def __init__(self, text: str, parent_data_element: DataElement): def resolve(self): assert isinstance(self.parent_data_element.data_type, DataGroupType) - if ( - self.data_element_name - not in self.parent_data_element.data_type.data_group.data_elements - ): + if self.data_element_name not in self.parent_data_element.data_type.data_group.data_elements: raise Exception( f"Data Element Value Constraint '{self.data_element_name}' not found in Data Group '{self.parent_data_element.data_type.data_group_name}'" ) - self.data_element = self.parent_data_element.data_type.data_group.data_elements[ - self.data_element_name - ] + self.data_element = self.parent_data_element.data_type.data_group.data_elements[self.data_element_name] match = self.data_element.data_type.value_pattern.match(self.data_element_value) if match is None: raise Exception( @@ -246,9 +221,7 @@ def _constraint_factory(text: str, parent_data_element: DataElement) -> Constrai if number_of_matches == 1: return match_type(text, parent_data_element) if number_of_matches == 0: - raise Exception( - f"No matching constraint for {text} in element {parent_data_element.name}." - ) + raise Exception(f"No matching constraint for {text} in element {parent_data_element.name}.") raise Exception(f"Multiple matches found for constraint, {text}") @@ -258,9 +231,7 @@ def _constraint_factory(text: str, parent_data_element: DataElement) -> Constrai class DataElement: pattern = _data_element_names - def __init__( - self, name: str, data_element_dictionary: dict, parent_data_group: DataGroup - ): + def __init__(self, name: str, data_element_dictionary: dict, parent_data_group: DataGroup): self.name = name self.dictionary = data_element_dictionary self.parent_data_group = parent_data_group @@ -272,9 +243,7 @@ def __init__( elif attribute == "Units": self.units = self.dictionary[attribute] elif attribute == "Data Type": - self.data_type = self.get_data_type( - parent_data_group, self.dictionary[attribute] - ) + self.data_type = self.get_data_type(parent_data_group, self.dictionary[attribute]) elif attribute == "Constraints": self.set_constraints(self.dictionary[attribute]) elif attribute == "Required": @@ -300,20 +269,14 @@ def __init__( f"Data Element={self.name}" ) - def get_data_type( - self, parent_data_group: DataGroup, attribute_str: str - ) -> DataType: + def get_data_type(self, parent_data_group: DataGroup, attribute_str: str) -> DataType: """ Returns the data type from the attribute string. """ try: - return self.parent_data_group.parent_schema.data_type_factory( - attribute_str, self - ) + return self.parent_data_group.parent_schema.data_type_factory(attribute_str, self) except RuntimeError: - for ( - reference_schema - ) in self.parent_data_group.parent_schema.reference_schemas.values(): + for reference_schema in self.parent_data_group.parent_schema.reference_schemas.values(): try: return reference_schema.data_type_factory(attribute_str, self) except RuntimeError: @@ -369,9 +332,7 @@ def __init__(self, name: str, data_group_dictionary: dict, parent_schema: Schema self.dictionary = data_group_dictionary self.parent_schema = parent_schema self.data_elements: dict[str, DataElement] = {} - self.id_data_element: Union[DataElement, None] = ( - None # data element containing unique id for this data group - ) + self.id_data_element: Union[DataElement, None] = None # data element containing unique id for this data group for data_element in self.dictionary["Data Elements"]: self.data_elements[data_element] = DataElement( data_element, self.dictionary["Data Elements"][data_element], self @@ -385,9 +346,7 @@ def resolve(self): class Enumerator: pattern = EnumerationType.value_pattern - def __init__( - self, name: str, enumerator_dictionary: dict, parent_enumeration: Enumeration - ): + def __init__(self, name: str, enumerator_dictionary: dict, parent_enumeration: Enumeration): self.name = name self.dictionary = enumerator_dictionary self.parent_enumeration = parent_enumeration @@ -400,15 +359,11 @@ def __init__(self, name: str, enumeration_dictionary: dict, parent_schema: Schem self.parent_schema = parent_schema self.enumerators = {} for enumerator in self.dictionary["Enumerators"]: - self.enumerators[enumerator] = Enumerator( - enumerator, self.dictionary["Enumerators"][enumerator], self - ) + self.enumerators[enumerator] = Enumerator(enumerator, self.dictionary["Enumerators"][enumerator], self) class DataGroupTemplate: - def __init__( - self, name: str, data_group_template_dictionary: dict, parent_schema: Schema - ): + def __init__(self, name: str, data_group_template_dictionary: dict, parent_schema: Schema): self.name = name self.dictionary = data_group_template_dictionary self.parent_schema = parent_schema @@ -456,9 +411,7 @@ def __init__(self, schema=None): single_type = rf"({base_types}|{re_string_types}|{self.data_group_types}|{self.enumeration_types}|{references})" alternatives = rf"\(({single_type})(,\s*{single_type})+\)" arrays = ArrayType.pattern - self.data_types = RegularExpressionPattern( - f"({single_type})|({alternatives})|({arrays})" - ) + self.data_types = RegularExpressionPattern(f"({single_type})|({alternatives})|({arrays})") # Values self.values = RegularExpressionPattern( @@ -493,9 +446,7 @@ def __init__(self, schema=None): class Schema: - def __init__( - self, file_path: pathlib.Path, parent_schema: Schema | None = None - ): # noqa: PLR0912 + def __init__(self, file_path: pathlib.Path, parent_schema: Schema | None = None): # noqa: PLR0912 self.file_path = file_path.absolute() self.source_dictionary = load(self.file_path) self.name = get_file_basename(self.file_path, depth=2) @@ -538,13 +489,9 @@ def __init__( for object_name in self.source_dictionary: object_type = self.source_dictionary[object_name]["Object Type"] if object_type == "Data Group": - self.data_groups[object_name] = DataGroup( - object_name, self.source_dictionary[object_name], self - ) + self.data_groups[object_name] = DataGroup(object_name, self.source_dictionary[object_name], self) elif object_type == "Enumeration": - self.enumerations[object_name] = Enumeration( - object_name, self.source_dictionary[object_name], self - ) + self.enumerations[object_name] = Enumeration(object_name, self.source_dictionary[object_name], self) elif object_type == "Data Type": self.data_types[object_name] = FundamentalDataType( object_name, self.source_dictionary[object_name], self @@ -593,9 +540,7 @@ def set_reference_schemas(self): parent_directory = self.file_path.parent for reference in self.source_dictionary["Schema"]["References"]: if reference == "core": - raise Exception( - f"Illegal reference schema name, {reference}. This name is reserved." - ) + raise Exception(f"Illegal reference schema name, {reference}. This name is reserved.") self.set_reference_schema( reference, pathlib.Path(parent_directory, f"{reference}.schema.yaml"), @@ -631,15 +576,11 @@ def get_data_group(self, data_group_name: str) -> DataGroup: matching_schemas.append(reference_schema) if len(matching_schemas) == 0: - raise Exception( - f'Data Group "{data_group_name}" not found in "{self.file_path}" or its referenced schemas' - ) + raise Exception(f'Data Group "{data_group_name}" not found in "{self.file_path}" or its referenced schemas') return matching_schemas[0].data_groups[data_group_name] - def data_type_factory( - self, text: str, parent_data_element: DataElement - ) -> DataType: + def data_type_factory(self, text: str, parent_data_element: DataElement) -> DataType: number_of_matches = 0 for data_type in self._data_type_list: if data_type.pattern.match(text):