iolanta 2.1.12__py3-none-any.whl → 2.1.14__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.
@@ -0,0 +1,36 @@
1
+ import csv
2
+ import io
3
+ from pathlib import Path
4
+
5
+ from rdflib import Literal
6
+
7
+ from iolanta.facets.cli.base import Renderable, RichFacet
8
+ from iolanta.facets.errors import NotALiteral
9
+
10
+ META = Path(__file__).parent / 'data' / 'query_result.yamlld'
11
+
12
+
13
+ class SelectResultCsvFacet(RichFacet):
14
+ """Render SELECT query results as CSV."""
15
+
16
+ META = META
17
+
18
+ def show(self) -> Renderable:
19
+ """Render SelectResult as CSV."""
20
+ if not isinstance(self.this, Literal):
21
+ raise NotALiteral(node=self.this)
22
+
23
+ query_result = self.this.value
24
+
25
+ if not query_result:
26
+ return ""
27
+
28
+ output = io.StringIO()
29
+ first_row = query_result[0]
30
+ fieldnames = first_row.keys()
31
+
32
+ writer = csv.DictWriter(output, fieldnames=fieldnames)
33
+ writer.writeheader()
34
+ writer.writerows(query_result)
35
+
36
+ return output.getvalue()
@@ -0,0 +1,24 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from rdflib import Literal
5
+
6
+ from iolanta.facets.cli.base import Renderable, RichFacet
7
+ from iolanta.facets.errors import NotALiteral
8
+
9
+ META = Path(__file__).parent / 'data' / 'query_result.yamlld'
10
+
11
+
12
+ class SelectResultJsonFacet(RichFacet):
13
+ """Render SELECT query results as JSON."""
14
+
15
+ META = META
16
+
17
+ def show(self) -> Renderable:
18
+ """Render SelectResult as JSON."""
19
+ if not isinstance(self.this, Literal):
20
+ raise NotALiteral(node=self.this)
21
+
22
+ query_result = self.this.value
23
+
24
+ return json.dumps(query_result, indent=2, default=str)
@@ -0,0 +1,48 @@
1
+ from pathlib import Path
2
+
3
+ from more_itertools import consume, first
4
+ from rdflib import Literal
5
+ from rich.table import Table
6
+
7
+ from iolanta.cli.formatters.pretty import pretty_print_value
8
+ from iolanta.facets.cli.base import Renderable, RichFacet
9
+ from iolanta.facets.errors import NotALiteral
10
+
11
+ META = Path(__file__).parent / 'data' / 'query_result.yamlld'
12
+
13
+
14
+ class SelectResultTableFacet(RichFacet):
15
+ """Render SELECT query results as table."""
16
+
17
+ META = META
18
+
19
+ def show(self) -> Renderable:
20
+ """Render SelectResult as table."""
21
+ if not isinstance(self.this, Literal):
22
+ raise NotALiteral(node=self.this)
23
+
24
+ query_result = self.this.value
25
+
26
+ if not query_result:
27
+ table = Table(show_header=True, header_style="bold magenta")
28
+ return table
29
+
30
+ columns = first(query_result).keys()
31
+
32
+ table = Table(
33
+ *columns,
34
+ show_header=True,
35
+ header_style="bold magenta",
36
+ )
37
+
38
+ consume(
39
+ table.add_row(
40
+ *[
41
+ str(pretty_print_value(value))
42
+ for value in row.values()
43
+ ],
44
+ )
45
+ for row in query_result
46
+ )
47
+
48
+ return table
@@ -45,22 +45,22 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
45
45
  """
46
46
 
47
47
  BINDINGS = [ # noqa: WPS115
48
- ('alt+left', 'back', 'Back'),
49
- ('alt+right', 'forward', 'Fwd'),
50
- ('f5', 'reload', '🔄 Reload'),
51
- ('escape', 'abort', '🛑 Abort'),
52
- ('f12', 'console', 'Console'),
48
+ ("alt+left", "back", "Back"),
49
+ ("alt+right", "forward", "Fwd"),
50
+ ("f5", "reload", "🔄 Reload"),
51
+ ("escape", "abort", "🛑 Abort"),
52
+ ("f12", "console", "Console"),
53
53
  ]
54
54
 
55
55
  def __init__(self):
56
56
  """Set Home as first tab."""
57
- super().__init__(id='page_switcher', initial='home')
57
+ super().__init__(id="page_switcher", initial="home")
58
58
  self.stop_file_watcher_event = threading.Event()
59
59
 
60
60
  def action_console(self):
61
61
  """Open dev console."""
62
62
  console_switcher = self.app.query_one(ConsoleSwitcher)
63
- console_switcher.current = 'console'
63
+ console_switcher.current = "console"
64
64
  console_switcher.query_one(DevConsole).focus()
65
65
 
66
66
  @functools.cached_property
@@ -70,7 +70,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
70
70
 
71
71
  def compose(self):
72
72
  """Home is the first page to open."""
73
- yield Home(id='home')
73
+ yield Home(id="home")
74
74
 
75
75
  def on_mount(self):
76
76
  """Navigate to the initial page."""
@@ -86,13 +86,13 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
86
86
  self.stop_file_watcher_event.set()
87
87
 
88
88
  def _watch_files(self):
89
- for _ in watchfiles.watch( # noqa: WPS352
89
+ for _ in watchfiles.watch( # noqa: WPS352
90
90
  self.iolanta.project_root,
91
91
  stop_event=self.stop_file_watcher_event,
92
92
  ):
93
93
  self.app.call_from_thread(self.action_reload)
94
94
 
95
- def render_iri( # noqa: WPS210
95
+ def render_iri( # noqa: WPS210
96
96
  self,
97
97
  this: NotLiteralNode,
98
98
  facet_iri: URIRef | None,
@@ -102,7 +102,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
102
102
  self.this = this
103
103
  iolanta: Iolanta = self.iolanta
104
104
 
105
- as_datatype = URIRef('https://iolanta.tech/cli/textual')
105
+ as_datatype = URIRef("https://iolanta.tech/cli/textual")
106
106
  choices = FacetFinder(
107
107
  iolanta=self.iolanta,
108
108
  node=this,
@@ -117,12 +117,10 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
117
117
  )
118
118
 
119
119
  if facet_iri is None:
120
- facet_iri = choices[0]['facet']
120
+ facet_iri = choices[0]["facet"]
121
121
 
122
122
  other_facets = [
123
- choice['facet']
124
- for choice in choices
125
- if choice['facet'] != facet_iri
123
+ choice["facet"] for choice in choices if choice["facet"] != facet_iri
126
124
  ]
127
125
  flip_options = [
128
126
  FlipOption(
@@ -141,7 +139,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
141
139
  facet = facet_class(
142
140
  this=self.this,
143
141
  iolanta=iolanta,
144
- as_datatype=URIRef('https://iolanta.tech/cli/textual'),
142
+ as_datatype=URIRef("https://iolanta.tech/cli/textual"),
145
143
  )
146
144
 
147
145
  try:
@@ -162,7 +160,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
162
160
  is_reload=is_reload,
163
161
  )
164
162
 
165
- def on_worker_state_changed( # noqa: WPS210
163
+ def on_worker_state_changed( # noqa: WPS210
166
164
  self,
167
165
  event: Worker.StateChanged,
168
166
  ):
@@ -173,7 +171,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
173
171
 
174
172
  if render_result.is_reload:
175
173
  # We are reloading the current page.
176
- current_page = self.query_one(f'#{self.current}', Page)
174
+ current_page = self.query_one(f"#{self.current}", Page)
177
175
  current_page.remove_children()
178
176
  current_page.mount(render_result.renderable)
179
177
 
@@ -184,7 +182,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
184
182
  else:
185
183
  # We are navigating to a new page.
186
184
  page_uid = uuid.uuid4().hex
187
- page_id = f'page_{page_uid}'
185
+ page_id = f"page_{page_uid}"
188
186
  page = Page(
189
187
  render_result.renderable,
190
188
  iri=render_result.iri,
@@ -210,7 +208,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
210
208
  def is_loading(self) -> bool:
211
209
  """Determine if the app is presently loading something."""
212
210
  for worker in self.workers:
213
- if worker.name == 'render_iri':
211
+ if worker.name == "render_iri":
214
212
  return True
215
213
 
216
214
  return False
@@ -219,31 +217,31 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
219
217
  """Reset Iolanta graph and re-render current view."""
220
218
  if self.history.current is None:
221
219
  return
222
-
220
+
223
221
  self.iolanta.reset()
224
222
 
225
223
  self.run_worker(
226
224
  functools.partial(
227
225
  self.render_iri,
228
226
  this=self.history.current.url,
229
- facet_iri=self.history.current.facet_iri,
227
+ facet_iri=None, # Re-resolve facet after reload so parse errors use TextualNoFacetFound
230
228
  is_reload=True,
231
229
  ),
232
230
  thread=True,
233
231
  exclusive=True,
234
- name='render_iri',
232
+ name="render_iri",
235
233
  )
236
234
  self.refresh_bindings()
237
235
 
238
236
  def action_abort(self):
239
237
  """Abort loading."""
240
238
  self.notify(
241
- 'Aborted.',
242
- severity='warning',
239
+ "Aborted.",
240
+ severity="warning",
243
241
  )
244
242
 
245
243
  for worker in self.workers:
246
- if worker.name == 'render_iri':
244
+ if worker.name == "render_iri":
247
245
  worker.cancel()
248
246
  break
249
247
 
@@ -252,18 +250,18 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
252
250
  def check_action(
253
251
  self,
254
252
  action: str,
255
- parameters: tuple[object, ...], # noqa: WPS110
253
+ parameters: tuple[object, ...], # noqa: WPS110
256
254
  ) -> bool | None:
257
255
  """Check if action is available."""
258
256
  is_loading = self.is_loading
259
257
  match action:
260
- case 'reload':
258
+ case "reload":
261
259
  return not is_loading
262
- case 'abort':
260
+ case "abort":
263
261
  return is_loading
264
- case 'back':
262
+ case "back":
265
263
  return bool(self.history.past)
266
- case 'forward':
264
+ case "forward":
267
265
  return bool(self.history.future)
268
266
 
269
267
  return True
@@ -280,12 +278,12 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
280
278
  # Direct calls (like line 77) pass Node objects directly.
281
279
  if isinstance(this, str):
282
280
  # Check if string represents a blank node (starts with "_:")
283
- if this.startswith('_:'):
281
+ if this.startswith("_:"):
284
282
  # Create a BNode with the full string (including the "_:")
285
283
  this = BNode(this)
286
284
  else:
287
285
  this = URIRef(this)
288
-
286
+
289
287
  self.run_worker(
290
288
  functools.partial(
291
289
  self.render_iri,
@@ -295,7 +293,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
295
293
  ),
296
294
  thread=True,
297
295
  exclusive=True,
298
- name='render_iri',
296
+ name="render_iri",
299
297
  )
300
298
  self.refresh_bindings()
301
299
 
@@ -319,8 +317,8 @@ class ConsoleSwitcher(ContentSwitcher):
319
317
  def __init__(self):
320
318
  """Specify initial params."""
321
319
  super().__init__(
322
- id='console_switcher',
323
- initial='page_switcher',
320
+ id="console_switcher",
321
+ initial="page_switcher",
324
322
  )
325
323
 
326
324
  def compose(self):
@@ -333,17 +331,17 @@ class DevConsole(RichLog):
333
331
  """Development console."""
334
332
 
335
333
  BINDINGS = [
336
- ('f12,escape', 'close', 'Close Console'),
334
+ ("f12,escape", "close", "Close Console"),
337
335
  ]
338
336
 
339
337
  def __init__(self):
340
338
  """Set default props for console."""
341
- super().__init__(highlight=False, markup=False, id='console')
339
+ super().__init__(highlight=False, markup=False, id="console")
342
340
 
343
341
  def action_close(self):
344
342
  """Close the dev console."""
345
343
  console_switcher = self.app.query_one(ConsoleSwitcher)
346
- console_switcher.current = 'page_switcher'
344
+ console_switcher.current = "page_switcher"
347
345
 
348
346
  page_switcher = console_switcher.query_one(PageSwitcher)
349
347
  page_switcher.visible_content.focus()
@@ -1,8 +1,6 @@
1
- # Remember we are HTTPS-izing all URIs in Iolanta graph
2
- PREFIX rdf: <https://www.w3.org/1999/02/22-rdf-syntax-ns#>
3
-
4
1
  SELECT ?instance WHERE {
5
- ?instance rdf:type $this .
2
+ ?instance a $this .
6
3
 
4
+ # Overcome what RDFLib does
7
5
  FILTER(!isLiteral(?instance)) .
8
6
  } ORDER BY ?instance
@@ -24,6 +24,14 @@ TEXT = """
24
24
  issues: https://github.com/iolanta.tech/iolanta/issues
