iolanta 2.1.4__tar.gz → 2.1.6__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 (145) hide show
  1. {iolanta-2.1.4 → iolanta-2.1.6}/PKG-INFO +4 -2
  2. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/main.py +44 -24
  3. iolanta-2.1.6/iolanta/facets/locator/sparql/get-query-to-facet.sparql +5 -0
  4. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/locator.py +4 -7
  5. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/page_switcher.py +1 -1
  6. iolanta-2.1.6/iolanta/facets/textual_graphs/__init__.py +6 -0
  7. iolanta-2.1.6/iolanta/facets/textual_graphs/data/textual_graphs.yamlld +23 -0
  8. iolanta-2.1.6/iolanta/facets/textual_graphs/facets.py +138 -0
  9. iolanta-2.1.6/iolanta/facets/textual_graphs/sparql/graphs.sparql +5 -0
  10. iolanta-2.1.6/iolanta/mcp/cli.py +39 -0
  11. iolanta-2.1.6/iolanta/mcp/prompts/nanopublication_assertion_authoring_rules.md +63 -0
  12. iolanta-2.1.6/iolanta/mcp/prompts/rules.md +83 -0
  13. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/mermaid/facet.py +0 -3
  14. iolanta-2.1.6/iolanta/mermaid/mermaid.yamlld +25 -0
  15. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/mermaid/models.py +4 -2
  16. iolanta-2.1.6/iolanta/mermaid/sparql/ask-has-triples.sparql +3 -0
  17. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/sparqlspace/processor.py +2 -2
  18. iolanta-2.1.6/iolanta/widgets/__init__.py +0 -0
  19. {iolanta-2.1.4 → iolanta-2.1.6}/pyproject.toml +7 -2
  20. iolanta-2.1.4/iolanta/mermaid/mermaid.yamlld +0 -42
  21. {iolanta-2.1.4 → iolanta-2.1.6}/README.md +0 -0
  22. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/__init__.py +0 -0
  23. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/base_plugin.py +0 -0
  24. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/__init__.py +0 -0
  25. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/formatters/__init__.py +0 -0
  26. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/formatters/choose.py +0 -0
  27. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/formatters/csv.py +0 -0
  28. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/formatters/json.py +0 -0
  29. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/formatters/pretty.py +0 -0
  30. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/models.py +0 -0
  31. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/cli/pretty_print.py +0 -0
  32. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/context.py +0 -0
  33. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/conversions.py +0 -0
  34. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/data/context.yaml +0 -0
  35. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/data/graph-triples.yamlld +0 -0
  36. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/data/iolanta.yaml +0 -0
  37. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/data/textual-browser.yaml +0 -0
  38. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/declension/__init__.py +0 -0
  39. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/declension/data/declension.yamlld +0 -0
  40. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/declension/facet.py +0 -0
  41. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/declension/sparql/declension.sparql +0 -0
  42. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/ensure_is_context.py +0 -0
  43. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/entry_points.py +0 -0
  44. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/errors.py +0 -0
  45. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/__init__.py +0 -0
  46. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/cli/__init__.py +0 -0
  47. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/cli/base.py +0 -0
  48. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/cli/default.py +0 -0
  49. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/cli/record.py +0 -0
  50. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/cli/sparql/link.sparql +0 -0
  51. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/cli/sparql/record.sparql +0 -0
  52. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/errors.py +0 -0
  53. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/facet.py +0 -0
  54. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/foaf_person_title/__init__.py +0 -0
  55. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/foaf_person_title/facet.py +0 -0
  56. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
  57. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/generic/__init__.py +0 -0
  58. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/generic/bool_literal.py +0 -0
  59. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/generic/date_literal.py +0 -0
  60. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/generic/default.py +0 -0
  61. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/generic/sparql/default.sparql +0 -0
  62. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/html/__init__.py +0 -0
  63. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/html/base.py +0 -0
  64. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/html/code_literal.py +0 -0
  65. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/html/default.py +0 -0
  66. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/icon.py +0 -0
  67. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/page_title.py +0 -0
  68. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/qname.py +0 -0
  69. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/__init__.py +0 -0
  70. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/app.py +0 -0
  71. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/facet.py +0 -0
  72. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/history.py +0 -0
  73. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/home.py +0 -0
  74. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/location.py +0 -0
  75. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/models.py +0 -0
  76. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_browser/page.py +0 -0
  77. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_class/__init__.py +0 -0
  78. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_class/facets.py +0 -0
  79. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_class/sparql/instances.sparql +0 -0
  80. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/__init__.py +0 -0
  81. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/facets.py +0 -0
  82. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
  83. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
  84. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
  85. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
  86. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
  87. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/templates/default.md +0 -0
  88. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
  89. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_default/widgets.py +0 -0
  90. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_graph/__init__.py +0 -0
  91. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_graph/facets.py +0 -0
  92. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
  93. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_graph_triples.py +0 -0
  94. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_link/__init__.py +0 -0
  95. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_link/facet.py +0 -0
  96. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
  97. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/facet.py +0 -0
  98. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/models.py +0 -0
  99. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
  100. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
  101. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
  102. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_no_facet_found.py +0 -0
  103. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_ontology/__init__.py +0 -0
  104. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_ontology/facets.py +0 -0
  105. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
  106. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
  107. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_property_pairs_table.py +0 -0
  108. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_provenance/__init__.py +0 -0
  109. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_provenance/facets.py +0 -0
  110. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
  111. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
  112. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/title/__init__.py +0 -0
  113. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/title/facets.py +0 -0
  114. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/title/sparql/title.sparql +0 -0
  115. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
  116. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/wikibase_statement_title/facets.py +0 -0
  117. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
  118. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/iolanta.py +0 -0
  119. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/labeled_triple_set/__init__.py +0 -0
  120. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +0 -0
  121. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/labeled_triple_set/labeled_triple_set.py +0 -0
  122. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/labeled_triple_set/sparql/triples.sparql +0 -0
  123. {iolanta-2.1.4/iolanta/mermaid → iolanta-2.1.6/iolanta/mcp}/__init__.py +0 -0
  124. {iolanta-2.1.4/iolanta/resolvers → iolanta-2.1.6/iolanta/mermaid}/__init__.py +0 -0
  125. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/mermaid/sparql/graph.sparql +0 -0
  126. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/mermaid/sparql/subgraphs.sparql +0 -0
  127. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/models.py +0 -0
  128. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/namespaces.py +0 -0
  129. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/node_to_qname.py +0 -0
  130. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/parse_quads.py +0 -0
  131. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/plugin.py +0 -0
  132. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/query_result.py +0 -0
  133. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/reformat_blank_nodes.py +0 -0
  134. {iolanta-2.1.4/iolanta/sparqlspace → iolanta-2.1.6/iolanta/resolvers}/__init__.py +0 -0
  135. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/resolvers/base.py +0 -0
  136. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/resolvers/dispatch.py +0 -0
  137. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/resolvers/pypi.py +0 -0
  138. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/resolvers/python_import.py +0 -0
  139. {iolanta-2.1.4/iolanta/widgets → iolanta-2.1.6/iolanta/sparqlspace}/__init__.py +0 -0
  140. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/sparqlspace/cli.py +0 -0
  141. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/sparqlspace/inference/wikibase-claim.sparql +0 -0
  142. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/sparqlspace/inference/wikibase-statement-property.sparql +0 -0
  143. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/sparqlspace/sparqlspace.py +0 -0
  144. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/widgets/description.py +0 -0
  145. {iolanta-2.1.4 → iolanta-2.1.6}/iolanta/widgets/mixin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: iolanta
