iolanta 2.0.1__py3-none-any.whl → 2.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
iolanta/data/context.yaml CHANGED
@@ -59,3 +59,18 @@
59
59
 
60
60
  rdf:type:
61
61
  "@type": "@id"
62
+
63
+ $: rdfs:label
64
+ →:
65
+ "@type": "@id"
66
+ "@id": iolanta:outputs
67
+
68
+ ⊆:
69
+ "@type": "@id"
70
+ "@id": rdfs:subClassOf
71
+
72
+ ⪯:
73
+ "@type": "@id"
74
+ "@id": iolanta:is-preferred-over
75
+
76
+ ↦: iolanta:matches
@@ -0,0 +1,6 @@
1
+ "@context": context.yaml
2
+ $id: python://iolanta.facets.textual_graph.GraphFacet
3
+ $: Graph Triples
4
+ →: https://iolanta.tech/cli/textual
5
+ ⪯: python://iolanta.facets.textual_default.InverseProperties
6
+ ↦: ASK WHERE { GRAPH $this { ?s ?p ?o } }
@@ -10,55 +10,75 @@
10
10
  dcterms: https://purl.org/dc/terms/
11
11
  xsd: https://www.w3.org/2001/XMLSchema#
12
12
 
13
+ iolanta:outputs:
14
+ "@type": "@id"
15
+
16
+ $: rdfs:label
17
+ →:
18
+ "@type": "@id"
19
+ "@id": iolanta:outputs
20
+
21
+ ⊆:
22
+ "@type": "@id"
23
+ "@id": rdfs:subClassOf
24
+
25
+ ⪯:
26
+ "@type": "@id"
27
+ "@id": iolanta:is-preferred-over
28
+
29
+ ↦: iolanta:matches
30
+
13
31
  "@included":
14
32
  - $id: iolanta:icon
15
- rdfs:label: Icon
16
- rdfs:subClassOf:
17
- $id: xsd:string
33
+ $: Icon
34
+ ⊆: xsd:string
18
35
 
19
36
  - $id: dcterms:creator
20
37
  iolanta:icon: ©️
21
38
 
22
39
  - $id: https://iolanta.tech/datatypes/icon
23
- rdfs:label: Icon
40
+ $: Icon
24
41
  iolanta:hasDefaultFacet:
25
42
  $id: python://iolanta.facets.icon.IconFacet
26
- iolanta:outputs:
27
- $id: https://iolanta.tech/datatypes/icon
43
+ →: https://iolanta.tech/datatypes/icon
28
44
 
29
45
  - $id: np:Nanopublication
30
- rdfs:label: Nanopublication
46
+ $: Nanopublication
31
47
  iolanta:hasInstanceFacet:
32
48
  $id: python://iolanta.facets.textual_nanopublication.NanopublicationFacet
33
- rdfs:label: Nanopublication
34
- iolanta:outputs:
35
- $id: https://iolanta.tech/cli/textual
36
- iolanta:is-preferred-over:
37
- $id: python://iolanta.facets.textual_default.TextualDefaultFacet
49
+ $: Nanopublication
50
+ →: https://iolanta.tech/cli/textual
51
+ ⪯:
52
+ - python://iolanta.facets.textual_default.TextualDefaultFacet
53
+ - python://iolanta.facets.textual_graph.GraphFacet
38
54
 
39
55
  - $id: https://iolanta.tech/cli/interactive
40
56
  iolanta:hasDefaultFacet:
41
57
  $id: python://iolanta.facets.textual_browser.TextualBrowserFacet
42
58
 