25
25
  """
26
26
 
27
+ PARSE_ERROR_TEMPLATE = """
28
+ **Parse error**
29
+
30
+ ```
31
+ {message}
32
+ ```
33
+ """
34
+
27
35
  CONTENT_TEMPLATE = """
28
36
  **File content**
29
37
 
@@ -46,7 +54,7 @@ class TextualNoFacetFound(Facet):
46
54
  def raw_content(self):
47
55
  """Content of the file, if applicable."""
48
56
  url = URL(self.this)
49
- if url.scheme != 'file':
57
+ if url.scheme != "file":
50
58
  return None
51
59
 
52
60
  path = Path(url.path)
@@ -59,32 +67,50 @@ class TextualNoFacetFound(Facet):
59
67
  if path.is_dir():
60
68
  return None
61
69
 
70
+ parse_error_msg = self._parse_error_message()
62
71
  file_content = path.read_text()
63
- return CONTENT_TEMPLATE.format(
72
+ content = CONTENT_TEMPLATE.format(
64
73
  content=file_content,
65
74
  type={
66
- '.yamlld': 'yaml',
67
- '.jsonld': 'json',
68
- }.get(path.suffix, ''),
75
+ ".yamlld": "yaml",
76
+ ".jsonld": "json",
77
+ ".ttl": "turtle",
78
+ }.get(path.suffix, ""),
69
79
  )
