Coverage for tests / unit / rdflibplus / test_datasetview.py: 100%
180 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-26 21:25 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-26 21:25 +0000
1"""Test DatasetView."""
3# ruff: noqa: D103, PLR2004
4import pytest
5from rdflib import Dataset, Graph, Literal, Namespace, URIRef
7from pythinfer.rdflibplus import DatasetView
9EX = Namespace("http://example.org/")
12@pytest.fixture
13def g0() -> Graph:
14 """Create a named graph with zero triples (for immutability tests)."""
15 return Graph(identifier=EX.graph0)
18@pytest.fixture
19def g1() -> Graph:
20 """Create a named graph with one triple."""
21 graph = Graph(identifier=EX.graph1)
22 graph.add((EX.subject1, EX.predicate1, Literal("object1")))
23 return graph
26@pytest.fixture
27def g2() -> Graph:
28 """Create a named graph with three triples."""
29 graph = Graph(identifier=EX.graph2)
30 graph.add((EX.subject2, EX.predicate2, Literal("object2")))
31 graph.add((EX.subject3, EX.predicate3, Literal("object3")))
32 graph.add((EX.subject4, EX.predicate4, Literal("object4")))
33 return graph
36@pytest.fixture
37def g3() -> Graph:
38 """Create a named graph with five triples."""
39 graph = Graph(identifier=EX.graph3)
40 graph.add((EX.subject5, EX.predicate5, Literal("object5")))
41 graph.add((EX.subject6, EX.predicate6, Literal("object6")))
42 graph.add((EX.subject7, EX.predicate7, Literal("object7")))
43 graph.add((EX.subject8, EX.predicate8, Literal("object8")))
44 graph.add((EX.subject9, EX.predicate9, Literal("object9")))
45 return graph
48@pytest.fixture
49def ds(g1: Graph, g2: Graph, g3: Graph) -> Dataset:
50 """Create a Dataset containing all three graphs."""
51 dataset = Dataset(default_union=True)
52 dataset.graph(g1)
53 dataset.graph(g2)
54 dataset.graph(g3)
55 return dataset
58def test_starting_assumptions(g1: Graph, g2: Graph, g3: Graph, ds: Dataset) -> None:
59 # Starting assumption: separate graphs with different sizes and with
60 # different stores
61 assert len(g1) == 1
62 assert len(g2) == 3
63 assert len(g3) == 5
64 assert g1.store != g2.store != g3.store
66 # A single Dataset contains all graphs
67 assert len(ds.graph(g1.identifier)) == 1
68 assert len(ds.graph(g2.identifier)) == 3
69 assert len(ds.graph(g3.identifier)) == 5
70 assert len(ds) == 9
73def test_dataset_behaviour_is_insufficient(
74 g1: Graph,
75 g3: Graph,
76 ds: Dataset,
77) -> None:
78 # Try to use a new Dataset as a subset view (and show it doesn't work)
79 ds2 = Dataset(store=ds.store)
80 ds2.add_graph(g1.identifier)
81 ds2.add_graph(g3.identifier)
83 # First, check that the explicitly added graphs are present:
84 assert len(ds2.graph(g1.identifier)) == 1
85 assert len(ds2.graph(g3.identifier)) == 5
87 # Now, show that it is *not a subset view*: all graphs are there.
88 # This would be 6 if Dataset only contained the two graphs added above
89 assert len(ds2) == 9
92def test_datasetview_basic_usage(g1: Graph, g2: Graph, g3: Graph, ds: Dataset) -> None:
93 ds_view = DatasetView(
94 original_ds=ds,
95 included_graph_ids=[
96 g1.identifier,
97 g3.identifier,
98 ],
99 )
101 assert len(ds_view.graph(g1.identifier)) == 1
102 assert len(ds_view.graph(g3.identifier)) == 5
103 assert len(ds_view) == 6
105 # Check that excluded graph is indeed excluded
106 with pytest.raises(PermissionError):
107 ds_view.graph(g2.identifier)
109 # Also check that works when providing full Graph object
110 assert len(ds_view.graph(g1)) == 1
111 assert len(ds_view.graph(g3)) == 5
112 with pytest.raises(PermissionError):
113 ds_view.graph(g2)
116def test_datasetview_different_selection(
117 g1: Graph,
118 g2: Graph,
119 g3: Graph,
120 ds: Dataset,
121) -> None:
122 ds_view2 = DatasetView(
123 original_ds=ds,
124 included_graph_ids=[
125 g2.identifier,
126 g3.identifier,
127 ],
128 )
130 assert len(ds_view2.graph(g2.identifier)) == 3
131 assert len(ds_view2.graph(g3.identifier)) == 5
132 assert len(ds_view2) == 8
134 with pytest.raises(PermissionError):
135 ds_view2.graph(g1.identifier)
138def test_quads_method(g1: Graph, g2: Graph, g3: Graph, ds: Dataset) -> None:
139 ds_view = DatasetView(
140 original_ds=ds,
141 included_graph_ids=[
142 g1.identifier,
143 g3.identifier,
144 ],
145 )
146 quads = list(ds_view.quads((None, None, None, None)))
147 expected_quads = list(ds.quads((None, None, None, None)))
148 # Filter expected_quads to only those in g1 and g3
149 expected_quads = [
150 quad for quad in expected_quads if quad[3] in {g1.identifier, g3.identifier}
151 ]
152 assert len(expected_quads) == 6
153 assert len(quads) == len(expected_quads)
154 assert set(quads) == set(expected_quads)
157def test_triples_method(g1: Graph, g2: Graph, g3: Graph, ds: Dataset) -> None:
158 ds_view = DatasetView(
159 original_ds=ds,
160 included_graph_ids=[
161 g1.identifier,
162 g3.identifier,
163 ],
164 )
165 triples = list(ds_view.triples((None, None, None)))
166 expected_triples = list(g1.triples((None, None, None))) + list(
167 g3.triples((None, None, None)),
168 )
170 assert len(expected_triples) == 6
171 assert len(triples) == len(expected_triples)
172 assert set(triples) == set(expected_triples)
174 # Also test with explicit context as kwarg
175 triples_ctx = list(
176 ds_view.triples((None, None, None), context=g1),
177 )
178 expected_triples_ctx = list(g1.triples((None, None, None)))
179 assert len(triples_ctx) == len(expected_triples_ctx)
180 assert set(triples_ctx) == set(expected_triples_ctx)
183def test_contains_method(g1: Graph, g2: Graph, g3: Graph, ds: Dataset) -> None:
184 ds_view = DatasetView(
185 original_ds=ds,
186 included_graph_ids=[
187 g1.identifier,
188 g3.identifier,
189 ],
190 )
191 # Triples in g1 and g3 should be found
192 assert (EX.subject1, EX.predicate1, Literal("object1")) in ds_view
193 assert (EX.subject5, EX.predicate5, Literal("object5")) in ds_view
195 # Triples in g2 should not be found
196 assert (EX.subject2, EX.predicate2, Literal("object2")) not in ds_view
199def test_iterating_over_dataset(g1: Graph, g2: Graph, g3: Graph, ds: Dataset) -> None:
200 ds_view = DatasetView(
201 original_ds=ds,
202 included_graph_ids=[
203 g1.identifier,
204 g3.identifier,
205 ],
206 )
207 triples = set(ds_view)
208 expected_triples = {(s, p, o, g1.identifier) for (s, p, o) in g1} | {
209 (s, p, o, g3.identifier) for (s, p, o) in g3
210 }
212 assert len(expected_triples) == 6
213 assert len(triples) == len(expected_triples)
214 assert triples == expected_triples
217def test_graph_method(g0: Graph, g1: Graph, g2: Graph, ds: Dataset) -> None:
218 ds_view = DatasetView(
219 original_ds=ds,
220 included_graph_ids=[
221 g0.identifier,
222 g2.identifier,
223 ],
224 )
225 # Accessing included graphs should work
226 graph0 = ds_view.graph(g0.identifier)
227 assert isinstance(graph0, Graph)
228 assert len(graph0) == 0
229 assert graph0 == g0
231 graph2 = ds_view.graph(g2.identifier)
232 assert isinstance(graph2, Graph)
233 assert len(graph2) == 3
234 assert graph2 == g2
236 # Accessing excluded graphs should raise an error
237 with pytest.raises(PermissionError):
238 ds_view.graph(g1.identifier)
240 # Accessing the default graph should raise an error because it is not
241 # explicitly included
242 with pytest.raises(PermissionError):
243 ds_view.graph(ds.default_graph.identifier)
245 # Make sure that the deprecated add_graph method behaves the same way
246 with pytest.raises(PermissionError):
247 ds_view.add_graph(g1.identifier)
250def test_remove_graph(g0: Graph, g1: Graph, g2: Graph, ds: Dataset) -> None:
251 ds_view = DatasetView(
252 original_ds=ds,
253 included_graph_ids=[
254 g1.identifier,
255 g2.identifier,
256 ],
257 )
258 n_orig_ds = len(ds)
259 assert len(ds_view) == 4
260 # Try to remove a graph from the view by identifier
261 ds_view.remove_graph(g1.identifier)
262 assert len(ds_view.graph(g1.identifier)) == 0
263 assert len(ds_view) == 3
265 # Check that the original dataset is also affected
266 assert len(ds.graph(g1.identifier)) == 0
267 assert len(ds) == n_orig_ds - 1
269 # Try to remove a graph from the view by Graph object
270 ds_view.remove_graph(g2)
271 assert len(ds_view.graph(g2.identifier)) == 0
272 assert len(ds_view) == 0
273 assert len(ds.graph(g2.identifier)) == 0
274 assert len(ds) == n_orig_ds - 4
276 # Now check that attempting to remove a graph not in the view raises an error
277 with pytest.raises(PermissionError):
278 ds_view.remove_graph(g0.identifier)
280 # Now check that None or default graph raises an error
281 with pytest.raises(PermissionError):
282 ds_view.remove_graph(None)
283 with pytest.raises(PermissionError):
284 ds_view.remove_graph(ds_view.default_graph)
287def test_add_triple(g0: Graph, g1: Graph, g2: Graph, ds: Dataset) -> None:
288 ds_view = DatasetView(
289 original_ds=ds,
290 included_graph_ids=[
291 g0.identifier,
292 g2.identifier,
293 ],
294 )
295 assert len(ds_view.graph(g0.identifier)) == 0
296 assert len(ds.graph(g0.identifier)) == 0
298 # Adding a triple to a graph included in the view should work like normal
299 ds_view.graph(g0.identifier).add(
300 (EX.subjectX, EX.predicateX, Literal("objectX")),
301 )
302 assert len(ds_view.graph(g0.identifier)) == 1
303 # Check that the original dataset reflects the change
304 assert len(ds.graph(g0.identifier)) == 1
306 # Adding a triple to a graph not included in the view should fail
307 with pytest.raises(PermissionError):
308 ds_view.graph(g1.identifier).add(
309 (EX.subjectY, EX.predicateY, Literal("objectY")),
310 )
312 # This holds for the default graph as well
313 with pytest.raises(PermissionError):
314 ds_view.graph(ds_view.default_graph.identifier).add(
315 (EX.subjectY, EX.predicateY, Literal("objectY")),
316 )
318 # Adding directly to included graphs should work
319 ds_view.add((EX.subjectY, EX.predicateY, Literal("objectY"), g0.identifier))
321 # But adding directly to the View also fails for excluded graphs
322 with pytest.raises(PermissionError):
323 ds_view.add((EX.subjectY, EX.predicateY, Literal("objectY"), g1.identifier))
324 with pytest.raises(PermissionError):
325 ds_view.add((EX.subjectY, EX.predicateY, Literal("objectY")))
328def test_remove_triple(g0: Graph, g1: Graph, g2: Graph, ds: Dataset) -> None:
329 ds_view = DatasetView(
330 original_ds=ds,
331 included_graph_ids=[
332 g0.identifier,
333 g2.identifier,
334 ],
335 )
336 # Removing a triple from a graph in the view should work like normal
337 ds_view.graph(g2.identifier).remove(
338 (EX.subject2, EX.predicate2, Literal("object2")),
339 )
340 assert len(ds_view.graph(g2.identifier)) == 2
341 # Check that the original dataset reflects the change
342 assert len(ds.graph(g2.identifier)) == 2
343 assert len(ds_view) == 2
345 # Removing a triple from a graph not included in the view should fail
346 with pytest.raises(PermissionError):
347 ds_view.graph(g1.identifier).remove(
348 (EX.subject1, EX.predicate1, Literal("object1")),
349 )
351 # Also check that removing directly from the view fails for excluded graphs
352 with pytest.raises(PermissionError):
353 ds_view.remove(
354 (EX.subject1, EX.predicate1, Literal("object1"), g1.identifier),
355 )
356 # This holds for the default graph as well
357 with pytest.raises(PermissionError):
358 ds_view.remove(
359 (EX.subject1, EX.predicate1, Literal("object1")),
360 )
362 # Now check removing directly from included graphs works
363 assert len(ds_view.graph(g2.identifier)) == 2
364 ds_view.remove(
365 (EX.subject3, EX.predicate3, Literal("object3"), g2.identifier),
366 )
367 assert len(ds_view.graph(g2.identifier)) == 1
368 assert len(ds.graph(g2.identifier)) == 1
369 assert len(ds_view) == 1
372def test_datasetview_preserves_namespace_bindings(
373 g1: Graph,
374 g2: Graph,
375 ds: Dataset,
376) -> None:
377 """Test that namespace bindings are preserved when serializing a DatasetView."""
378 # Bind custom namespaces to the dataset
379 CUSTOM1 = Namespace("http://custom1.example.org/")
380 CUSTOM2 = Namespace("http://custom2.example.org/")
381 ds.bind("custom1", CUSTOM1)
382 ds.bind("custom2", CUSTOM2)
384 # Add triples using these custom namespaces via the dataset's graphs
385 # (not the original graph objects, which have separate stores)
386 ds.graph(g1.identifier).add(
387 (CUSTOM1.subject_custom1, CUSTOM1.predicate_custom1, Literal("value1"))
388 )
389 ds.graph(g2.identifier).add(
390 (CUSTOM2.subject_custom2, CUSTOM2.predicate_custom2, Literal("value2"))
391 )
393 # Create a DatasetView that excludes one graph (g3)
394 ds_view = DatasetView(
395 original_ds=ds,
396 included_graph_ids=[g1.identifier, g2.identifier],
397 )
399 # Serialize the view
400 serialized = str(ds_view.serialize(format="trig"))
402 # Check that namespace bindings are preserved in serialization
403 assert "@prefix custom1: <http://custom1.example.org/>" in serialized
404 assert "@prefix custom2: <http://custom2.example.org/>" in serialized