iolanta 2.0.2__py3-none-any.whl → 2.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
iolanta/cli/main.py CHANGED
@@ -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),
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 } }
@@ -13,57 +13,75 @@
13
13
  iolanta:outputs:
14
14
  "@type": "@id"
15
15
 
16
+ iolanta:when-no-facet-found:
17
+ "@type": "@id"
18
+
16
19
  $: rdfs:label
20
+ →:
21
+ "@type": "@id"
22
+ "@id": iolanta:outputs
23
+
24
+ ⊆:
25
+ "@type": "@id"
26
+ "@id": rdfs:subClassOf
27
+
28
+ ⪯:
29
+ "@type": "@id"
30
+ "@id": iolanta:is-preferred-over
31
+
32
+ ↦: iolanta:matches
17
33
 
18
34
  "@included":
19
35
  - $id: iolanta:icon
20
- rdfs:label: Icon
21
- rdfs:subClassOf:
22
- $id: xsd:string
36
+ $: Icon
37
+ ⊆: xsd:string
23
38
 
24
39
  - $id: dcterms:creator
25
40
  iolanta:icon: ©️
26
41
 
27
42
  - $id: https://iolanta.tech/datatypes/icon
28
- rdfs:label: Icon
43
+ $: Icon
29
44
  iolanta:hasDefaultFacet:
30
45
  $id: python://iolanta.facets.icon.IconFacet
31
- iolanta:outputs:
32
- $id: https://iolanta.tech/datatypes/icon
46
+ →: https://iolanta.tech/datatypes/icon
33
47
 
34
48
  - $id: np:Nanopublication
35
- rdfs:label: Nanopublication
49
+ $: Nanopublication
36
50
  iolanta:hasInstanceFacet:
37
51
  $id: python://iolanta.facets.textual_nanopublication.NanopublicationFacet
38
- rdfs:label: Nanopublication
39
- iolanta:outputs:
40
- $id: https://iolanta.tech/cli/textual
41
- iolanta:is-preferred-over:
42
- $id: python://iolanta.facets.textual_default.TextualDefaultFacet
52
+ $: Nanopublication
53
+ →: https://iolanta.tech/cli/textual
54
+ ⪯:
55
+ - python://iolanta.facets.textual_default.TextualDefaultFacet
56
+ - python://iolanta.facets.textual_graph.GraphFacet
43
57
 
44
58
  - $id: https://iolanta.tech/cli/interactive
45
59
  iolanta:hasDefaultFacet:
46
60
  $id: python://iolanta.facets.textual_browser.TextualBrowserFacet
47
61
 
