rdf4j-python 0.1.1a0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
@@ -1,18 +1,41 @@
1
+ from typing import Iterable, Optional
2
+
1
3
  import httpx
2
4
  import rdflib
5
+ import rdflib.resource
6
+ import rdflib.serializer
7
+ import rdflib.store
3
8
 
4
- from rdf4j_python import AsyncApiClient
9
+ from rdf4j_python._client import AsyncApiClient
10
+ from rdf4j_python._driver._async_named_graph import AsyncNamedGraph
5
11
  from rdf4j_python.exception.repo_exception import (
6
12
  NamespaceException,
7
13
  RepositoryInternalException,
8
14
  RepositoryNotFoundException,
15
+ RepositoryUpdateException,
16
+ )
17
+ from rdf4j_python.model import Namespace, RDF4JDataSet
18
+ from rdf4j_python.model.term import (
19
+ Context,
20
+ Object,
21
+ Predicate,
22
+ RDFStatement,
23
+ Subject,
9
24
  )
10
- from rdf4j_python.model._namespace import Namespace
11
25
  from rdf4j_python.utils.const import Rdf4jContentType
26
+ from rdf4j_python.utils.helpers import serialize_statements
12
27
 
13
28
 
14
29
  class AsyncRdf4JRepository:
30
+ """Asynchronous interface for interacting with an RDF4J repository."""
31
+
15
32
  def __init__(self, client: AsyncApiClient, repository_id: str):
33
+ """Initializes the repository interface.
34
+
35
+ Args:
36
+ client (AsyncApiClient): The RDF4J API client.
37
+ repository_id (str): The ID of the RDF4J repository.
38
+ """
16
39
  self._client = client
17
40
  self._repository_id = repository_id
18
41
 
@@ -22,6 +45,16 @@ class AsyncRdf4JRepository:
22
45
  infer: bool = True,
23
46
  accept: Rdf4jContentType = Rdf4jContentType.SPARQL_RESULTS_JSON,
24
47
  ):
48
+ """Executes a SPARQL SELECT query.
49
+
50
+ Args:
51
+ sparql_query (str): The SPARQL query string.
52
+ infer (bool): Whether to include inferred statements. Defaults to True.
53
+ accept (Rdf4jContentType): The expected response format.
54
+
55
+ Returns:
56
+ dict or str: Parsed JSON results or raw response text.
57
+ """
25
58
  path = f"/repositories/{self._repository_id}"
26
59
  params = {"query": sparql_query, "infer": str(infer).lower()}
27
60
  headers = {"Accept": accept.value}
@@ -32,26 +65,33 @@ class AsyncRdf4JRepository:
32
65
  return response.text
33
66
 
34
67
  async def update(self, sparql_update: str):
68
+ """Executes a SPARQL UPDATE command.
69
+
70
+ Args:
71
+ sparql_update (str): The SPARQL update string.
72
+
73
+ Raises:
74
+ RepositoryNotFoundException: If the repository doesn't exist.
75
+ httpx.HTTPStatusError: If the update fails.
76
+ """
35
77
  path = f"/repositories/{self._repository_id}/statements"
36
78
  headers = {"Content-Type": Rdf4jContentType.SPARQL_UPDATE.value}
37
79
  response = await self._client.post(path, data=sparql_update, headers=headers)
38
80
  self._handle_repo_not_found_exception(response)
39
81
  response.raise_for_status()
40
82
 
41
- async def replace_statements(
42
- self, rdf_data: str, content_type: Rdf4jContentType = Rdf4jContentType.TURTLE
43
- ):
44
- path = f"/repositories/{self._repository_id}/statements"
45
- headers = {"Content-Type": content_type.value}
46
- response = await self._client.put(path, data=rdf_data, headers=headers)
47
- self._handle_repo_not_found_exception(response)
48
- response.raise_for_status()
49
-
50
83
  async def get_namespaces(self):
84
+ """Retrieves all namespaces in the repository.
85
+
86
+ Returns:
87
+ list[Namespace]: A list of namespace objects.
88
+
89
+ Raises:
90
+ RepositoryNotFoundException: If the repository doesn't exist.
91
+ """
51
92
  path = f"/repositories/{self._repository_id}/namespaces"
52
93
  headers = {"Accept": Rdf4jContentType.SPARQL_RESULTS_JSON}
