iolanta 2.1.19__tar.gz → 2.1.20__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 (175) hide show
  1. {iolanta-2.1.19 → iolanta-2.1.20}/PKG-INFO +1 -1
  2. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/main.py +3 -0
  3. iolanta-2.1.20/iolanta/kglint/__init__.py +5 -0
  4. iolanta-2.1.20/iolanta/kglint/facet.py +183 -0
  5. iolanta-2.1.20/iolanta/kglint/kglint.yamlld +23 -0
  6. iolanta-2.1.20/iolanta/kglint/models.py +80 -0
  7. iolanta-2.1.20/iolanta/labeled_triple_set/sparql/triples.sparql +5 -0
  8. {iolanta-2.1.19 → iolanta-2.1.20}/pyproject.toml +2 -1
  9. {iolanta-2.1.19 → iolanta-2.1.20}/README.md +0 -0
  10. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/__init__.py +0 -0
  11. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/base_plugin.py +0 -0
  12. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/__init__.py +0 -0
  13. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/formatters/__init__.py +0 -0
  14. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/formatters/choose.py +0 -0
  15. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/formatters/csv.py +0 -0
  16. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/formatters/json.py +0 -0
  17. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/formatters/pretty.py +0 -0
  18. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/models.py +0 -0
  19. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/cli/pretty_print.py +0 -0
  20. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/context.py +0 -0
  21. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/conversions.py +0 -0
  22. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/data/context.yaml +0 -0
  23. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/data/graph-triples.yamlld +0 -0
  24. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/data/iolanta.yaml +0 -0
  25. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/data/textual-browser.yaml +0 -0
  26. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/declension/__init__.py +0 -0
  27. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/declension/data/declension.yamlld +0 -0
  28. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/declension/facet.py +0 -0
  29. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/declension/sparql/declension.sparql +0 -0
  30. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/ensure_is_context.py +0 -0
  31. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/entry_points.py +0 -0
  32. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/errors.py +0 -0
  33. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/__init__.py +0 -0
  34. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/cli/__init__.py +0 -0
  35. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/cli/base.py +0 -0
  36. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/cli/default.py +0 -0
  37. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/cli/record.py +0 -0
  38. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/cli/sparql/link.sparql +0 -0
  39. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/cli/sparql/record.sparql +0 -0
  40. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/errors.py +0 -0
  41. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/facet.py +0 -0
  42. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/foaf_person_title/__init__.py +0 -0
  43. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/foaf_person_title/facet.py +0 -0
  44. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
  45. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/generic/__init__.py +0 -0
  46. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/generic/bool_literal.py +0 -0
  47. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/generic/date_literal.py +0 -0
  48. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/generic/default.py +0 -0
  49. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/generic/sparql/default.sparql +0 -0
  50. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/html/__init__.py +0 -0
  51. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/html/base.py +0 -0
  52. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/html/code_literal.py +0 -0
  53. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/html/default.py +0 -0
  54. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/icon.py +0 -0
  55. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/locator/sparql/get-query-to-facet.sparql +0 -0
  56. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/locator.py +0 -0
  57. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/__init__.py +0 -0
  58. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/facet.py +0 -0
  59. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/inference/blocks.sparql +0 -0
  60. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/inference/has-task-default-type.sparql +0 -0
  61. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/inference/task.sparql +0 -0
  62. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/inference/unblocked.sparql +0 -0
  63. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/mermaid_roadmap.yamlld +0 -0
  64. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/sparql/edges.sparql +0 -0
  65. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mermaid_roadmap/sparql/nodes.sparql +0 -0
  66. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mkdocs_material_insiders_markdown/__init__.py +0 -0
  67. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mkdocs_material_insiders_markdown/data/mkdocs_material_insiders_markdown.yamlld +0 -0
  68. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mkdocs_material_insiders_markdown/facet.py +0 -0
  69. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/mkdocs_material_insiders_markdown/templates/datatype.jinja2.md +0 -0
  70. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/page_title.py +0 -0
  71. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/qname.py +0 -0
  72. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/__init__.py +0 -0
  73. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/ask_result_csv.py +0 -0
  74. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/ask_result_json.py +0 -0
  75. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/ask_result_table.py +0 -0
  76. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/construct_result_csv.py +0 -0
  77. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/construct_result_json.py +0 -0
  78. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/construct_result_table.py +0 -0
  79. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/data/query_result.yamlld +0 -0
  80. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/select_result_csv.py +0 -0
  81. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/select_result_json.py +0 -0
  82. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/query/select_result_table.py +0 -0
  83. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/__init__.py +0 -0
  84. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/app.py +0 -0
  85. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/facet.py +0 -0
  86. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/history.py +0 -0
  87. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/home.py +0 -0
  88. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/location.py +0 -0
  89. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/models.py +0 -0
  90. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/page.py +0 -0
  91. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_browser/page_switcher.py +0 -0
  92. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_class/__init__.py +0 -0
  93. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_class/facets.py +0 -0
  94. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_class/sparql/instances.sparql +0 -0
  95. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_class/textual-class.yamlld +0 -0
  96. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/__init__.py +0 -0
  97. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/facets.py +0 -0
  98. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
  99. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
  100. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
  101. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
  102. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
  103. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/templates/default.md +0 -0
  104. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/textual-inverse-properties.yamlld +0 -0
  105. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
  106. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_default/widgets.py +0 -0
  107. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graph/__init__.py +0 -0
  108. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graph/facets.py +0 -0
  109. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
  110. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graph_triples.py +0 -0
  111. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graphs/__init__.py +0 -0
  112. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graphs/data/textual_graphs.yamlld +0 -0
  113. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graphs/facets.py +0 -0
  114. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_graphs/sparql/graphs.sparql +0 -0
  115. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_link/__init__.py +0 -0
  116. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_link/facet.py +0 -0
  117. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
  118. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_nanopublication/facet.py +0 -0
  119. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_nanopublication/models.py +0 -0
  120. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
  121. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
  122. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
  123. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_no_facet_found.py +0 -0
  124. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_ontology/__init__.py +0 -0
  125. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_ontology/facets.py +0 -0
  126. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
  127. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
  128. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_property_pairs_table.py +0 -0
  129. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_provenance/__init__.py +0 -0
  130. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_provenance/facets.py +0 -0
  131. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
  132. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
  133. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/title/__init__.py +0 -0
  134. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/title/facets.py +0 -0
  135. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/title/sparql/title.sparql +0 -0
  136. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/title/title.yamlld +0 -0
  137. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
  138. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/wikibase_statement_title/facets.py +0 -0
  139. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
  140. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/iolanta.py +0 -0
  141. {iolanta-2.1.19/iolanta/labeled_triple_set → iolanta-2.1.20/iolanta/kglint}/sparql/triples.sparql +0 -0
  142. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/labeled_triple_set/__init__.py +0 -0
  143. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +0 -0
  144. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/labeled_triple_set/labeled_triple_set.py +0 -0
  145. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mcp/__init__.py +0 -0
  146. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mcp/cli.py +0 -0
  147. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/__init__.py +0 -0
  148. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/facet.py +0 -0
  149. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/mermaid.yamlld +0 -0
  150. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/models.py +0 -0
  151. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/sparql/ask-has-triples.sparql +0 -0
  152. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/sparql/graph.sparql +0 -0
  153. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/mermaid/sparql/subgraphs.sparql +0 -0
  154. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/models.py +0 -0
  155. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/namespaces.py +0 -0
  156. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/node_to_qname.py +0 -0
  157. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/parse_quads.py +0 -0
  158. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/plugin.py +0 -0
  159. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/query_result.py +0 -0
  160. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/reformat_blank_nodes.py +0 -0
  161. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/resolvers/__init__.py +0 -0
  162. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/resolvers/base.py +0 -0
  163. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/resolvers/dispatch.py +0 -0
  164. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/resolvers/pypi.py +0 -0
  165. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/resolvers/python_import.py +0 -0
  166. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/__init__.py +0 -0
  167. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/cli.py +0 -0
  168. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/inference/wikidata-prop-label.sparql +0 -0
  169. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/inference/wikidata-statement-label.sparql +0 -0
  170. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/processor.py +0 -0
  171. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/redirects.py +0 -0
  172. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/sparqlspace/sparqlspace.py +0 -0
  173. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/widgets/__init__.py +0 -0
  174. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/widgets/description.py +0 -0
  175. {iolanta-2.1.19 → iolanta-2.1.20}/iolanta/widgets/mixin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iolanta
