diff --git a/buildingmotif/dataclasses/template.py b/buildingmotif/dataclasses/template.py index 81c3a5a49..10f6708f4 100644 --- a/buildingmotif/dataclasses/template.py +++ b/buildingmotif/dataclasses/template.py @@ -337,7 +337,15 @@ def inline_dependencies(self) -> "Template": deptempl_opt_args.update(deptempl.parameters) # convert our set of optional params to a list and assign to the parent template - templ.optional_args = list(templ_optional_args.union(deptempl_opt_args)) + # 1. get required parameters from the original template + # 2. calculate all optional requirements from the dependency template and the original template + # 3. remove required parameters from the optional requirements + # This avoids a bug where an optional dependency makes reference to a required parameter, and then + # subsequent inlining of the dependency without optional args would remove the required parameter + required = templ.parameters - templ_optional_args + templ.optional_args = list( + templ_optional_args.union(deptempl_opt_args) - required + ) # append the inlined template into the parent's body templ.body += deptempl.body diff --git a/tests/unit/fixtures/optional-inline/template.yml b/tests/unit/fixtures/optional-inline/template.yml new file mode 100644 index 000000000..39a0b2070 --- /dev/null +++ b/tests/unit/fixtures/optional-inline/template.yml @@ -0,0 +1,36 @@ +sensor: + body: > + @prefix P: . + @prefix s223: . + P:name a s223:Sensor ; + s223:observes P:property . + + +water-temperature: + body: > + @prefix P: . + @prefix quantitykind: . + @prefix qudt: . + @prefix unit: . + @prefix s223: . + @prefix g36: . + P:name a s223:QuantifiableObservableProperty ; + qudt:hasQuantityKind quantitykind:Temperature; + qudt:hasUnit unit:DEG_C . + + + +hot-water-coil: + body: > + @prefix P: . + @prefix s223: . + @prefix g36: . + P:name a s223:HeatingCoil; + s223:hasProperty P:supply-water-temp . + P:supply-water-temp-sensor a s223:Sensor . + optional: ["supply-water-temp-sensor"] + dependencies: + - template: water-temperature + args: {"name": "supply-water-temp"} + - template: sensor + args: {"name": "supply-water-temp-sensor", "property": "supply-water-temp"} diff --git a/tests/unit/test_template_api.py b/tests/unit/test_template_api.py index 03f076d83..6ff5a085f 100644 --- a/tests/unit/test_template_api.py +++ b/tests/unit/test_template_api.py @@ -179,6 +179,16 @@ def test_template_inline_dependencies(bm: BuildingMOTIF): } +def test_template_inline_dependencies_with_optional(bm: BuildingMOTIF): + # fixes https://github.com/NREL/BuildingMOTIF/issues/237 + lib = Library.load(directory="tests/unit/fixtures/optional-inline") + templ = lib.get_template_by_name("hot-water-coil") + templ = templ.inline_dependencies() + bindings, _ = templ.fill(BLDG, include_optional=False) + assert "supply-water-temp" in bindings.keys() + assert "name" in bindings.keys() + + def test_template_evaluate_with_optional(bm: BuildingMOTIF): """ Test that template evaluation works with optional parameters.