53
94
  response = await self._client.get(path, headers=headers)
54
-
55
95
  result = rdflib.query.Result.parse(
56
96
  response, format=Rdf4jContentType.SPARQL_RESULTS_JSON
57
97
  )
@@ -59,6 +99,16 @@ class AsyncRdf4JRepository:
59
99
  return [Namespace.from_rdflib_binding(binding) for binding in result.bindings]
60
100
 
61
101
  async def set_namespace(self, prefix: str, namespace: str):
102
+ """Sets a namespace prefix.
103
+
104
+ Args:
105
+ prefix (str): The namespace prefix.
106
+ namespace (str): The namespace URI.
107
+
108
+ Raises:
109
+ RepositoryNotFoundException: If the repository doesn't exist.
110
+ NamespaceException: If the request fails.
111
+ """
62
112
  path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
63
113
  headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
64
114
  response = await self._client.put(path, content=namespace, headers=headers)
@@ -67,6 +117,18 @@ class AsyncRdf4JRepository:
67
117
  raise NamespaceException(f"Failed to set namespace: {response.text}")
68
118
 
69
119
  async def get_namespace(self, prefix: str) -> Namespace:
120
+ """Gets a namespace by its prefix.
121
+
122
+ Args:
123
+ prefix (str): The namespace prefix.
124
+
125
+ Returns:
126
+ Namespace: The namespace object.
127
+
128
+ Raises:
129
+ RepositoryNotFoundException: If the repository doesn't exist.
130
+ NamespaceException: If retrieval fails.
131
+ """
70
132
  path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
71
133
  headers = {"Accept": Rdf4jContentType.NTRIPLES.value}
72
134
  response = await self._client.get(path, headers=headers)
@@ -78,12 +140,42 @@ class AsyncRdf4JRepository:
78
140
  return Namespace(prefix, response.text)
79
141
 
80
142
  async def delete_namespace(self, prefix: str):
143
+ """Deletes a namespace by prefix.
144
+
145
+ Args:
146
+ prefix (str): The namespace prefix.
147
+
148
+ Raises:
149
+ RepositoryNotFoundException: If the repository doesn't exist.
150
+ httpx.HTTPStatusError: If deletion fails.
151
+ """
81
152
  path = f"/repositories/{self._repository_id}/namespaces/{prefix}"
82
153
  response = await self._client.delete(path)
83
154
  self._handle_repo_not_found_exception(response)
84
155
  response.raise_for_status()
85
156
 
157
+ async def clear_all_namespaces(self):
158
+ """Removes all namespaces from the repository.
159
+
160
+ Raises:
161
+ RepositoryNotFoundException: If the repository doesn't exist.
162
+ httpx.HTTPStatusError: If clearing fails.
163
+ """
164
+ path = f"/repositories/{self._repository_id}/namespaces"
165
+ response = await self._client.delete(path)
166
+ self._handle_repo_not_found_exception(response)
167
+ response.raise_for_status()
168
+
86
169
  async def size(self) -> int:
170
+ """Gets the number of statements in the repository.
171
+
172
+ Returns:
173
+ int: The total number of RDF statements.
174
+
175
+ Raises:
176
+ RepositoryNotFoundException: If the repository doesn't exist.
177
+ RepositoryInternalException: If retrieval fails.
178
+ """
87
179
  path = f"/repositories/{self._repository_id}/size"
88
180
  response = await self._client.get(path)
89
181
  self._handle_repo_not_found_exception(response)
@@ -93,17 +185,198 @@ class AsyncRdf4JRepository:
93
185
 
94
186
  return int(response.text.strip())
95
187
 
