Skip to content

Commit

Permalink
Fix autogeneration of templates (#329)
Browse files Browse the repository at this point in the history
* factor out the guarantee method and add unit tests

* add function to utils
  • Loading branch information
gtfierro authored Jul 3, 2024
1 parent 1f14497 commit 0b4c683
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 8 deletions.
24 changes: 18 additions & 6 deletions buildingmotif/dataclasses/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
from buildingmotif import get_building_motif
from buildingmotif.dataclasses.shape_collection import ShapeCollection
from buildingmotif.namespaces import CONSTRAINT, PARAM, SH, A
from buildingmotif.utils import _gensym, get_template_parts_from_shape, replace_nodes
from buildingmotif.utils import (
_gensym,
_guarantee_unique_template_name,
get_template_parts_from_shape,
replace_nodes,
)

if TYPE_CHECKING:
from buildingmotif.dataclasses import Library, Model, Template
Expand Down Expand Up @@ -162,7 +167,8 @@ def resolve(self, lib: "Library") -> List["Template"]:
inst = _gensym()
body.add((self.focus, self.path, inst))
body.add((inst, A, self.classname))
return [lib.create_template(f"resolve_{focus}{name}", body)]
template_name = _guarantee_unique_template_name(lib, f"resolve{focus}{name}")
return [lib.create_template(template_name, body)]


@dataclass(frozen=True, unsafe_hash=True)
Expand Down Expand Up @@ -250,7 +256,10 @@ def resolve(self, lib: "Library") -> List["Template"]:
if self.extra_body:
replace_nodes(self.extra_body, {PARAM.name: inst})
body += self.extra_body
templ = lib.create_template(f"resolve{focus}{name}", body)
template_name = _guarantee_unique_template_name(
lib, f"resolve{focus}{name}"
)
templ = lib.create_template(template_name, body)
if self.extra_deps:
from buildingmotif.dataclasses.template import Template

Expand Down Expand Up @@ -331,7 +340,8 @@ def resolve(self, lib: "Library") -> List["Template"]:
for _ in range(self.minc or 0):
inst = _gensym()
body.add((self.focus, self.path, inst))
return [lib.create_template(f"resolve{focus}{name}", body)]
template_name = _guarantee_unique_template_name(lib, f"resolve{focus}{name}")
return [lib.create_template(template_name, body)]


@dataclass(frozen=True)
Expand All @@ -356,7 +366,8 @@ def resolve(self, lib: "Library") -> List["Template"]:
body = Graph()
name = re.split(r"[#\/]", self.classname)[-1]
body.add((self.focus, A, self.classname))
return [lib.create_template(f"resolveSelf{name}", body)]
template_name = _guarantee_unique_template_name(lib, f"resolveSelf{name}")
return [lib.create_template(template_name, body)]


@dataclass(frozen=True)
Expand Down Expand Up @@ -385,7 +396,8 @@ def resolve(self, lib: "Library") -> List["Template"]:
for _ in range(self.expectedCount):
template_body = Graph()
template_body.add((PARAM["name"], A, self.classname))
templs.append(lib.create_template(f"resolveAdd{name}", template_body))
template_name = _guarantee_unique_template_name(lib, f"resolveAdd{name}")
templs.append(lib.create_template(template_name, template_body))
return templs


Expand Down
22 changes: 21 additions & 1 deletion buildingmotif/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
from rdflib.compare import _TripleCanonicalizer
from rdflib.paths import ZeroOrOne
from rdflib.term import Node
from sqlalchemy.exc import NoResultFound

from buildingmotif.namespaces import OWL, PARAM, RDF, SH, XSD, bind_prefixes

if TYPE_CHECKING:
from buildingmotif.dataclasses import Template
from buildingmotif.dataclasses import Library, Template

Triple = Tuple[Node, Node, Node]
_gensym_counter = 0
Expand Down Expand Up @@ -49,6 +50,25 @@ def _param_name(param: URIRef) -> str:
return param[len(PARAM) :]


def _guarantee_unique_template_name(library: "Library", name: str) -> str:
"""
Ensure that the template name is unique in the library by appending an increasing
number. This is only called when we are generating templates from GraphDiffs and
the names are local to the Library. The Library is intended to be ephemeral so these
names will not be around for long.
"""
idx = 1
original_name = name
try:
while library.get_template_by_name(name):
name = f"{original_name}_{idx}"
idx += 1
except NoResultFound:
# this means that the template does not exist and we can use the original name
pass
return name


def copy_graph(g: Graph, preserve_blank_nodes: bool = True) -> Graph:
"""
Copy a graph. Creates new blank nodes so that these remain unique to each Graph
Expand Down
22 changes: 21 additions & 1 deletion tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
from rdflib import Graph, Literal, Namespace, URIRef

from buildingmotif import BuildingMOTIF
from buildingmotif.dataclasses import Model, ShapeCollection
from buildingmotif.dataclasses import Library, Model, ShapeCollection
from buildingmotif.namespaces import BRICK, SH, XSD, A
from buildingmotif.utils import (
PARAM,
_guarantee_unique_template_name,
_param_name,
_strip_param,
get_parameters,
Expand Down Expand Up @@ -378,3 +379,22 @@ def test_strip_param():
assert (
expected == output
), f"Input {input_val} should be {expected} but was {output}"


def test_guarantee_unique_template_name(bm):
# make new library
lib = Library.create("test")
# add a template
lib.create_template("test_template", None)

# create a new template with the same name
name = _guarantee_unique_template_name(lib, "test_template")
assert name == "test_template_1"

lib.create_template(name, None)

# create a new template with the same name
name = _guarantee_unique_template_name(lib, "test_template")
assert name == "test_template_2"

lib.create_template(name, None)

0 comments on commit 0b4c683

Please sign in to comment.