iolanta 2.1.4__py3-none-any.whl → 2.1.7__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.
- iolanta/cli/main.py +44 -24
- iolanta/data/context.yaml +1 -1
- iolanta/data/iolanta.yaml +1 -1
- iolanta/data/textual-browser.yaml +1 -7
- iolanta/declension/data/declension.yamlld +1 -1
- iolanta/facets/locator/sparql/get-query-to-facet.sparql +5 -0
- iolanta/facets/locator.py +4 -7
- iolanta/facets/mkdocs_material_insiders_markdown/__init__.py +4 -0
- iolanta/facets/mkdocs_material_insiders_markdown/data/mkdocs_material_insiders_markdown.yamlld +20 -0
- iolanta/facets/mkdocs_material_insiders_markdown/facet.py +86 -0
- iolanta/facets/mkdocs_material_insiders_markdown/templates/datatype.jinja2.md +24 -0
- iolanta/facets/textual_browser/page_switcher.py +1 -1
- iolanta/facets/textual_graphs/__init__.py +6 -0
- iolanta/facets/textual_graphs/data/textual_graphs.yamlld +23 -0
- iolanta/facets/textual_graphs/facets.py +138 -0
- iolanta/facets/textual_graphs/sparql/graphs.sparql +5 -0
- iolanta/iolanta.py +4 -3
- iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +1 -1
- iolanta/mcp/__init__.py +0 -0
- iolanta/mcp/cli.py +39 -0
- iolanta/mcp/prompts/nanopublication_assertion_authoring_rules.md +63 -0
- iolanta/mcp/prompts/rules.md +83 -0
- iolanta/mermaid/facet.py +0 -3
- iolanta/mermaid/mermaid.yamlld +7 -24
- iolanta/mermaid/models.py +4 -2
- iolanta/mermaid/sparql/ask-has-triples.sparql +3 -0
- iolanta/models.py +0 -3
- iolanta/namespaces.py +2 -2
- iolanta/parse_quads.py +2 -2
- iolanta/sparqlspace/inference/wikidata-prop-label.sparql +10 -0
- iolanta/sparqlspace/inference/wikidata-statement-label.sparql +27 -0
- iolanta/sparqlspace/processor.py +80 -78
- {iolanta-2.1.4.dist-info → iolanta-2.1.7.dist-info}/METADATA +6 -3
- {iolanta-2.1.4.dist-info → iolanta-2.1.7.dist-info}/RECORD +36 -22
- {iolanta-2.1.4.dist-info → iolanta-2.1.7.dist-info}/WHEEL +1 -1
- {iolanta-2.1.4.dist-info → iolanta-2.1.7.dist-info}/entry_points.txt +3 -1
- iolanta/sparqlspace/inference/wikibase-claim.sparql +0 -9
- iolanta/sparqlspace/inference/wikibase-statement-property.sparql +0 -9
iolanta/cli/main.py
CHANGED
|
@@ -11,7 +11,7 @@ from rdflib import Literal, URIRef
|
|
|
11
11
|
from rich.console import Console
|
|
12
12
|
from rich.markdown import Markdown
|
|
13
13
|
from rich.table import Table
|
|
14
|
-
from typer import Argument, Exit, Option, Typer
|
|
14
|
+
from typer import Argument, Exit, Option, Typer, Context
|
|
15
15
|
from yarl import URL
|
|
16
16
|
|
|
17
17
|
from iolanta.cli.models import LogLevel
|
|
@@ -24,9 +24,7 @@ DEFAULT_LANGUAGE = locale.getlocale()[0].split('_')[0]
|
|
|
24
24
|
console = Console()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
app = Typer(
|
|
28
|
-
no_args_is_help=True,
|
|
29
|
-
)
|
|
27
|
+
app = Typer(no_args_is_help=True)
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
def string_to_node(name: str) -> NotLiteralNode:
|
|
@@ -52,19 +50,10 @@ def decode_datatype(datatype: str) -> URIRef:
|
|
|
52
50
|
return URIRef(f'https://iolanta.tech/datatypes/{datatype}')
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
str, Option(
|
|
60
|
-
'--as',
|
|
61
|
-
),
|
|
62
|
-
] = 'https://iolanta.tech/cli/interactive',
|
|
63
|
-
language: Annotated[
|
|
64
|
-
str, Option(
|
|
65
|
-
help='Data language to prefer.',
|
|
66
|
-
),
|
|
67
|
-
] = DEFAULT_LANGUAGE,
|
|
53
|
+
def render_and_return(
|
|
54
|
+
url: str,
|
|
55
|
+
as_datatype: str,
|
|
56
|
+
language: str = DEFAULT_LANGUAGE,
|
|
68
57
|
log_level: LogLevel = LogLevel.ERROR,
|
|
69
58
|
):
|
|
70
59
|
"""Render a given URL."""
|
|
@@ -124,13 +113,38 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
|
|
|
124
113
|
project_root=path,
|
|
125
114
|
)
|
|
126
115
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
return iolanta.render(
|
|
117
|
+
node=URIRef(node),
|
|
118
|
+
as_datatype=decode_datatype(as_datatype),
|
|
119
|
+
)
|
|
120
|
+
|
|
132
121
|
|
|
122
|
+
@app.command(name='render')
|
|
123
|
+
def render_command( # noqa: WPS231, WPS238, WPS210, C901
|
|
124
|
+
url: Annotated[str, Argument()],
|
|
125
|
+
as_datatype: Annotated[
|
|
126
|
+
str, Option(
|
|
127
|
+
'--as',
|
|
128
|
+
),
|
|
129
|
+
] = 'https://iolanta.tech/cli/interactive',
|
|
130
|
+
language: Annotated[
|
|
131
|
+
str, Option(
|
|
132
|
+
help='Data language to prefer.',
|
|
133
|
+
),
|
|
134
|
+
] = DEFAULT_LANGUAGE,
|
|
135
|
+
log_level: LogLevel = LogLevel.ERROR,
|
|
136
|
+
):
|
|
137
|
+
"""Render a given URL."""
|
|
138
|
+
try:
|
|
139
|
+
renderable = render_and_return(url, as_datatype, language, log_level)
|
|
133
140
|
except DocumentedError as documented_error:
|
|
141
|
+
level = {
|
|
142
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
143
|
+
LogLevel.INFO: logging.INFO,
|
|
144
|
+
LogLevel.WARNING: logging.WARNING,
|
|
145
|
+
LogLevel.ERROR: logging.ERROR,
|
|
146
|
+
}[log_level]
|
|
147
|
+
|
|
134
148
|
if level in {logging.DEBUG, logging.INFO}:
|
|
135
149
|
raise
|
|
136
150
|
|
|
@@ -143,12 +157,18 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
|
|
|
143
157
|
raise Exit(1)
|
|
144
158
|
|
|
145
159
|
except Exception as err:
|
|
146
|
-
|
|
160
|
+
level = {
|
|
161
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
162
|
+
LogLevel.INFO: logging.INFO,
|
|
163
|
+
LogLevel.WARNING: logging.WARNING,
|
|
164
|
+
LogLevel.ERROR: logging.ERROR,
|
|
165
|
+
}[log_level]
|
|
166
|
+
|
|
167
|
+
if level in {logging.DEBUG, logging.INFO}:
|
|
147
168
|
raise
|
|
148
169
|
|
|
149
170
|
console.print(str(err))
|
|
150
171
|
raise Exit(1)
|
|
151
|
-
|
|
152
172
|
else:
|
|
153
173
|
# FIXME: An intermediary Literal can be used to dispatch rendering.
|
|
154
174
|
match renderable:
|
iolanta/data/context.yaml
CHANGED
iolanta/data/iolanta.yaml
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
foaf: https://xmlns.com/foaf/0.1/
|
|
5
5
|
owl: https://www.w3.org/2002/07/owl#
|
|
6
6
|
iolanta: https://iolanta.tech/
|
|
7
|
-
rdfs: "
|
|
7
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
|
|
8
8
|
rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
|
|
9
9
|
|
|
10
10
|
$included:
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
foaf: https://xmlns.com/foaf/0.1/
|
|
5
5
|
owl: https://www.w3.org/2002/07/owl#
|
|
6
6
|
iolanta: https://iolanta.tech/
|
|
7
|
-
rdfs: "
|
|
7
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
|
|
8
8
|
rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
|
|
9
9
|
np: https://www.nanopub.org/nschema#
|
|
10
10
|
dcterms: https://purl.org/dc/terms/
|
|
@@ -88,12 +88,6 @@
|
|
|
88
88
|
iolanta:hasDefaultFacet:
|
|
89
89
|
$id: pkg:pypi/iolanta#textual-link
|
|
90
90
|
|
|
91
|
-
- $id: https://wikiba.se/ontology#Statement
|
|
92
|
-
iolanta:hasInstanceFacet:
|
|
93
|
-
$id: pkg:pypi/iolanta#wikibase-statement-title
|
|
94
|
-
$: Title
|
|
95
|
-
→: https://iolanta.tech/datatypes/title
|
|
96
|
-
|
|
97
91
|
- $id: rdfs:Class
|
|
98
92
|
iolanta:hasInstanceFacet:
|
|
99
93
|
$id: pkg:pypi/iolanta#textual-class
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
foaf: https://xmlns.com/foaf/0.1/
|
|
5
5
|
owl: https://www.w3.org/2002/07/owl#
|
|
6
6
|
iolanta: https://iolanta.tech/
|
|
7
|
-
rdfs: "
|
|
7
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
|
|
8
8
|
rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
|
|
9
9
|
dcterms: https://purl.org/dc/terms/
|
|
10
10
|
dcam: https://purl.org/dc/dcam/
|
iolanta/facets/locator.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from functools import cached_property
|
|
3
3
|
from graphlib import TopologicalSorter
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
from typing import Iterable, List, TypedDict
|
|
5
6
|
|
|
6
7
|
import funcy
|
|
@@ -18,13 +19,9 @@ class FoundRow(TypedDict):
|
|
|
18
19
|
output_datatype: NotLiteralNode
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
GET_QUERY_TO_FACET =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
iolanta:matches ?match ;
|
|
25
|
-
iolanta:outputs $as_datatype .
|
|
26
|
-
}
|
|
27
|
-
"""
|
|
22
|
+
GET_QUERY_TO_FACET = (
|
|
23
|
+
Path(__file__).parent / 'locator' / 'sparql' / 'get-query-to-facet.sparql'
|
|
24
|
+
).read_text()
|
|
28
25
|
|
|
29
26
|
|
|
30
27
|
def reorder_rows_by_facet_preferences( # noqa: WPS214, WPS210
|
iolanta/facets/mkdocs_material_insiders_markdown/data/mkdocs_material_insiders_markdown.yamlld
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"@context":
|
|
2
|
+
"@import": https://json-ld.org/contexts/dollar-convenience.jsonld
|
|
3
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
|
|
4
|
+
rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
|
|
5
|
+
iolanta: https://iolanta.tech/
|
|
6
|
+
|
|
7
|
+
$: rdfs:label
|
|
8
|
+
|
|
9
|
+
→:
|
|
10
|
+
"@type": "@id"
|
|
11
|
+
"@id": iolanta:outputs
|
|
12
|
+
|
|
13
|
+
↦: iolanta:matches
|
|
14
|
+
|
|
15
|
+
$id: pkg:pypi/iolanta#mkdocs-material-insiders-markdown-datatype
|
|
16
|
+
$: OutputDatatype (mkdocs-material-insiders-markdown)
|
|
17
|
+
|
|
18
|
+
→: https://iolanta.tech/datatypes/mkdocs-material-insiders-markdown
|
|
19
|
+
|
|
20
|
+
↦: ASK WHERE { $this a iolanta:OutputDatatype }
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from jinja2 import Environment, FileSystemLoader
|
|
4
|
+
from rdflib import URIRef
|
|
5
|
+
|
|
6
|
+
from iolanta.facets.facet import Facet
|
|
7
|
+
from iolanta.namespaces import DATATYPES, IOLANTA, RDF, RDFS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MkDocsMaterialInsidersMarkdownFacet(Facet[str]):
|
|
11
|
+
"""Render rdfs:Datatype nodes as mkdocs-material-insiders-markdown."""
|
|
12
|
+
|
|
13
|
+
META = Path(__file__).parent / 'data' / 'mkdocs_material_insiders_markdown.yamlld'
|
|
14
|
+
"""Render rdfs:Datatype nodes as mkdocs-material-insiders-markdown."""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def _template_env(self) -> Environment:
|
|
18
|
+
"""Jinja2 template environment."""
|
|
19
|
+
template_path = Path(__file__).parent / 'templates'
|
|
20
|
+
return Environment(
|
|
21
|
+
loader=FileSystemLoader(str(template_path)),
|
|
22
|
+
autoescape=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def show(self) -> str:
|
|
26
|
+
"""Render the datatype as markdown."""
|
|
27
|
+
# Get the label using title facet
|
|
28
|
+
label = self.render(
|
|
29
|
+
self.this,
|
|
30
|
+
as_datatype=DATATYPES.title,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Get the comment/description
|
|
34
|
+
comment_rows = self.query(
|
|
35
|
+
"""
|
|
36
|
+
SELECT ?comment WHERE {
|
|
37
|
+
$this rdfs:comment ?comment .
|
|
38
|
+
}
|
|
39
|
+
LIMIT 1
|
|
40
|
+
""",
|
|
41
|
+
this=self.this,
|
|
42
|
+
)
|
|
43
|
+
comment = str(comment_rows[0]['comment']) if comment_rows else None
|
|
44
|
+
|
|
45
|
+
# Get all types (rdf:type)
|
|
46
|
+
type_rows = self.query(
|
|
47
|
+
"""
|
|
48
|
+
SELECT ?type WHERE {
|
|
49
|
+
$this rdf:type ?type .
|
|
50
|
+
}
|
|
51
|
+
""",
|
|
52
|
+
this=self.this,
|
|
53
|
+
)
|
|
54
|
+
types = [
|
|
55
|
+
{
|
|
56
|
+
'uri': row['type'],
|
|
57
|
+
'title': self.render(row['type'], as_datatype=DATATYPES.title),
|
|
58
|
+
}
|
|
59
|
+
for row in type_rows
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# Get all superclasses (rdfs:subClassOf)
|
|
63
|
+
superclass_rows = self.query(
|
|
64
|
+
"""
|
|
65
|
+
SELECT ?superclass WHERE {
|
|
66
|
+
$this rdfs:subClassOf ?superclass .
|
|
67
|
+
}
|
|
68
|
+
""",
|
|
69
|
+
this=self.this,
|
|
70
|
+
)
|
|
71
|
+
superclasses = [
|
|
72
|
+
{
|
|
73
|
+
'uri': row['superclass'],
|
|
74
|
+
'title': self.render(row['superclass'], as_datatype=DATATYPES.title),
|
|
75
|
+
}
|
|
76
|
+
for row in superclass_rows
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
template = self._template_env.get_template('datatype.jinja2.md')
|
|
80
|
+
return template.render(
|
|
81
|
+
label=label,
|
|
82
|
+
comment=comment,
|
|
83
|
+
types=types,
|
|
84
|
+
superclasses=superclasses,
|
|
85
|
+
)
|
|
86
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# {{ label }}
|
|
2
|
+
|
|
3
|
+
<table>
|
|
4
|
+
<thead>
|
|
5
|
+
</thead>
|
|
6
|
+
<tbody>
|
|
7
|
+
{% if types %}
|
|
8
|
+
<tr>
|
|
9
|
+
<td>∈ Instance Of</td>
|
|
10
|
+
<td>{% for type in types %}<code><a href="{{ type.uri }}">{{ type.title }}</a></code>{% if not loop.last %}, {% endif %}{% endfor %}</td>
|
|
11
|
+
</tr>
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% if superclasses %}
|
|
14
|
+
<tr>
|
|
15
|
+
<td>⊊ Subclass Of</td>
|
|
16
|
+
<td>{% for superclass in superclasses %}<code><a href="{{ superclass.uri }}">{{ superclass.title }}</a></code>{% if not loop.last %}, {% endif %}{% endfor %}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
{% endif %}
|
|
19
|
+
</tbody>
|
|
20
|
+
</table>
|
|
21
|
+
|
|
22
|
+
{% if comment %}
|
|
23
|
+
{{ comment | safe }}
|
|
24
|
+
{% endif %}
|
|
@@ -323,7 +323,7 @@ class DevConsole(RichLog):
|
|
|
323
323
|
|
|
324
324
|
def __init__(self):
|
|
325
325
|
"""Set default props for console."""
|
|
326
|
-
super().__init__(highlight=
|
|
326
|
+
super().__init__(highlight=False, markup=False, id='console')
|
|
327
327
|
|
|
328
328
|
def action_close(self):
|
|
329
329
|
"""Close the dev console."""
|
|
@@ -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
|
+
)
|
iolanta/iolanta.py
CHANGED
|
@@ -119,7 +119,7 @@ class Iolanta: # noqa: WPS214
|
|
|
119
119
|
processor='sparqlspace',
|
|
120
120
|
initBindings=kwargs,
|
|
121
121
|
)
|
|
122
|
-
except
|
|
122
|
+
except SyntaxError as err:
|
|
123
123
|
raise SPARQLParseException(
|
|
124
124
|
error=err,
|
|
125
125
|
query=query_text,
|
|
@@ -171,8 +171,9 @@ class Iolanta: # noqa: WPS214
|
|
|
171
171
|
self.logger.error(f'{source} | {parser_not_found}')
|
|
172
172
|
continue
|
|
173
173
|
except YAMLLDError as yaml_ld_error:
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
# .add() only processes local files, so errors should be raised
|
|
175
|
+
self.logger.error(f'{source_file} | {yaml_ld_error}')
|
|
176
|
+
raise
|
|
176
177
|
except ValueError as value_error:
|
|
177
178
|
self.logger.error(f'{source} | {value_error}')
|
|
178
179
|
continue
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
foaf: https://xmlns.com/foaf/0.1/
|
|
5
5
|
owl: https://www.w3.org/2002/07/owl#
|
|
6
6
|
iolanta: https://iolanta.tech/
|
|
7
|
-
rdfs: "
|
|
7
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#"
|
|
8
8
|
rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
|
|
9
9
|
dcterms: https://purl.org/dc/terms/
|
|
10
10
|
dcam: https://purl.org/dc/dcam/
|
iolanta/mcp/__init__.py
ADDED
|
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()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# How to author Nanopublication Assertions with Iolanta
|
|
2
|
+
|
|
3
|
+
## What are Nanopublications?
|
|
4
|
+
|
|
5
|
+
Nanopublications are a special type of Linked Data that contain structured knowledge statements with three main components:
|
|
6
|
+
|
|
7
|
+
1. **Assertion** - The core knowledge claim or statement
|
|
8
|
+
2. **Provenance** - Information about how the assertion was derived (sources, methods, contributors)
|
|
9
|
+
3. **Publication Info** - Metadata about the nanopublication itself (author, creation date, etc.)
|
|
10
|
+
|
|
11
|
+
Nanopublications are cryptographically signed and published in the decentralized **Nanopublication Registry**, making them:
|
|
12
|
+
- Irrevocably attributed to the author
|
|
13
|
+
- Protected from tampering
|
|
14
|
+
- Referenceable by unique IDs
|
|
15
|
+
- Machine readable and reusable
|
|
16
|
+
- Decentralized and persistent
|
|
17
|
+
|
|
18
|
+
## Assertion-Only Workflow
|
|
19
|
+
|
|
20
|
+
**NP00.** Nanopublication assertion graphs must also satisfy the general rules for Linked Data authoring and workflow. That is provided in the MCP prompt named `ld_authoring_rules`.
|
|
21
|
+
|
|
22
|
+
**NP01.** We focus only on writing the **assertion graph** of the nanopublication.
|
|
23
|
+
|
|
24
|
+
**NP02.** Follow the standard YAML-LD authoring rules (R00-R23) for creating the assertion.
|
|
25
|
+
|
|
26
|
+
**NP03.** The assertion should express a single, clear knowledge claim that can stand alone.
|
|
27
|
+
|
|
28
|
+
**NP04.** Use proper Linked Data vocabularies and resolvable URIs for all entities and relationships.
|
|
29
|
+
|
|
30
|
+
**NP05.** After the assertion graph is ready, follow this workflow:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Expand the YAML-LD to JSON-LD
|
|
34
|
+
pyld expand assertion.yamlld > expanded.jsonld
|
|
35
|
+
|
|
36
|
+
# Create nanopublication from the assertion
|
|
37
|
+
np create from-assertion expanded.jsonld > nanopublication.trig
|
|
38
|
+
|
|
39
|
+
# Publish the nanopublication (when ready)
|
|
40
|
+
np publish nanopublication.trig
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**NP06.** The `pyld expand` command converts YAML-LD to expanded JSON-LD format.
|
|
44
|
+
|
|
45
|
+
**NP07.** The `np create from-assertion` command automatically generates the provenance and publication info components.
|
|
46
|
+
|
|
47
|
+
**NP08.** The `np publish` command cryptographically signs and publishes the nanopublication to the registry.
|
|
48
|
+
|
|
49
|
+
**NP09.** Use the Iolanta MCP `render_uri` tool to validate the assertion before proceeding with the workflow.
|
|
50
|
+
|
|
51
|
+
**NP10.** Save Mermaid visualizations of the assertion for documentation purposes.
|
|
52
|
+
|
|
53
|
+
## Best Practices for Assertions
|
|
54
|
+
|
|
55
|
+
**NP11.** Keep assertions focused on a single, verifiable claim.
|
|
56
|
+
|
|
57
|
+
**NP12.** Use canonical URIs from established knowledge bases (DBpedia, Wikidata, etc.).
|
|
58
|
+
|
|
59
|
+
**NP13.** Include sufficient context and metadata to make the assertion meaningful.
|
|
60
|
+
|
|
61
|
+
**NP14.** Ensure the assertion can be understood independently of external context.
|
|
62
|
+
|
|
63
|
+
**NP15.** Use standard vocabularies and well-established ontologies for relationships.
|