96
- async def add_statement(self, subject: str, predicate: str, object: str):
188
+ async def get_statements(
189
+ self,
190
+ subject: Optional[Subject] = None,
191
+ predicate: Optional[Predicate] = None,
192
+ object_: Optional[Object] = None,
193
+ contexts: Optional[list[Context]] = None,
194
+ infer: bool = True,
195
+ ) -> RDF4JDataSet:
196
+ """Retrieves statements matching the given pattern.
197
+
198
+ Args:
199
+ subject (Optional[Subject]): Filter by subject.
200
+ predicate (Optional[Predicate]): Filter by predicate.
201
+ object_ (Optional[Object]): Filter by object.
202
+ contexts (Optional[list[Context]]): Filter by context (named graph).
203
+
204
+ Returns:
205
+ DataSet: Dataset of matching RDF statements.
206
+
207
+ Raises:
208
+ RepositoryNotFoundException: If the repository doesn't exist.
209
+ """
210
+ path = f"/repositories/{self._repository_id}/statements"
211
+ params = {}
212
+
213
+ if subject:
214
+ params["subj"] = subject.n3()
215
+ if predicate:
216
+ params["pred"] = predicate.n3()
217
+ if object_:
218
+ params["obj"] = object_.n3()
219
+ if contexts:
220
+ params["context"] = [ctx.n3() for ctx in contexts]
221
+ params["infer"] = str(infer).lower()
222
+
223
+ headers = {"Accept": Rdf4jContentType.NQUADS}
224
+ response = await self._client.get(path, params=params, headers=headers)
225
+ dataset = RDF4JDataSet()
226
+ dataset.parse(data=response.text, format="nquads")
227
+ return dataset
228
+
229
+ async def delete_statements(
230
+ self,
231
+ subject: Optional[Subject] = None,
232
+ predicate: Optional[Predicate] = None,
233
+ object_: Optional[Object] = None,
234
+ contexts: Optional[list[Context]] = None,
235
+ ):
236
+ """Deletes statements from the repository matching the given pattern.
237
+
238
+ Args:
239
+ subject (Optional[Subject]): Filter by subject (N-Triples encoded).
240
+ predicate (Optional[Predicate]): Filter by predicate (N-Triples encoded).
241
+ object_ (Optional[Object]): Filter by object (N-Triples encoded).
242
+ contexts (Optional[list[Context]]): One or more specific contexts to restrict deletion to.
243
+ Use 'null' as a string to delete context-less statements.
244
+
245
+ Raises:
246
+ RepositoryNotFoundException: If the repository does not exist.
247
+ RepositoryUpdateException: If the deletion fails.
248
+ """
249
+ path = f"/repositories/{self._repository_id}/statements"
250
+ params = {}
251
+
252
+ if subject:
253
+ params["subj"] = subject.n3()
254
+ if predicate:
255
+ params["pred"] = predicate.n3()
256
+ if object_:
257
+ params["obj"] = object_.n3()
258
+ if contexts:
259
+ params["context"] = [ctx.n3() for ctx in contexts]
260
+
261
+ response = await self._client.delete(path, params=params)
262
+ self._handle_repo_not_found_exception(response)
263
+
264
+ if response.status_code != httpx.codes.NO_CONTENT:
265
+ raise RepositoryUpdateException(
266
+ f"Failed to delete statements: {response.text}"
267
+ )
268
+
269
+ async def add_statement(
270
+ self,
271
+ subject: Subject,
272
+ predicate: Predicate,
273
+ object: Object,
274
+ context: Optional[Context] = None,
275
+ ):
276
+ """Adds a single RDF statement to the repository.
277
+
278
+ Args:
279
+ subject (Node): The subject of the triple.
280
+ predicate (Node): The predicate of the triple.
281
+ object (Node): The object of the triple.
282
+ context (IdentifiedNode): The context (named graph).
283
+
284
+ Raises:
285
+ RepositoryNotFoundException: If the repository doesn't exist.
286
+ httpx.HTTPStatusError: If addition fails.
287
+ """
97
288
  path = f"/repositories/{self._repository_id}/statements"
98
- headers = {"Content-Type": Rdf4jContentType.NTRIPLES.value}
99
289
  response = await self._client.post(
100
- path, data=f"{subject} {predicate} {object}.", headers=headers
290
+ path,
291
+ content=serialize_statements([(subject, predicate, object, context)]),
292
+ headers={"Content-Type": Rdf4jContentType.NQUADS},
101
293
  )
102
294
  self._handle_repo_not_found_exception(response)