80
+ if parse_error_msg:
81
+ content = PARSE_ERROR_TEMPLATE.format(message=parse_error_msg) + content
82
+ return content
83
+
84
+ def _parse_error_message(self) -> str | None:
85
+ """Return parse error message for this node if it failed to load."""
86
+ rows = self.query(
87
+ """
88
+ SELECT ?msg WHERE {
89
+ GRAPH <iolanta://_meta> { $this iolanta:parse-error ?msg }
90
+ }
91
+ """,
92
+ this=self.this,
93
+ )
94
+ if row := funcy.first(rows):
95
+ return str(row["msg"])
96
+ return None
70
97
 
71
98
  @property
72
99
  def subgraphs_description(self) -> str:
73
100
  """Return a formatted description of subgraphs, if any exist."""
74
101
  rows = self.query(
75
- 'SELECT ?subgraph WHERE { $this iolanta:has-sub-graph ?subgraph }',
102
+ "SELECT ?subgraph WHERE { $this iolanta:has-sub-graph ?subgraph }",
76
103
  this=self.this,
77
104
  )
78
- subgraphs = funcy.lpluck('subgraph', rows)
105
+ subgraphs = funcy.lpluck("subgraph", rows)
79
106
  if subgraphs:
80
107
  return SUBGRAPHS_TEMPLATE.format(
81
- formatted_subgraphs='\n'.join([
82
- f'- {subgraph}'
83
- for subgraph in subgraphs
84
- ]),
108
+ formatted_subgraphs="\n".join(
109
+ [f"- {subgraph}" for subgraph in subgraphs]
110
+ ),
85
111
  )
