iolanta 2.1.1__py3-none-any.whl → 2.1.6__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.
Files changed (54) hide show
  1. iolanta/cli/main.py +83 -43
  2. iolanta/declension/__init__.py +0 -0
  3. iolanta/declension/data/declension.yamlld +39 -0
  4. iolanta/declension/facet.py +44 -0
  5. iolanta/declension/sparql/declension.sparql +8 -0
  6. iolanta/facets/cli/record.py +2 -2
  7. iolanta/facets/facet.py +1 -22
  8. iolanta/facets/foaf_person_title/facet.py +2 -2
  9. iolanta/facets/generic/bool_literal.py +3 -3
  10. iolanta/facets/generic/date_literal.py +1 -1
  11. iolanta/facets/generic/default.py +2 -2
  12. iolanta/facets/html/code_literal.py +3 -3
  13. iolanta/facets/icon.py +1 -1
  14. iolanta/facets/locator/sparql/get-query-to-facet.sparql +5 -0
  15. iolanta/facets/locator.py +5 -11
  16. iolanta/facets/qname.py +2 -2
  17. iolanta/facets/textual_browser/app.py +7 -3
  18. iolanta/facets/textual_browser/facet.py +1 -1
  19. iolanta/facets/textual_browser/page_switcher.py +13 -18
  20. iolanta/facets/textual_class/facets.py +3 -3
  21. iolanta/facets/textual_class/sparql/instances.sparql +4 -1
  22. iolanta/facets/textual_default/facets.py +5 -5
  23. iolanta/facets/textual_default/widgets.py +1 -1
  24. iolanta/facets/textual_graph/facets.py +3 -3
  25. iolanta/facets/textual_graph_triples.py +1 -1
  26. iolanta/facets/textual_graphs/__init__.py +6 -0
  27. iolanta/facets/textual_graphs/data/textual_graphs.yamlld +23 -0
  28. iolanta/facets/textual_graphs/facets.py +138 -0
  29. iolanta/facets/textual_graphs/sparql/graphs.sparql +5 -0
  30. iolanta/facets/textual_link/facet.py +4 -4
  31. iolanta/facets/textual_nanopublication/facet.py +1 -1
  32. iolanta/facets/textual_no_facet_found.py +3 -1
  33. iolanta/facets/textual_ontology/facets.py +3 -3
  34. iolanta/facets/textual_provenance/facets.py +1 -1
  35. iolanta/facets/title/facets.py +1 -4
  36. iolanta/facets/wikibase_statement_title/facets.py +2 -2
  37. iolanta/iolanta.py +2 -2
  38. iolanta/labeled_triple_set/__init__.py +0 -0
  39. iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +42 -0
  40. iolanta/labeled_triple_set/labeled_triple_set.py +137 -0
  41. iolanta/labeled_triple_set/sparql/triples.sparql +5 -0
  42. iolanta/mcp/__init__.py +0 -0
  43. iolanta/mcp/cli.py +39 -0
  44. iolanta/mcp/prompts/nanopublication_assertion_authoring_rules.md +63 -0
  45. iolanta/mcp/prompts/rules.md +83 -0
  46. iolanta/mermaid/facet.py +0 -3
  47. iolanta/mermaid/mermaid.yamlld +7 -24
  48. iolanta/mermaid/models.py +38 -9
  49. iolanta/mermaid/sparql/ask-has-triples.sparql +3 -0
  50. iolanta/sparqlspace/processor.py +6 -3
  51. {iolanta-2.1.1.dist-info → iolanta-2.1.6.dist-info}/METADATA +7 -7
  52. {iolanta-2.1.1.dist-info → iolanta-2.1.6.dist-info}/RECORD +54 -36
  53. {iolanta-2.1.1.dist-info → iolanta-2.1.6.dist-info}/WHEEL +1 -1
  54. {iolanta-2.1.1.dist-info → iolanta-2.1.6.dist-info}/entry_points.txt +4 -0
