Skip to content

Commit 2640bf4

Browse files
authored
Merge pull request #92 from apdavison/linked-node-embedding
Change embed_linked_nodes from bool to LinkedNodeEmbedding enum
2 parents 61316c1 + 1c6cd5a commit 2640bf4

6 files changed

Lines changed: 131 additions & 22 deletions

File tree

build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120

121121
env = Environment(loader=FileSystemLoader(os.path.dirname(os.path.realpath(__file__))), autoescape=select_autoescape())
122122
context = {
123-
"version": "0.5.0",
123+
"version": "0.5.1.dev.0",
124124
}
125125

126126
with open("target/pyproject.toml", "w") as fp:

pipeline/src/base.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from datetime import date, datetime
1111
from collections import defaultdict
12+
from enum import Enum
1213
import json
1314
from typing import Union
1415

@@ -17,17 +18,31 @@
1718
from .registry import Registry
1819

1920

20-
def value_to_jsonld(value, include_empty_properties=True, embed_linked_nodes=True):
21+
class LinkedNodeEmbedding(Enum):
22+
ALWAYS = "always"
23+
NEVER = "never"
24+
IF_NECESSARY = "if necessary"
25+
26+
27+
def value_to_jsonld(value, include_empty_properties=True, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS):
2128
if isinstance(value, LinkedMetadata):
22-
if embed_linked_nodes:
29+
if embed_linked_nodes in (LinkedNodeEmbedding.ALWAYS, True):
2330
item = value.to_jsonld(
2431
with_context=False,
2532
include_empty_properties=include_empty_properties,
2633
embed_linked_nodes=embed_linked_nodes,
2734
)
28-
else:
29-
if hasattr(value, "id") and value.id is None:
35+
elif value.id is None:
36+
if embed_linked_nodes == LinkedNodeEmbedding.IF_NECESSARY:
37+
item = value.to_jsonld(
38+
with_context=False,
39+
include_empty_properties=include_empty_properties,
40+
embed_linked_nodes=embed_linked_nodes,
41+
)
42+
else:
43+
assert embed_linked_nodes in (LinkedNodeEmbedding.NEVER, False)
3044
raise ValueError("Exporting as a stand-alone JSON-LD document requires @id to be defined.")
45+
else:
3146
item = {"@id": value.id}
3247
elif isinstance(value, EmbeddedMetadata):
3348
item = value.to_jsonld(
@@ -62,7 +77,12 @@ def has_property(self, name):
6277
return True
6378
return False
6479

65-
def to_jsonld(self, include_empty_properties=True, embed_linked_nodes=True, with_context=True):
80+
def to_jsonld(
81+
self,
82+
include_empty_properties=True,
83+
embed_linked_nodes=LinkedNodeEmbedding.ALWAYS,
84+
with_context=True
85+
):
6686
"""
6787
Return a represention of this metadata node as a dictionary that can be directly serialized to JSON-LD.
6888
"""

pipeline/src/collection.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import json
1212
import os
1313
from .registry import lookup_type
14-
from .base import Link
14+
from .base import Link, LinkedNodeEmbedding
1515

1616

1717
DEFAULT_VERSION = "v5"
@@ -149,7 +149,9 @@ def save(self, path, individual_files=False, include_empty_properties=False, gro
149149
"@context": data_context,
150150
"@graph": [
151151
node.to_jsonld(
152-
embed_linked_nodes=False, include_empty_properties=include_empty_properties, with_context=False
152+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
153+
include_empty_properties=include_empty_properties,
154+
with_context=False
153155
)
154156
for node in self
155157
],
@@ -179,7 +181,10 @@ def save(self, path, individual_files=False, include_empty_properties=False, gro
179181
else:
180182
file_path = os.path.join(path, f"{file_identifier}.jsonld")
181183
with open(file_path, "w") as fp:
182-
data = node.to_jsonld(embed_linked_nodes=False, include_empty_properties=include_empty_properties)
184+
data = node.to_jsonld(
185+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
186+
include_empty_properties=include_empty_properties
187+
)
183188
json.dump(data, fp, indent=2)
184189
output_paths.append(file_path)
185190
return output_paths

pipeline/tests/test_collections.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import shutil
77
import json
88

9+
from openminds.base import LinkedNodeEmbedding
910
from openminds.collection import Collection
1011
import openminds.latest.controlled_terms
1112
import openminds.latest.core as omcore
@@ -45,8 +46,8 @@ def test_round_trip_single_file():
4546
new_person = person
4647
break
4748

48-
p = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=True)
49-
np = new_person.to_jsonld(include_empty_properties=False, embed_linked_nodes=True)
49+
p = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS)
50+
np = new_person.to_jsonld(include_empty_properties=False, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS)
5051
assert p == np
5152

5253

@@ -72,8 +73,8 @@ def test_round_trip_multi_file():
7273
new_person = person
7374
break
7475

75-
p = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=True)
76-
np = new_person.to_jsonld(include_empty_properties=False, embed_linked_nodes=True)
76+
p = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS)
77+
np = new_person.to_jsonld(include_empty_properties=False, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS)
7778
assert p == np
7879