3
- Version: 2.1.4
3
+ Version: 2.1.6
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -17,9 +17,11 @@ Requires-Dist: deepmerge (>=0.1.1)
17
17
  Requires-Dist: diskcache (>=5.6.3)
18
18
  Requires-Dist: documented (>=0.1.1)
19
19
  Requires-Dist: dominate (>=2.6.0)
20
+ Requires-Dist: fastmcp (>=2.12.4,<3.0.0)
20
21
  Requires-Dist: funcy (>=2.0)
21
22
  Requires-Dist: loguru (>=0.7.3)
22
23
  Requires-Dist: more-itertools (>=9.0.0)
24
+ Requires-Dist: nanopub (>=2.1.0,<3.0.0)
23
25
  Requires-Dist: owlrl (>=6.0.2)
24
26
  Requires-Dist: oxrdflib (>=0.4.0)
25
27
  Requires-Dist: packageurl-python (>=0.17.5)
@@ -31,7 +33,7 @@ Requires-Dist: rich (>=13.3.1)
31
33
  Requires-Dist: textual (>=0.83.0)
32
34
  Requires-Dist: typer (>=0.9.0)
33
35
  Requires-Dist: watchfiles (>=1.0.4)
34
- Requires-Dist: yaml-ld (>=1.1.12)
36
+ Requires-Dist: yaml-ld (>=1.1.14,<2.0.0)
35
37
  Requires-Dist: yarl (>=1.9.4)
