Skip to content

Commit cd084d4

Browse files
committed
Merge branch 'pipeline' into save-schema-version
* pipeline: missed a couple of needed changes bug-fix: in avoiding infinite recursion, it is whether we've seen "self" and the "property" before that matters, not whether we've seen the value before. update property name to the one used in "latest" Fix #73 - Infinite recursion in validate() Add Python 3.14 to the matrix for running CI tests
2 parents 3383b94 + ba033c5 commit cd084d4

File tree

6 files changed

+43
-14
lines changed

6 files changed

+43
-14
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ["3.9", "3.13"]
15+
python-version: ["3.9", "3.13", "3.14"]
1616
steps:
1717

1818
- name: Checkout Repository

.github/workflows/test.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ on:
88
jobs:
99
build_and_test:
1010
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
python-version: ["3.9", "3.12", "3.14"]
1114
steps:
1215

1316
- name: Checkout Repository
1417
uses: actions/checkout@v4
1518

16-
- name: Set up Python 3.12
19+
- name: Set up Python ${{ matrix.python-version }}
1720
uses: actions/setup-python@v5
1821
with:
19-
python-version: 3.12
22+
python-version: ${{ matrix.python-version }}
2023

2124
- name: Run build
2225
run: |

pipeline/src/base.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,21 @@ def validate(self, ignore=None):
168168
169169
Returns a dict containing information about any validation failures.
170170
"""
171+
return self._validate(ignore=ignore)
172+
173+
def _validate(self, ignore=None, seen=None):
174+
# this is implemented as an internal method so that the
175+
# "seen" set, needed to avoid possible infinite recursion,
176+
# can be hidden from the public interface.
177+
if seen is None:
178+
seen = set()
171179
failures = defaultdict(list)
172180
for property in self.properties:
173181
value = getattr(self, property.name, None)
174-
for key, values in property.validate(value, ignore=ignore).items():
175-
failures[key] += values
182+
if (id(self), property.name) not in seen:
183+
seen.add((id(self), property.name))
184+
for key, values in property.validate(value, ignore=ignore, seen=seen).items():
185+
failures[key] += values
176186
return failures
177187

178188
@property
@@ -304,7 +314,7 @@ def __str__(self):
304314
def to_jsonld(self):
305315
return self.value
306316

307-
def validate(self, ignore=None):
317+
def _validate(self, ignore=None, seen=None):
308318
if ignore is None:
309319
ignore = []
310320
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
@@ -275,3 +275,19 @@ def test_issue0056():
275275
assert failures["multiplicity"] == ['digital_identifier does not accept multiple values, but contains 2']
276276
data = dataset.to_jsonld()
277277
json.dumps(data) # this should not raise an Exception
278+
279+
280+
def test_issue0073():
281+
# https://github.com/openMetadataInitiative/openMINDS_Python/issues/73
282+
# Infinite recursion in validate()
283+
ds1 = omcore.DatasetVersion(
284+
short_name="ds1",
285+
is_variant_of=None
286+
)
287+
ds2 = omcore.DatasetVersion(
288+
short_name="ds2",
289+
is_variant_of=ds1
290+
)
291+
ds1.is_variant_of = ds2
292+
293+
failures = ds1.validate()

0 commit comments

Comments
 (0)