Skip to content

Commit ba033c5

Browse files
Merge pull request #76 from apdavison/issue73
Fix for #73 - Infinite recursion in validate()
2 parents e6b603c + ac2b1e7 commit ba033c5

File tree

4 files changed

+37
-11
lines changed

4 files changed

+37
-11
lines changed

pipeline/src/base.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,21 @@ def validate(self, ignore=None):
161161
162162
Returns a dict containing information about any validation failures.
163163
"""
164+
return self._validate(ignore=ignore)
165+
166+
def _validate(self, ignore=None, seen=None):
167+
# this is implemented as an internal method so that the
168+
# "seen" set, needed to avoid possible infinite recursion,
169+
# can be hidden from the public interface.
170+
if seen is None:
171+
seen = set()
164172
failures = defaultdict(list)
165173
for property in self.properties:
166174
value = getattr(self, property.name, None)
167-
for key, values in property.validate(value, ignore=ignore).items():
168-
failures[key] += values
175+
if (id(self), property.name) not in seen:
176+
seen.add((id(self), property.name))
177+
for key, values in property.validate(value, ignore=ignore, seen=seen).items():
178+
failures[key] += values
169179
return failures
170180

171181
@property
@@ -298,7 +308,7 @@ def __str__(self):
298308
def to_jsonld(self):
299309
return self.value
300310

301-
def validate(self, ignore=None):
311+
def _validate(self, ignore=None, seen=None):
302312
if ignore is None:
303313
ignore = []
304314
failures = defaultdict(list)

pipeline/src/properties.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,16 @@ def types(self):
100100
def is_link(self) -> bool:
101101
return issubclass(self.types[0], Node)
102102

103-
def validate(self, value, ignore=None):
103+
def validate(self, value, ignore=None, seen=None):
104104
"""
105105
Check whether `value` satisfies all constraints.
106106
107107
Arguments:
108108
value: the value to be checked
109109
ignore: an optional list of check types that should be ignored
110110
("required", "type", "multiplicity")
111+
seen: for internal use: contains a set with Python object ids that have
112+
already been encountered in the validation tree.
111113
112114
Returns a dict containing information about any validation failures.
113115
"""
@@ -131,11 +133,10 @@ def validate(self, value, ignore=None):
131133
else:
132134
item_type = f"value contains {type(item)}"
133135
failures["type"].append(
134-
f"{self.name}: Expected {', '.join(t.__name__ for t in self.types)}, " +
135-
item_type
136+
f"{self.name}: Expected {', '.join(t.__name__ for t in self.types)}, " + item_type
136137
)
137138
elif isinstance(item, (Node, IRI)):
138-
failures.update(item.validate(ignore=ignore))
139+
failures.update(item._validate(ignore=ignore, seen=seen))
139140
if self.min_items:
140141
if len(value) < self.min_items and "multiplicity" not in ignore:
141142
failures["multiplicity"].append(
@@ -167,11 +168,10 @@ def validate(self, value, ignore=None):
167168
else:
168169
value_type = f"value contains {type(value)}"
169170
failures["type"].append(
170-
f"{self.name}: Expected {', '.join(t.__name__ for t in self.types)}, " +
171-
value_type
171+
f"{self.name}: Expected {', '.join(t.__name__ for t in self.types)}, " + value_type
172172
)
173173
elif isinstance(value, (Node, IRI)):
174-
failures.update(value.validate(ignore=ignore))
174+
failures.update(value._validate(ignore=ignore, seen=seen))
175175
# todo: check formatting, multiline
176176
return failures
177177

pipeline/tests/test_instantiation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_IRI():
6969
for value in valid_iris:
7070
iri = IRI(value)
7171
assert iri.value == value
72-
failures = iri.validate()
72+
failures = iri._validate()
7373
if value.startswith("http"):
7474
assert not failures
7575
else:

pipeline/tests/test_regressions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,19 @@ def test_issue0056():
254254
assert failures["multiplicity"] == ['digital_identifier does not accept multiple values, but contains 2']
255255
data = dataset.to_jsonld()
256256
json.dumps(data) # this should not raise an Exception
257+
258+
259+
def test_issue0073():
260+
# https://github.com/openMetadataInitiative/openMINDS_Python/issues/73
261+
# Infinite recursion in validate()
262+
ds1 = omcore.DatasetVersion(
263+
short_name="ds1",
264+
is_variant_of=None
265+
)
266+
ds2 = omcore.DatasetVersion(
267+
short_name="ds2",
268+
is_variant_of=ds1
269+
)
270+
ds1.is_variant_of = ds2
271+
272+
failures = ds1.validate()

0 commit comments

Comments
 (0)