7980

@@ -92,8 +93,8 @@ def test_round_trip_multi_file_group_by_schema():
9293
new_person = person
9394
break
9495

95-
p = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=True)
96-
np = new_person.to_jsonld(include_empty_properties=False, embed_linked_nodes=True)
96+
p = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS)
97+
np = new_person.to_jsonld(include_empty_properties=False, embed_linked_nodes=LinkedNodeEmbedding.ALWAYS)
9798
assert p == np
9899

99100

pipeline/tests/test_instantiation.py

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import pytest
99

10-
from openminds.base import Node, IRI, Link
10+
from openminds.base import Node, IRI, Link, LinkedNodeEmbedding
1111
from utils import build_fake_node
1212

1313
module_names = (
@@ -114,8 +114,74 @@ def test_link():
114114
}
115115
assert my_dsv1.to_jsonld(
116116
include_empty_properties=False,
117-
embed_linked_nodes=False
117+
embed_linked_nodes=LinkedNodeEmbedding.NEVER
118118
) == my_dsv2.to_jsonld(
119119
include_empty_properties=False,
120-
embed_linked_nodes=False
120+
embed_linked_nodes=LinkedNodeEmbedding.NEVER
121121
) == expected
122+
123+
124+
def test_linked_node_embedding():
125+
from openminds.v4.core import Organization, Person
126+
from openminds.v4.core.actors.affiliation import Affiliation
127+
128+
uni = Organization(full_name="University of Somewhere", id="_:001")
129+
person_with_id = Person(
130+
given_name="Ada",
131+
family_name="Lovelace",
132+
id="_:002",
133+
affiliations=[Affiliation(member_of=uni)],
134+
)
135+
person_without_id = Person(
136+
given_name="Ada",
137+
family_name="Lovelace",
138+
affiliations=[Affiliation(member_of=uni)],
139+
)
140+
141+
# ALWAYS: linked nodes are embedded inline
142+
result = person_with_id.to_jsonld(
143+
include_empty_properties=False,
144+
embed_linked_nodes=LinkedNodeEmbedding.ALWAYS,
145+
)
146+
affiliation = result["affiliation"][0]
147+
assert affiliation["memberOf"]["@type"] == "https://openminds.om-i.org/types/Organization"
148+
assert affiliation["memberOf"]["fullName"] == "University of Somewhere"
149+
150+
# NEVER: linked nodes with id are replaced by {"@id": ...}
151+
result = person_with_id.to_jsonld(
152+
include_empty_properties=False,
153+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
154+
)
155+
affiliation = result["affiliation"][0]
156+
assert affiliation["memberOf"] == {"@id": "_:001"}
157+
158+
# NEVER: raises ValueError when a linked node has no id
159+
uni_no_id = Organization(full_name="University of Nowhere")
160+
person_with_unidentified_org = Person(
161+
given_name="Ada",
162+
family_name="Lovelace",
163+
id="_:003",
164+
affiliations=[Affiliation(member_of=uni_no_id)],
165+
)
166+
with pytest.raises(ValueError, match="requires @id to be defined"):
167+
person_with_unidentified_org.to_jsonld(
168+
include_empty_properties=False,
169+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
170+
)
171+
172+
# IF_NECESSARY: linked nodes with id are replaced by {"@id": ...}
173+
result = person_with_id.to_jsonld(
174+
include_empty_properties=False,
175+
embed_linked_nodes=LinkedNodeEmbedding.IF_NECESSARY,
176+
)
177+
affiliation = result["affiliation"][0]
178+
assert affiliation["memberOf"] == {"@id": "_:001"}
179+
180+
# IF_NECESSARY: linked nodes without id are embedded inline
181+
result = person_with_unidentified_org.to_jsonld(
182+
include_empty_properties=False,
183+
embed_linked_nodes=LinkedNodeEmbedding.IF_NECESSARY,
184+
)
185+
affiliation = result["affiliation"][0]
186+
assert affiliation["memberOf"]["@type"] == "https://openminds.om-i.org/types/Organization"
187+
assert affiliation["memberOf"]["fullName"] == "University of Nowhere"