48
- - $id: https://iolanta.tech/cli/textual
49
- iolanta:hasDefaultFacet:
50
- $id: python://iolanta.facets.textual_default.TextualDefaultFacet
51
- rdfs:label: Properties
52
- iolanta:is-preferred-over:
53
- $id: python://iolanta.facets.textual_default.InverseProperties
54
-
55
- - $id: https://iolanta.tech/cli/textual
56
- iolanta:hasDefaultFacet:
57
- $id: python://iolanta.facets.textual_default.InverseProperties
58
- rdfs:label: Inverse Properties
59
- iolanta:is-preferred-over:
60
- $id: python://iolanta.facets.textual_graph.GraphFacet
62
+ - $id: python://iolanta.facets.textual_default.TextualDefaultFacet
63
+ $: Properties
64
+ ⪯: python://iolanta.facets.textual_default.InverseProperties
65
+ →: https://iolanta.tech/cli/textual
66
+ ↦: |
67
+ ASK WHERE {
68
+ GRAPH ?graph { $this ?property ?value }
69
+ FILTER (?graph != <iolanta://_meta>)
70
+ }
71
+
72
+ - $id: python://iolanta.facets.textual_default.InverseProperties
73
+ $: Inverse Properties
74
+ →: https://iolanta.tech/cli/textual
75
+ ↦: |
76
+ ASK WHERE {
77
+ GRAPH ?graph { ?something ?property $this }
78
+ FILTER (?graph != <iolanta://_meta>)
79
+ }
61
80
 
62
81
  - $id: "urn:"
63
82
  iolanta:hasFacetByPrefix:
64
83
  $id: python://iolanta.facets.textual_provenance.TextualProvenanceFacet
65
- iolanta:outputs:
66
- $id: https://iolanta.tech/cli/textual
84
+ →: https://iolanta.tech/cli/textual
67
85
 
68
86
  - $id: https://iolanta.tech/cli/link
69
87
  iolanta:hasDefaultFacet:
@@ -72,47 +90,44 @@
72
90
  - $id: https://wikiba.se/ontology#Statement
73
91
  iolanta:hasInstanceFacet:
74
92
  $id: python://iolanta.facets.wikibase_statement_title.WikibaseStatementTitle
75
- rdfs:label: Title
76
- iolanta:outputs:
77
- $id: https://iolanta.tech/datatypes/title
93
+ $: Title
94
+ →: https://iolanta.tech/datatypes/title
78
95
 
79
96
  - $id: rdfs:Class
80
97
  iolanta:hasInstanceFacet:
81
98
  $id: python://iolanta.facets.textual_class.Class
82
- rdfs:label: Instances
83
- iolanta:outputs:
84
- $id: https://iolanta.tech/cli/textual
85
- iolanta:is-preferred-over:
86
- - $id: python://iolanta.facets.textual_browser.TextualBrowserFacet
87
- - $id: python://iolanta.facets.textual_default.InverseProperties
99
+ $: Instances
100
+ →: https://iolanta.tech/cli/textual
101
+ ⪯:
102
+ - python://iolanta.facets.textual_browser.TextualBrowserFacet
103
+ - python://iolanta.facets.textual_default.InverseProperties
88
104
 
89
105
  - $id: owl:Ontology
90
106
  iolanta:hasInstanceFacet:
91
107
  $id: python://iolanta.facets.textual_ontology.OntologyFacet
92
- rdfs:label: Ontology
93
- iolanta:outputs:
94
- $id: https://iolanta.tech/cli/textual
95
- iolanta:is-preferred-over:
96
- - $id: python://iolanta.facets.textual_default.TextualDefaultFacet
97
-
98
- - $id: iolanta:Graph
99
- iolanta:hasInstanceFacet:
100
- $id: python://iolanta.facets.textual_graph.GraphFacet
101
- rdfs:label: Graph Triples
102
- iolanta:outputs:
103
- $id: https://iolanta.tech/cli/textual
108
+ $: Ontology
109
+ →: https://iolanta.tech/cli/textual
110
+ ⪯: python://iolanta.facets.textual_default.TextualDefaultFacet
104
111
 
105
112
  - $id: https://iolanta.tech/datatypes/textual-graph-triples
106
- rdfs:label: Graph Triples
113
+ $: Graph Triples
107
114
  iolanta:hasDefaultFacet:
108
115
  $id: python://iolanta.facets.textual_graph_triples.GraphTriplesFacet
109
- iolanta:outputs:
110
- $id: https://iolanta.tech/datatypes/textual-graph-triples
116
+ →: https://iolanta.tech/datatypes/textual-graph-triples
111
117
 
112
118
  - $id: https://iolanta.tech/qname
113
119
  iolanta:hasDefaultFacet:
114
120
  $id: python://iolanta.facets.qname.QNameFacet
115
121
 
116
122
  - $id: python://iolanta.facets.textual_property_pairs_table.TextualPropertyPairsTableFacet
117
- iolanta:outputs: https://iolanta.tech/cli/textual
118
123
  $: Subjects → Objects
124
+ →: https://iolanta.tech/cli/textual
125
+ ↦: ASK WHERE { ?subject $this ?object }
126
+
127
+ - $id: python://iolanta.facets.textual_class.Class
128
+ ↦:
129
+ - ASK WHERE { ?instance a $this }
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
iolanta/facets/locator.py CHANGED
@@ -18,6 +18,15 @@ class FoundRow(TypedDict):
18
18
  output_datatype: NotLiteralNode
19
19
 
20
20
 
21
+ GET_QUERY_TO_FACET = """
22
+ SELECT ?facet ?match WHERE {
23
+ ?facet
24
+ iolanta:matches ?match ;
25
+ iolanta:outputs $as_datatype .
26
+ }
27
+ """
28
+
29
+
21
30
  def reorder_rows_by_facet_preferences( # noqa: WPS214, WPS210
22
31
  rows: list[FoundRow],
23
32
  ordering: set[tuple[URIRef, URIRef]],
@@ -108,16 +117,14 @@ class FacetFinder: # noqa: WPS214
108
117
  if not isinstance(self.node, URIRef):
109
118
  return
110
119
 
111
- if self.as_datatype != URIRef('https://iolanta.tech/cli/textual'):
112
- return
120
+ rows = self.iolanta.query(
121
+ GET_QUERY_TO_FACET,
122
+ as_datatype=self.as_datatype,
123
+ )
113
124
 
114
- # TODO: Retrieve queries from the graph
115
- # TODO: Verify datatype for which we are visualizing $this
116
125
  query_to_facet = {
117
- 'ASK WHERE { ?subject $this ?object }': URIRef(
118
- 'python://iolanta.facets.textual_property_pairs_table'
119
- '.TextualPropertyPairsTableFacet',
120
- ),
126
+ row['match']: row['facet']
127
+ for row in rows
121
128
  }
122
129
 
123
130
  for query, facet in query_to_facet.items():
@@ -233,6 +240,22 @@ class FacetFinder: # noqa: WPS214
233
240
  for row in rows
234
241
  ]
235
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
+
236
259
  def retrieve_facets_preference_ordering(self) -> set[tuple[URIRef, URIRef]]:
237
260
  """
238
261
  Construct partial ordering on the set of facets.
@@ -256,6 +279,9 @@ class FacetFinder: # noqa: WPS214
256
279
  """Return all suitable facets."""
257
280
  rows = list(self._found_facets())
258
281
 
282
+ if not rows:
283
+ rows = self.by_facet_not_found()
284
+
259
285
  if len(rows) == 1:
260
286
  # Nothing to order.
261
287
  return rows
@@ -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,68 @@
1
+ from pathlib import Path
2
+
3
+ from rich.markdown import Markdown
4
+ from textual.containers import Vertical
5
+ from yarl import URL
6
+
7
+ from iolanta import Facet
8
+ from iolanta.facets.page_title import PageTitle
9
+ from iolanta.widgets.description import Description
10
+
11
+ TEXT = """
12
+ **😕 Iolanta is unable to visualize this resource**
13
+
14
+ * The URI might be incorrect;
15
+ * Or, no edges might exist which involve it;
16
+ * Or maybe Iolanta does not know of such edges.
17
+ {content}
18
+ **What can you do?**
19
+
20
+ * If you feel this might indicate a bug 🐛, please do let us know at GitHub
21
+ issues: https://github.com/iolanta.tech/iolanta/issues
22
+ """
23
+
24
+ CONTENT_TEMPLATE = """
25
+ **File content**
26
+
27
+ ```{type}
28
+ {content}
29
+ ```
30
+ """
31
+
32
+
33
+ class TextualNoFacetFound(Facet):
34
+ """Facet to handle the case when no facet is found."""
35
+
36
+ @property
37
+ def raw_content(self):
38
+ """Content of the file, if applicable."""
39
+ url = URL(self.uriref)
40
+ if url.scheme != 'file':
41
+ return None
42
+
43
+ path = Path(url.path)
44
+ if not path.is_relative_to(self.iolanta.project_root):
45
+ return None
46
+
47
+ if not path.exists():
48
+ return None
49
+
50
+ if path.is_dir():
51
+ return None
52
+
53
+ file_content = path.read_text()
54
+ return CONTENT_TEMPLATE.format(
55
+ content=file_content,
56
+ type={
57
+ '.yamlld': 'yaml',
58
+ }.get(path.suffix, ''),
59
+ )
60
+
61
+ def show(self):
62
+ """Compose the page."""
63
+ return Vertical(
64
+ PageTitle(self.this),
65
+ Description(
66
+ Markdown(TEXT.format(content=self.raw_content or '')),
67
+ ),
68
+ )
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
 
@@ -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
+ )
417
495
 
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)
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)
501
+
502
+ NanopubQueryPlugin(graph=self.graph)(query, bindings=initBindings)
423
503
 
424
504
  self.maybe_apply_inference()
425
505
 
@@ -542,12 +622,6 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
542
622
  path=not_found.path,
543
623
  )
544
624
 
545
- self.graph.add((
546
- source_uri,
547
- RDF.type,
548
- IOLANTA.Graph,
549
- ))
550
-
551
625
  self.graph.add((
552
626
  source_uri,
553
627
  RDF.type,
@@ -555,38 +629,18 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
555
629
  source_uri,
556
630
  ))
557
631
 
558
- self.graph.add((
559
- IOLANTA.Graph,
560
- RDF.type,
561
- RDFS.Class,
562
- ))
563
-
564
632
  self._mark_as_loaded(source_uri)
565
633
 
566
634
  return Loaded()
567
635
 
568
636
  except Exception as err:
569
637
  self.logger.info(f'{source} | Failed: {err}')
570
-
571
- self.graph.add((
572
- source_uri,
573
- RDF.type,
574
- IOLANTA.Graph,
575
- ))
576
-
577
638
  self.graph.add((
578
639
  URIRef(source),
579
640
  RDF.type,
580
641
  IOLANTA['failed'],
581
642
  source_uri,
582
643
  ))
583
-
584
- self.graph.add((
585
- IOLANTA.Graph,
586
- RDF.type,
587
- RDFS.Class,
588
- ))
589
-
590
644
  self._mark_as_loaded(source_uri)
591
645
 
592
646
  return Loaded()
@@ -601,18 +655,6 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
601
655
  ))
602
656
  source = _resolved_source
603
657
 
604
- self.graph.add((
605
- source_uri,
606
- RDF.type,
607
- IOLANTA.Graph,
608
- ))
609
-
610
- self.graph.add((
611
- IOLANTA.Graph,
612
- RDF.type,
613
- RDFS.Class,
614
- ))
615
-
616
658
  self._mark_as_loaded(source_uri)
617
659
 
618
660
  try: # noqa: WPS225
@@ -647,13 +689,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
647
689
  )
648
690
 
649
691
  if not quads:
650
- self.logger.warning('{source} | No data found', source=source)
651
- self.graph.addN([(
652
- source_uri,
653
- RDF.type,
654
- IOLANTA.Graph,
655
- source_uri,
656
- )])
692
+ self.logger.info('{source} | No data found', source=source)
657
693
  return Loaded()
658
694
 
659
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
+ )
@@ -0,0 +1,12 @@
1
+ from textual.widgets import Label
2
+
3
+
4
+ class Description(Label):
5
+ """Free form textual description."""
6
+
7
+ DEFAULT_CSS = """
8
+ Description {
9
+ padding: 1;
10
+ padding-left: 4;
11
+ }
12
+ """ # noqa: WPS115
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.2
3
+ Version: 2.0.6
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
 
@@ -6,20 +6,17 @@ iolanta/cli/formatters/choose.py,sha256=LWzsO_9IBSSgYNIyLlItkp8TNvpW3v6YCQ8-6kbI
6
6
  iolanta/cli/formatters/csv.py,sha256=ceJ_DTz0beqeK-d6FPBQqqjXrziEfF0FRSLoGZCt_fs,760
7
7
  iolanta/cli/formatters/json.py,sha256=Og5B9UrSM_0NWqW5Afpsy6WH8ZfYgPMVXYvT3i-43Jc,748
8
8
  iolanta/cli/formatters/pretty.py,sha256=IypZRAr2vNqcXFY6NOIc75mpyfpFWh56aCBlOPDDieQ,2901
9
- iolanta/cli/main.py,sha256=exDWPljllufn7DiGC9lS1pvojFX9YZJciQ2RPCedKMU,3127
9
+ iolanta/cli/main.py,sha256=W1SoQK9kncaSUsy4X5yMrzu8lQUlu-eE6YyIu4dNnec,3167
10
10
  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=USGe54XuNMNYYl-Hed1bKxeqZLUhrb1jNRUDbWpay4A,21990
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=8aGKLN_xnSX5o3dpBLsh8kPMfEsiysOd2QX-pn0Vfd4,4031
19
+ iolanta/data/textual-browser.yaml,sha256=SajFDOdqISVLsBVneS3uh3s8vuYAUIuC0nWvVqIWMZ0,4141
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
@@ -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=0FNISVG_Q4Mk8j6ZX0tmIM-YfnewdKJSf7BYQIwb-lc,8636
45
+ iolanta/facets/locator.py,sha256=b9TEK5_5SklTfEc6lUt7DJt1mKXsiL_urA2zxMk59EY,9152
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
@@ -82,6 +79,7 @@ iolanta/facets/textual_nanopublication/models.py,sha256=MphggSvKmYac6v866GOKW5lc
82
79
  iolanta/facets/textual_nanopublication/nanopublication_widget.py,sha256=sQPgEx25XJ15VqB5zjZuz2q2bf_B5GWXEp32FAufxQ4,2387
83
80
  iolanta/facets/textual_nanopublication/term_list_widget.py,sha256=NZNATKjFmgI2sE5R0ebyQh5Qk2g-kf-MT4YZ-Vw78M4,992
84
81
  iolanta/facets/textual_nanopublication/term_widget.py,sha256=uI5msCTSIYumAIS3I8nBfUz57_vRzvCKkouevrS3Qwk,3536
82
+ iolanta/facets/textual_no_facet_found.py,sha256=gYRSAYDX0t6EHln0S8msLZE27Svfef8iPVfZQ3Vf8Ns,1625
85
83
  iolanta/facets/textual_ontology/__init__.py,sha256=3H6bfYaEbXFr90C6ZpGWC4O-24uaO18tnTXx7mckQSA,94
86
84
  iolanta/facets/textual_ontology/facets.py,sha256=60g8ANmePb9_i_-d4ui-FdtNwg9aktrOKXHkTQtLp1w,3338
87
85
  iolanta/facets/textual_ontology/sparql/terms.sparql,sha256=oVLxN452nqog_95qRaTWnvar6rxPNxPrRonSo7oFFTg,324
@@ -97,7 +95,7 @@ iolanta/facets/title/sparql/title.sparql,sha256=4rz47tjwX2OJavWMzftaYKil1-ZHB76Z
97
95
  iolanta/facets/wikibase_statement_title/__init__.py,sha256=_yk1akxgSJOiUBJIc8QGrD2vovvmx_iw_vJDuv1rD7M,91
98
96
  iolanta/facets/wikibase_statement_title/facets.py,sha256=mUH7twlAgoeX7DgLQuRBQv4ORT6GWbN-0eJ1aliSfiQ,724
99
97
  iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql,sha256=n07DQWxKqB5c3CA4kacq2HSN0R0dLgnMnLP1AxMo5YA,320
100
- iolanta/iolanta.py,sha256=ZZ5hBXuVci4v-36xYLeaqDjgo85kEnnYCA07eJMo9ME,14230
98
+ iolanta/iolanta.py,sha256=wCt_7aUz9TOvZTJvpJQ-qVWkOpmM531uyxIk9l3n-lk,14229
101
99
  iolanta/loaders/__init__.py,sha256=QTiKCsQc1BTS-IlY2CQsN9iVpEIPqYFvI9ERMYVZCbU,99
102
100
  iolanta/loaders/base.py,sha256=-DxYwqG1bfDXB2p_S-mKpkc_3Sh14OHhePbe65Iq3-s,3381
103
101
  iolanta/loaders/data_type_choice.py,sha256=zRUXBIzjvuW28P_dhMDVevE9C8EFEIx2_X39WydWrtM,1982
@@ -110,7 +108,7 @@ iolanta/loaders/scheme_choice.py,sha256=GHA4JAD-Qq3uNdej4vs_bCrN1WMRshVRvOMxaYyP
110
108
  iolanta/models.py,sha256=L0iFaga4-PCaWP18WmT03NLK_oT7q49V0WMTQskoffI,2824
111
109
  iolanta/namespaces.py,sha256=fyWDCGPwU8Cs8d-Vmci4H0-fuIe7BMSevvEKFvG7Gf4,1153
112
110
  iolanta/node_to_qname.py,sha256=a82_qpgT87cbekY_76tTkl4Z-6Rz6am4UGIQChUf9Y0,794
113
- iolanta/parse_quads.py,sha256=nQeX9zSjDoITH1lmd25xhKq44bFTqcayFbAguXfFyh4,2790
111
+ iolanta/parse_quads.py,sha256=2UydATeDassOrpjWfjwWnMjnSS5RZw5KBFIeKHGJ36M,2660
114
112
  iolanta/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
113
  iolanta/parsers/base.py,sha256=ZFjj0BLzW4985BdC6PwbngenhMuSYW5mNLpprZRWjhA,1048
116
114
  iolanta/parsers/dict_parser.py,sha256=t_OK2meUh49DqSaOYkSgEwxMKUNNgjJY8rZAyL4NQKI,4546
@@ -125,9 +123,16 @@ iolanta/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
125
123
  iolanta/resolvers/base.py,sha256=cc9bcrVZ0wTwn85I-IYCwcIRU5Lwaph8D00C0dwSOm8,302
126
124
  iolanta/resolvers/python_import.py,sha256=kDOhApBDFfxnrDgK9M7ztMkqXfcny81Zo86FgPFb-LI,1301
127
125
  iolanta/shortcuts.py,sha256=j8b0E_yeoas8GumsPOfxO2v2jO0noqpmKJ6LFxFfsu4,1892
126
+ iolanta/sparqlspace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
+ iolanta/sparqlspace/cli.py,sha256=pb6q03lzrU8OaZ4A3QAQEmaFYcQ_-sHUrPhRs6GE88w,1590
128
+ iolanta/sparqlspace/inference/wikibase-claim.sparql,sha256=JSawj3VTc9ZPoU9mvh1w1AMYb3cWyZ3x1rEYWUsKZ6A,209
129
+ iolanta/sparqlspace/inference/wikibase-statement-property.sparql,sha256=SkSHZZlxWVDwBM3aLo0Q7hLuOj9BsIQnXtcuAuwaxqU,240
130
+ iolanta/sparqlspace/processor.py,sha256=CXxdi7ORpddXxPh0UlnRG7oqNT9q-WdSPMxqB4CgOis,23570
131
+ iolanta/sparqlspace/sparqlspace.py,sha256=Y8_ZPXwuGEXbEes6XQjaQWA2Zv9y8SWxMPDFdqVBGFo,796
128
132
  iolanta/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
133
+ iolanta/widgets/description.py,sha256=Qj5R2wgxJzlAHU0LHyKIoZSCkGk4VBuImWIyiUallhs,215
129
134
  iolanta/widgets/mixin.py,sha256=nDRCOc-gizCf1a5DAcYs4hW8eZEd6pHBPFsfm0ncv7E,251
130
- iolanta-2.0.2.dist-info/METADATA,sha256=BMCrSsOvKpNz7vzyS1iFXXWffpee_tGU1Rtvf_BF4Xc,2230
131
- iolanta-2.0.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
132
- iolanta-2.0.2.dist-info/entry_points.txt,sha256=fIp9g4kzjSNcTsTSjUCk4BIG3laHd3b3hUZlkjgFAGU,179
133
- iolanta-2.0.2.dist-info/RECORD,,
135
+ iolanta-2.0.6.dist-info/METADATA,sha256=zqVS0cP1WoIfWm-4NCgDrrryi5uxKwILIyquK8khpGA,2230
136
+ iolanta-2.0.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
137
+ iolanta-2.0.6.dist-info/entry_points.txt,sha256=z9RPLmGuz3tYGPV7Qf4nNjuSJYdTUS9enC4595poe-M,221
138
+ iolanta-2.0.6.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