103
- response.raise_for_status()
295
+ if response.status_code != httpx.codes.NO_CONTENT:
296
+ raise RepositoryUpdateException(f"Failed to add statement: {response.text}")
297
+
298
+ async def add_statements(self, statements: Iterable[RDFStatement]):
299
+ """Adds a list of RDF statements to the repository.
300
+
301
+ Args:
302
+ statements (Iterable[RDFStatement]): A list of RDF statements.
303
+ RDFStatement: A tuple of subject, predicate, object, and context.
304
+
305
+ Raises:
306
+ RepositoryNotFoundException: If the repository doesn't exist.
307
+ httpx.HTTPStatusError: If addition fails.
308
+ """
309
+ path = f"/repositories/{self._repository_id}/statements"
310
+ response = await self._client.post(
311
+ path,
312
+ content=serialize_statements(statements),
313
+ headers={"Content-Type": Rdf4jContentType.NQUADS},
314
+ )
315
+ self._handle_repo_not_found_exception(response)
316
+ if response.status_code != httpx.codes.NO_CONTENT:
317
+ raise RepositoryUpdateException(
318
+ f"Failed to add statements: {response.text}"
319
+ )
320
+
321
+ async def replace_statements(
322
+ self,
323
+ statements: Iterable[RDFStatement],
324
+ contexts: Optional[list[Context]] = None,
325
+ base_uri: Optional[str] = None,
326
+ ):
327
+ """Replaces all repository statements with the given RDF data.
328
+
329
+ Args:
330
+ statements (Iterable[RDFStatement]): RDF statements to load.
331
+ contexts (Optional[list[Context]]): One or more specific contexts to restrict deletion to.
332
+
333
+ Raises:
334
+ RepositoryNotFoundException: If the repository doesn't exist.
335
+ httpx.HTTPStatusError: If the operation fails.
336
+ """
337
+ path = f"/repositories/{self._repository_id}/statements"
338
+ headers = {"Content-Type": Rdf4jContentType.NQUADS.value}
339
+
340
+ params = {}
341
+ if contexts:
342
+ params["context"] = [ctx.n3() for ctx in contexts]
343
+ if base_uri:
344
+ params["baseUri"] = base_uri
345
+
346
+ response = await self._client.put(
347
+ path,
348
+ content=serialize_statements(statements),
349
+ headers=headers,
350
+ params=params,
351
+ )
352
+ self._handle_repo_not_found_exception(response)
353
+ if response.status_code != httpx.codes.NO_CONTENT:
354
+ raise RepositoryUpdateException(
355
+ f"Failed to replace statements: {response.text}"
356
+ )
357
+
358
+ async def get_named_graph(self, graph: str) -> AsyncNamedGraph:
359
+ """Retrieves a named graph in the repository.
360
+
361
+ Returns:
362
+ AsyncNamedGraph: A named graph object.
363
+ """
364
+ return AsyncNamedGraph(self._client, self._repository_id, graph)
104
365
 
105
366
  def _handle_repo_not_found_exception(self, response: httpx.Response):
367
+ """Raises a RepositoryNotFoundException if response is 404.
368
+
369
+ Args:
370
+ response (httpx.Response): HTTP response object.
371
+
372
+ Raises:
373
+ RepositoryNotFoundException: If repository is not found.
374
+ """
106
375
  if response.status_code == httpx.codes.NOT_FOUND:
107
376
  raise RepositoryNotFoundException(
108
377
  f"Repository {self._repository_id} not found"
109
378
  )
379
+
380
+ @property
381
+ def repository_id(self) -> str:
382
+ return self._repository_id
@@ -0,0 +1,5 @@
1
+ """
2
+ RDF4J Python Exception Module
3
+ """
4
+
5
+ from .repo_exception import * # noqa: F403
@@ -1,13 +1,34 @@
1
- class RepositoryCreationException(Exception): ...
1
+ class RepositoryCreationException(Exception):
2
+ """
3
+ Exception raised when a repository creation fails.
4
+ """
2
5
 
3
6
 
4
- class RepositoryDeletionException(Exception): ...
7
+ class RepositoryDeletionException(Exception):
8
+ """
9
+ Exception raised when a repository deletion fails.
10
+ """
5
11
 
6
12
 
7
- class NamespaceException(Exception): ...
13
+ class NamespaceException(Exception):
14
+ """
15
+ Exception raised when a namespace operation fails.
16
+ """
8
17
 
9
18
 
10
- class RepositoryNotFoundException(Exception): ...
19
+ class RepositoryNotFoundException(Exception):
20
+ """
21
+ Exception raised when a repository is not found.
22
+ """
11
23
 
12
24
 
