rdf4j-python 0.1.2__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/PKG-INFO +2 -2
  2. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/pyproject.toml +3 -6
  3. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/_driver/_async_named_graph.py +8 -7
  4. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/_driver/_async_rdf4j_db.py +5 -5
  5. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/_driver/_async_repository.py +48 -39
  6. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/model/__init__.py +0 -2
  7. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/model/_namespace.py +38 -15
  8. rdf4j_python-0.1.3/rdf4j_python/model/_repository_info.py +62 -0
  9. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/model/repository_config.py +106 -53
  10. rdf4j_python-0.1.3/rdf4j_python/model/term.py +20 -0
  11. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/model/vocabulary.py +1 -0
  12. rdf4j_python-0.1.3/rdf4j_python/utils/helpers.py +20 -0
  13. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/PKG-INFO +2 -2
  14. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/SOURCES.txt +1 -2
  15. rdf4j_python-0.1.3/rdf4j_python.egg-info/requires.txt +2 -0
  16. rdf4j_python-0.1.3/tests/test_async_named_graph.py +89 -0
  17. rdf4j_python-0.1.3/tests/test_helpers.py +21 -0
  18. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/tests/test_rdf4j_repository.py +83 -65
  19. rdf4j_python-0.1.2/rdf4j_python/model/_base_model.py +0 -48
  20. rdf4j_python-0.1.2/rdf4j_python/model/_dataset.py +0 -38
  21. rdf4j_python-0.1.2/rdf4j_python/model/_repository_info.py +0 -52
  22. rdf4j_python-0.1.2/rdf4j_python/model/term.py +0 -14
  23. rdf4j_python-0.1.2/rdf4j_python/utils/helpers.py +0 -22
  24. rdf4j_python-0.1.2/rdf4j_python.egg-info/requires.txt +0 -2
  25. rdf4j_python-0.1.2/tests/test_async_named_graph.py +0 -83
  26. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/LICENSE +0 -0
  27. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/README.md +0 -0
  28. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/__init__.py +0 -0
  29. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/_client/__init__.py +0 -0
  30. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/_client/_client.py +0 -0
  31. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/_driver/__init__.py +0 -0
  32. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/exception/__init__.py +0 -0
  33. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/exception/repo_exception.py +0 -0
  34. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/utils/__init__.py +0 -0
  35. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python/utils/const.py +0 -0
  36. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/dependency_links.txt +0 -0
  37. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/rdf4j_python.egg-info/top_level.txt +0 -0
  38. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/setup.cfg +0 -0
  39. {rdf4j_python-0.1.2 → rdf4j_python-0.1.3}/tests/test_client.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdf4j-python
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: The Python client for RDF4J
5
5
  Author-email: Chengxu Bian <cbian564@gmail.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: httpx>=0.28.1
10
- Requires-Dist: rdflib>=7.1.4
10
+ Requires-Dist: pyoxigraph>=0.4.10
11
11
  Dynamic: license-file
12
12
 
13
13
  # 🐍 rdf4j-python
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "rdf4j-python"
3
3
  authors = [{ name = "Chengxu Bian", email = "cbian564@gmail.com" }]
4
- version = "0.1.2"
4
+ version = "0.1.3"
5
5
  description = "The Python client for RDF4J"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.10"
8
- dependencies = ["httpx>=0.28.1", "rdflib>=7.1.4"]
8
+ dependencies = ["httpx>=0.28.1", "pyoxigraph>=0.4.10"]
9
9
 
10
10
  [dependency-groups]
