iolanta 2.0.5__tar.gz → 2.0.7__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 (138) hide show
  1. {iolanta-2.0.5 → iolanta-2.0.7}/PKG-INFO +2 -2
  2. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/__init__.py +0 -1
  3. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/main.py +2 -2
  4. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/data/textual-browser.yaml +6 -0
  5. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/facet.py +0 -7
  6. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/locator.py +19 -0
  7. iolanta-2.0.7/iolanta/facets/textual_no_facet_found.py +100 -0
  8. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/iolanta.py +3 -101
  9. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/models.py +25 -6
  10. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/namespaces.py +2 -0
  11. iolanta-2.0.7/iolanta/parse_quads.py +171 -0
  12. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/sparqlspace/processor.py +9 -24
  13. iolanta-2.0.7/iolanta/widgets/description.py +31 -0
  14. {iolanta-2.0.5 → iolanta-2.0.7}/pyproject.toml +2 -2
  15. iolanta-2.0.5/iolanta/loaders/__init__.py +0 -2
  16. iolanta-2.0.5/iolanta/loaders/base.py +0 -124
  17. iolanta-2.0.5/iolanta/loaders/data_type_choice.py +0 -66
  18. iolanta-2.0.5/iolanta/loaders/dict_loader.py +0 -57
  19. iolanta-2.0.5/iolanta/loaders/errors.py +0 -29
  20. iolanta-2.0.5/iolanta/loaders/http.py +0 -127
  21. iolanta-2.0.5/iolanta/loaders/local_directory.py +0 -148
  22. iolanta-2.0.5/iolanta/loaders/local_file.py +0 -107
  23. iolanta-2.0.5/iolanta/loaders/scheme_choice.py +0 -72
  24. iolanta-2.0.5/iolanta/parse_quads.py +0 -104
  25. iolanta-2.0.5/iolanta/parsers/base.py +0 -41
  26. iolanta-2.0.5/iolanta/parsers/dict_parser.py +0 -171
  27. iolanta-2.0.5/iolanta/parsers/errors.py +0 -35
  28. iolanta-2.0.5/iolanta/parsers/json.py +0 -35
  29. iolanta-2.0.5/iolanta/parsers/markdown.py +0 -58
  30. iolanta-2.0.5/iolanta/parsers/yaml.py +0 -46
  31. iolanta-2.0.5/iolanta/shortcuts.py +0 -63
  32. iolanta-2.0.5/iolanta/widgets/__init__.py +0 -0
  33. {iolanta-2.0.5 → iolanta-2.0.7}/README.md +0 -0
  34. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/base_plugin.py +0 -0
  35. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/__init__.py +0 -0
  36. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/formatters/__init__.py +0 -0
  37. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/formatters/choose.py +0 -0
  38. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/formatters/csv.py +0 -0
  39. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/formatters/json.py +0 -0
  40. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/formatters/pretty.py +0 -0
  41. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/models.py +0 -0
  42. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/cli/pretty_print.py +0 -0
  43. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/context.py +0 -0
  44. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/conversions.py +0 -0
  45. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/data/cli.yaml +0 -0
  46. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/data/context.yaml +0 -0
  47. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/data/graph-triples.yamlld +0 -0
  48. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/data/html.yaml +0 -0
  49. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/data/iolanta.yaml +0 -0
  50. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/ensure_is_context.py +0 -0
  51. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/entry_points.py +0 -0
  52. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/errors.py +0 -0
  53. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/__init__.py +0 -0
  54. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/cli/__init__.py +0 -0
  55. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/cli/base.py +0 -0
  56. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/cli/default.py +0 -0
  57. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/cli/record.py +0 -0
  58. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/cli/sparql/link.sparql +0 -0
  59. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/cli/sparql/record.sparql +0 -0
  60. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/errors.py +0 -0
  61. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/foaf_person_title/__init__.py +0 -0
  62. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/foaf_person_title/facet.py +0 -0
  63. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/foaf_person_title/sparql/names.sparql +0 -0
  64. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/generic/__init__.py +0 -0
  65. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/generic/bool_literal.py +0 -0
  66. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/generic/date_literal.py +0 -0
  67. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/generic/default.py +0 -0
  68. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/generic/sparql/default.sparql +0 -0
  69. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/html/__init__.py +0 -0
  70. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/html/base.py +0 -0
  71. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/html/code_literal.py +0 -0
  72. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/html/default.py +0 -0
  73. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/icon.py +0 -0
  74. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/page_title.py +0 -0
  75. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/qname.py +0 -0
  76. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/__init__.py +0 -0
  77. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/app.py +0 -0
  78. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/facet.py +0 -0
  79. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/history.py +0 -0
  80. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/home.py +0 -0
  81. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/location.py +0 -0
  82. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/models.py +0 -0
  83. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/page.py +0 -0
  84. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_browser/page_switcher.py +0 -0
  85. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_class/__init__.py +0 -0
  86. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_class/facets.py +0 -0
  87. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_class/sparql/instances.sparql +0 -0
  88. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/__init__.py +0 -0
  89. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/facets.py +0 -0
  90. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/sparql/inverse-properties.sparql +0 -0
  91. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/sparql/label.sparql +0 -0
  92. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +0 -0
  93. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/sparql/properties.sparql +0 -0
  94. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/tcss/default.tcss +0 -0
  95. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/templates/default.md +0 -0
  96. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/triple_uri_ref.py +0 -0
  97. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_default/widgets.py +0 -0
  98. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_graph/__init__.py +0 -0
  99. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_graph/facets.py +0 -0
  100. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_graph/sparql/triples.sparql +0 -0
  101. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_graph_triples.py +0 -0
  102. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_link/__init__.py +0 -0
  103. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_link/facet.py +0 -0
  104. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_nanopublication/__init__.py +0 -0
  105. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_nanopublication/facet.py +0 -0
  106. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_nanopublication/models.py +0 -0
  107. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_nanopublication/nanopublication_widget.py +0 -0
  108. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_nanopublication/term_list_widget.py +0 -0
  109. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_nanopublication/term_widget.py +0 -0
  110. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_ontology/__init__.py +0 -0
  111. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_ontology/facets.py +0 -0
  112. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_ontology/sparql/terms.sparql +0 -0
  113. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +0 -0
  114. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_property_pairs_table.py +0 -0
  115. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_provenance/__init__.py +0 -0
  116. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_provenance/facets.py +0 -0
  117. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_provenance/sparql/graphs.sparql +0 -0
  118. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/textual_provenance/sparql/triples.sparql +0 -0
  119. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/title/__init__.py +0 -0
  120. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/title/facets.py +0 -0
  121. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/title/sparql/title.sparql +0 -0
  122. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/wikibase_statement_title/__init__.py +0 -0
  123. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/wikibase_statement_title/facets.py +0 -0
  124. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +0 -0
  125. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/node_to_qname.py +0 -0
  126. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/plugin.py +0 -0
  127. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/query_result.py +0 -0
  128. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/reformat_blank_nodes.py +0 -0
  129. {iolanta-2.0.5/iolanta/parsers → iolanta-2.0.7/iolanta/resolvers}/__init__.py +0 -0
  130. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/resolvers/base.py +0 -0
  131. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/resolvers/python_import.py +0 -0
  132. {iolanta-2.0.5/iolanta/resolvers → iolanta-2.0.7/iolanta/sparqlspace}/__init__.py +0 -0
  133. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/sparqlspace/cli.py +0 -0
  134. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/sparqlspace/inference/wikibase-claim.sparql +0 -0
  135. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/sparqlspace/inference/wikibase-statement-property.sparql +0 -0
  136. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/sparqlspace/sparqlspace.py +0 -0
  137. {iolanta-2.0.5/iolanta/sparqlspace → iolanta-2.0.7/iolanta/widgets}/__init__.py +0 -0
  138. {iolanta-2.0.5 → iolanta-2.0.7}/iolanta/widgets/mixin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.5