3
- Version: 2.1.19
3
+ Version: 2.1.20
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -54,6 +54,9 @@ def decode_datatype(datatype: str) -> URIRef:
54
54
  if datatype.startswith("http"):
55
55
  return URIRef(datatype)
56
56
 
57
+ if "/" in datatype:
58
+ return URIRef(f"https://iolanta.tech/{datatype}")
59
+
57
60
  return URIRef(f"https://iolanta.tech/datatypes/{datatype}")
58
61
 
59
62
 
@@ -0,0 +1,5 @@
1
+ """Knowledge Graph linter facet."""
2
+
3
+ from iolanta.kglint.facet import KGLint
4
+
5
+ __all__ = ['KGLint']
@@ -0,0 +1,183 @@
1
+ """KGLint facet: deterministic KG linter with assertions and labels."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ import funcy
7
+ from rdflib import BNode, Literal, Node, URIRef
8
+
9
+ from iolanta import Facet
10
+ from iolanta.kglint.models import (
11
+ Assertion,
12
+ AssertionCode,
13
+ LabelEntry,
14
+ NodeBlank,
15
+ NodeLiteral,
16
+ NodeObject,
17
+ NodeURI,
18
+ Report,
19
+ TripleRef,
20
+ )
21
+ from iolanta.namespaces import DATATYPES
22
+
23
+ QNAME_RE = re.compile(r'^\S+:\S+$')
24
+
25
+
26
+ def _serialize(node: Node) -> str:
27
+ """Serialize node to stable string (N-Triples style)."""
28
+ return node.n3()
29
+
30
+
31
+ def _looks_like_qname(label: str) -> bool:
32
+ """True if label has the form prefix:localName (QName-shaped)."""
33
+ return bool(QNAME_RE.match(label))
34
+
35
+
36
+ class KGLint(Facet[str]):
37
+ """Deterministic Knowledge Graph linter: assertions + URI/blank labels."""
38
+
39
+ META = Path(__file__).parent / 'kglint.yamlld'
40
+
41
+ def _render_label(self, node: Node) -> str:
42
+ """Human-readable label for a node (title facet)."""
43
+ return str(self.render(node, as_datatype=DATATYPES.title))
44
+
45
+ @funcy.cached_property
46
+ @funcy.post_processing(list)
47
+ def _triples(self):
48
+ """All triples in the graph (cached)."""
49
+ for row in self.stored_query('triples.sparql', graph=self.this):
50
+ yield (row['subject'], row['predicate'], row['object'])
51
+
52
+ @funcy.cached_property
53
+ @funcy.post_processing(set)
54
+ def _nodes(self):
55
+ """URIRef and BNode terms that appear in triples (cached)."""
56
+ for s, p, o in self._triples:
57
+ for term in (s, p, o):
58
+ if isinstance(term, (URIRef, BNode)):
59
+ yield term
60
+
61
+ @funcy.cached_property
62
+ @funcy.post_processing(dict)
63
+ def label_by_node(self):
64
+ """Rendered label per node (cached)."""
65
+ for node in self._nodes:
66
+ yield node, self._render_label(node)
67
+
68
+ @funcy.cached_property
69
+ @funcy.post_processing(funcy.group_values)
70
+ def _triples_by_node(self):
71
+ """Map each node to list of triples it participates in (cached)."""
72
+ nodes = self._nodes
73
+ for s, p, o in self._triples:
74
+ for term in (s, p, o):
75
+ if term in nodes:
76
+ yield term, (s, p, o)
77
+
78
+ def _node_to_object(self, node: Node) -> NodeObject:
79
+ """Convert RDF node to JSON node object (literal, uri, or blank)."""
80
+ match node:
81
+ case Literal():
82
+ return NodeLiteral(
83
+ value=str(node),
84
+ datatype=str(node.datatype) if node.datatype else None,
85
+ language=node.language or None,
86
+ )
87
+ case URIRef():
88
+ return NodeURI(value=str(node), label=self.label_by_node[node])
89
+ case BNode():
90
+ return NodeBlank(value=node.n3(), label=self.label_by_node[node])
91
+
92
+ def _label_entries(self):
93
+ """Yield label entries (node + triples) in deterministic order."""
94
+ label_by_node = self.label_by_node
95
+ triples_by_node = self._triples_by_node
96
+ for node in sorted(self._nodes, key=_serialize):
97
+ triples_refs = [
98
+ TripleRef(
99
+ s=self._node_to_object(s),
100
+ p=self._node_to_object(p),
101
+ o=self._node_to_object(o),
102
+ )
103
+ for s, p, o in sorted(
104
+ triples_by_node[node],
105
+ key=lambda t: (_serialize(t[0]), _serialize(t[1]), _serialize(t[2])),
106
+ )
107
+ ]
108
+ if isinstance(node, URIRef):
109
+ entry_node = NodeURI(value=str(node), label=label_by_node[node])
110
+ else:
111
+ entry_node = NodeBlank(value=node.n3(), label=label_by_node[node])
112
+ yield LabelEntry(node=entry_node, triples=triples_refs)
113
+
114
+ def _assertions(self):
115
+ """Yield all assertions (literal/uri/blank checks)."""
116
+ label_by_node = self.label_by_node
117
+ for s, p, o in self._triples:
118
+ if isinstance(o, Literal):
119
+ label = self._render_label(o)
120
+ if label.startswith('http'):
121
+ yield Assertion(
122
+ severity='warning',
123
+ code=AssertionCode.LITERAL_LOOKS_LIKE_URI,
124
+ target=TripleRef(
125
+ s=self._node_to_object(s),
126
+ p=self._node_to_object(p),
127
+ o=self._node_to_object(o),
128
+ ),
129
+ message=(
130
+ 'This RDF literal seems to be actually a URL. '
131
+ 'Good chance is that it should not be a literal.'
132
+ ),
133
+ )
134
+ elif _looks_like_qname(label):
135
+ yield Assertion(
136
+ severity='warning',
137
+ code=AssertionCode.LITERAL_LOOKS_LIKE_QNAME,
138
+ target=TripleRef(
139
+ s=self._node_to_object(s),
140
+ p=self._node_to_object(p),
141
+ o=self._node_to_object(o),
142
+ ),
143
+ message=(
144
+ 'This RDF literal seems to be actually a QName '
145
+ '(prefixed URI). Good chance is that it should '
146
+ 'not be a literal.'
147
+ ),
148
+ )
149
+ for node in self._nodes:
150
+ label = label_by_node[node]
151
+ if isinstance(node, URIRef):
152
+ if str(node) == label:
153
+ yield Assertion(
154
+ severity='error',
155
+ code=AssertionCode.URI_LABEL_IDENTICAL,
156
+ target=NodeURI(value=str(node), label=label),
157
+ message=(
158
+ 'For this URI, the label is the same as the URI. '
159
+ 'We were unable to render that URI.'
160
+ ),
161
+ )
162
+ elif isinstance(node, BNode):
163
+ if str(node) == label:
164
+ yield Assertion(
165
+ severity='warning',
166
+ code=AssertionCode.BLANK_LABEL_IDENTICAL,
167
+ target=NodeBlank(value=node.n3(), label=label),
168
+ message=(
169
+ 'For this blank node, the label is the same as '
170
+ 'the blank node. We were unable to render that '
171
+ 'blank node.'
172
+ ),
173
+ )
174
+
175
+ def show(self) -> str:
176
+ """Build report (assertions + labels) and return JSON string."""
177
+ labels_list = list(self._label_entries())
178
+ assertions_list = sorted(
179
+ self._assertions(),
180
+ key=lambda a: (a.severity, a.code, a.target.model_dump_json()),
181
+ )
182
+ report = Report(assertions=assertions_list, labels=labels_list)
183
+ return report.model_dump_json(indent=2, exclude_none=True)
@@ -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
+
6
+ iolanta:outputs:
7
+ "@type": "@id"
8
+
9
+ $: rdfs:label
10
+ →:
11
+ "@type": "@id"
12
+ "@id": iolanta:outputs
13
+
14
+ ↦: iolanta:matches
15
+
16
+ $id: pkg:pypi/iolanta#kglint
17
+ $: KGLint
18
+ →:
19
+ $id: https://iolanta.tech/kglint/json
20
+ $: KGLint JSON
21
+ ↦:
22
+ - ASK WHERE { GRAPH $this { ?s ?p ?o } }
23
+ - ASK WHERE { $this <https://iolanta.tech/has-sub-graph> ?subgraph }
@@ -0,0 +1,80 @@
1
+ """Pydantic models for kglint report output."""
2
+
3
+ from enum import StrEnum
4
+ from typing import Annotated, Literal as LiteralType
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class AssertionCode(StrEnum):
10
+ """Deterministic assertion codes emitted by the linter."""
11
+
12
+ LITERAL_LOOKS_LIKE_URI = 'literal-looks-like-uri'
13
+ LITERAL_LOOKS_LIKE_QNAME = 'literal-looks-like-qname'
14
+ URI_LABEL_IDENTICAL = 'uri-label-identical'
15
+ BLANK_LABEL_IDENTICAL = 'blank-label-identical'
16
+
17
+
18
+ class NodeLiteral(BaseModel):
19
+ """Literal term as object: value, datatype, language."""
20
+
21
+ type: LiteralType['literal'] = 'literal'
22
+ value: str
23
+ datatype: str | None = None
24
+ language: str | None = None
25
+
26
+
27
+ class NodeURI(BaseModel):
28
+ """URI term as object: value and rendered label."""
29
+
30
+ type: LiteralType['uri'] = 'uri'
31
+ value: str
32
+ label: str
33
+
34
+
35
+ class NodeBlank(BaseModel):
36
+ """Blank node term as object: value and rendered label."""
37
+
38
+ type: LiteralType['blank'] = 'blank'
39
+ value: str
40
+ label: str
41
+
42
+
43
+ NodeObject = Annotated[
44
+ NodeLiteral | NodeURI | NodeBlank,
45
+ Field(discriminator='type'),
46
+ ]
47
+
48
+
49
+ class TripleRef(BaseModel):
50
+ """A triple (s, p, o as node objects). Used in assertions and label entries."""
51
+
52
+ type: LiteralType['triple'] = 'triple'
53
+ s: NodeObject
54
+ p: NodeObject
55
+ o: NodeObject
56
+
57
+
58
+ class Assertion(BaseModel):
59
+ """Single validation assertion (error or warning)."""
60
+
61
+ severity: LiteralType['error', 'warning']
62
+ code: AssertionCode
63
+ target: NodeURI | NodeBlank | TripleRef
64
+ message: str
65
+
66
+
67
+ class LabelEntry(BaseModel):
68
+ """URI/blank node (as object with type, value, label) and participating triples."""
69
+
70
+ node: NodeURI | NodeBlank
71
+ triples: list[TripleRef] = Field(default_factory=list)
72
+
73
+
74
+
75
+
76
+ class Report(BaseModel):
77
+ """KGLint report: assertions and labels."""
78
+
79
+ assertions: list[Assertion] = Field(default_factory=list)
80
+ labels: list[LabelEntry] = Field(default_factory=list)
@@ -0,0 +1,5 @@
1
+ SELECT * WHERE {
2
+ GRAPH $graph {
3
+ ?subject ?predicate ?object .
4
+ }
5
+ }
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "iolanta"
3
- version = "2.1.19"
3
+ version = "2.1.20"
4
4
  description = "Semantic Web browser"
5
5
  authors = ["Anatoly Scherbakov <altaisoft@gmail.com>"]
6
6
  license = "MIT"
@@ -67,6 +67,7 @@ mermaid-graph = "iolanta.mermaid.facet:Mermaid"
67
67
  mermaid-roadmap = "iolanta.facets.mermaid_roadmap.facet:MermaidRoadmap"
68
68
  rich-declension-table = "iolanta.declension.facet:RichDeclensionTable"
69
69
  labeled-triple-set = "iolanta.labeled_triple_set.labeled_triple_set:LabeledTripleSet"
70
+ kglint = "iolanta.kglint.facet:KGLint"
70
71
  select-result-table-facet = "iolanta.facets.query.select_result_table:SelectResultTableFacet"
71
72
  select-result-json-facet = "iolanta.facets.query.select_result_json:SelectResultJsonFacet"
72
73
  select-result-csv-facet = "iolanta.facets.query.select_result_csv:SelectResultCsvFacet"
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