11
11
  dev = [
@@ -14,10 +14,7 @@ dev = [
14
14
  "pytest-docker>=3.2.1",
15
15
  "ruff>=0.11.8",
16
16
  ]
17
- docs = [
18
- "furo>=2024.8.6",
19
- "sphinx>=8",
20
- ]
17
+ docs = ["furo>=2024.8.6", "sphinx>=8"]
21
18
 
22
19
 
23
20
  [tool.pytest.ini_options]
@@ -1,8 +1,9 @@
1
1
  from typing import Iterable
2
2
 
3
+ import pyoxigraph as og
4
+
3
5
  from rdf4j_python._client import AsyncApiClient
4
- from rdf4j_python.model import RDF4JDataSet
5
- from rdf4j_python.model.term import IRI, RDFStatement
6
+ from rdf4j_python.model.term import IRI, Quad, QuadResultSet, Triple
6
7
  from rdf4j_python.utils.const import Rdf4jContentType
7
8
  from rdf4j_python.utils.helpers import serialize_statements
8
9
 
@@ -22,11 +23,11 @@ class AsyncNamedGraph:
22
23
  self._repository_id = repository_id
23
24
  self._graph_uri = graph_uri
24
25
 
25
- async def get(self) -> RDF4JDataSet:
26
+ async def get(self) -> QuadResultSet:
26
27
  """Fetches all RDF statements from this named graph.
27
28
 
28
29
  Returns:
29
- str: RDF data serialized in the requested format.
30
+ QuadResultSet: RDF data serialized in the requested format.
30
31
 
31
32
  Raises:
32
33
  httpx.HTTPStatusError: If the request fails.
@@ -35,13 +36,13 @@ class AsyncNamedGraph:
35
36
  headers = {"Accept": Rdf4jContentType.NQUADS}
36
37
  response = await self._client.get(path, headers=headers)
37
38
  response.raise_for_status()
38
- return RDF4JDataSet.from_raw_text(response.text)
39
+ return og.parse(response.content, format=og.RdfFormat.N_QUADS)
39
40
 
40
- async def add(self, statements: Iterable[RDFStatement]):
41
+ async def add(self, statements: Iterable[Quad] | Iterable[Triple]):
41
42
  """Adds RDF statements to this named graph.
42
43
 
43
44
  Args:
44
- statements (Iterable[RDFStatement]): RDF statements to add.
45
+ statements (Iterable[Quad] | Iterable[Triple]): RDF statements to add.
45
46
 
46
47
  Raises:
47
48
  httpx.HTTPStatusError: If the request fails.
@@ -1,5 +1,5 @@
1
1
  import httpx
2
- import rdflib
2
+ import pyoxigraph as og
3
3
 
4
4
  from rdf4j_python._client import AsyncApiClient
5
5
  from rdf4j_python.exception.repo_exception import (
@@ -66,12 +66,12 @@ class AsyncRdf4j:
66
66
  "/repositories",
67
67
  headers={"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON},
68
68
  )
69
- result = rdflib.query.Result.parse(
70
- response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
69
+ query_solutions = og.parse_query_results(
70
+ response.text, format=og.QueryResultsFormat.JSON
71
71
  )
72
72
  return [
73
- RepositoryMetadata.from_rdflib_binding(binding)
74
- for binding in result.bindings
73
+ RepositoryMetadata.from_sparql_query_solution(query_solution)
74
+ for query_solution in query_solutions
75
75
  ]
76
76
 
77
77
  async def get_repository(self, repository_id: str) -> AsyncRdf4JRepository:
@@ -1,10 +1,7 @@
1
1
  from typing import Iterable, Optional
2
2
 
3
3
  import httpx
4
- import rdflib
5
- import rdflib.resource
6
- import rdflib.serializer
7
- import rdflib.store
4
+ import pyoxigraph as og
8
5
 
9
6
  from rdf4j_python._client import AsyncApiClient
10
7
  from rdf4j_python._driver._async_named_graph import AsyncNamedGraph
@@ -14,13 +11,16 @@ from rdf4j_python.exception.repo_exception import (
14
11
  RepositoryNotFoundException,
15
12
  RepositoryUpdateException,
16
13
  )
17
- from rdf4j_python.model import Namespace, RDF4JDataSet
14
+ from rdf4j_python.model import Namespace
18
15
  from rdf4j_python.model.term import (
16
+ IRI,
19
17
  Context,
20
18
  Object,
21
19
  Predicate,
22
- RDFStatement,
20
+ Quad,
21
+ QuadResultSet,
23
22
  Subject,
23
+ Triple,
24
24
  )
25
25
  from rdf4j_python.utils.const import Rdf4jContentType
26
26
  from rdf4j_python.utils.helpers import serialize_statements
@@ -57,7 +57,7 @@ class AsyncRdf4JRepository:
57
57
  """
58
58
  path = f"/repositories/{self._repository_id}"
59
59
  params = {"query": sparql_query, "infer": str(infer).lower()}
60
- headers = {"Accept": accept.value}
60
+ headers = {"Accept": accept}
61
61
  response = await self._client.get(path, params=params, headers=headers)
62
62
  self._handle_repo_not_found_exception(response)
63
63
  if "json" in response.headers.get("Content-Type", ""):
@@ -92,26 +92,32 @@ class AsyncRdf4JRepository:
92
92
  path = f"/repositories/{self._repository_id}/namespaces"
93
93
  headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
94
94
  response = await self._client.get(path, headers=headers)
95
- result = rdflib.query.Result.parse(
96
- response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
97
- )
98
95
  self._handle_repo_not_found_exception(response)
99
- return [Namespace.from_rdflib_binding(binding) for binding in result.bindings]
100
96
 
101
- async def set_namespace(self, prefix: str, namespace: str):
97
+ query_solutions = og.parse_query_results(
98
+ response.text, format=og.QueryResultsFormat.JSON
99
+ )
100
+ return [
101
+ Namespace.from_sparql_query_solution(query_solution)
102
+ for query_solution in query_solutions
103
+ ]
104
+
105
+ async def set_namespace(self, prefix: str, namespace: IRI):
102
106
  """Sets a namespace prefix.
103
107
 
104
108
  Args:
105
109
  prefix (str): The namespace prefix.
106
- namespace (str): The namespace URI.
110
+ namespace (IRI): The namespace URI.
107
111
 
108
112
  Raises:
109
113
  RepositoryNotFoundException: If the repository doesn't exist.
110
114
  NamespaceException: If the request fails.
111
115
  """
112
116
  path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
113
- headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
114
- response = await self._client.put(path, content=namespace, headers=headers)
117
+ headers = {"Content-Type": Rdf4jContentType.NTRIPLES}
118
+ response = await self._client.put(
119
+ path, content=namespace.value, headers=headers
120
+ )
115
121
  self._handle_repo_not_found_exception(response)
116
122
  if response.status_code != httpx.codes.NO_CONTENT:
117
123
  raise NamespaceException(f"Failed to set namespace: {response.text}")
@@ -130,7 +136,7 @@ class AsyncRdf4JRepository:
130
136
  NamespaceException: If retrieval fails.
131
137
  """
132
138
  path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
133
- headers = {"Accept": Rdf4jContentType.NTRIPLES.value}
139
+ headers = {"Accept": Rdf4jContentType.NTRIPLES}
134
140
  response = await self._client.get(path, headers=headers)
135
141
  self._handle_repo_not_found_exception(response)
136
142
 
@@ -192,7 +198,7 @@ class AsyncRdf4JRepository:
192
198
  object_: Optional[Object] = None,
193
199
  contexts: Optional[list[Context]] = None,
194
200
  infer: bool = True,
195
- ) -> RDF4JDataSet:
201
+ ) -> QuadResultSet:
196
202
  """Retrieves statements matching the given pattern.
197
203
 
198
204
  Args:
@@ -202,7 +208,7 @@ class AsyncRdf4JRepository:
202
208
  contexts (Optional[list[Context]]): Filter by context (named graph).
203
209
 
204
210
  Returns:
205
- DataSet: Dataset of matching RDF statements.
211
+ QuadResultSet: QuadResultSet of matching RDF statements.
206
212
 
207
213
  Raises:
208
214
  RepositoryNotFoundException: If the repository doesn't exist.
@@ -211,20 +217,18 @@ class AsyncRdf4JRepository:
211
217
  params = {}
212
218
 
213
219
  if subject:
214
- params["subj"] = subject.n3()
220
+ params["subj"] = str(subject)
215
221
  if predicate:
216
- params["pred"] = predicate.n3()
222
+ params["pred"] = str(predicate)
217
223
  if object_:
218
- params["obj"] = object_.n3()
224
+ params["obj"] = str(object_)
219
225
  if contexts:
220
- params["context"] = [ctx.n3() for ctx in contexts]
226
+ params["context"] = [str(ctx) for ctx in contexts]
221
227
  params["infer"] = str(infer).lower()
222
228
 
223
229
  headers = {"Accept": Rdf4jContentType.NQUADS}
224
230
  response = await self._client.get(path, params=params, headers=headers)
225
- dataset = RDF4JDataSet()
226
- dataset.parse(data=response.text, format="nquads")
227
- return dataset
231
+ return og.parse(response.content, format=og.RdfFormat.N_QUADS)
228
232
 
229
233
  async def delete_statements(
230
234
  self,
@@ -250,13 +254,13 @@ class AsyncRdf4JRepository:
250
254
  params = {}
251
255
 
252
256
  if subject:
253
- params["subj"] = subject.n3()
257
+ params["subj"] = str(subject)
254
258
  if predicate:
255
- params["pred"] = predicate.n3()
259
+ params["pred"] = str(predicate)
256
260
  if object_:
257
- params["obj"] = object_.n3()
261
+ params["obj"] = str(object_)
258
262
  if contexts:
259
- params["context"] = [ctx.n3() for ctx in contexts]
263
+ params["context"] = [str(ctx) for ctx in contexts]
260
264
 
261
265
  response = await self._client.delete(path, params=params)
262
266
  self._handle_repo_not_found_exception(response)
@@ -286,21 +290,26 @@ class AsyncRdf4JRepository:
286
290
  httpx.HTTPStatusError: If addition fails.
287
291
  """
288
292
  path = f"/repositories/{self._repository_id}/statements"
293
+ statement: Triple | Quad
294
+ if context is None:
295
+ statement = Triple(subject, predicate, object)
296
+ else:
297
+ statement = Quad(subject, predicate, object, context)
298
+
289
299
  response = await self._client.post(
290
300
  path,
291
- content=serialize_statements([(subject, predicate, object, context)]),
301
+ content=serialize_statements([statement]),
292
302
  headers={"Content-Type": Rdf4jContentType.NQUADS},
293
303
  )
294
304
  self._handle_repo_not_found_exception(response)
295
305
  if response.status_code != httpx.codes.NO_CONTENT:
296
306
  raise RepositoryUpdateException(f"Failed to add statement: {response.text}")
297
307
 
298
- async def add_statements(self, statements: Iterable[RDFStatement]):
308
+ async def add_statements(self, statements: Iterable[Quad] | Iterable[Triple]):
299
309
  """Adds a list of RDF statements to the repository.
300
310
 
301
311
  Args:
302
- statements (Iterable[RDFStatement]): A list of RDF statements.
303
- RDFStatement: A tuple of subject, predicate, object, and context.
312
+ statements (Iterable[Quad] | Iterable[Triple]): A list of RDF statements.
304
313
 
305
314
  Raises:
306
315
  RepositoryNotFoundException: If the repository doesn't exist.
@@ -320,26 +329,26 @@ class AsyncRdf4JRepository:
320
329
 
321
330
  async def replace_statements(
322
331
  self,
323
- statements: Iterable[RDFStatement],
324
- contexts: Optional[list[Context]] = None,
332
+ statements: Iterable[Quad] | Iterable[Triple],
333
+ contexts: Optional[Iterable[Context]] = None,
325
334
  base_uri: Optional[str] = None,
326
335
  ):
327
336
  """Replaces all repository statements with the given RDF data.
328
337
 
329
338
  Args:
330
- statements (Iterable[RDFStatement]): RDF statements to load.
331
- contexts (Optional[list[Context]]): One or more specific contexts to restrict deletion to.
339
+ statements (Iterable[Quad] | Iterable[Triple]): RDF statements to load.
340
+ contexts (Optional[Iterable[Context]]): One or more specific contexts to restrict deletion to.
332
341
 
333
342
  Raises:
334
343
  RepositoryNotFoundException: If the repository doesn't exist.
335
344
  httpx.HTTPStatusError: If the operation fails.
336
345
  """
337
346
  path = f"/repositories/{self._repository_id}/statements"
338
- headers = {"Content-Type": Rdf4jContentType.NQUADS.value}
347
+ headers = {"Content-Type": Rdf4jContentType.NQUADS}
339
348
 
340
349
  params = {}
341
350
  if contexts:
342
- params["context"] = [ctx.n3() for ctx in contexts]
351
+ params["context"] = [str(ctx) for ctx in contexts]
343
352
  if base_uri:
344
353
  params["baseUri"] = base_uri
345
354
 
@@ -2,12 +2,10 @@
2
2
  RDF4J Python Model Module
3
3
  """
4
4
 
5
- from ._dataset import RDF4JDataSet
6
5
  from ._namespace import Namespace
7
6
  from ._repository_info import RepositoryMetadata
8
7
 
9
8
  __all__ = [
10
9
  "Namespace",
11
10
  "RepositoryMetadata",
12
- "RDF4JDataSet",
13
11
  ]
@@ -1,11 +1,32 @@
1
- from typing import Mapping
2
-
3
- from rdflib.namespace import Namespace as RdflibNamespace
4
- from rdflib.term import Identifier, Variable
1
+ import pyoxigraph as og
5
2
 
6
3
  from rdf4j_python.model.term import IRI
7
4
 
8
- from ._base_model import _BaseModel
5
+
6
+ class _Namespace(str):
7
+ def __new__(cls, value: str | bytes) -> "Namespace":
8
+ try:
9
+ rt = str.__new__(cls, value)
10
+ except UnicodeDecodeError:
11
+ rt = str.__new__(cls, value, "utf-8")
12
+ return rt
13
+
14
+ def term(self, name: str) -> IRI:
15
+ return IRI(self + (name if isinstance(name, str) else ""))
16
+
17
+ def __getitem__(self, key: str) -> IRI:
18
+ return self.term(key)
19
+
20
+ def __getattr__(self, name: str) -> IRI:
21
+ if name.startswith("__"):
22
+ raise AttributeError
23
+ return self.term(name)
24
+
25
+ def __repr__(self) -> str:
26
+ return f"Namespace({super().__repr__()})"
27
+
28
+ def __contains__(self, ref: str) -> bool:
29
+ return ref.startswith(self)
9
30
 
10
31
 
11
32
  class Namespace:
@@ -14,7 +35,7 @@ class Namespace:
14
35
  """
15
36
 
16
37
  _prefix: str
17
- _namespace: RdflibNamespace
38
+ _namespace: _Namespace
18
39
 
19
40
  def __init__(self, prefix: str, namespace: str):
20
41
  """
@@ -25,24 +46,26 @@ class Namespace:
25
46
  namespace (str): The namespace URI.
26
47
  """
27
48
  self._prefix = prefix
28
- self._namespace = RdflibNamespace(namespace)
49
+ self._namespace = _Namespace(namespace)
29
50
 
30
51
  @classmethod
31
- def from_rdflib_binding(cls, binding: Mapping[Variable, Identifier]) -> "Namespace":
52
+ def from_sparql_query_solution(
53
+ cls, query_solution: og.QuerySolution
54
+ ) -> "Namespace":
32
55
  """
33
- Creates a Namespace from a RDFlib binding.
56
+ Creates a Namespace from a binding.
34
57
 
35
58
  Args:
36
- binding (Mapping[Variable, Identifier]): The RDFlib binding.
59
+ binding (Mapping[Variable, Identifier]): The binding.
37
60
 
38
61
  Returns:
39
62
  Namespace: The created Namespace.
40
63
  """
41
- prefix = _BaseModel.get_literal(binding, "prefix", "")
42
- namespace = _BaseModel.get_literal(binding, "namespace", "")
64
+ prefix: og.Literal = query_solution[og.Variable("prefix")]
65
+ namespace: og.NamedNode = query_solution[og.Variable("namespace")]
43
66
  return cls(
44
- prefix=prefix,
45
- namespace=namespace,
67
+ prefix=prefix.value,
68
+ namespace=namespace.value,
46
69
  )
47
70
 
48
71
  def __str__(self):
@@ -85,7 +108,7 @@ class Namespace:
85
108
  Returns:
86
109
  IRI: The IRI for the term.
87
110
  """
88
- return IRI(self._namespace.term(name))
111
+ return self._namespace.term(name)
89
112
 
90
113
  def __getitem__(self, item: str) -> IRI:
91
114
  """
@@ -0,0 +1,62 @@
1
+ from dataclasses import dataclass
2
+
3
+ import pyoxigraph as og
4
+
5
+
6
+ @dataclass
7
+ class RepositoryMetadata:
8
+ """
9
+ Represents a repository metadata RDF4J.
10
+ """
11
+
12
+ id: str # The repository identifier
13
+ uri: str # The full URI to the repository
14
+ title: str # A human-readable title (currently reusing id)
15
+ readable: bool # Whether the repository is readable
16
+ writable: bool # Whether the repository is writable
17
+
18
+ def __str__(self):
19
+ """
20
+ Returns a string representation of the RepositoryMetadata.
21
+
22
+ Returns:
23
+ str: A string representation of the RepositoryMetadata.
24
+ """
25
+ return f"Repository(id={self.id}, title={self.title}, uri={self.uri})"
26
+
27
+ @classmethod
28
+ def from_sparql_query_solution(
29
+ cls, query_solution: og.QuerySolution
30
+ ) -> "RepositoryMetadata":
31
+ """
32
+ Create a RepositoryMetadata instance from a SPARQL query result.
33
+
34
+ Args:
35
+ query_solution (og.QuerySolution): The SPARQL query result.
36
+
37
+ Returns:
38
+ RepositoryMetadata: The RepositoryMetadata instance.
39
+
40
+ Raises:
41
+ ValueError: If the query solution is missing required fields.
42
+ """
43
+
44
+ # Construct and return the Repository object
45
+ if query_solution["id"] is None:
46
+ raise ValueError("id is required")
47
+ if query_solution["uri"] is None:
48
+ raise ValueError("uri is required")
49
+ if query_solution["title"] is None:
50
+ raise ValueError("title is required")
51
+ if query_solution["readable"] is None:
52
+ raise ValueError("readable is required")
53
+ if query_solution["writable"] is None:
54
+ raise ValueError("writable is required")
55
+
56
+ return cls(
57
+ id=query_solution["id"].value,
58
+ uri=query_solution["uri"].value,
59
+ title=query_solution["title"].value,
60
+ readable=bool(query_solution["readable"].value),
61
+ writable=bool(query_solution["writable"].value),
62
+ )