3
+ Version: 2.0.7
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -32,7 +32,7 @@ Requires-Dist: rich (>=13.3.1)
32
32
  Requires-Dist: textual (>=0.83.0)
33
33
  Requires-Dist: typer (>=0.9.0)
34
34
  Requires-Dist: watchfiles (>=1.0.4,<2.0.0)
35
- Requires-Dist: yaml-ld (>=1.1.7)
35
+ Requires-Dist: yaml-ld (>=1.1.9)
36
36
  Requires-Dist: yarl (>=1.9.4)
37
37
  Description-Content-Type: text/markdown
38
38
 
@@ -1,4 +1,3 @@
1
1
  from iolanta.base_plugin import IolantaBase
2
2
  from iolanta.facets import Facet
3
3
  from iolanta.plugin import Plugin
4
- from iolanta.shortcuts import as_document
@@ -94,7 +94,7 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
94
94
  )
95
95
 
96
96
  node_url = URL(url)
97
- if node_url.scheme:
97
+ if node_url.scheme and node_url.scheme != 'file':
98
98
  node = URIRef(url)
99
99
 
100
100
  iolanta: Iolanta = Iolanta(
@@ -102,7 +102,7 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
102
102
  logger=logger,
103
103
  )
104
104
  else:
105
- path = Path(url).absolute()
105
+ path = Path(node_url.path).absolute()
106
106
  node = URIRef(f'file://{path}')