@@ -32,7 +32,7 @@ class TextualDefaultFacet(Facet[Widget]): # noqa: WPS214
32
32
  """Properties of current node & their values."""
33
33
  property_rows = self.stored_query(
34
34
  self.query_file_name,
35
- iri=self.iri,
35
+ iri=self.this,
36
36
  )
37
37
 
38
38
  property_pairs = [
@@ -64,11 +64,11 @@ class TextualDefaultFacet(Facet[Widget]): # noqa: WPS214
64
64
  property_values = [
65
65
  LiteralPropertyValue(
66
66
  property_value=property_value,
67
- subject=self.iri,
67
+ subject=self.this,
68
68
  property_iri=property_iri,
69
69
  ) if isinstance(property_value, Literal) else PropertyValue(
70
70
  property_value=property_value,
71
- subject=self.iri,
71
+ subject=self.this,
72
72
  property_iri=property_iri,
73
73
  property_qname=self.iolanta.node_as_qname(property_iri),
74
74
  )
@@ -135,7 +135,7 @@ class TextualDefaultFacet(Facet[Widget]): # noqa: WPS214
135
135
  def show(self) -> Widget:
136
136
  """Render the content."""
137
137
  return VerticalScroll(
138
- PageTitle(self.iri),
138
+ PageTitle(self.this),
139
139
  Static(self.description or ''),
140
140
  self.properties,
141
141
  )
@@ -149,7 +149,7 @@ class InverseProperties(TextualDefaultFacet):
149
149
  def show(self) -> Widget:
150
150
  """Render the content."""
151
151
  return VerticalScroll(
152
- PageTitle(self.iri, extra='[i]& its inverse RDF properties[/i]'),
152
+ PageTitle(self.this, extra='[i]& its inverse RDF properties[/i]'),
153
153
  Static(self.description or ''),
154
154
  self.properties,
155
155
  )
@@ -138,7 +138,7 @@ class PropertyValue(Widget, can_focus=True, inherit_bindings=False):
138
138
  self.property_iri = property_iri
139
139
  super().__init__()
140
140
  self.renderable = Text( # noqa: WPS601
141
- f'⏳ {property_qname}',
141
+ f'⏳ {property_value}',
142
142
  style='#696969',
143
143
  )
144
144
 
@@ -14,17 +14,17 @@ class GraphFacet(Facet[Widget]):
14
14
  """Show the widget."""
15
15
  triples = [
16
16
  Triple(triple['subject'], triple['predicate'], triple['object'])
17
- for triple in self.stored_query('triples.sparql', graph=self.iri)
17
+ for triple in self.stored_query('triples.sparql', graph=self.this)
18
18
  ]
19
19
 
20
20
  triple_count = len(triples)
21
21
 
22
22
  triples_view = self.iolanta.render(
23
- self.iri,
23
+ self.this,
24
24
  as_datatype=DATATYPES['textual-graph-triples'],
25
25
  )
26
26
 
27
27
  return VerticalScroll(
28
- PageTitle(self.iri, extra=f'({triple_count} triples)'),
28
+ PageTitle(self.this, extra=f'({triple_count} triples)'),
29
29
  triples_view,
30
30
  )
@@ -138,7 +138,7 @@ class GraphTriplesFacet(Facet[Widget]):
138
138
  }
139
139
  ORDER BY ?subject ?predicate ?object
140
140
  """,
141
- graph=self.iri,
141
+ graph=self.this,
142
142
  )
143
143
 
144
144
  triples = [
@@ -0,0 +1,6 @@
1
+ """Facet to render named graphs."""
2
+
3
+ from iolanta.facets.textual_graphs.facets import Graphs
4
+
5
+ __all__ = ['Graphs']
6
+
@@ -0,0 +1,23 @@
1
+ "@context":
2
+ "@import": https://json-ld.org/contexts/dollar-convenience.jsonld
3
+ iolanta: https://iolanta.tech/
4
+ rdfs: http://www.w3.org/2000/01/rdf-schema#
5
+ rdfg: http://www.w3.org/2009/rdfg#
6
+
7
+ $: rdfs:label
8
+ iolanta:is-preferred-over:
9
+ "@type": "@id"
10
+
11
+ iolanta:outputs:
12
+ "@type": "@id"
13
+
14
+ $id: rdfg:Graph
15
+ iolanta:facet:
16
+ $id: pkg:pypi/iolanta#textual-graphs
17
+ $: Named Graphs
18
+
19
+ iolanta:outputs: https://iolanta.tech/cli/textual
20
+
21
+ iolanta:is-preferred-over:
22
+ - pkg:pypi/iolanta#textual-properties
23
+ - pkg:pypi/iolanta#textual-graph-triples
@@ -0,0 +1,138 @@
1
+ from collections import defaultdict
2
+ from pathlib import Path
3
+ from typing import Iterable, NamedTuple
4
+
5
+ import funcy
6
+ from rdflib import Literal, Node
7
+ from textual.containers import Vertical
8
+ from textual.coordinate import Coordinate
9
+ from textual.widgets import DataTable
10
+
11
+ from iolanta.facets.facet import Facet
12
+ from iolanta.facets.page_title import PageTitle
13
+ from iolanta.models import NotLiteralNode
14
+ from iolanta.namespaces import DATATYPES
15
+ from iolanta.widgets.mixin import IolantaWidgetMixin
16
+
17
+
18
+ class GraphRow(NamedTuple):
19
+ """A row in the graphs table."""
20
+
21
+ graph: NotLiteralNode
22
+ count: int
23
+
24
+
25
+ class GraphsTable(IolantaWidgetMixin, DataTable):
26
+ """Render graphs as a table with graph URI and triple count."""
27
+
28
+ BINDINGS = [
29
+ ('enter', 'goto', 'Goto'),
30
+ ]
31
+
32
+ def __init__(self, rows: Iterable[GraphRow]):
33
+ """Construct."""
34
+ super().__init__(show_header=True, cell_padding=1)
35
+ self.graph_rows = list(rows)
36
+
37
+ def render_human_readable_cells(self):
38
+ """Replace the cells with their human readable titles."""
39
+ terms_and_coordinates = sorted(
40
+ self.node_to_coordinates.items(),
41
+ key=lambda node_and_coordinates_pair: len(
42
+ node_and_coordinates_pair[1],
43
+ ),
44
+ reverse=True,
45
+ )
46
+
47
+ for term, coordinates in terms_and_coordinates:
48
+ title = str(self.iolanta.render(term, as_datatype=DATATYPES.title))
49
+ for coordinate in coordinates:
50
+ self.app.call_from_thread(
51
+ self.update_cell_at,
52
+ coordinate,
53
+ value=title,
54
+ update_width=False,
55
+ )
56
+
57
+ @funcy.cached_property
58
+ @funcy.post_processing(dict)
59
+ def coordinate_to_node(self):
60
+ """Return a mapping of coordinates to their corresponding nodes."""
61
+ for row_number, (graph, _count) in enumerate(self.graph_rows):
62
+ yield Coordinate(row_number, 0), graph
63
+
64
+ @funcy.cached_property
65
+ def node_to_coordinates(self) -> defaultdict[Node, list[Coordinate]]:
66
+ """Map node to coordinates where it appears."""
67
+ node_to_coordinate = [
68
+ (node, coordinate)
69
+ for coordinate, node in self.coordinate_to_node.items()
70
+ ]
71
+ return funcy.group_values(node_to_coordinate)
72
+
73
+ def format_as_loading(self, node: Node) -> str:
74
+ """Intermediate version of a value while it is loading."""
75
+ if isinstance(node, Literal):
76
+ node_text = f'⌛ {node}'
77
+ else:
78
+ node_text = self.iolanta.node_as_qname(node)
79
+ node_text = f'⌛ {node_text}'
80
+
81
+ return node_text
82
+
83
+ def on_mount(self):
84
+ """Fill the table and start rendering."""
85
+ self.add_columns('Graph', 'Triples Count')
86
+ self.cell_padding = 1
87
+
88
+ for graph, count in self.graph_rows:
89
+ self.add_row(
90
+ self.format_as_loading(graph),
91
+ str(count),
92
+ )
93
+
94
+ self.run_worker(
95
+ self.render_human_readable_cells,
96
+ thread=True,
97
+ )
98
+
99
+ def action_goto(self):
100
+ """Navigate to the selected graph."""
101
+ if self.cursor_coordinate:
102
+ node = self.coordinate_to_node.get(self.cursor_coordinate)
103
+ if node is not None:
104
+ self.app.action_goto(node)
105
+
106
+
107
+ class GraphsBody(Vertical):
108
+ """Container for graphs table."""
109
+
110
+ DEFAULT_CSS = """
111
+ GraphsBody {
112
+ height: auto;
113
+ max-height: 100%;
114
+ }
115
+ """
116
+
117
+
118
+ class Graphs(Facet):
119
+ """Render named graphs as a table."""
120
+
121
+ META = Path(__file__).parent / 'data' / 'textual_graphs.yamlld'
122
+
123
+ def show(self):
124
+ """Construct the table."""
125
+ rows = self.stored_query('graphs.sparql')
126
+
127
+ return GraphsBody(
128
+ PageTitle(self.this),
129
+ GraphsTable(
130
+ [
131
+ GraphRow(
132
+ graph=row['graph'],
133
+ count=int(row['count'].value),
134
+ )
135
+ for row in rows
136
+ ],
137
+ ),
138
+ )
@@ -0,0 +1,5 @@
1
+ SELECT ?graph (COUNT(?s) AS ?count) WHERE {
2
+ GRAPH ?graph {
3
+ ?s ?p ?o .
4
+ }
5
+ } GROUP BY ?graph ORDER BY DESC(?count) ?graph
@@ -11,13 +11,13 @@ class TextualLinkFacet(Facet[str | Text]):
11
11
 
12
12
  def show(self) -> str | Text:
13
13
  """Render the link, or literal text, whatever."""
14
- if isinstance(self.iri, Literal):
15
- return f'[b grey37]{self.iri}[/b grey37]'
14
+ if isinstance(self.this, Literal):
15
+ return f'[b grey37]{self.this}[/b grey37]'
16
16
 
17
17
  label = self.render(
18
- self.iri,
18
+ self.this,
19
19
  as_datatype=DATATYPES.title,
20
20
  )
21
21
 
22
- invocation = f"app.goto('{self.iri}')"
22
+ invocation = f"app.goto('{self.this}')"
23
23
  return f'[@click="{invocation}"]{label}[/]'
@@ -11,4 +11,4 @@ class NanopublicationFacet(Facet[Widget]):
11
11
 
12
12
  def show(self) -> Widget:
13
13
  """Render a nanopublication."""
14
- return NanopublicationScreen(uri=self.iri)
14
+ return NanopublicationScreen(uri=self.this)
@@ -12,6 +12,7 @@ from iolanta.widgets.description import Description
12
12
  TEXT = """
13
13
  **😕 Iolanta is unable to visualize this resource**
14
14
 
15
+ * The reference type ({reference_type}) might be incorrect;
15
16
  * The URI might be incorrect;
16
17
  * Or, no edges might exist which involve it;
17
18
  * Or maybe Iolanta does not know of such edges.
@@ -44,7 +45,7 @@ class TextualNoFacetFound(Facet):
44
45
  @property
45
46
  def raw_content(self):
46
47
  """Content of the file, if applicable."""
47
- url = URL(self.uriref)
48
+ url = URL(self.this)
48
49
  if url.scheme != 'file':
49
50
  return None
50
51
 
@@ -94,6 +95,7 @@ class TextualNoFacetFound(Facet):
94
95
  TEXT.format(
95
96
  content=self.raw_content or '',
96
97
  subgraphs=self.subgraphs_description or '',
98
+ reference_type=type(self.this).__name__,
97
99
  ),
98
100
  ),