pipeline/tests/test_regressions.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
from openminds import Collection, IRI
8+
from openminds.base import LinkedNodeEmbedding
89
import openminds.latest
910
import openminds.v4
1011
import openminds.v5
@@ -114,7 +115,11 @@ def test_issue0007a(om):
114115
om.core.Affiliation(member_of=uni2),
115116
]
116117

117-
actual = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=False, with_context=True)
118+
actual = person.to_jsonld(
119+
include_empty_properties=False,
120+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
121+
with_context=True
122+
)
118123
expected = {
119124
"@context": {"@vocab": "https://openminds.om-i.org/props/"},
120125
"@id": "_:001",
@@ -192,7 +197,11 @@ def test_issue0007b(om):
192197
om.core.Membership(member=person2)
193198
]
194199

195-
actual = uni1.to_jsonld(include_empty_properties=False, embed_linked_nodes=False, with_context=True)
200+
actual = uni1.to_jsonld(
201+
include_empty_properties=False,
202+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
203+
with_context=True
204+
)
196205
expected = {
197206
"@context": {"@vocab": "https://openminds.om-i.org/props/"},
198207
"@id": "_:002",
@@ -273,7 +282,11 @@ def test_issue0008a(om):
273282
family_name="Professor",
274283
affiliations=[om.core.Affiliation(member_of=uni1, end_date=date(2023, 9, 30))],
275284
)
276-
actual = person.to_jsonld(include_empty_properties=False, embed_linked_nodes=False, with_context=True)
285+
actual = person.to_jsonld(
286+
include_empty_properties=False,
287+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
288+
with_context=True
289+
)
277290
expected = {
278291
"@context": {"@vocab": "https://openminds.om-i.org/props/"},
279292
"@id": "_:002",
@@ -308,7 +321,11 @@ def test_issue0008b(om):
308321
id="_:001",
309322
memberships=om.core.Membership(member=person, end_date=date(2023, 9, 30))
310323
)
311-
actual = uni1.to_jsonld(include_empty_properties=False, embed_linked_nodes=False, with_context=True)
324+
actual = uni1.to_jsonld(
325+
include_empty_properties=False,
326+
embed_linked_nodes=LinkedNodeEmbedding.NEVER,
327+
with_context=True
328+
)
312329
expected = {
313330
'@context': {'@vocab': 'https://openminds.om-i.org/props/'},
314331
'@id': '_:001',

0 commit comments

Comments
 (0)