107
107
  iolanta: Iolanta = Iolanta(
108
108
  language=Literal(language),
@@ -13,6 +13,9 @@
13
13
  iolanta:outputs:
14
14
  "@type": "@id"
15
15
 
16
+ iolanta:when-no-facet-found:
17
+ "@type": "@id"
18
+
16
19
  $: rdfs:label
17
20
  →:
18
21
  "@type": "@id"
@@ -125,3 +128,6 @@
125
128
  ↦:
126
129
  - ASK WHERE { ?instance a $this }
127
130
  - ASK WHERE { $this rdfs:subClassOf ?superclass }
131
+
132
+ - $id: https://iolanta.tech/cli/textual
133
+ iolanta:when-no-facet-found: python://iolanta.facets.textual_no_facet_found.TextualNoFacetFound
@@ -85,13 +85,6 @@ class Facet(Generic[FacetOutput]):
85
85
  """Preferred language for Iolanta output."""
86
86
  return self.iolanta.language
87
87
 
88
- def find_triple(
89
- self,
90
- triple: TripleTemplate,
91
- ) -> Triple | None:
92
- """Lightweight procedure to find a triple by template."""
93
- return self.iolanta.find_triple(triple_template=triple)
94
-
95
88
  @cached_property
96
89
  def logger(self):
97
90
  """Logger."""
@@ -240,6 +240,22 @@ class FacetFinder: # noqa: WPS214
240
240
  for row in rows
241
241
  ]
242
242
 
243
+ def by_facet_not_found(self) -> Iterable[FoundRow]:
244
+ """What facet to show if no facets are found?"""
245
+ rows = self.iolanta.query( # noqa: WPS462
246
+ """
247
+ SELECT ?facet ?output_datatype WHERE {
248
+ $output_datatype iolanta:when-no-facet-found ?facet .
249
+ }
250
+ """,
251
+ output_datatype=self.as_datatype,
252
+ )
253
+
254
+ return [
255
+ FoundRow(facet=row['facet'], output_datatype=row['output_datatype'])
256
+ for row in rows
257
+ ]
258
+
243
259
  def retrieve_facets_preference_ordering(self) -> set[tuple[URIRef, URIRef]]:
244
260
  """
245
261
  Construct partial ordering on the set of facets.
@@ -263,6 +279,9 @@ class FacetFinder: # noqa: WPS214
263
279
  """Return all suitable facets."""
264
280
  rows = list(self._found_facets())
265
281
 
282
+ if not rows:
283
+ rows = self.by_facet_not_found()
284
+
266
285
  if len(rows) == 1:
267
286
  # Nothing to order.
268
287
  return rows
@@ -0,0 +1,100 @@
1
+ from pathlib import Path
2
+
3
+ import funcy
4
+ from rich.markdown import Markdown
5
+ from textual.containers import Vertical
6
+ from yarl import URL
7
+
8
+ from iolanta import Facet
9
+ from iolanta.facets.page_title import PageTitle
10
+ from iolanta.widgets.description import Description
11
+
12
+ TEXT = """
13
+ **😕 Iolanta is unable to visualize this resource**
14
+
15
+ * The URI might be incorrect;
16
+ * Or, no edges might exist which involve it;
17
+ * Or maybe Iolanta does not know of such edges.
18
+ {content}
19
+ {subgraphs}
20
+ **What can you do?**
21
+
22
+ * If you feel this might indicate a bug 🐛, please do let us know at GitHub
23
+ issues: https://github.com/iolanta.tech/iolanta/issues
24
+ """
25
+
26
+ CONTENT_TEMPLATE = """
27
+ **File content**
28
+
29
+ ```{type}
30
+ {content}
31
+ ```
32
+ """
33
+
34
+ SUBGRAPHS_TEMPLATE = """
35
+ **Subgraphs**
36
+
37
+ {formatted_subgraphs}
38
+ """
39
+
40
+
41
+ class TextualNoFacetFound(Facet):
42
+ """Facet to handle the case when no facet is found."""
43
+
44
+ @property
45
+ def raw_content(self):
46
+ """Content of the file, if applicable."""
47
+ url = URL(self.uriref)
48
+ if url.scheme != 'file':
49
+ return None
50
+
51
+ path = Path(url.path)
52
+ if not path.is_relative_to(self.iolanta.project_root):
53
+ return None
54
+
55
+ if not path.exists():
56
+ return None
57
+
58
+ if path.is_dir():
59
+ return None
60
+
61
+ file_content = path.read_text()
62
+ return CONTENT_TEMPLATE.format(
63
+ content=file_content,
64
+ type={
65
+ '.yamlld': 'yaml',
66
+ '.jsonld': 'json',
67
+ }.get(path.suffix, ''),
68
+ )
69
+
70
+ @property
71
+ def subgraphs_description(self) -> str:
72
+ """Return a formatted description of subgraphs, if any exist."""
73
+ rows = self.query(
74
+ 'SELECT ?subgraph WHERE { $this iolanta:has-sub-graph ?subgraph }',
75
+ this=self.this,
76
+ )
77
+ subgraphs = funcy.lpluck('subgraph', rows)
78
+ if subgraphs:
79
+ return SUBGRAPHS_TEMPLATE.format(
80
+ formatted_subgraphs='\n'.join([
81
+ f'- {subgraph}'
82
+ for subgraph in subgraphs
83
+ ]),
84
+ )
85
+
86
+ return ''
87
+
88
+ def show(self):
89
+ """Compose the page."""
90
+ return Vertical(
91
+ PageTitle(self.this),
92
+ Description(
93
+ Markdown(
94
+ TEXT.format(
95
+ content=self.raw_content or '',
96
+ subgraphs=self.subgraphs_description or '',
97
+ ),
98
+ ),
99
+ ),
100
+ )
@@ -4,22 +4,19 @@ from pathlib import Path
4
4
  from typing import ( # noqa: WPS235
5
5
  Annotated,
6
6
  Any,
7
- Dict,
8
7
  Iterable,
9
8
  List,
10
9
  Mapping,
11
10
  Optional,
12
11
  Protocol,
13
12
  Set,
14
- Tuple,
15
13
  Type,
16
14
  )
17
15
 
18
- import funcy
19
16
  import loguru
20
17
  import yaml_ld
21
18
  from pyparsing import ParseException
22
- from rdflib import ConjunctiveGraph, Graph, Literal, Namespace, URIRef
19
+ from rdflib import ConjunctiveGraph, Graph, Literal, URIRef
23
20
  from rdflib.namespace import NamespaceManager
24
21
  from rdflib.plugins.sparql.processor import SPARQLResult
25
22
  from rdflib.term import Node
@@ -32,18 +29,9 @@ from iolanta.errors import UnresolvedIRI
32
29
  from iolanta.facets.errors import FacetError
33
30
  from iolanta.facets.facet import Facet
34
31
  from iolanta.facets.locator import FacetFinder
35
- from iolanta.loaders.base import SourceType
36
- from iolanta.loaders.local_directory import merge_contexts
37
- from iolanta.models import (
38
- ComputedQName,
39
- LDContext,
40
- NotLiteralNode,
41
- Triple,
42
- TripleTemplate,
43
- )
32
+ from iolanta.models import ComputedQName, LDContext, NotLiteralNode
44
33
  from iolanta.node_to_qname import node_to_qname
45
34
  from iolanta.parse_quads import parse_quads
46
- from iolanta.parsers.yaml import YAML
47
35
  from iolanta.plugin import Plugin
48
36
  from iolanta.query_result import (
49
37
  QueryResult,
@@ -103,12 +91,6 @@ class Iolanta: # noqa: WPS214
103
91
 
104
92
  logger: LoggerProtocol = loguru.logger
105
93
 
106
- sources_added_not_yet_inferred: list[SourceType] = field(
107
- default_factory=list,
108
- init=False,
109
- repr=False,
110
- )
111
-
112
94
  could_not_retrieve_nodes: Set[Node] = field(
113
95
  default_factory=set,
114
96
  init=False,
@@ -182,23 +164,6 @@ class Iolanta: # noqa: WPS214
182
164
 
183
165
  return format_query_bindings(sparql_result.bindings)
184
166
 
185
- @functools.cached_property
186
- def namespaces_to_bind(self) -> Dict[str, Namespace]:
187
- """
188
- Namespaces globally specified for the graph.
189
-
190
- FIXME: Probably get rid of this, I do not know.
191
- """
192
- return {
193
- key: Namespace(value)
194
- for key, value in self.default_context['@context'].items() # noqa
195
- if (
196
- isinstance(value, str)
197
- and not value.startswith('@') # noqa: W503
198
- and not key.startswith('@') # noqa: W503
199
- )
200
- }
201
-
202
167
  def reset(self):
203
168
  """Reset Iolanta graph."""
204
169
  self.graph = _create_default_graph() # noqa: WPS601
@@ -212,7 +177,6 @@ class Iolanta: # noqa: WPS214
212
177
  ) -> 'Iolanta':
213
178
  """Parse & load information from given URL into the graph."""
214
179
  self.logger.info(f'Adding to graph: {source}')
215
- self.sources_added_not_yet_inferred.append(source)
216
180
 
217
181
  if not isinstance(source, Path):
218
182
  source = Path(source)
@@ -262,17 +226,7 @@ class Iolanta: # noqa: WPS214
262
226
  self.logger.info(f'{source_file} | No data found')
263
227
  continue
264
228
 
265
- quad_tuples = [
266
- tuple([
267
- normalize_term(term) for term in replace(
268
- quad,
269
- graph=graph,
270
- ).as_tuple()
271
- ])
272
- for quad in quads
273
- ]
274
-
275
- self.graph.addN(quad_tuples)
229
+ self.graph.addN(quads)
276
230
 
277
231
  return self
278
232
 
@@ -317,20 +271,6 @@ class Iolanta: # noqa: WPS214
317
271
  if path := plugin.context_path:
318
272
  yield path
319
273
 
320
- @functools.cached_property
321
- def default_context(self) -> LDContext:
322
- """Construct default context from plugins."""
323
- context_documents = [
324
- YAML().as_jsonld_document(path.open('r'))
325
- for path in self.context_paths
326
- ]
327
-
328
- for context in context_documents:
329
- if isinstance(context, list):
330
- raise ValueError('Context cannot be a list: %s', context)
331
-
332
- return merge_contexts(*context_documents) # type: ignore
333
-
334
274
  def add_files_from_plugins(self):
335
275
  """