13
- class RepositoryInternalException(Exception): ...
25
+ class RepositoryInternalException(Exception):
26
+ """
27
+ Exception raised when a repository internal error occurs.
28
+ """
29
+
30
+
31
+ class RepositoryUpdateException(Exception):
32
+ """
33
+ Exception raised when a repository update fails.
34
+ """
@@ -1,16 +1,13 @@
1
- from ._namespace import IRI, Namespace
2
- from ._repository_config import (
3
- MemoryStoreConfig,
4
- NativeStoreConfig,
5
- RepositoryConfig,
6
- )
1
+ """
2
+ RDF4J Python Model Module
3
+ """
4
+
5
+ from ._dataset import RDF4JDataSet
6
+ from ._namespace import Namespace
7
7
  from ._repository_info import RepositoryMetadata
8
8
 
9
9
  __all__ = [
10
- "IRI",
11
10
  "Namespace",
12
- "RepositoryConfig",
13
- "MemoryStoreConfig",
14
- "NativeStoreConfig",
15
11
  "RepositoryMetadata",
12
+ "RDF4JDataSet",
16
13
  ]
@@ -5,13 +5,25 @@ from rdflib.term import Identifier, Literal, URIRef, Variable
5
5
 
6
6
 
7
7
  class _BaseModel(ABC):
8
+ """Abstract base class providing utility methods for parsing RDF query results."""
9
+
8
10
  @staticmethod
9
11
  def get_literal(
10
12
  result: Mapping[Variable, Identifier],
11
13
  var_name: str,
12
14
  default: Optional[str] = None,
13
- ):
14
- """Extract and convert an RDFLib Literal to a Python value."""
15
+ ) -> Optional[str]:
16
+ """
17
+ Extracts a literal value from a SPARQL query result.
18
+
19
+ Args:
20
+ result (Mapping[Variable, Identifier]): A mapping of variable bindings from a query result.
21
+ var_name (str): The variable name to extract.
22
+ default (Optional[str], optional): The value to return if the variable is not found or is not a Literal. Defaults to None.
23
+
24
+ Returns:
25
+ Optional[str]: The Python representation of the literal, or the default value.
26
+ """
15
27
  val = result.get(Variable(var_name))
16
28
  return val.toPython() if isinstance(val, Literal) else default
17
29
 
@@ -20,7 +32,17 @@ class _BaseModel(ABC):
20
32
  result: Mapping[Variable, Identifier],
21
33
  var_name: str,
22
34
  default: Optional[str] = None,
23
- ):
24
- """Extract and convert an RDFLib URIRef to a string."""
35
+ ) -> Optional[str]:
36
+ """
37
+ Extracts a URI value from a SPARQL query result.
38
+
39
+ Args:
40
+ result (Mapping[Variable, Identifier]): A mapping of variable bindings from a query result.
41
+ var_name (str): The variable name to extract.
42
+ default (Optional[str], optional): The value to return if the variable is not found or is not a URIRef. Defaults to None.
43
+
44
+ Returns:
45
+ Optional[str]: The URI string, or the default value.
46
+ """
25
47
  val = result.get(Variable(var_name))
26
48
  return str(val) if isinstance(val, URIRef) else default
@@ -0,0 +1,38 @@
1
+ from rdflib import Dataset as _Dataset
2
+
3
+ from rdf4j_python.model.term import IRI
4
+
5
+
6
+ class RDF4JDataSet(_Dataset):
7
+ """
8
+ An RDFLib Dataset subclass with RDF4J-specific utility methods.
9
+ """
10
+
11
+ def as_list(self) -> list[tuple]:
12
+ """
13
+ Converts all quads in the dataset to a list of 4-tuples.
14
+
15
+ Replaces the RDF4J default context IRI ("urn:x-rdflib:default") with None.
16
+
17
+ Returns:
18
+ list[tuple]: A list of (subject, predicate, object, context) quads.
19
+ """
20
+ return [
21
+ (s, p, o, ctx if ctx != IRI("urn:x-rdflib:default") else None)
22
+ for s, p, o, ctx in self.quads((None, None, None, None))
23
+ ]
24
+
25
+ @staticmethod
26
+ def from_raw_text(text: str) -> "RDF4JDataSet":
27
+ """
28
+ Parses a string of N-Quads RDF data into an RDF4JDataSet.
29
+
30
+ Args:
31
+ text (str): The RDF data in N-Quads format.
32
+
33
+ Returns:
34
+ RDF4JDataSet: A populated dataset.
35
+ """
36
+ ds = RDF4JDataSet()
37
+ ds.parse(data=text, format="nquads")
38
+ return ds
@@ -1,25 +1,43 @@
1
1
  from typing import Mapping