86
112
 
87
- return ''
113
+ return ""
88
114
 
89
115
  def show(self):
90
116
  """Compose the page."""
@@ -93,8 +119,8 @@ class TextualNoFacetFound(Facet):
93
119
  Description(
94
120
  Markdown(
95
121
  TEXT.format(
96
- content=self.raw_content or '',
97
- subgraphs=self.subgraphs_description or '',
122
+ content=self.raw_content or "",
123
+ subgraphs=self.subgraphs_description or "",
98
124
  reference_type=type(self.this).__name__,
99
125
  ),
100
126
  ),
iolanta/iolanta.py CHANGED
@@ -17,6 +17,7 @@ import yaml_ld
17
17
  from pyparsing import ParseException
18
18
  from rdflib import ConjunctiveGraph, Graph, Literal, URIRef
19
19
  from rdflib.namespace import NamespaceManager
20
+ from rdflib.plugins.parsers.notation3 import BadSyntax
20
21
  from rdflib.plugins.sparql.processor import SPARQLResult
21
22
  from rdflib.term import Node
22
23
  from yaml_ld.document_loaders.content_types import ParserNotFound
@@ -256,10 +257,20 @@ class Iolanta: # noqa: WPS214, WPS338
256
257
  except ParserNotFound as parser_not_found:
257
258
  self.logger.error(f"{source} | {parser_not_found}")
258
259
  continue
259
- except YAMLLDError as yaml_ld_error:
260
- # .add() only processes local files, so errors should be raised
261
- self.logger.error(f"{source_file} | {yaml_ld_error}")
262
- raise
260
+ except (YAMLLDError, BadSyntax) as parse_error:
261
+ self.logger.warning("%s | parse error: %s", source_file, parse_error)
262
+ file_iri = path_to_iri(source_file)
263
+ self.graph.addN(
264
+ [
265
+ (
266
+ file_iri,
267
+ namespaces.IOLANTA["parse-error"],
268
+ Literal(str(parse_error)),
269
+ namespaces.META,
270
+ ),
271
+ ]
272
+ )
273
+ continue
263
274
  except ValueError as value_error:
264
275
  self.logger.error(f"{source} | {value_error}")
265
276
  continue
@@ -362,7 +373,6 @@ class Iolanta: # noqa: WPS214, WPS338
362
373
  ) -> Any:
363
374
  """Find an Iolanta facet for a node and render it."""
364
375
  node = normalize_term(node)
365
-
366
376
  if not as_datatype:
367
377
  raise ValueError(
368
378
  f"Please provide the datatype to render {node} as.",
iolanta/mcp/cli.py CHANGED
@@ -1,6 +1,9 @@
1
+ from pathlib import Path
1
2
  from typing import Annotated
2
3
 
3
4
  from fastmcp import FastMCP
5
+ from rdflib import URIRef
6
+ from yarl import URL
4
7
 
5
8
  from iolanta.cli.main import render_and_return
6
9
 
@@ -9,11 +12,21 @@ mcp = FastMCP("Iolanta MCP Server")
9
12
 
10
13
  @mcp.tool()
11
14
  def render_uri(
12
- uri: Annotated[str, 'URL, or file system path, to render'],
13
- as_format: Annotated[str, 'Format to render as. Examples: `labeled-triple-set`, `mermaid`'],
15
+ uri: Annotated[str, "URL, or file system path, to render"],
16
+ as_format: Annotated[
17
+ str, "Format to render as. Examples: `labeled-triple-set`, `mermaid`"
18
+ ],
14
19
  ) -> str:
15
20
  """Render a URI."""
16
- return str(render_and_return(uri, as_format))
21
+ # Parse string URL to URIRef
22
+ node_url = URL(uri)
23
+ if node_url.scheme and node_url.scheme != "file":
24
+ node = URIRef(uri)
25
+ else:
26
+ path = Path(node_url.path).absolute()
27
+ node = URIRef(f"file://{path}")
28
+
29
+ return str(render_and_return(node=node, as_datatype=as_format))
17
30
 
18
31
 
19
32
  def app():
iolanta/mermaid/models.py CHANGED
@@ -12,7 +12,10 @@ from iolanta.models import NotLiteralNode # noqa: WPS202
12
12
 
13
13
 
14
14
  def escape_label(label: str) -> str:
15
- """Escape a label to prevent Mermaid from interpreting URLs as markdown links and handle quotes.
15
+ """Escape a label and return it wrapped in appropriate quotes.
16
+
17
+ Returns the label with URLs stripped, quotes escaped, and wrapped in quotes.
18
+ Uses single quotes if label contains double quotes to avoid escaping issues.
16
19
 
17
20
  Escapes quotes in labels that will be wrapped in quotes in Mermaid syntax.
18
21
  """
@@ -20,8 +23,15 @@ def escape_label(label: str) -> str:
20
23
  safe_label = (
21
24
  label.replace("https://", "").replace("http://", "").replace("www.", "")
22
25
  )
23
- # Escape quotes for use inside quoted strings in Mermaid
24
- return safe_label.replace('"', r'\"')
26
+ # Use single quotes if label contains double quotes to avoid escaping issues
27
+ use_single = '"' in safe_label
28
+ quote_char = "'" if use_single else '"'
29
+ # Escape the quote character that will be used for wrapping
30
+ if use_single:
31
+ escaped_label = safe_label.replace("'", r"\'")
32
+ else:
33
+ escaped_label = safe_label.replace('"', r"\"")
34
+ return f"{quote_char}{escaped_label}{quote_char}"
25
35
 
26
36
 
27
37
  class Direction(enum.StrEnum):
@@ -54,14 +64,13 @@ class MermaidURINode(MermaidScalar, frozen=True):
54
64
  def maybe_title(self):
55
65
  if not self.title:
56
66
  return ""
57
- # Escape URLs to prevent Mermaid from interpreting them as markdown links
58
- safe_title = escape_label(self.title)
59
- return f'("{safe_title}")'
67
+ quoted_title = escape_label(self.title)
68
+ return f"({quoted_title})"
60
69
 
61
70
  @property
62
71
  def id(self):
63
72
  return re.sub(
64
- r"[:\/\.#()]", "_", urllib_parse.unquote(str(self.url)).strip("/")
73
+ r"[:\/\.#()?=&+]", "_", urllib_parse.unquote(str(self.url)).strip("/")
65
74
  )
66
75
 
67
76
 
@@ -85,7 +94,7 @@ class MermaidLiteral(MermaidScalar, frozen=True):
85
94
 
86
95
 
87
96
  class MermaidBlankNode(MermaidScalar):
88
- """{self.id}("{self.escaped_title}")"""
97
+ """{self.id}({self.escaped_title})"""
89
98
 
90
99
  node: BNode
91
100
  title: str
@@ -102,7 +111,7 @@ class MermaidBlankNode(MermaidScalar):
102
111
 
103
112
  class MermaidEdge(MermaidScalar):
104
113
  """
105
- {self.source.id} --- {self.id}(["{self.escaped_title}"])--> {self.target.id}
114
+ {self.source.id} --- {self.id}([{self.escaped_title}])--> {self.target.id}
106
115
  click {self.id} "{self.predicate}"
107
116
  class {self.id} predicate
108
117
  """
@@ -130,7 +139,7 @@ class MermaidEdge(MermaidScalar):
130
139
 
131
140
  class MermaidSubgraph(Documented, BaseModel, arbitrary_types_allowed=True, frozen=True):
132
141
  """
133
- subgraph {self.id}["{self.escaped_title}"]
142
+ subgraph {self.id}[{self.escaped_title}]
134
143
  direction {self.direction}
135
144
  {self.formatted_body}
136
145
  end
@@ -557,15 +557,6 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
557
557
  path=not_found.path,
558
558
  )
559
559
 
560
- self.graph.add(
561
- (
562
- source_uri,
563
- RDF.type,
564
- IOLANTA["not-found"],
565
- source_uri,
566
- )
567
- )
568
-
569
560
  self._mark_as_loaded(source_uri)
570
561
 
571
562
  return Loaded()