336
276
  Load files from plugins.
@@ -434,44 +374,6 @@ class Iolanta: # noqa: WPS214
434
374
  error=err,
435
375
  ) from err
436
376
 
437
- def retrieve_triple(self, triple_template: TripleTemplate) -> Triple:
438
- """Retrieve remote data to project directory."""
439
- for plugin in self.plugins:
440
- # FIXME Parallelization?
441
- plugin.retrieve_triple(triple_template)
442
-
443
- if not downloaded_files:
444
- self.could_not_retrieve_nodes.add(node)
445
-
446
- for path in downloaded_files:
447
- self.add(path)
448
-
449
- return self
450
-
451
- def maybe_infer(self):
452
- """
453
- Apply inference lazily.
454
-
455
- Only run inference if there are new files added after last inference.
456
- """
457
- if self.sources_added_not_yet_inferred:
458
- self.infer()
459
-
460
- def find_triple(
461
- self,
462
- triple_template: TripleTemplate,
463
- ) -> Triple | None:
464
- """Lightweight procedure to find a triple by template."""
465
- triples = self.graph.triples(
466
- (triple_template.subject, triple_template.predicate, triple_template.object),
467
- )
468
-
469
- raw_triple = funcy.first(triples)
470
- if raw_triple:
471
- return Triple(*raw_triple)
472
-
473
- return self.retrieve_triple(triple_template)
474
-
475
377
  def node_as_qname(self, node: Node):