43
- - $id: https://iolanta.tech/cli/textual
44
- iolanta:hasDefaultFacet:
45
- $id: python://iolanta.facets.textual_default.TextualDefaultFacet
46
- rdfs:label: Properties
47
- iolanta:is-preferred-over:
48
- $id: python://iolanta.facets.textual_default.InverseProperties
59
+ - $id: python://iolanta.facets.textual_default.TextualDefaultFacet
60
+ $: Properties
61
+ ⪯: python://iolanta.facets.textual_default.InverseProperties
62
+ →: https://iolanta.tech/cli/textual
63
+ ↦: |
64
+ ASK WHERE {
65
+ GRAPH ?graph { $this ?property ?value }
66
+ FILTER (?graph != <iolanta://_meta>)
67
+ }
49
68
 
50
- - $id: https://iolanta.tech/cli/textual
51
- iolanta:hasDefaultFacet:
52
- $id: python://iolanta.facets.textual_default.InverseProperties
53
- rdfs:label: Inverse Properties
54
- iolanta:is-preferred-over:
55
- $id: python://iolanta.facets.textual_graph.GraphFacet
69
+ - $id: python://iolanta.facets.textual_default.InverseProperties
70
+ $: Inverse Properties
71
+ →: https://iolanta.tech/cli/textual
72
+ ↦: |
73
+ ASK WHERE {
74
+ GRAPH ?graph { ?something ?property $this }
75
+ FILTER (?graph != <iolanta://_meta>)
76
+ }
56
77
 
57
78
  - $id: "urn:"
58
79
  iolanta:hasFacetByPrefix:
59
80
  $id: python://iolanta.facets.textual_provenance.TextualProvenanceFacet
60
- iolanta:outputs:
61
- $id: https://iolanta.tech/cli/textual
81
+ →: https://iolanta.tech/cli/textual
62
82
 
63
83
  - $id: https://iolanta.tech/cli/link
64
84
  iolanta:hasDefaultFacet:
@@ -67,43 +87,41 @@
67
87
  - $id: https://wikiba.se/ontology#Statement
68
88
  iolanta:hasInstanceFacet:
69
89
  $id: python://iolanta.facets.wikibase_statement_title.WikibaseStatementTitle
70
- rdfs:label: Title
71
- iolanta:outputs:
72
- $id: https://iolanta.tech/datatypes/title
90
+ $: Title
91
+ →: https://iolanta.tech/datatypes/title
73
92
 
74
93
  - $id: rdfs:Class
75
94
  iolanta:hasInstanceFacet:
76
95
  $id: python://iolanta.facets.textual_class.Class
77
- rdfs:label: Instances
78
- iolanta:outputs:
79
- $id: https://iolanta.tech/cli/textual
80
- iolanta:is-preferred-over:
81
- - $id: python://iolanta.facets.textual_browser.TextualBrowserFacet
82
- - $id: python://iolanta.facets.textual_default.InverseProperties
96
+ $: Instances
97
+ →: https://iolanta.tech/cli/textual
98
+ ⪯:
99
+ - python://iolanta.facets.textual_browser.TextualBrowserFacet
100
+ - python://iolanta.facets.textual_default.InverseProperties
83
101
 
84
102
  - $id: owl:Ontology
85
103
  iolanta:hasInstanceFacet:
86
104
  $id: python://iolanta.facets.textual_ontology.OntologyFacet
87
- rdfs:label: Ontology
88
- iolanta:outputs:
89
- $id: https://iolanta.tech/cli/textual
90
- iolanta:is-preferred-over:
91
- - $id: python://iolanta.facets.textual_default.TextualDefaultFacet
92
-
93
- - $id: iolanta:Graph
94
- iolanta:hasInstanceFacet:
95
- $id: python://iolanta.facets.textual_graph.GraphFacet
96
- rdfs:label: Graph Triples
97
- iolanta:outputs:
98
- $id: https://iolanta.tech/cli/textual
105
+ $: Ontology
106
+ →: https://iolanta.tech/cli/textual
107
+ ⪯: python://iolanta.facets.textual_default.TextualDefaultFacet
99
108
 
100
109
  - $id: https://iolanta.tech/datatypes/textual-graph-triples
101
- rdfs:label: Graph Triples
110
+ $: Graph Triples
102
111
  iolanta:hasDefaultFacet:
103
112
  $id: python://iolanta.facets.textual_graph_triples.GraphTriplesFacet
104
- iolanta:outputs:
105
- $id: https://iolanta.tech/datatypes/textual-graph-triples
113
+ →: https://iolanta.tech/datatypes/textual-graph-triples
106
114
 
107
115
  - $id: https://iolanta.tech/qname
108
116
  iolanta:hasDefaultFacet:
109
117
  $id: python://iolanta.facets.qname.QNameFacet
118
+
119
+ - $id: python://iolanta.facets.textual_property_pairs_table.TextualPropertyPairsTableFacet
120
+ $: Subjects → Objects
121
+ →: https://iolanta.tech/cli/textual
122
+ ↦: ASK WHERE { ?subject $this ?object }
123
+
124
+ - $id: python://iolanta.facets.textual_class.Class
125
+ ↦:
126
+ - ASK WHERE { ?instance a $this }
127
+ - ASK WHERE { $this rdfs:subClassOf ?superclass }
iolanta/facets/facet.py CHANGED
@@ -20,6 +20,11 @@ class Facet(Generic[FacetOutput]):
20
20
  iolanta: 'iolanta.Iolanta' = field(repr=False)
21
21
  as_datatype: Optional[NotLiteralNode] = None
22
22
 
23
+ @property
24
+ def this(self) -> NotLiteralNode:
25
+ """This node."""
26
+ return self.iri
27
+
23
28
  @property
24
29
  def stored_queries_path(self) -> Path:
25
30
  """Construct directory for stored queries for this facet."""
iolanta/facets/locator.py CHANGED
@@ -15,7 +15,16 @@ class FoundRow(TypedDict):
15
15
  """Facet and datatype to render an IRI."""
16
16
 
17
17
  facet: NotLiteralNode
18
- as_datatype: NotLiteralNode
18
+ output_datatype: NotLiteralNode
19
+
20
+
21
+ GET_QUERY_TO_FACET = """
22
+ SELECT ?facet ?match WHERE {
23
+ ?facet
24
+ iolanta:matches ?match ;
25
+ iolanta:outputs $as_datatype .
26
+ }
27
+ """
19
28
 
20
29
 
21
30
  def reorder_rows_by_facet_preferences( # noqa: WPS214, WPS210
@@ -103,6 +112,27 @@ class FacetFinder: # noqa: WPS214
103
112
  key=self.row_sorter_by_output_datatype,
104
113
  )
105
114
 
115
+ def by_sparql(self) -> Iterable[FoundRow]:
116
+ """Determine facet by SHACL shape of the data."""
117
+ if not isinstance(self.node, URIRef):
118
+ return
119
+
120
+ rows = self.iolanta.query(
121
+ GET_QUERY_TO_FACET,
122
+ as_datatype=self.as_datatype,
123
+ )
124
+
125
+ query_to_facet = {
126
+ row['match']: row['facet']
127
+ for row in rows
128
+ }
129
+
130
+ for query, facet in query_to_facet.items():
131
+ # TODO: Verify that `query` is an ASK query
132
+ is_matching = self.iolanta.query(query, this=self.node)
133
+ if is_matching:
134
+ yield FoundRow(facet=facet, output_datatype=self.as_datatype)
135
+
106
136
  def by_prefix(self) -> Iterable[FoundRow]:
107
137
  """Determine facet by URL prefix.
108
138
 
@@ -254,6 +284,7 @@ class FacetFinder: # noqa: WPS214
254
284
 
255
285
  def _found_facets(self) -> Iterable[FoundRow]:
256
286
  """Compose a stream of all possible facet choices."""
287
+ yield from self.by_sparql()
257
288
  yield from self.by_prefix()
258
289
  yield from self.by_datatype()
259
290
  yield from self.by_facet()
@@ -6,7 +6,7 @@ import funcy
6
6
  from rich.console import RenderResult
7
7
  from rich.text import Text
8
8
  from textual.binding import Binding, BindingType
9
- from textual.containers import Vertical
9
+ from textual.containers import Vertical # removed unused VerticalScroll (F401)
10
10
  from textual.reactive import Reactive
11
11
  from textual.widget import Widget
12
12
  from textual.widgets import Label, ListItem, ListView
@@ -70,10 +70,8 @@ class InstancesList(ListView): # noqa: WPS214
70
70
 
71
71
  DEFAULT_CSS = """
72
72
  InstancesList {
73
- height: auto;
74
-
73
+ height: 1fr;
75
74
  layout: vertical;
76
- overflow-x: hidden;
77
75
  overflow-y: auto;
78
76
  }
79
77
  """
@@ -121,31 +119,6 @@ class InstancesList(ListView): # noqa: WPS214
121
119
  parent_class=self.parent_class,
122
120
  )
123
121
 
124
- def on_list_view_highlighted(self):
125
- """
126
- Find out whether the last item of the list is highlighted.
127
-
128
- If yes then add more elements.
129
- """
130
- if not self._nodes or (self.index >= len(self._nodes) - 1):
131
- new_instance_items = list(self.stream_instance_items_chunk())
132
- self.run_worker(
133
- functools.partial(
134
- self.render_newly_added_instances,
135
- new_instance_items,
136
- ),
137
- thread=True,
138
- )
139
-
140
- # #126 `extend()` here will lead to freeze in some situations
141
- for new_item in new_instance_items:
142
- self.append(new_item)
143
-
144
- def on_list_item__child_clicked(self) -> None: # noqa: WPS116
145
- """Navigate on click."""
146
- # FIXME if we call `action_goto()` here we'll navigate to the item that
147
- # was selected _prior_ to this click.
148
-
149
122
  def action_goto(self):
150
123
  """Navigate."""
151
124
  self.app.action_goto(self.highlighted_child.node)
@@ -160,6 +133,32 @@ class InstancesList(ListView): # noqa: WPS214
160
133
  ),
161
134
  )
162
135
 
136
+ def compose(self):
137
+ """Fill in the instances."""
138
+ for instance in self.instances: # noqa: WPS526
139
+ yield InstanceItem(
140
+ node=instance,
141
+ parent_class=self.parent_class,
142
+ )
143
+
144
+ def render_instances(self):
145
+ """Render all instances."""
146
+ for instance_item in self.children:
147
+ self.app.call_from_thread(
148
+ instance_item.update,
149
+ self.app.iolanta.render(
150
+ instance_item.node,
151
+ as_datatype=DATATYPES.title,
152
+ ),
153
+ )
154
+
155
+ def on_mount(self):
156
+ """Render the first chunk of instances."""
157
+ self.run_worker(
158
+ self.render_instances,
159
+ thread=True,
160
+ )
161
+
163
162
 
164
163
  class Bottom(Label):
165
164
  """Label below the instances list."""
@@ -198,29 +197,19 @@ class Class(Facet[Widget]):
198
197
  We have to stop if a subsequent attempt returns no results. That's why
199
198
  we can't use `funcy.distinct()` or something similar.
200
199
  """
201
- known_instances: set[NotLiteralNode] = set()
202
- while True:
203
- instances = set(
204
- funcy.pluck(
205
- 'instance',
206
- self.stored_query('instances.sparql', iri=self.iri),
207
- ),
208
- ).difference(known_instances)
209
-
210
- if not instances:
211
- return
212
-
213
- yield from instances
214
-
215
- known_instances.update(instances)
200
+ return set(
201
+ funcy.pluck(
202
+ 'instance',
203
+ self.stored_query('instances.sparql', iri=self.iri),
204
+ ),
205
+ )
216
206
 
217
207
  def show(self) -> Widget:
218
208
  """Render the instances list."""
219
209
  return InstancesBody(
220
210
  PageTitle(self.iri),
221
211
  InstancesList(
222
- instances=self.stream_instances(),
212
+ instances=list(self.stream_instances()),
223
213
  parent_class=self.iri,
224
214
  ),
225
- Bottom('Select the last element to try loading more instances.'),
226
215
  )
@@ -1,5 +1,5 @@
1
1
  SELECT ?instance WHERE {
2
- ?instance rdf:type $iri .
2
+ ?instance a $iri .
3
3
 
4
4
  FILTER(!isLiteral(?instance)) .
5
5
  } ORDER BY ?instance
@@ -0,0 +1,133 @@
1
+ from collections import defaultdict
2
+ from typing import Iterable
3
+
4
+ import funcy
5
+ from rdflib import Literal, Node
6
+ from rich.style import Style
7
+ from rich.text import Text
8
+ from textual.containers import Vertical
9
+ from textual.coordinate import Coordinate
10
+ from textual.widgets import DataTable
11
+
12
+ from iolanta import Facet
13
+ from iolanta.facets.page_title import PageTitle
14
+ from iolanta.models import NotLiteralNode
15
+ from iolanta.namespaces import DATATYPES
16
+ from iolanta.widgets.mixin import IolantaWidgetMixin
17
+
18
+ PAIRS_QUERY = """
19
+ SELECT ?subject ?object WHERE {
20
+ ?subject $this ?object .
21
+ } ORDER BY ?subject ?object
22
+ """
23
+
24
+
25
+ class PairsTable(IolantaWidgetMixin, DataTable):
26
+ """Render subject → object properties as a table with two columns."""
27
+
28
+ BINDINGS = [
29
+ ('enter', 'goto', 'Goto'),
30
+ ]
31
+
32
+ def __init__(self, pairs: Iterable[tuple[NotLiteralNode, Node]]):
33
+ """Construct."""
34
+ super().__init__(show_header=False, cell_padding=1)
35
+ self.pairs = list(pairs)
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 = 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=Text(title, no_wrap=False),
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, (subject, object_term) in enumerate(self.pairs):
62
+ yield Coordinate(row_number, 0), subject
63
+ yield Coordinate(row_number, 1), object_term
64
+
65
+ @funcy.cached_property
66
+ def node_to_coordinates(self) -> defaultdict[Node, list[Coordinate]]:
67
+ """Map node to coordinates where it appears."""
68
+ node_to_coordinate = [
69
+ (node, coordinate)
70
+ for coordinate, node in self.coordinate_to_node.items()
71
+ ]
72
+ return funcy.group_values(node_to_coordinate)
73
+
74
+ def format_as_loading(self, node: Node) -> Text:
75
+ """Intermediate version of a value while it is loading."""
76
+ if isinstance(node, Literal):
77
+ node_text = f'⌛ {node}'
78
+ else:
79
+ node_text = self.iolanta.node_as_qname(node)
80
+ node_text = f'⌛ {node_text}'
81
+
82
+ return Text(
83
+ node_text,
84
+ style=Style(dim=True),
85
+ no_wrap=False,
86
+ )
87
+
88
+ def on_mount(self):
89
+ """Fill the table and start rendering."""
90
+ self.add_columns('Subject', 'Object')
91
+ self.cell_padding = 1
92
+
93
+ for subject, object_term in self.pairs:
94
+ self.add_row(
95
+ self.format_as_loading(subject),
96
+ self.format_as_loading(object_term),
97
+ )
98
+
99
+ self.run_worker(
100
+ self.render_human_readable_cells,
101
+ thread=True,
102
+ )
103
+
104
+ def action_goto(self):
105
+ """Navigate to the selected node."""
106
+ if self.cursor_coordinate:
107
+ node = self.coordinate_to_node.get(self.cursor_coordinate)
108
+ if node is not None:
109
+ self.app.action_goto(node)
110
+
111
+
112
+ class TextualPropertyPairsTableFacet(Facet):
113
+ """Render a table of subject → object pairs for a property."""
114
+
115
+ def show(self):
116
+ """Construct the table."""
117
+ rows = self.query(
118
+ PAIRS_QUERY,
119
+ this=self.this,
120
+ )
121
+
122
+ return Vertical(
123
+ PageTitle(
124
+ self.this,
125
+ extra='— Subjects & Objects connected by this property',
126
+ ),
127
+ PairsTable(
128
+ [
129
+ (row['subject'], row['object'])
130
+ for row in rows
131
+ ],
132
+ ),
133
+ )
iolanta/iolanta.py CHANGED
@@ -28,7 +28,6 @@ from yaml_ld.errors import YAMLLDError
28
28
 
29
29
  from iolanta import entry_points, namespaces
30
30
  from iolanta.conversions import path_to_iri
31
- from iolanta.cyberspace.processor import normalize_term
32
31
  from iolanta.errors import UnresolvedIRI
33
32
  from iolanta.facets.errors import FacetError
34
33
  from iolanta.facets.facet import Facet
@@ -53,6 +52,7 @@ from iolanta.query_result import (
53
52
  format_query_bindings,
54
53
  )
55
54
  from iolanta.resolvers.python_import import PythonImportResolver
55
+ from iolanta.sparqlspace.processor import normalize_term
56
56
 
57
57
 
58
58
  class LoggerProtocol(Protocol):
@@ -161,7 +161,7 @@ class Iolanta: # noqa: WPS214
161
161
  try:
162
162
  sparql_result: SPARQLResult = self.graph.query(
163
163
  query_text,
164
- processor='cyberspace',
164
+ processor='sparqlspace',
165
165
  initBindings=kwargs,
166
166
  )
167
167
  except ParseException as err:
@@ -259,7 +259,7 @@ class Iolanta: # noqa: WPS214
259
259
  )
260
260
 
261
261
  if not quads:
262
- self.logger.warning(f'{source_file} | No data found')
262
+ self.logger.info(f'{source_file} | No data found')
263
263
  continue
264
264
 
265
265
  quad_tuples = [
iolanta/parse_quads.py CHANGED
@@ -73,13 +73,6 @@ def parse_quads(
73
73
  graph,
74
74
  )
75
75
 
76
- yield Quad(
77
- graph_name,
78
- RDF.type,
79
- IOLANTA.Graph,
80
- IOLANTA.meta,
81
- )
82
-
83
76
  for quad in quads:
84
77
  try:
85
78
  yield Quad(
@@ -0,0 +1,64 @@
1
+ from enum import StrEnum
2
+ from typing import Annotated
3
+
4
+ import rich
5
+ from rdflib import Node
6
+ from rdflib.query import Result
7
+ from rich.table import Table
8
+ from typer import Option, Typer
9
+
10
+ from iolanta.sparqlspace.sparqlspace import SPARQLSpace
11
+
12
+ app = Typer()
13
+
14
+
15
+ class OutputFormat(StrEnum):
16
+ """Output formats for the query command."""
17
+
18
+ CSV = 'csv' # noqa: WPS115
19
+ JSON = 'json' # noqa: WPS115
20
+ TABLE = 'table' # noqa: WPS115
21
+
22
+
23
+ def _format_node(node: Node):
24
+ return node
25
+
26
+
27
+ def _format_result(query_result: Result, output_format: OutputFormat):
28
+ match output_format:
29
+ case OutputFormat.CSV:
30
+ return query_result.serialize(format='csv').decode()
31
+
32
+ case OutputFormat.JSON:
33
+ return query_result.serialize(format='json').decode()
34
+
35
+ case OutputFormat.TABLE:
36
+ table = Table(*query_result.vars)
37
+ for row in query_result:
38
+ table.add_row(
39
+ *[
40
+ _format_node(node)
41
+ for node in row
42
+ ],
43
+ )
44
+ return table
45
+
46
+ raise NotImplementedError(f'Output format {output_format} not implemented.')
47
+
48
+
49
+ @app.command(name='query')
50
+ def query_command(
51
+ query: str,
52
+ output_format: Annotated[
53
+ OutputFormat,
54
+ Option(help='Output format.'),
55
+ ] = OutputFormat.TABLE,
56
+ ):
57
+ """Execute a SPARQL query."""
58
+ query_result = SPARQLSpace().query(query)
59
+ rich.print(
60
+ _format_result(
61
+ query_result=query_result,
62
+ output_format=output_format,
63
+ ),
64
+ )
@@ -1,6 +1,5 @@
1
1
  import dataclasses
2
2
  import datetime
3
- import itertools
4
3
  import re
5
4
  import time
6
5
  from pathlib import Path
@@ -13,9 +12,11 @@ import funcy
13
12
  import loguru
14
13
  import platformdirs
15
14
  import reasonable
15
+ import requests
16
16
  import yaml_ld
17
17
  from nanopub import NanopubClient
18
- from rdflib import ConjunctiveGraph, Namespace, URIRef, Variable
18
+ from rdflib import ConjunctiveGraph, Dataset, Namespace, URIRef, Variable
19
+ from rdflib.namespace import RDF as original_RDF
19
20
  from rdflib.plugins.sparql.algebra import translateQuery
20
21
  from rdflib.plugins.sparql.evaluate import evalQuery
21
22
  from rdflib.plugins.sparql.parser import parseQuery
@@ -23,6 +24,7 @@ from rdflib.plugins.sparql.parserutils import CompValue
23
24
  from rdflib.plugins.sparql.sparql import Query
24
25
  from rdflib.query import Processor
25
26
  from rdflib.term import BNode, Literal, Node
27
+ from requests import HTTPError
26
28
  from requests.exceptions import ConnectionError
27
29
  from yaml_ld.document_loaders.content_types import ParserNotFound
28
30
  from yaml_ld.errors import NotFound, YAMLLDError
@@ -97,13 +99,16 @@ def find_retractions_for(nanopublication: URIRef) -> set[URIRef]:
97
99
  )
98
100
  client.grlc_urls = [use_server]
99
101
 
100
- retractions = client.find_retractions_of(
101
- str(nanopublication).replace(
102
- 'https://',
103
- 'http://',
104
- ),
102
+ http_url = str(nanopublication).replace(
103
+ 'https://',
104
+ 'http://',
105
105
  )
106
106
 
107
+ try:
108
+ retractions = client.find_retractions_of(http_url)
109
+ except HTTPError:
110
+ return set()
111
+
107
112
  return {URIRef(retraction) for retraction in retractions}
108
113
 
109
114
 
@@ -111,7 +116,7 @@ def _extract_from_mapping( # noqa: WPS213
111
116
  algebra: Mapping[str, Any],
112
117
  ) -> Iterable[URIRef | Variable]:
113
118
  match algebra.name:
114
- case 'SelectQuery' | 'Project' | 'Distinct':
119
+ case 'SelectQuery' | 'AskQuery' | 'Project' | 'Distinct':
115
120
  yield from extract_mentioned_urls(algebra['p'])
116
121
 
117
122
  case 'BGP':
@@ -260,7 +265,7 @@ def _extract_nanopublication_uris(
260
265
  ) -> Iterable[URIRef]:
261
266
  """Extract nanopublications to get retracting information for."""
262
267
  match algebra.name:
263
- case 'SelectQuery' | 'Project' | 'Distinct' | 'Graph':
268
+ case 'SelectQuery' | 'AskQuery' | 'Project' | 'Distinct' | 'Graph':
264
269
  yield from _extract_nanopublication_uris(algebra['p'])
265
270
 
266
271
  case 'BGP':
@@ -293,6 +298,79 @@ def apply_redirect(source: URIRef) -> URIRef:
293
298
  return source
294
299
 
295
300
 
301
+ def extract_triples(algebra: CompValue) -> Iterable[tuple[Node, Node, Node]]:
302
+ """Extract triples from a SPARQL query algebra instance."""
303
+ if isinstance(algebra, CompValue):
304
+ for key, value in algebra.items(): # noqa: WPS110
305
+ if key == 'triples':
306
+ yield from value
307
+
308
+ else:
309
+ yield from extract_triples(value)
310
+
311
+
312
+ @dataclasses.dataclass(frozen=True)
313
+ class NanopubQueryPlugin:
314
+ """Import additional information from Nanopublications Registry."""
315
+
316
+ graph: Dataset
317
+
318
+ def __call__(self, query: Query, bindings: dict[str, Any]):
319
+ """Get stuff from Nanopub Registry, if it makes sense."""
320
+ class_uris = resolve_variables(
321
+ set(self._find_classes(query.algebra)),
322
+ bindings,
323
+ )
324
+ for class_uri in class_uris:
325
+ if self._is_from_nanopubs(class_uri):
326
+ self._load_instances(class_uri)
327
+
328
+ def _find_classes(self, algebra: CompValue) -> Iterable[URIRef]:
329
+ triples = extract_triples(algebra)
330
+ for _subject, potential_type, potential_class in triples:
331
+ if potential_type == original_RDF.type:
332
+ yield potential_class
333
+
334
+ @funcy.retry(errors=HTTPError, tries=3, timeout=3)
335
+ def _load_instances(self, class_uri: URIRef):
336
+ """
337
+ Load instances from Nanopub Registry.
338
+
339
+ FIXME: Can we cache this?
340
+ """
341
+ response = requests.post( # noqa: S113
342
+ 'https://query.knowledgepixels.com/repo/full',
343
+ data={
344
+ 'query': 'CONSTRUCT WHERE { ?instance a <%s> }' % class_uri,
345
+ },
346
+ headers={
347
+ 'Accept': 'application/ld+json',
348
+ },
349
+ )
350
+
351
+ response.raise_for_status()
352
+
353
+ self.graph.get_context(BNode()).parse(
354
+ data=response.text,
355
+ format='json-ld',
356
+ )
357
+
358
+ def _is_from_nanopubs(self, class_uri: URIRef) -> bool:
359
+ if not isinstance(class_uri, URIRef):
360
+ raise ValueError(f'Not a URIRef: {class_uri}')
361
+
362
+ return self.graph.query( # noqa: WPS462
363
+ """
364
+ ASK WHERE {
365
+ ?_whatever <https://purl.org/nanopub/x/introduces> $class
366
+ }
367
+ """,
368
+ initBindings={
369
+ 'class': class_uri,
370
+ },
371
+ ).askAnswer
372
+
373
+
296
374
  @dataclasses.dataclass(frozen=True)
297
375
  class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
298
376
  """
@@ -401,25 +479,27 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
401
479
  parse_tree = parseQuery(strOrQuery)
402
480
  query = translateQuery(parse_tree, base, initNs)
403
481
 
404
- self.load_retracting_nanopublications_by_query(
405
- query=query,
406
- bindings=initBindings,
407
- base=base,
408
- namespaces=initNs,
409
- )
482
+ self.load_retracting_nanopublications_by_query(
483
+ query=query,
484
+ bindings=initBindings,
485
+ base=base,
486
+ namespaces=initNs,
487
+ )
410
488
 
411
- query, urls = extract_mentioned_urls_from_query(
412
- query=query,
413
- bindings=initBindings,
414
- base=base,
415
- namespaces=initNs,
416
- )
489
+ query, urls = extract_mentioned_urls_from_query(
490
+ query=query,
491
+ bindings=initBindings,
492
+ base=base,
493
+ namespaces=initNs,
494
+ )
495
+
496
+ for url in urls:
497
+ try:
498
+ self.load(url)
499
+ except Exception as err:
500
+ self.logger.error(f'Failed to load {url}: {err}', url, err)
417
501
 
418
- for url in urls:
419
- try:
420
- self.load(url)
421
- except Exception as err:
422
- self.logger.error(f'Failed to load {url}: {err}', url, err)
502
+ NanopubQueryPlugin(graph=self.graph)(query, bindings=initBindings)
423
503
 
424
504
  self.maybe_apply_inference()
425
505
 
@@ -429,7 +509,12 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
429
509
 
430
510
  query_result = evalQuery(self.graph, query, initBindings, base)
431
511
 
432
- bindings = list(query_result['bindings'])
512
+ try:
513
+ bindings = list(query_result['bindings'])
514
+ except KeyError:
515
+ # This was probably an ASK query
516
+ return query_result
517
+
433
518
  for row in bindings:
434
519
  for _, maybe_iri in row.items():
435
520
  if (
@@ -537,12 +622,6 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
537
622
  path=not_found.path,
538
623
  )
539
624
 
540
- self.graph.add((
541
- source_uri,
542
- RDF.type,
543
- IOLANTA.Graph,
544
- ))
545
-
546
625
  self.graph.add((
547
626
  source_uri,
548
627
  RDF.type,
@@ -550,38 +629,18 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
550
629
  source_uri,
551
630
  ))
552
631
 
553
- self.graph.add((
554
- IOLANTA.Graph,
555
- RDF.type,
556
- RDFS.Class,
557
- ))
558
-
559
632
  self._mark_as_loaded(source_uri)
560
633
 
561
634
  return Loaded()
562
635
 
563
636
  except Exception as err:
564
637
  self.logger.info(f'{source} | Failed: {err}')
565
-
566
- self.graph.add((
567
- source_uri,
568
- RDF.type,
569
- IOLANTA.Graph,
570
- ))
571
-
572
638
  self.graph.add((
573
639
  URIRef(source),
574
640
  RDF.type,
575
641
  IOLANTA['failed'],
576
642
  source_uri,
577
643
  ))
578
-
579
- self.graph.add((
580
- IOLANTA.Graph,
581
- RDF.type,
582
- RDFS.Class,
583
- ))
584
-
585
644
  self._mark_as_loaded(source_uri)
586
645
 
587
646
  return Loaded()
@@ -596,18 +655,6 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
596
655
  ))
597
656
  source = _resolved_source
598
657
 
599
- self.graph.add((
600
- source_uri,
601
- RDF.type,
602
- IOLANTA.Graph,
603
- ))
604
-
605
- self.graph.add((
606
- IOLANTA.Graph,
607
- RDF.type,
608
- RDFS.Class,
609
- ))
610
-
611
658
  self._mark_as_loaded(source_uri)
612
659
 
613
660
  try: # noqa: WPS225
@@ -642,13 +689,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
642
689
  )
643
690
 
644
691
  if not quads:
645
- self.logger.warning('{source} | No data found', source=source)
646
- self.graph.addN([(
647
- source_uri,
648
- RDF.type,
649
- IOLANTA.Graph,
650
- source_uri,
651
- )])
692
+ self.logger.info('{source} | No data found', source=source)
652
693
  return Loaded()
653
694
 
654
695
  quad_tuples = [
@@ -0,0 +1,32 @@
1
+ import functools
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import rdflib
7
+ from rdflib.plugins.sparql.sparql import Query
8
+ from rdflib.query import Result
9
+
10
+
11
+ @dataclass
12
+ class SPARQLSpace:
13
+ """SPARQL interface to the Web of Data."""
14
+
15
+ graph: rdflib.Dataset = field(
16
+ default_factory=functools.partial(
17
+ rdflib.Dataset,
18
+ default_union=True,
19
+ ),
20
+ )
21
+ path: Annotated[
22
+ Path | None,
23
+ 'Local directory to load into the graph.',
24
+ ] = None
25
+
26
+ def query(self, query: str | Query, **bindings) -> Result:
27
+ """Execute a SPARQL query."""
28
+ return self.graph.query(
29
+ query_object=query,
30
+ processor='sparqlspace',
31
+ initBindings=bindings,
32
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.1
3
+ Version: 2.0.5
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -11,15 +11,12 @@ iolanta/cli/models.py,sha256=cjbpowdzI4wAP0DUk3qoVHyimk6AZwlXi9CGmusZTuM,159
11
11
  iolanta/cli/pretty_print.py,sha256=M6E3TmhzA6JY5GeUVmDZLmOh5u70-393PVit4voFKDI,977
12
12
  iolanta/context.py,sha256=bZR-tbZIrDQ-Vby01PMDZ6ifxM-0YMK68RJvAsyqCTs,507
13
13
  iolanta/conversions.py,sha256=hbLwRF1bAbOxy17eMWLHhYksbdCWN-v4-0y0wn3XSSg,1185
14
- iolanta/cyberspace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- iolanta/cyberspace/inference/wikibase-claim.sparql,sha256=JSawj3VTc9ZPoU9mvh1w1AMYb3cWyZ3x1rEYWUsKZ6A,209
16
- iolanta/cyberspace/inference/wikibase-statement-property.sparql,sha256=SkSHZZlxWVDwBM3aLo0Q7hLuOj9BsIQnXtcuAuwaxqU,240
17
- iolanta/cyberspace/processor.py,sha256=rxO5MaZEoXD2Kzknq7eRH3yuv8kQ5UILy8qkJxramMY,21828
18
14
  iolanta/data/cli.yaml,sha256=TsnldYXoY5GIzoNuPDvwBKGw8eAEForZW1FCKqKI0Kg,1029
19
- iolanta/data/context.yaml,sha256=OULEeDkSqshabaXF_gMujgwFLDJvt9eQn_9FftUlSUw,1424
15
+ iolanta/data/context.yaml,sha256=LUBasiBKgQeGAYjYV_T5XvgPlrdzACeKaZwY_rKzjtI,1636
16
+ iolanta/data/graph-triples.yamlld,sha256=oACKRwqWcMp6TBobXMG6gbbnaauyd6wlK-gH2vVkPps,241
20
17
  iolanta/data/html.yaml,sha256=hVFdLWLy8FMY8xpOrJMYc-tE3S0Nq83xuxVkjRW_7rI,517
21
18
  iolanta/data/iolanta.yaml,sha256=xubIFBNU02lmFXhgOSuyQwUcZD3xCqVfeVAZMvOxKbI,1433
22
- iolanta/data/textual-browser.yaml,sha256=ViMQJLiqa3CHdXluv9Fk3bEtyYC2ezNoHO7kQEp_N7w,3799
19
+ iolanta/data/textual-browser.yaml,sha256=hWN-EtLCoA8FCuihZWIAvLktara6O6BB7_E_bNukEhk,3947
23
20
  iolanta/ensure_is_context.py,sha256=9aok8asyEx7KPesOR28VBDb3Ch9kfc3eoCpQSJwj07U,717
24
21
  iolanta/entry_points.py,sha256=DZbf-udlEwELFGqeWENj0M2BOUPOWlmGJdqyaEtnot0,504
25
22
  iolanta/errors.py,sha256=t_RltahnoEvcytVa1BOq2MADgHps3JNd_h5-SFu7_wM,1250
@@ -31,7 +28,7 @@ iolanta/facets/cli/record.py,sha256=lBsECxLkwVSXC-yHmUwfosxAdLBI-0UoqHe8GOCablY,
31
28
  iolanta/facets/cli/sparql/link.sparql,sha256=WtWEfLAvdGc2gP0IhZil6Vglkydc3VO24vk4GwRnR5I,163
32
29
  iolanta/facets/cli/sparql/record.sparql,sha256=GBWkmNelvaSDbvl7v0-LsJHdjFPbsSAW49kNFOUeoGQ,63
33
30
  iolanta/facets/errors.py,sha256=sEBmnzhFcRIgOT18hfNwyMMjry0waa6IB-jC2NVTA50,4124
34
- iolanta/facets/facet.py,sha256=jDbtdcu-qhT9zrXVLw_wMkApTlk0xKm2aCLRpd7cfNA,2720
31
+ iolanta/facets/facet.py,sha256=QByUVrvt_XVBoVhfuyk6lKO--pauhMEN79GWmgejdIo,2822
35
32
  iolanta/facets/foaf_person_title/__init__.py,sha256=oj4MNVQvv8Dysb27xiWjtZCii8-nT7-WFa3WMWUwbtU,67
36
33
  iolanta/facets/foaf_person_title/facet.py,sha256=Q9TajjGp1v729quDzAM_Lfzawls3yujp1Z_6jXrG3gY,632
37
34
  iolanta/facets/foaf_person_title/sparql/names.sparql,sha256=p_2hHZXhEaJ8IwGlvLoN0vb0vhGqo44uAVRpDyTzflU,107
@@ -45,7 +42,7 @@ iolanta/facets/html/base.py,sha256=JcpK7YM_QQE9vwH5w5F_EgPkPv-XRzOrcZlVSy4LhIs,1
45
42
  iolanta/facets/html/code_literal.py,sha256=qCddzBrg6Y5XMIKohFQ52Tf9GPOcU7bj83tfAU1FLLU,394
46
43
  iolanta/facets/html/default.py,sha256=Dmf_kYiL2M954iigyYsiWrMstwZ1nvxKetuR6aW3xYY,647
47
44
  iolanta/facets/icon.py,sha256=ddZcolx1Q5_wo3w0jqiCzcc5Lsru6-jA7s7oAd1T8Og,494
48
- iolanta/facets/locator.py,sha256=tFwxGT4ujwEjwkgTevK6gwWfj3_1lU9Q305IU7a-I3Y,7697
45
+ iolanta/facets/locator.py,sha256=oiktgFGOkhVNDh4KzFY1Os--9-MSY32NYlGr-OXXxqY,8561
49
46
  iolanta/facets/page_title.py,sha256=TwgZK2g_e5UoWYjKNgDzzkmq1EI3cY58680iC8N9kZI,1407
50
47
  iolanta/facets/qname.py,sha256=ztyBbjjcW8dNZzuiNMqhcWfAUxk-gSjbseVGrQE7kVY,531
51
48
  iolanta/facets/textual_browser/__init__.py,sha256=sKgDvXOwib9n9d63kdtKCEv26-FoL0VN6zxDmfcheZ8,104
@@ -58,8 +55,8 @@ iolanta/facets/textual_browser/models.py,sha256=DqTBjhkkTt5mNwqr4DzNbPSqzV-QtNqf
58
55
  iolanta/facets/textual_browser/page.py,sha256=NkcQ5rSKZRbp63C8ozgsR_iVhcKHGv_SytUCQyGa7ss,786
59
56
  iolanta/facets/textual_browser/page_switcher.py,sha256=WOsFY9hSFEnuf3za1eCUhPaQ6uTaeBuMPIAHjHhnvDY,9950
60
57
  iolanta/facets/textual_class/__init__.py,sha256=tiL0p-3JspGcBRj4qa3rmoBFAuadk71l2ja2lJN6CEs,75
61
- iolanta/facets/textual_class/facets.py,sha256=qDljrtu7poPuvwRFwtmOlLccsIMhAKrzIQ5_OHeXQZE,6493
62
- iolanta/facets/textual_class/sparql/instances.sparql,sha256=tmG4tuYrROKo3A7idjOEblAeNPXiiExHxc6k3fhalSM,113
58
+ iolanta/facets/textual_class/facets.py,sha256=tascZiue3tbYfpIsnP8yY2dgXQIQzoQ_HPzU7DbVbao,5999
59
+ iolanta/facets/textual_class/sparql/instances.sparql,sha256=EW4BmuNxiRDA8djuEtvZpLLJlNSIshvId4ExlaYPcZE,106
63
60
  iolanta/facets/textual_default/__init__.py,sha256=snxA0FEY9qfAxNv3MlZLrJsXugD4dvs5hLStZWV7soM,158
64
61
  iolanta/facets/textual_default/facets.py,sha256=Pf5GttQ7EG0ZodjwwVXzYMffHus3MAZdJUIcbddcYtw,4738
65
62
  iolanta/facets/textual_default/sparql/inverse-properties.sparql,sha256=daHNdhmh92Q77CSf7ULbhxg57CuYsRFfMnXQz4VYEug,64
@@ -86,6 +83,7 @@ iolanta/facets/textual_ontology/__init__.py,sha256=3H6bfYaEbXFr90C6ZpGWC4O-24uaO
86
83
  iolanta/facets/textual_ontology/facets.py,sha256=60g8ANmePb9_i_-d4ui-FdtNwg9aktrOKXHkTQtLp1w,3338
87
84
  iolanta/facets/textual_ontology/sparql/terms.sparql,sha256=oVLxN452nqog_95qRaTWnvar6rxPNxPrRonSo7oFFTg,324
88
85
  iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql,sha256=q9TmU15deL0da28mpo_8W8fgMSEcENfYeqLyM0zVbTg,65
86
+ iolanta/facets/textual_property_pairs_table.py,sha256=Drqc_G_6QhzNmrrfDU170eKTGrVmvQ6JMYu4ir--iUk,4176
89
87
  iolanta/facets/textual_provenance/__init__.py,sha256=k5-_iK8Lrdwr5ZEJaDxq-UhGYe4G_adXVqGfOA5DAP8,114
90
88
  iolanta/facets/textual_provenance/facets.py,sha256=vv3UQsI2duB36DW5Zkw3sqgAXBPmK_xAo7cU0O7jF8g,3767
91
89
  iolanta/facets/textual_provenance/sparql/graphs.sparql,sha256=B45uKFd-1vrBuMDSbTURjUUEjHt51vAbqdL4tUcgMvk,103
@@ -96,7 +94,7 @@ iolanta/facets/title/sparql/title.sparql,sha256=4rz47tjwX2OJavWMzftaYKil1-ZHB76Z
96
94
  iolanta/facets/wikibase_statement_title/__init__.py,sha256=_yk1akxgSJOiUBJIc8QGrD2vovvmx_iw_vJDuv1rD7M,91
97
95
  iolanta/facets/wikibase_statement_title/facets.py,sha256=mUH7twlAgoeX7DgLQuRBQv4ORT6GWbN-0eJ1aliSfiQ,724
98
96
  iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql,sha256=n07DQWxKqB5c3CA4kacq2HSN0R0dLgnMnLP1AxMo5YA,320
99
- iolanta/iolanta.py,sha256=ZZ5hBXuVci4v-36xYLeaqDjgo85kEnnYCA07eJMo9ME,14230
97
+ iolanta/iolanta.py,sha256=wCt_7aUz9TOvZTJvpJQ-qVWkOpmM531uyxIk9l3n-lk,14229
100
98
  iolanta/loaders/__init__.py,sha256=QTiKCsQc1BTS-IlY2CQsN9iVpEIPqYFvI9ERMYVZCbU,99
101
99
  iolanta/loaders/base.py,sha256=-DxYwqG1bfDXB2p_S-mKpkc_3Sh14OHhePbe65Iq3-s,3381
102
100
  iolanta/loaders/data_type_choice.py,sha256=zRUXBIzjvuW28P_dhMDVevE9C8EFEIx2_X39WydWrtM,1982
@@ -109,7 +107,7 @@ iolanta/loaders/scheme_choice.py,sha256=GHA4JAD-Qq3uNdej4vs_bCrN1WMRshVRvOMxaYyP
109
107
  iolanta/models.py,sha256=L0iFaga4-PCaWP18WmT03NLK_oT7q49V0WMTQskoffI,2824
110
108
  iolanta/namespaces.py,sha256=fyWDCGPwU8Cs8d-Vmci4H0-fuIe7BMSevvEKFvG7Gf4,1153
111
109
  iolanta/node_to_qname.py,sha256=a82_qpgT87cbekY_76tTkl4Z-6Rz6am4UGIQChUf9Y0,794
112
- iolanta/parse_quads.py,sha256=nQeX9zSjDoITH1lmd25xhKq44bFTqcayFbAguXfFyh4,2790
110
+ iolanta/parse_quads.py,sha256=2UydATeDassOrpjWfjwWnMjnSS5RZw5KBFIeKHGJ36M,2660
113
111
  iolanta/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
112
  iolanta/parsers/base.py,sha256=ZFjj0BLzW4985BdC6PwbngenhMuSYW5mNLpprZRWjhA,1048
115
113
  iolanta/parsers/dict_parser.py,sha256=t_OK2meUh49DqSaOYkSgEwxMKUNNgjJY8rZAyL4NQKI,4546
@@ -124,9 +122,15 @@ iolanta/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
124
122
  iolanta/resolvers/base.py,sha256=cc9bcrVZ0wTwn85I-IYCwcIRU5Lwaph8D00C0dwSOm8,302
125
123
  iolanta/resolvers/python_import.py,sha256=kDOhApBDFfxnrDgK9M7ztMkqXfcny81Zo86FgPFb-LI,1301
126
124
  iolanta/shortcuts.py,sha256=j8b0E_yeoas8GumsPOfxO2v2jO0noqpmKJ6LFxFfsu4,1892
125
+ iolanta/sparqlspace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
+ iolanta/sparqlspace/cli.py,sha256=pb6q03lzrU8OaZ4A3QAQEmaFYcQ_-sHUrPhRs6GE88w,1590
127
+ iolanta/sparqlspace/inference/wikibase-claim.sparql,sha256=JSawj3VTc9ZPoU9mvh1w1AMYb3cWyZ3x1rEYWUsKZ6A,209
128
+ iolanta/sparqlspace/inference/wikibase-statement-property.sparql,sha256=SkSHZZlxWVDwBM3aLo0Q7hLuOj9BsIQnXtcuAuwaxqU,240
129
+ iolanta/sparqlspace/processor.py,sha256=CXxdi7ORpddXxPh0UlnRG7oqNT9q-WdSPMxqB4CgOis,23570
130
+ iolanta/sparqlspace/sparqlspace.py,sha256=Y8_ZPXwuGEXbEes6XQjaQWA2Zv9y8SWxMPDFdqVBGFo,796
127
131
  iolanta/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
132
  iolanta/widgets/mixin.py,sha256=nDRCOc-gizCf1a5DAcYs4hW8eZEd6pHBPFsfm0ncv7E,251
129
- iolanta-2.0.1.dist-info/METADATA,sha256=pcU438uX93qgFW-t4I6fQwDMJcM62G1wXO8FtLxg9zM,2230
130
- iolanta-2.0.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
131
- iolanta-2.0.1.dist-info/entry_points.txt,sha256=fIp9g4kzjSNcTsTSjUCk4BIG3laHd3b3hUZlkjgFAGU,179
132
- iolanta-2.0.1.dist-info/RECORD,,
133
+ iolanta-2.0.5.dist-info/METADATA,sha256=YSkBOGK0TGD_gdzzJKXxbm52W-4qtlVFpAumPPweabE,2230
134
+ iolanta-2.0.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
135
+ iolanta-2.0.5.dist-info/entry_points.txt,sha256=z9RPLmGuz3tYGPV7Qf4nNjuSJYdTUS9enC4595poe-M,221
136
+ iolanta-2.0.5.dist-info/RECORD,,
@@ -1,9 +1,10 @@
1
1
  [console_scripts]
2
2
  iolanta=iolanta.cli:app
3
+ sparqlspace=iolanta.sparqlspace.cli:app
3
4
 
4
5
  [iolanta.plugins]
5
6
  base=iolanta:IolantaBase
6
7
 
7
8
  [rdf.plugins.queryprocessor]
8
- cyberspace=iolanta.cyberspace.processor:GlobalSPARQLProcessor
9
+ sparqlspace=iolanta.sparqlspace.processor:GlobalSPARQLProcessor
9
10
 
File without changes