99
101
  ),
@@ -48,7 +48,7 @@ class OntologyFacet(Facet[Widget]):
48
48
  @cached_property
49
49
  def grouped_terms(self) -> dict[NotLiteralNode | None, list[TermAndStatus]]:
50
50
  """Group terms by VANN categories."""
51
- rows = self.stored_query('terms.sparql', iri=self.iri)
51
+ rows = self.stored_query('terms.sparql', iri=self.this)
52
52
  grouped = [
53
53
  (
54
54
  row.get('group'),
@@ -80,7 +80,7 @@ class OntologyFacet(Facet[Widget]):
80
80
 
81
81
  vocabs = funcy.lpluck(
82
82
  'vocab',
83
- self.stored_query('visualization-vocab.sparql', iri=self.iri),
83
+ self.stored_query('visualization-vocab.sparql', iri=self.this),
84
84
  )
85
85
 
86
86
  for vocab in vocabs:
@@ -94,7 +94,7 @@ class OntologyFacet(Facet[Widget]):
94
94
  )
95
95
 
96
96
  return Vertical(
97
- PageTitle(self.iri),
97
+ PageTitle(self.this),
98
98
  TermsContent(
99
99
  Static(
100
100
  Columns(
@@ -109,7 +109,7 @@ class TextualProvenanceFacet(Facet[Widget]):
109
109
 
110
110
  def show(self) -> Widget:
111
111
  """Obtain & render provenance info."""
112
- uri = TripleURIRef(self.iri)
112
+ uri = TripleURIRef(self.this)
113
113
  triple = uri.as_triple()
114
114
 
115
115
  graphs = funcy.lpluck(
@@ -31,7 +31,4 @@ class TitleFacet(Facet[str]):
31
31
  if label := row.get(alternative):
32
32
  return str(label)
33
33
 
34
- return self.render(
35
- self.iri,
36
- as_datatype=URIRef('https://iolanta.tech/qname'),
37
- )
34
+ return str(self.this)
@@ -12,14 +12,14 @@ class WikibaseStatementTitle(Facet[str]):
12
12
  """Render the title."""
13
13
  rows = self.stored_query(
14
14
  'statement-title.sparql',
15
- statement=self.iri,
15
+ statement=self.this,
16
16
  language=self.language,
17
17
  )
18
18
 
19
19
  row = funcy.first(rows)
20
20
  if not row:
21
21
  return self.render(
22
- self.iri,
22
+ self.this,
23
23
  as_datatype=URIRef('https://iolanta.tech/qname'),
24
24
  )
25
25
 
iolanta/iolanta.py CHANGED
@@ -292,7 +292,7 @@ class Iolanta: # noqa: WPS214
292
292
  facet_class = self.facet_resolver.resolve(found['facet'])
293
293
 
294
294
  facet = facet_class(
295
- iri=node,
295
+ this=node,
296
296
  iolanta=self,
297
297
  as_datatype=found['output_datatype'],
298
298
  )
@@ -328,7 +328,7 @@ class Iolanta: # noqa: WPS214
328
328
 
329
329
  facet_instances = [
330
330
  facet_class(
331
- iri=node,
331
+ this=node,
332
332
  iolanta=self,
333
333
  as_datatype=output_datatype,
334
334
  )
File without changes
@@ -0,0 +1,42 @@
1
+ "@context":
2
+ "@import": https://json-ld.org/contexts/dollar-convenience.jsonld
3
+ vann: https://purl.org/vocab/vann/
4
+ foaf: https://xmlns.com/foaf/0.1/
5
+ owl: https://www.w3.org/2002/07/owl#
6
+ iolanta: https://iolanta.tech/
7
+ rdfs: "https://www.w3.org/2000/01/rdf-schema#"
8
+ rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
9
+ dcterms: https://purl.org/dc/terms/
10
+ dcam: https://purl.org/dc/dcam/
11
+
12
+ iolanta:outputs:
13
+ "@type": "@id"
14
+
15
+ iolanta:when-no-facet-found:
16
+ "@type": "@id"
17
+
18
+ $: rdfs:label
19
+ →:
20
+ "@type": "@id"
21
+ "@id": iolanta:outputs
22
+
23
+ ⊆:
24
+ "@type": "@id"
25
+ "@id": rdfs:subClassOf
26
+
27
+ ⪯:
28
+ "@type": "@id"
29
+ "@id": iolanta:is-preferred-over
30
+
31
+ ↦: iolanta:matches
32
+ iolanta:hasDefaultFacet:
33
+ "@type": "@id"
34
+
35
+ $id: pkg:pypi/iolanta#labeled-triple-set
36
+ $: Labeled Triple Set
37
+ →:
38
+ $id: https://iolanta.tech/datatypes/labeled-triple-set
39
+ $: Labeled Triple Set
40
+ ↦:
41
+ - ASK WHERE { GRAPH $this { ?s ?p ?o } }
42
+ - ASK WHERE { $this <https://iolanta.tech/has-sub-graph> ?subgraph }
@@ -0,0 +1,137 @@
1
+ from pathlib import Path
2
+ from typing import Annotated, Iterable
3
+ from typing import Literal as TypingLiteral
4
+
5
+ from pydantic import (
6
+ AnyUrl,
7
+ BaseModel,
8
+ Field,
9
+ TypeAdapter,
10
+ field_serializer,
11
+ field_validator,
12
+ validator,
13
+ )
14
+ from rdflib import BNode, Literal, Node, URIRef
15
+
16
+ from iolanta import Facet
17
+ from iolanta.models import NotLiteralNode
18
+ from iolanta.namespaces import DATATYPES
19
+
20
+
21
+ class WithFeedback(BaseModel):
22
+ feedback: Annotated[list[str], Field(default_factory=list)]
23
+
24
+
25
+ class LabeledURI(WithFeedback, BaseModel):
26
+ type: TypingLiteral['uri'] = 'uri'
27
+ uri: AnyUrl
28
+ label: str
29
+
30
+ def construct_feedback(self) -> Iterable[str]:
31
+ if str(self.label) == str(self.uri):
32
+ yield (
33
+ 'For this URI, the label is the same as the URI. We were '
34
+ 'unable to render that URI.'
35
+ )
36
+
37
+
38
+ class LabeledBlank(WithFeedback, BaseModel):
39
+ type: TypingLiteral['blank'] = 'blank'
40
+ identifier: str
41
+ label: str
42
+
43
+
44
+ class LabeledLiteral(WithFeedback, BaseModel):
45
+ type: TypingLiteral['literal'] = 'literal'
46
+ value: str
47
+ language: str | None
48
+ datatype: str | None
49
+ label: str
50
+
51
+
52
+ class LabeledTriple(BaseModel):
53
+ subject: LabeledURI | LabeledBlank
54
+ predicate: LabeledURI
55
+ object_: LabeledURI | LabeledBlank | LabeledLiteral
56
+
57
+
58
+ def construct_uri_feedback(uri: AnyUrl, label: str) -> Iterable[str]:
59
+ if str(uri) == str(label):
60
+ yield (
61
+ 'For this URI, the label is the same as the URI. We were '
62
+ 'unable to render that URI.'
63
+ )
64
+
65
+
66
+ def construct_blank_feedback(bnode, label) -> Iterable[str]:
67
+ if str(bnode) == str(label):
68
+ yield (
69
+ 'For this blank node, the label is the same as the blank node. '
70
+ 'We were unable to render that blank node.'
71
+ )
72
+
73
+
74
+ def construct_literal_feedback(literal, label):
75
+ if label.startswith('http'):
76
+ yield (
77
+ 'This RDF literal seems to be actually a URL. Good chance is '
78
+ 'that it should not be a literal.'
79
+ )
80
+
81
+ elif ':' in label[:5]:
82
+ yield (
83
+ 'This RDF literal seems to be actually a QName (prefixed URI). '
84
+ 'Good chance is that it should not be a literal.'
85
+ )
86
+
87
+
88
+ class LabeledTripleSet(Facet[list[LabeledTriple]]):
89
+ """A set of labeled triples."""
90
+
91
+ META = Path(__file__).parent / 'data' / 'labeled_triple_set.yamlld'
92
+
93
+ def render_label(self, node: NotLiteralNode) -> str:
94
+ return self.render(node, as_datatype=DATATYPES.title)
95
+
96
+ def parse_term(self, term: Node):
97
+ match term:
98
+ case URIRef() as uriref:
99
+ uri = AnyUrl(uriref)
100
+ label = self.render_label(uriref)
101
+
102
+ return LabeledURI(
103
+ uri=uri,
104
+ label=label,
105
+ feedback=list(construct_uri_feedback(uri=uri, label=label)),
106
+ )
107
+
108
+ case BNode() as bnode:
109
+ label = self.render_label(bnode)
110
+ return LabeledBlank(
111
+ identifier=bnode,
112
+ label=label,
113
+ feedback=list(construct_blank_feedback(bnode=bnode, label=label)),
114
+ )
115
+
116
+ case Literal() as literal:
117
+ label = self.render_label(literal)
118
+ return LabeledLiteral(
119
+ value=literal,
120
+ language=literal.language,
121
+ datatype=literal.datatype,
122
+ label=label,
123
+ feedback=list(construct_literal_feedback(literal=literal, label=label)),
124
+ )
125
+
126
+ def show(self):
127
+ rows = self.stored_query('triples.sparql', graph=self.this)
128
+ triples = [
129
+ LabeledTriple(
130
+ subject=self.parse_term(row['subject']),
131
+ predicate=self.parse_term(row['predicate']),
132
+ object_=self.parse_term(row['object']),
133
+ )
134
+ for row in rows
135
+ ]
136
+
137
+ return TypeAdapter(list[LabeledTriple]).dump_json(triples, indent=2).decode()
@@ -0,0 +1,5 @@
1
+ SELECT * WHERE {
2
+ GRAPH $graph {
3
+ ?subject ?predicate ?object .
4
+ }
5
+ }
File without changes
iolanta/mcp/cli.py ADDED
@@ -0,0 +1,39 @@
1
+ from typing import Annotated
2
+ from fastmcp import FastMCP
3
+ from pathlib import Path
4
+
5
+ from iolanta.cli.main import render_and_return
6
+
7
+ mcp = FastMCP("Iolanta MCP Server")
8
+
9
+
10
+ @mcp.tool()
11
+ 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`']
14
+ ) -> str:
15
+ """Render a URI."""
16
+ result = render_and_return(uri, as_format)
17
+ return str(result)
18
+
19
+
20
+ @mcp.prompt(description="How to author Linked Data with Iolanta")
21
+ def ld_authoring_rules() -> str:
22
+ """How to author Linked Data with Iolanta."""
23
+ rules_path = Path(__file__).parent / 'prompts' / 'rules.md'
24
+ return rules_path.read_text()
25
+
26
+
27
+ @mcp.prompt(description="How to author nanopublication assertions with Iolanta")
28
+ def nanopublication_assertion_authoring_rules() -> str:
29
+ """How to author nanopublication assertions with Iolanta."""
30
+ rules_path = Path(__file__).parent / 'prompts' / 'nanopublication_assertion_authoring_rules.md'
31
+ return rules_path.read_text()
32
+
33
+
34
+ def app():
35
+ mcp.run()
36
+
37
+
38
+ if __name__ == "__main__":
39
+ app()