476
378
  """
477
379
  Render node as a QName if possible.
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ import re
2
2
  from enum import Enum
3
3
  from typing import Any, Dict, List, NamedTuple, Union
4
4
 
@@ -82,8 +82,14 @@ class TripleTemplate(NamedTuple):
82
82
  object: Node | None
83
83
 
84
84
 
85
- @dataclass
86
- class Quad:
85
+ def _normalize_term(term: Node):
86
+ if isinstance(term, URIRef) and term.startswith('http://'):
87
+ return URIRef(re.sub('^http', 'https', term))
88
+
89
+ return term
90
+
91
+
92
+ class Quad(NamedTuple):
87
93
  """Triple assigned to a named graph."""
88
94
 
89
95
  subject: Node
@@ -107,6 +113,19 @@ class Quad:
107
113
  f'{rendered_graph})' # noqa: WPS326
108
114
  )
109
115
 
110
- def as_tuple(self):
111
- """Represent quad as a tuple which `Graph.addN()` would understand."""
112
- return self.subject, self.predicate, self.object, self.graph
116
+ def replace(self, mapping: dict[Node, URIRef]):
117
+ """Replace variables in the quad."""
118
+ terms = [
119
+ mapping.get(term, term)
120
+ for term in self
121
+ ]
122
+
123
+ return Quad(*terms)
124
+
125
+ def normalize(self) -> 'Quad':
126
+ """Normalize the quad by applying normalization to all its terms."""
127
+ terms = [
128
+ _normalize_term(term)
129
+ for term in self
130
+ ]
131
+ return Quad(*terms)
@@ -8,6 +8,8 @@ NP = rdflib.Namespace('https://www.nanopub.org/nschema#')
8
8
  RDFG = rdflib.Namespace('https://www.w3.org/2004/03/trix/rdfg-1/')
9
9
  SDO = rdflib.SDO
10
10
 
11
+ META = rdflib.URIRef('iolanta://_meta')
12
+
11
13
 
12
14
  class DC(rdflib.DC):
13
15
  _NS = rdflib.Namespace('https://purl.org/dc/elements/1.1/')
@@ -0,0 +1,171 @@
1
+ import dataclasses
2
+ import hashlib
3
+ from types import MappingProxyType
4
+ from typing import Iterable, Optional
5
+
6
+ from documented import DocumentedError
7
+ from rdflib import BNode, Literal, URIRef
8
+ from rdflib.term import Node
9
+
10
+ from iolanta.errors import UnresolvedIRI
11
+ from iolanta.models import Quad
12
+ from iolanta.namespaces import IOLANTA, META
13
+
14
+ NORMALIZE_TERMS_MAP = MappingProxyType({
15
+ URIRef(_url := 'https://www.w3.org/2002/07/owl'): URIRef(f'{_url}#'),
16
+ URIRef(_url := 'https://www.w3.org/2000/01/rdf-schema'): URIRef(f'{_url}#'),
17
+ })
18
+
19
+
20
+ def parse_term( # noqa: C901
21
+ term,
22
+ blank_node_prefix,
23
+ ) -> Node:
24
+ """Parse N-Quads term into a Quad."""
25
+ if term is None:
26
+ raise SpaceInProperty()
27
+
28
+ term_type = term['type']
29
+ term_value = term['value']
30
+
31
+ if term_type == 'IRI':
32
+ return URIRef(term_value)
33
+
34
+ if term_type == 'literal':
35
+ language = term.get('language')
36
+
37
+ if datatype := term.get('datatype'):
38
+ datatype = URIRef(datatype)
39
+
40
+ if language and datatype:
41
+ datatype = None
42
+
43
+ return Literal(
44
+ term_value,
45
+ datatype=datatype,
46
+ lang=language,
47
+ )
48
+
49
+ if term_type == 'blank node':
50
+ return BNode(
51
+ value=term_value.replace('_:', f'{blank_node_prefix}_'),
52
+ )
53
+
54
+ raise ValueError(f'Unknown term: {term}')
55
+
56
+
57
+ def construct_subgraph_name(subgraph_name: str, graph: URIRef) -> URIRef:
58
+ """
59
+ Construct a proper subgraph name URI from a base name and graph.
60
+
61
+ If the subgraph name already starts with the graph URI, return it as is.
62
+ Otherwise, append the name as a fragment to the graph URI.
63
+ """
64
+ if subgraph_name.startswith(str(graph)):
65
+ return URIRef(subgraph_name)
66
+
67
+ return URIRef(f'{graph}#{subgraph_name}')
68
+
69
+
70
+ def _parse_quads_per_subgraph(
71
+ raw_quads,
72
+ blank_node_prefix: str,
73
+ graph: URIRef,
74
+ subgraph: URIRef,
75
+ ) -> Iterable[Quad]:
76
+ for quad in raw_quads:
77
+ try:
78
+ yield Quad(
79
+ subject=parse_term(quad['subject'], blank_node_prefix),
80
+ predicate=parse_term(quad['predicate'], blank_node_prefix),
81
+ object=parse_term(quad['object'], blank_node_prefix),
82
+ graph=subgraph,
83
+ )
84
+ except SpaceInProperty as err:
85
+ raise dataclasses.replace(
86
+ err,
87
+ iri=graph,
88
+ )
89
+
90
+
91
+ def parse_quads(
92
+ quads_document,
93
+ graph: URIRef,
94
+ blank_node_prefix: str = '',
95
+ ) -> Iterable[Quad]:
96
+ """Parse an N-Quads output into a Quads stream."""
97
+ blank_node_prefix = hashlib.md5( # noqa: S324
98
+ blank_node_prefix.encode(),
99
+ ).hexdigest()
100
+ blank_node_prefix = f'_:{blank_node_prefix}'
101
+
102
+ subgraph_names = {
103
+ URIRef(subgraph_name): construct_subgraph_name(
104
+ subgraph_name,
105
+ graph=graph,
106
+ )
107
+ for subgraph_name in quads_document.keys()
108
+ if subgraph_name != '@default'
109
+ }
110
+ subgraph_names[graph] = graph
111
+
112
+ for subgraph, quads in quads_document.items():
113
+ if subgraph == '@default':
114
+ subgraph = graph # noqa: WPS440
115
+
116
+ else:
117
+ subgraph = URIRef(subgraph)
118
+
119
+ yield Quad(
120
+ graph,
121
+ IOLANTA['has-sub-graph'],
122
+ subgraph_names[subgraph],
123
+ META,
124
+ )
125
+
126
+ quads = _parse_quads_per_subgraph(
127
+ quads,
128
+ blank_node_prefix=blank_node_prefix,
129
+ graph=subgraph,
130
+ subgraph=subgraph_names[subgraph],
131
+ )
132
+
133
+ for quad in quads: # noqa: WPS526
134
+ yield quad.replace(
135
+ subgraph_names | NORMALIZE_TERMS_MAP | {
136
+ # To enable nanopub rendering
137
+ URIRef('http://purl.org/nanopub/temp/np/'): graph,
138
+ },
139
+ ).normalize()
140
+
141
+
142
+ def raise_if_term_is_qname(term_value: str):
143
+ """Raise an error if a QName is provided instead of a full IRI."""
144
+ prefix, etc = term_value.split(':', 1)
145
+
146
+ if etc.startswith('/'):
147
+ return
148
+
149
+ if prefix in {'local', 'templates', 'urn'}:
150
+ return
151
+
152
+ raise UnresolvedIRI(
153
+ iri=term_value,
154
+ prefix=prefix,
155
+ )
156
+
157
+
158
+ @dataclasses.dataclass
159
+ class SpaceInProperty(DocumentedError):
160
+ """
161
+ Space in property.
162
+
163
+ That impedes JSON-LD parsing.
164
+
165
+ Please do not use spaces in property names in JSON or YAML data; use `title`
166
+ or other methods instead.
167
+
168
+ Document IRI: {self.iri}
169
+ """
170
+
171
+ iri: Optional[URIRef] = None
@@ -24,8 +24,7 @@ from rdflib.plugins.sparql.parserutils import CompValue
24
24
  from rdflib.plugins.sparql.sparql import Query
25
25
  from rdflib.query import Processor
26
26
  from rdflib.term import BNode, Literal, Node
27
- from requests import HTTPError
28
- from requests.exceptions import ConnectionError
27
+ from requests.exceptions import ConnectionError, InvalidSchema
29
28
  from yaml_ld.document_loaders.content_types import ParserNotFound
30
29
  from yaml_ld.errors import NotFound, YAMLLDError
31
30
  from yarl import URL
@@ -36,19 +35,14 @@ from iolanta.namespaces import ( # noqa: WPS235
36
35
  DCTERMS,
37
36
  FOAF,
38
37
  IOLANTA,
38
+ META,
39
39
  OWL,
40
40
  PROV,
41
41
  RDF,
42
42
  RDFS,
43
43
  VANN,
44
44
  )
45
- from iolanta.parse_quads import parse_quads
46
-
47
- NORMALIZE_TERMS_MAP = MappingProxyType({
48
- URIRef(_url := 'https://www.w3.org/2002/07/owl'): URIRef(f'{_url}#'),
49
- URIRef(_url := 'https://www.w3.org/2000/01/rdf-schema'): URIRef(f'{_url}#'),
50
- })
51
-
45
+ from iolanta.parse_quads import NORMALIZE_TERMS_MAP, parse_quads
52
46
 
53
47
  REASONING_ENABLED = True
54
48
  OWL_REASONING_ENABLED = False
@@ -94,9 +88,7 @@ def find_retractions_for(nanopublication: URIRef) -> set[URIRef]:
94
88
  # context of this dirty hack.
95
89
  use_server = 'http://grlc.nanopubs.lod.labs.vu.nl/api/local/local/'
96
90
 
97
- client = NanopubClient(
98
- use_server=use_server,
99
- )
91
+ client = NanopubClient(use_server=use_server)
100
92
  client.grlc_urls = [use_server]
101
93
 
102
94
  http_url = str(nanopublication).replace(
@@ -106,7 +98,7 @@ def find_retractions_for(nanopublication: URIRef) -> set[URIRef]:
106
98
 
107
99
  try:
108
100
  retractions = client.find_retractions_of(http_url)
109
- except HTTPError:
101
+ except (requests.HTTPError, InvalidSchema):
110
102
  return set()
111
103
 
112
104
  return {URIRef(retraction) for retraction in retractions}
@@ -331,7 +323,7 @@ class NanopubQueryPlugin:
331
323
  if potential_type == original_RDF.type:
332
324
  yield potential_class
333
325
 
334
- @funcy.retry(errors=HTTPError, tries=3, timeout=3)
326
+ @funcy.retry(errors=requests.HTTPError, tries=3, timeout=3)
335
327
  def _load_instances(self, class_uri: URIRef):
336
328
  """
337
329
  Load instances from Nanopub Registry.
@@ -537,7 +529,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
537
529
  uri,
538
530
  IOLANTA['last-loaded-time'],
539
531
  None,
540
- URIRef('iolanta://_meta'),
532
+ META,
541
533
  )),
542
534
  ) is not None
543
535
 
@@ -546,7 +538,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
546
538
  uri,
547
539
  IOLANTA['last-loaded-time'],
548
540
  Literal(datetime.datetime.now()),
549
- URIRef('iolanta://_meta'),
541
+ META,
550
542
  ))
551
543
 
552
544
  def _follow_is_visualized_with_links(self, uri: URIRef):
@@ -692,14 +684,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
692
684
  self.logger.info('{source} | No data found', source=source)
693
685
  return Loaded()
694
686
 
695
- quad_tuples = [
696
- tuple([
697
- normalize_term(term) for term in quad.as_tuple()
698
- ])
699
- for quad in quads
700
- ]
701
-
702
- self.graph.addN(quad_tuples)
687
+ self.graph.addN(quads)
703
688
  self.graph.last_not_inferred_source = source
704
689
 
705
690
  into_graphs = ', '.join({