2
2
 
3
- from rdflib import URIRef
4
3
  from rdflib.namespace import Namespace as RdflibNamespace
5
4
  from rdflib.term import Identifier, Variable
6
5
 
7
- from ._base_model import _BaseModel
8
-
6
+ from rdf4j_python.model.term import IRI
9
7
 
10
- class IRI(URIRef): ...
8
+ from ._base_model import _BaseModel
11
9
 
12
10
 
13
11
  class Namespace:
12
+ """
13
+ Represents a namespace in RDF4J.
14
+ """
15
+
14
16
  _prefix: str
15
17
  _namespace: RdflibNamespace
16
18
 
17
19
  def __init__(self, prefix: str, namespace: str):
20
+ """
21
+ Initializes a new Namespace.
22
+
23
+ Args:
24
+ prefix (str): The prefix of the namespace.
25
+ namespace (str): The namespace URI.
26
+ """
18
27
  self._prefix = prefix
19
28
  self._namespace = RdflibNamespace(namespace)
20
29
 
21
30
  @classmethod
22
31
  def from_rdflib_binding(cls, binding: Mapping[Variable, Identifier]) -> "Namespace":
32
+ """
33
+ Creates a Namespace from a RDFlib binding.
34
+
35
+ Args:
36
+ binding (Mapping[Variable, Identifier]): The RDFlib binding.
37
+
38
+ Returns:
39
+ Namespace: The created Namespace.
40
+ """
23
41
  prefix = _BaseModel.get_literal(binding, "prefix", "")
24
42
  namespace = _BaseModel.get_literal(binding, "namespace", "")
25
43
  return cls(
@@ -28,29 +46,89 @@ class Namespace:
28
46
  )
29
47
 
30
48
  def __str__(self):
49
+ """
50
+ Returns a string representation of the Namespace.
51
+
52
+ Returns:
53
+ str: A string representation of the Namespace.
54
+ """
31
55
  return f"{self._prefix}: {self._namespace}"
32
56
 
33
57
  def __repr__(self):
58
+ """
59
+ Returns a string representation of the Namespace.
60
+
61
+ Returns:
62
+ str: A string representation of the Namespace.
63
+ """
34
64
  return f"Namespace(prefix={self._prefix}, namespace={self._namespace})"
35
65
 
36
66
  def __contains__(self, item: str) -> bool:
67
+ """
68
+ Checks if the Namespace contains a given item.
69
+
70
+ Args:
71
+ item (str): The item to check.
72
+
73
+ Returns:
74
+ bool: True if the Namespace contains the item, False otherwise.
75
+ """
37
76
  return item in self._namespace
38
77
 
39
78
  def term(self, name: str) -> IRI:
79
+ """
80
+ Returns the IRI for a given term.
81
+
82
+ Args:
83
+ name (str): The term name.
84
+
85
+ Returns:
86
+ IRI: The IRI for the term.
87
+ """
40
88
  return IRI(self._namespace.term(name))
41
89
 
42
90
  def __getitem__(self, item: str) -> IRI:
91
+ """
92
+ Returns the IRI for a given term.
93
+
94
+ Args:
95
+ item (str): The term name.
96
+
97
+ Returns:
98
+ IRI: The IRI for the term.
99
+ """
43
100
  return self.term(item)
44
101
 
45
102
  def __getattr__(self, item: str) -> IRI:
103
+ """
104
+ Returns the IRI for a given term.
105
+
106
+ Args:
107
+ item (str): The term name.
108
+
109
+ Returns:
110
+ IRI: The IRI for the term.
111
+ """
46
112
  if item.startswith("__"):
47
113
  raise AttributeError
48
114
  return self.term(item)
49
115
 
50
116
  @property
51
117
  def namespace(self) -> IRI:
118
+ """
119
+ Returns the namespace URI.
120
+
121
+ Returns:
122
+ IRI: The namespace URI.
123
+ """
52
124
  return IRI(self._namespace)
53
125
 
54
126
  @property
55
127
  def prefix(self) -> str:
128
+ """
129
+ Returns the prefix of the namespace.
130
+
131
+ Returns:
132
+ str: The prefix of the namespace.
133
+ """
56
134
  return self._prefix