iolanta 2.0.2__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 } }
@@ -14,56 +14,71 @@
14
14
  "@type": "@id"
15
15
 
16
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
17
30
 
18
31
  "@included":
19
32
  - $id: iolanta:icon
20
- rdfs:label: Icon
21
- rdfs:subClassOf:
22
- $id: xsd:string
33
+ $: Icon
34
+ ⊆: xsd:string
23
35
 
24
36
  - $id: dcterms:creator
25
37
  iolanta:icon: ©️
26
38
 
27
39
  - $id: https://iolanta.tech/datatypes/icon
28
- rdfs:label: Icon
40
+ $: Icon
29
41
  iolanta:hasDefaultFacet:
30
42
  $id: python://iolanta.facets.icon.IconFacet
31
- iolanta:outputs:
32
- $id: https://iolanta.tech/datatypes/icon
43
+ →: https://iolanta.tech/datatypes/icon
33
44
 
34
45
  - $id: np:Nanopublication
35
- rdfs:label: Nanopublication
46
+ $: Nanopublication
36
47
  iolanta:hasInstanceFacet:
37
48
  $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
49
+ $: Nanopublication
50
+ →: https://iolanta.tech/cli/textual
51
+ ⪯:
52
+ - python://iolanta.facets.textual_default.TextualDefaultFacet
53
+ - python://iolanta.facets.textual_graph.GraphFacet
43
54
 
44
55
  - $id: https://iolanta.tech/cli/interactive
45
56
  iolanta:hasDefaultFacet:
46
57
  $id: python://iolanta.facets.textual_browser.TextualBrowserFacet
47
58
 
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
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
+ }
68
+
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
+ }
61
77
 
62
78
  - $id: "urn:"
63
79
  iolanta:hasFacetByPrefix:
64
80
  $id: python://iolanta.facets.textual_provenance.TextualProvenanceFacet
65
- iolanta:outputs:
66
- $id: https://iolanta.tech/cli/textual
81
+ →: https://iolanta.tech/cli/textual
67
82
 
68
83
  - $id: https://iolanta.tech/cli/link
69
84
  iolanta:hasDefaultFacet:
@@ -72,47 +87,41 @@
72
87
  - $id: https://wikiba.se/ontology#Statement
73
88
  iolanta:hasInstanceFacet:
74
89
  $id: python://iolanta.facets.wikibase_statement_title.WikibaseStatementTitle
75
- rdfs:label: Title
76
- iolanta:outputs:
77
- $id: https://iolanta.tech/datatypes/title
90
+ $: Title
91
+ →: https://iolanta.tech/datatypes/title
78
92
 
79
93
  - $id: rdfs:Class
80
94
  iolanta:hasInstanceFacet:
81
95
  $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
96
+ $: Instances
97
+ →: https://iolanta.tech/cli/textual
98
+ ⪯:
99
+ - python://iolanta.facets.textual_browser.TextualBrowserFacet
100
+ - python://iolanta.facets.textual_default.InverseProperties
88
101
 
89
102
  - $id: owl:Ontology
90
103
  iolanta:hasInstanceFacet:
91
104
  $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
105
+ $: Ontology
106
+ →: https://iolanta.tech/cli/textual
107
+ ⪯: python://iolanta.facets.textual_default.TextualDefaultFacet
104
108
 
105
109
  - $id: https://iolanta.tech/datatypes/textual-graph-triples
106
- rdfs:label: Graph Triples
110
+ $: Graph Triples
107
111
  iolanta:hasDefaultFacet:
108
112
  $id: python://iolanta.facets.textual_graph_triples.GraphTriplesFacet
109
- iolanta:outputs:
110
- $id: https://iolanta.tech/datatypes/textual-graph-triples
113
+ →: https://iolanta.tech/datatypes/textual-graph-triples
111
114
 
112
115
  - $id: https://iolanta.tech/qname
113
116
  iolanta:hasDefaultFacet:
114
117
  $id: python://iolanta.facets.qname.QNameFacet
115
118
 
116
119
  - $id: python://iolanta.facets.textual_property_pairs_table.TextualPropertyPairsTableFacet
117
- iolanta:outputs: https://iolanta.tech/cli/textual
118
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/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():
@@ -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
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
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.2
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=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=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
@@ -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=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
@@ -97,7 +94,7 @@ iolanta/facets/title/sparql/title.sparql,sha256=4rz47tjwX2OJavWMzftaYKil1-ZHB76Z
97
94
  iolanta/facets/wikibase_statement_title/__init__.py,sha256=_yk1akxgSJOiUBJIc8QGrD2vovvmx_iw_vJDuv1rD7M,91
98
95
  iolanta/facets/wikibase_statement_title/facets.py,sha256=mUH7twlAgoeX7DgLQuRBQv4ORT6GWbN-0eJ1aliSfiQ,724
99
96
  iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql,sha256=n07DQWxKqB5c3CA4kacq2HSN0R0dLgnMnLP1AxMo5YA,320
100
- iolanta/iolanta.py,sha256=ZZ5hBXuVci4v-36xYLeaqDjgo85kEnnYCA07eJMo9ME,14230
97
+ iolanta/iolanta.py,sha256=wCt_7aUz9TOvZTJvpJQ-qVWkOpmM531uyxIk9l3n-lk,14229
101
98
  iolanta/loaders/__init__.py,sha256=QTiKCsQc1BTS-IlY2CQsN9iVpEIPqYFvI9ERMYVZCbU,99
102
99
  iolanta/loaders/base.py,sha256=-DxYwqG1bfDXB2p_S-mKpkc_3Sh14OHhePbe65Iq3-s,3381
103
100
  iolanta/loaders/data_type_choice.py,sha256=zRUXBIzjvuW28P_dhMDVevE9C8EFEIx2_X39WydWrtM,1982
@@ -110,7 +107,7 @@ iolanta/loaders/scheme_choice.py,sha256=GHA4JAD-Qq3uNdej4vs_bCrN1WMRshVRvOMxaYyP
110
107
  iolanta/models.py,sha256=L0iFaga4-PCaWP18WmT03NLK_oT7q49V0WMTQskoffI,2824
111
108
  iolanta/namespaces.py,sha256=fyWDCGPwU8Cs8d-Vmci4H0-fuIe7BMSevvEKFvG7Gf4,1153
112
109
  iolanta/node_to_qname.py,sha256=a82_qpgT87cbekY_76tTkl4Z-6Rz6am4UGIQChUf9Y0,794
113
- iolanta/parse_quads.py,sha256=nQeX9zSjDoITH1lmd25xhKq44bFTqcayFbAguXfFyh4,2790
110
+ iolanta/parse_quads.py,sha256=2UydATeDassOrpjWfjwWnMjnSS5RZw5KBFIeKHGJ36M,2660
114
111
  iolanta/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
112
  iolanta/parsers/base.py,sha256=ZFjj0BLzW4985BdC6PwbngenhMuSYW5mNLpprZRWjhA,1048
116
113
  iolanta/parsers/dict_parser.py,sha256=t_OK2meUh49DqSaOYkSgEwxMKUNNgjJY8rZAyL4NQKI,4546
@@ -125,9 +122,15 @@ iolanta/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
125
122
  iolanta/resolvers/base.py,sha256=cc9bcrVZ0wTwn85I-IYCwcIRU5Lwaph8D00C0dwSOm8,302
126
123
  iolanta/resolvers/python_import.py,sha256=kDOhApBDFfxnrDgK9M7ztMkqXfcny81Zo86FgPFb-LI,1301
127
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
128
131
  iolanta/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
132
  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,,
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