36
38
  Description-Content-Type: text/markdown
37
39
 
@@ -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
- @app.command(name='browse')
56
- def render_command( # noqa: WPS231, WPS238, WPS210, C901
57
- url: Annotated[str, Argument()],
58
- as_datatype: Annotated[
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
- try:
128
- renderable = iolanta.render(
129
- node=URIRef(node),
130
- as_datatype=decode_datatype(as_datatype),
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
- if iolanta.logger.level in {logging.DEBUG, logging.INFO}:
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:
@@ -0,0 +1,5 @@
1
+ SELECT ?facet ?match WHERE {
2
+ ?facet
3
+ iolanta:matches ?match ;
4
+ iolanta:outputs $as_datatype .
5
+ }
@@ -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
- SELECT ?facet ?match WHERE {
23
- ?facet
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
@@ -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=True, markup=True, id='console')
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,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
@@ -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.
@@ -0,0 +1,83 @@
1
+ # How to author Linked Data with Iolanta
2
+
3
+ **R00.** Follow this YAML-LD authoring workflow:
4
+ - Draft YAML-LD from user text
5
+ - Use the Iolanta MCP `render_uri` tool with `as_format: labeled-triple-set` to validate and get feedback
6
+ - Address the feedback, correct the YAML-LD document appropriately
7
+ - **After each change to the YAML-LD file, re-run the validation to check for new feedback**
8
+
9
+ **R01.** Acceptance Criteria:
10
+
11
+ - The document fits the original statement the user wanted to express;
12
+ - No negative feedback is received.
13
+
14
+ **R02.** Use YAML-LD format, which is JSON-LD in YAML syntax, for writing Linked Data.
15
+
16
+ **R03.** Always quote the @ character in YAML since it's reserved. Use `"@id":` instead of `@id:`.
17
+
18
+ **R04.** Prefer YAML-LD Convenience Context which maps @-keywords to $-keywords that don't need quoting: `"@type"` → `$type`, `"@id"` → `$id`, `"@graph"` → `$graph`.
19
+
20
+ **R05.** Use the dollar-convenience context with `@import` syntax instead of array syntax. This provides cleaner, more readable YAML-LD documents.
21
+
22
+ Example:
23
+ ```yaml
24
+ "@context":
25
+ "@import": "https://json-ld.org/contexts/dollar-convenience.jsonld"
26
+
27
+ schema: "https://schema.org/"
28
+ wd: "https://www.wikidata.org/entity/"
29
+
30
+ author:
31
+ "@id": "https://schema.org/author"
32
+ "@type": "@id"
33
+ ```
34
+
35
+ Instead of:
36
+ ```yaml
37
+ "@context":
38
+ - "https://json-ld.org/contexts/dollar-convenience.jsonld"
39
+ - schema: "https://schema.org/"
40
+ - wd: "https://www.wikidata.org/entity/"
41
+ - author:
42
+ "@id": "https://schema.org/author"
43
+ "@type": "@id"
44
+ ```
45
+
46
+ **R06.** Reduce quoting when not required by YAML syntax rules.
47
+
48
+ **R07.** Do not use mock URLs like `https://example.org`. Use resolvable URLs that preferably point to Linked Data.
49
+
50
+ **R08.** Use URIs that convey meaning and are renderable with Linked Data visualization tools. Search for appropriate URIs from sources like DBPedia or Wikidata.
51
+
52
+ **R09.** Use the Iolanta MCP `render_uri` tool with `as_format: mermaid` to generate Mermaid graph visualizations of Linked Data. If the user asks, you can save them to `.mmd` files for preview and documentation purposes.
53
+
54
+ **R10.** For language tags, use YAML-LD syntax: `rdfs:label: { $value: "text", $language: "lang" }` instead of Turtle syntax `"text"@lang`.
55
+
56
+ **R11.** Do not attach labels to external URIs that are expected to return Linked Data. Iolanta will fetch those URIs and render labels from the fetched data.
57
+
58
+ **R12.** Use `"@type": "@id"` in the context to coerce properties to IRIs instead of using `$id` wrappers in the document body.
59
+
60
+ **R13.** For software packages, use `schema:SoftwareApplication` as the main type rather than `codemeta:SoftwareSourceCode`.
61
+
62
+ **R14.** Use Wikidata entities for programming languages (e.g., `https://www.wikidata.org/entity/Q28865` for Python) instead of string literals.
63
+
64
+ **R15.** Use proper ORCID URIs for authors (e.g., `https://orcid.org/0009-0001-8740-4213`) and coerce them to IRIs in the context.
65
+
66
+ **R16.** For tools that provide both library and CLI functionality, classify as `schema:Tool` with `schema:applicationSubCategory: Command-line tool`.
67
+
68
+ **R17.** Use real, resolvable repository URLs (e.g., `https://github.com/iolanta-tech/python-yaml-ld`) instead of placeholder URLs.
69
+
70
+ **R18.** Include comprehensive metadata: name, description, author, license, programming language, version, repository links, and application category.
71
+
72
+ **R19.** Use standard vocabularies: schema.org, RDFS, RDF, DCTerms, FOAF, and CodeMeta when appropriate.
73
+
74
+ **R20.** Validate Linked Data using the Iolanta MCP `render_uri` tool with `as_format: labeled-triple-set` to check for URL-as-literal issues and proper IRI handling.
75
+
76
+ **R21.** Do not add `rdfs:label` to external URIs that are expected to return Linked Data. If a URI does not exist or cannot be resolved, do not mask this fact by adding labels. Instead, use a different, existing URI or document the issue with a comment.
77
+
78
+ **R22.** Define URI coercion in the context using `"@type": "@id"` rather than using `$id` wrappers in the document body. This keeps the document body clean and readable while ensuring proper URI handling.
79
+
80
+ **R23.** When defining local shortcuts for URIs in the context, use dashed-case (e.g., `appears-in`, `named-after`) instead of camelCase (e.g., `appearsIn`, `namedAfter`). This improves readability and follows common YAML conventions.
81
+
82
+ **R24.** Do not rely upon `owl:sameAs` or `schema:sameAs` to express identity relationships. This necessitates OWL inference at the side of the reader, which is performance-taxing and tends to create conflicts. Instead, use direct URIs for entities without relying on sameAs statements for identity.
83
+
@@ -122,6 +122,3 @@ class Mermaid(Facet[str]):
122
122
  direct_children = self.construct_mermaid_for_graph(self.this)
123
123
  subgraphs = self.construct_mermaid_subgraphs()
124
124
  return str(Diagram(children=[*direct_children, *subgraphs]))
125
-
126
-
127
-
@@ -0,0 +1,25 @@
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
+
6
+ $: rdfs:label
7
+ →:
8
+ "@type": "@id"
9
+ "@id": iolanta:outputs
10
+
11
+ ↦:
12
+ "@id": iolanta:matches
13
+ "@type": iolanta:SPARQLText
14
+
15
+ $id: pkg:pypi/iolanta#mermaid-graph
16
+ $: Mermaid Graph
17
+
18
+ →:
19
+ $id: https://iolanta.tech/datatypes/mermaid
20
+ $: Mermaid
21
+ $type: iolanta:OutputDatatype
22
+
23
+ ↦:
24
+ - ASK WHERE { GRAPH $this { ?s ?p ?o } }
25
+ - ASK WHERE { $this iolanta:has-sub-graph ?subgraph }
@@ -60,7 +60,9 @@ class MermaidLiteral(Documented, BaseModel, arbitrary_types_allowed=True, frozen
60
60
 
61
61
  @property
62
62
  def id(self) -> str:
63
- value_hash = hashlib.md5(str(self.literal.value).encode()).hexdigest()
63
+ # Use the lexical form of the literal, not rdflib's .value (which may be empty for typed literals),
64
+ # to ensure different texts get distinct node IDs in Mermaid.
65
+ value_hash = hashlib.md5(str(self.literal).encode()).hexdigest()
64
66
  return f'Literal-{value_hash}'
65
67
 
66
68
 
@@ -142,7 +144,7 @@ class Diagram(Documented, BaseModel):
142
144
  """
143
145
  graph {self.direction}
144
146
  {self.formatted_body}
145
- classDef predicate fill:none,stroke:none,stroke-width:0px;
147
+ classDef predicate fill:transparent,stroke:transparent,stroke-width:0px;
146
148
  """
147
149
 
148
150
  children: list[MermaidScalar | MermaidSubgraph]
@@ -0,0 +1,3 @@
1
+ ASK WHERE {
2
+ GRAPH $this { ?s ?p ?o }
3
+ }
@@ -159,7 +159,7 @@ def _extract_from_mapping( # noqa: WPS213
159
159
 
160
160
  case unknown_name:
161
161
  formatted_keys = ', '.join(algebra.keys())
162
- loguru.logger.error(
162
+ loguru.logger.info(
163
163
  'Unknown SPARQL expression '
164
164
  f'{unknown_name}({formatted_keys}): {algebra}',
165
165
  )
@@ -512,7 +512,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
512
512
  try:
513
513
  self.load(url)
514
514
  except Exception as err:
515
- self.logger.error(f'Failed to load {url}: {err}', url, err)
515
+ self.logger.exception(f'Failed to load {url}: {err}', url, err)
516
516
 
517
517
  NanopubQueryPlugin(graph=self.graph)(query, bindings=initBindings)
518
518
 
File without changes
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "iolanta"
3
- version = "2.1.4"
3
+ version = "2.1.6"
4
4
  description = "Semantic Web browser"
5
5
  authors = ["Anatoly Scherbakov <altaisoft@gmail.com>"]
6
6
  license = "MIT"
@@ -23,16 +23,20 @@ rich = ">=13.3.1"
23
23
  textual = ">=0.83.0"
24
24
  yarl = ">=1.9.4"
25
25
  boltons = ">=24.0.0"
26
- yaml-ld = ">=1.1.12"
26
+ yaml-ld = "^1.1.14"
27
27
  reasonable = ">=0.2.6"
28
28
  oxrdflib = ">=0.4.0"
29
29
  loguru = ">=0.7.3"
30
30
  diskcache = ">=5.6.3"
31
31
  watchfiles = ">=1.0.4"
32
32
  packageurl-python = ">=0.17.5"
33
+ fastmcp = "^2.12.4"
34
+ nanopub = "^2.1.0"
33
35
 
34
36
  [tool.poetry.scripts]
35
37
  iolanta = "iolanta.cli:app"
38
+ iolanta-mcp = "iolanta.mcp.cli:app"
39
+
36
40
  sparqlspace = "iolanta.sparqlspace.cli:app"
37
41
 
38
42
  [tool.poetry.plugins."iolanta.plugins"]
@@ -48,6 +52,7 @@ textual-graph-triples = "iolanta.facets.textual_graph_triples:GraphTriplesFacet"
48
52
  qname = "iolanta.facets.qname:QNameFacet"
49
53
  textual-property-pairs = "iolanta.facets.textual_property_pairs_table:TextualPropertyPairsTableFacet"
50
54
  textual-class = "iolanta.facets.textual_class:Class"
55
+ textual-graphs = "iolanta.facets.textual_graphs:Graphs"
51
56
  textual-no-facet-found = "iolanta.facets.textual_no_facet_found:TextualNoFacetFound"
52
57
  textual-nanopublication = "iolanta.facets.textual_nanopublication:NanopublicationFacet"
53
58
  textual-provenance = "iolanta.facets.textual_provenance:TextualProvenanceFacet"
@@ -1,42 +0,0 @@
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#mermaid-graph
36
- $: Mermaid Graph
37
- →:
38
- $id: https://iolanta.tech/datatypes/mermaid
39
- $: Mermaid
40
- ↦:
41
- - ASK WHERE { GRAPH $this { ?s ?p ?o } }
42
- - ASK WHERE { $this iolanta:has-sub-graph ?subgraph }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes