iolanta 2.0.0__py3-none-any.whl → 2.0.2__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.
@@ -7,7 +7,7 @@ from iolanta.cli.formatters.json import print_json
7
7
  from iolanta.cli.formatters.pretty import pretty_print
8
8
  from iolanta.models import QueryResultsFormat
9
9
  from iolanta.node_to_qname import node_to_qname
10
- from ldflex.ldflex import SelectResult
10
+ from iolanta.query_result import SelectResult
11
11
 
12
12
 
13
13
  # @cli_print.instance(SelectResult)
@@ -4,7 +4,7 @@ import sys
4
4
  from classes import typeclass
5
5
  from rdflib import Graph
6
6
 
7
- from ldflex.ldflex import QueryResult, SelectResult
7
+ from iolanta.query_result import QueryResult, SelectResult
8
8
 
9
9
 
10
10
  @typeclass
@@ -4,7 +4,7 @@ import sys
4
4
  from classes import typeclass
5
5
  from rdflib import Graph
6
6
 
7
- from ldflex.ldflex import QueryResult, SelectResult
7
+ from iolanta.query_result import QueryResult, SelectResult
8
8
 
9
9
 
10
10
  @typeclass
@@ -1,5 +1,4 @@
1
1
  import itertools
2
- from datetime import date
3
2
  from typing import Union
4
3
 
5
4
  from classes import typeclass
@@ -10,7 +9,7 @@ from rich.table import Table
10
9
 
11
10
  from iolanta.cli.pretty_print import render_literal_value
12
11
  from iolanta.models import ComputedQName
13
- from ldflex.ldflex import QueryResult, SelectResult
12
+ from iolanta.query_result import QueryResult, SelectResult
14
13
 
15
14
 
16
15
  @typeclass
@@ -111,7 +111,7 @@ def _extract_from_mapping( # noqa: WPS213
111
111
  algebra: Mapping[str, Any],
112
112
  ) -> Iterable[URIRef | Variable]:
113
113
  match algebra.name:
114
- case 'SelectQuery' | 'Project' | 'Distinct':
114
+ case 'SelectQuery' | 'AskQuery' | 'Project' | 'Distinct':
115
115
  yield from extract_mentioned_urls(algebra['p'])
116
116
 
117
117
  case 'BGP':
@@ -260,7 +260,7 @@ def _extract_nanopublication_uris(
260
260
  ) -> Iterable[URIRef]:
261
261
  """Extract nanopublications to get retracting information for."""
262
262
  match algebra.name:
263
- case 'SelectQuery' | 'Project' | 'Distinct' | 'Graph':
263
+ case 'SelectQuery' | 'AskQuery' | 'Project' | 'Distinct' | 'Graph':
264
264
  yield from _extract_nanopublication_uris(algebra['p'])
265
265
 
266
266
  case 'BGP':
@@ -429,7 +429,12 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
429
429
 
430
430
  query_result = evalQuery(self.graph, query, initBindings, base)
431
431
 
432
- bindings = list(query_result['bindings'])
432
+ try:
433
+ bindings = list(query_result['bindings'])
434
+ except KeyError:
435
+ # This was probably an ASK query
436
+ return query_result
437
+
433
438
  for row in bindings:
434
439
  for _, maybe_iri in row.items():
435
440
  if (
@@ -10,6 +10,11 @@
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
+
13
18
  "@included":
14
19
  - $id: iolanta:icon
15
20
  rdfs:label: Icon
@@ -107,3 +112,7 @@
107
112
  - $id: https://iolanta.tech/qname
108
113
  iolanta:hasDefaultFacet:
109
114
  $id: python://iolanta.facets.qname.QNameFacet
115
+
116
+ - $id: python://iolanta.facets.textual_property_pairs_table.TextualPropertyPairsTableFacet
117
+ iolanta:outputs: https://iolanta.tech/cli/textual
118
+ $: Subjects → Objects
iolanta/facets/facet.py CHANGED
@@ -2,13 +2,12 @@ import inspect
2
2
  from dataclasses import dataclass, field
3
3
  from functools import cached_property
4
4
  from pathlib import Path
5
- from typing import Any, Generic, Iterable, List, Optional, TypeVar, Union
5
+ from typing import Any, Generic, Iterable, Optional, TypeVar, Union
6
6
 
7
7
  from rdflib.term import BNode, Literal, Node, URIRef
8
8
 
9
9
  from iolanta.models import NotLiteralNode, Triple, TripleTemplate
10
- from ldflex import LDFlex
11
- from ldflex.ldflex import QueryResult, SPARQLQueryArgument
10
+ from iolanta.query_result import QueryResult, SPARQLQueryArgument
12
11
 
13
12
  FacetOutput = TypeVar('FacetOutput')
14
13
 
@@ -21,16 +20,16 @@ class Facet(Generic[FacetOutput]):
21
20
  iolanta: 'iolanta.Iolanta' = field(repr=False)
22
21
  as_datatype: Optional[NotLiteralNode] = None
23
22
 
23
+ @property
24
+ def this(self) -> NotLiteralNode:
25
+ """This node."""
26
+ return self.iri
27
+
24
28
  @property
25
29
  def stored_queries_path(self) -> Path:
26
30
  """Construct directory for stored queries for this facet."""
27
31
  return Path(inspect.getfile(self.__class__)).parent / 'sparql'
28
32
 
29
- @property
30
- def ldflex(self) -> LDFlex:
31
- """Extract LDFLex instance."""
32
- return self.iolanta.ldflex
33
-
34
33
  @cached_property
35
34
  def uriref(self) -> NotLiteralNode:
36
35
  """Format as URIRef."""
@@ -45,7 +44,7 @@ class Facet(Generic[FacetOutput]):
45
44
  **kwargs: SPARQLQueryArgument,
46
45
  ) -> QueryResult:
47
46
  """SPARQL query."""
48
- return self.ldflex.query(
47
+ return self.iolanta.query(
49
48
  query_text=query_text,
50
49
  **kwargs,
51
50
  )
iolanta/facets/locator.py CHANGED
@@ -15,7 +15,7 @@ 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
19
 
20
20
 
21
21
  def reorder_rows_by_facet_preferences( # noqa: WPS214, WPS210
@@ -103,6 +103,29 @@ class FacetFinder: # noqa: WPS214
103
103
  key=self.row_sorter_by_output_datatype,
104
104
  )
105
105
 
106
+ def by_sparql(self) -> Iterable[FoundRow]:
107
+ """Determine facet by SHACL shape of the data."""
108
+ if not isinstance(self.node, URIRef):
109
+ return
110
+
111
+ if self.as_datatype != URIRef('https://iolanta.tech/cli/textual'):
112
+ return
113
+
114
+ # TODO: Retrieve queries from the graph
115
+ # TODO: Verify datatype for which we are visualizing $this
116
+ query_to_facet = {
117
+ 'ASK WHERE { ?subject $this ?object }': URIRef(
118
+ 'python://iolanta.facets.textual_property_pairs_table'
119
+ '.TextualPropertyPairsTableFacet',
120
+ ),
121
+ }
122
+
123
+ for query, facet in query_to_facet.items():
124
+ # TODO: Verify that `query` is an ASK query
125
+ is_matching = self.iolanta.query(query, this=self.node)
126
+ if is_matching:
127
+ yield FoundRow(facet=facet, output_datatype=self.as_datatype)
128
+
106
129
  def by_prefix(self) -> Iterable[FoundRow]:
107
130
  """Determine facet by URL prefix.
108
131
 
@@ -254,6 +277,7 @@ class FacetFinder: # noqa: WPS214
254
277
 
255
278
  def _found_facets(self) -> Iterable[FoundRow]:
256
279
  """Compose a stream of all possible facet choices."""
280
+ yield from self.by_sparql()
257
281
  yield from self.by_prefix()
258
282
  yield from self.by_datatype()
259
283
  yield from self.by_facet()
@@ -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
@@ -18,8 +18,10 @@ from typing import ( # noqa: WPS235
18
18
  import funcy
19
19
  import loguru
20
20
  import yaml_ld
21
- from rdflib import ConjunctiveGraph, Literal, Namespace, URIRef
21
+ from pyparsing import ParseException
22
+ from rdflib import ConjunctiveGraph, Graph, Literal, Namespace, URIRef
22
23
  from rdflib.namespace import NamespaceManager
24
+ from rdflib.plugins.sparql.processor import SPARQLResult
23
25
  from rdflib.term import Node
24
26
  from yaml_ld.document_loaders.content_types import ParserNotFound
25
27
  from yaml_ld.errors import YAMLLDError
@@ -44,8 +46,13 @@ from iolanta.node_to_qname import node_to_qname
44
46
  from iolanta.parse_quads import parse_quads
45
47
  from iolanta.parsers.yaml import YAML
46
48
  from iolanta.plugin import Plugin
49
+ from iolanta.query_result import (
50
+ QueryResult,
51
+ SPARQLParseException,
52
+ SPARQLQueryArgument,
53
+ format_query_bindings,
54
+ )
47
55
  from iolanta.resolvers.python_import import PythonImportResolver
48
- from ldflex import LDFlex
49
56
 
50
57
 
51
58
  class LoggerProtocol(Protocol):
@@ -124,14 +131,56 @@ class Iolanta: # noqa: WPS214
124
131
  for plugin_class in self.plugin_classes
125
132
  ]
126
133
 
127
- @property
128
- def ldflex(self) -> LDFlex:
134
+ def query(
135
+ self,
136
+ query_text: str,
137
+ **kwargs: SPARQLQueryArgument,
138
+ ) -> QueryResult:
129
139
  """
130
- Create ldflex instance.
140
+ Run a SPARQL `SELECT`, `CONSTRUCT`, or `ASK` query.
131
141
 
132
- FIXME: Get rid of it.
142
+ Args:
143
+ query_text: The SPARQL text;
144
+ **kwargs: bind variables in the query to values if necessary. For
145
+ example:
146
+
147
+ ```python
148
+ iolanta.query(
149
+ 'SELECT ?title WHERE { ?page rdfs:label ?title }',
150
+ ?page=page_iri,
151
+ )
152
+ ```
153
+
154
+ Returns:
155
+ Results of the query:
156
+
157
+ - a graph for `CONSTRUCT`,
158
+ - a list of dicts for `SELECT`,
159
+ - or a boolean for `ASK`.
133
160
  """
134
- return LDFlex(self.graph)
161
+ try:
162
+ sparql_result: SPARQLResult = self.graph.query(
163
+ query_text,
164
+ processor='cyberspace',
165
+ initBindings=kwargs,
166
+ )
167
+ except ParseException as err:
168
+ raise SPARQLParseException(
169
+ error=err,
170
+ query=query_text,
171
+ ) from err
172
+
173
+ if sparql_result.askAnswer is not None:
174
+ return sparql_result.askAnswer
175
+
176
+ if sparql_result.graph is not None:
177
+ graph: Graph = sparql_result.graph
178
+ for prefix, namespace in self.graph.namespaces():
179
+ graph.bind(prefix, namespace)
180
+
181
+ return graph
182
+
183
+ return format_query_bindings(sparql_result.bindings)
135
184
 
136
185
  @functools.cached_property
137
186
  def namespaces_to_bind(self) -> Dict[str, Namespace]:
@@ -253,12 +302,6 @@ class Iolanta: # noqa: WPS214
253
302
  self.graph.bind(prefix='dcterms', namespace=namespaces.DCTERMS)
254
303
  self.graph.bind(prefix='rdfg', namespace=namespaces.RDFG)
255
304
 
256
- @property
257
- def query(self):
258
- """Make a SPARQL query."""
259
- self.maybe_infer()
260
- return self.ldflex.query
261
-
262
305
  @functools.cached_property
263
306
  def context_paths(self) -> Iterable[Path]:
264
307
  """
@@ -0,0 +1,73 @@
1
+ from dataclasses import dataclass
2
+ from typing import Dict, List, Mapping, Optional, Union
3
+
4
+ from documented import DocumentedError
5
+ from frozendict import frozendict
6
+ from pyparsing import ParseException
7
+ from rdflib import Graph
8
+ from rdflib.term import Identifier, Node, Variable
9
+
10
+ SelectRow = Mapping[str, Node]
11
+
12
+
13
+ class SelectResult(List[SelectRow]):
14
+ """Result of a SPARQL SELECT."""
15
+
16
+ @property
17
+ def first(self) -> Optional[SelectRow]:
18
+ """Return first element of the list."""
19
+ return self[0] if self else None
20
+
21
+
22
+ SPARQLQueryArgument = Optional[Union[Node, str, int, float]]
23
+
24
+
25
+ QueryResult = Union[
26
+ SelectResult, # SELECT
27
+ Graph, # CONSTRUCT
28
+ bool, # ASK
29
+ ]
30
+
31
+
32
+ def format_query_bindings(
33
+ bindings: List[Dict[Variable, Identifier]],
34
+ ) -> SelectResult:
35
+ """
36
+ Format bindings before returning them.
37
+
38
+ Converts Variable to str for ease of addressing.
39
+ """
40
+ return SelectResult([
41
+ frozendict({
42
+ str(variable_name): rdf_value
43
+ for variable_name, rdf_value # noqa: WPS361
44
+ in row.items()
45
+ })
46
+ for row in bindings
47
+ ])
48
+
49
+
50
+ @dataclass
51
+ class SPARQLParseException(DocumentedError):
52
+ """
53
+ SPARQL query is invalid.
54
+
55
+ Error:
56
+
57
+ ```
58
+ {self.error}
59
+ ```
60
+
61
+ Query:
62
+ ```sparql hl_lines="{self.highlight_code}"
63
+ {self.query}
64
+ ```
65
+ """ # noqa: D412
66
+
67
+ error: ParseException
68
+ query: str
69
+
70
+ @property
71
+ def highlight_code(self):
72
+ """Define lines to highlight."""
73
+ return self.error.lineno
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -2,10 +2,10 @@ iolanta/__init__.py,sha256=BvLP-LYFmNYS5F8VPXdzpEEMk8zcm9NMmlUWw9yEtj0,153
2
2
  iolanta/base_plugin.py,sha256=vI4DRSIITlKZw8x7Q58BFkChWlg1h8zV-tuNvIKURBI,86
3
3
  iolanta/cli/__init__.py,sha256=IV6_RPmrbpPWbdKojuFczwaJAaKm1DIDQh5aZWbeR1U,69
4
4
  iolanta/cli/formatters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- iolanta/cli/formatters/choose.py,sha256=Ac4hNoptvnhuJB77K9KOn5oMF7j2RxmNuaLko28Wlns,1122
6
- iolanta/cli/formatters/csv.py,sha256=OVucZxhcMjihUli0wkbSOKo500-i7DNV0Zdf6oIougU,753
7
- iolanta/cli/formatters/json.py,sha256=jkNldFApSWw0kcMkeIPvI2Vt4JTE-Rvx5mWqKJb3Sj4,741
8
- iolanta/cli/formatters/pretty.py,sha256=Ik75CR5GMBDJCvES9eF0bQPj64ZJD40iFDSSZH0s9aA,2920
5
+ iolanta/cli/formatters/choose.py,sha256=LWzsO_9IBSSgYNIyLlItkp8TNvpW3v6YCQ8-6kbICI4,1129
6
+ iolanta/cli/formatters/csv.py,sha256=ceJ_DTz0beqeK-d6FPBQqqjXrziEfF0FRSLoGZCt_fs,760
7
+ iolanta/cli/formatters/json.py,sha256=Og5B9UrSM_0NWqW5Afpsy6WH8ZfYgPMVXYvT3i-43Jc,748
8
+ iolanta/cli/formatters/pretty.py,sha256=IypZRAr2vNqcXFY6NOIc75mpyfpFWh56aCBlOPDDieQ,2901
9
9
  iolanta/cli/main.py,sha256=exDWPljllufn7DiGC9lS1pvojFX9YZJciQ2RPCedKMU,3127
10
10
  iolanta/cli/models.py,sha256=cjbpowdzI4wAP0DUk3qoVHyimk6AZwlXi9CGmusZTuM,159
11
11
  iolanta/cli/pretty_print.py,sha256=M6E3TmhzA6JY5GeUVmDZLmOh5u70-393PVit4voFKDI,977
@@ -14,12 +14,12 @@ iolanta/conversions.py,sha256=hbLwRF1bAbOxy17eMWLHhYksbdCWN-v4-0y0wn3XSSg,1185
14
14
  iolanta/cyberspace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  iolanta/cyberspace/inference/wikibase-claim.sparql,sha256=JSawj3VTc9ZPoU9mvh1w1AMYb3cWyZ3x1rEYWUsKZ6A,209
16
16
  iolanta/cyberspace/inference/wikibase-statement-property.sparql,sha256=SkSHZZlxWVDwBM3aLo0Q7hLuOj9BsIQnXtcuAuwaxqU,240
17
- iolanta/cyberspace/processor.py,sha256=rxO5MaZEoXD2Kzknq7eRH3yuv8kQ5UILy8qkJxramMY,21828
17
+ iolanta/cyberspace/processor.py,sha256=USGe54XuNMNYYl-Hed1bKxeqZLUhrb1jNRUDbWpay4A,21990
18
18
  iolanta/data/cli.yaml,sha256=TsnldYXoY5GIzoNuPDvwBKGw8eAEForZW1FCKqKI0Kg,1029
19
19
  iolanta/data/context.yaml,sha256=OULEeDkSqshabaXF_gMujgwFLDJvt9eQn_9FftUlSUw,1424
20
20
  iolanta/data/html.yaml,sha256=hVFdLWLy8FMY8xpOrJMYc-tE3S0Nq83xuxVkjRW_7rI,517
21
21
  iolanta/data/iolanta.yaml,sha256=xubIFBNU02lmFXhgOSuyQwUcZD3xCqVfeVAZMvOxKbI,1433
22
- iolanta/data/textual-browser.yaml,sha256=ViMQJLiqa3CHdXluv9Fk3bEtyYC2ezNoHO7kQEp_N7w,3799
22
+ iolanta/data/textual-browser.yaml,sha256=8aGKLN_xnSX5o3dpBLsh8kPMfEsiysOd2QX-pn0Vfd4,4031
23
23
  iolanta/ensure_is_context.py,sha256=9aok8asyEx7KPesOR28VBDb3Ch9kfc3eoCpQSJwj07U,717
24
24
  iolanta/entry_points.py,sha256=DZbf-udlEwELFGqeWENj0M2BOUPOWlmGJdqyaEtnot0,504
25
25
  iolanta/errors.py,sha256=t_RltahnoEvcytVa1BOq2MADgHps3JNd_h5-SFu7_wM,1250
@@ -31,7 +31,7 @@ iolanta/facets/cli/record.py,sha256=lBsECxLkwVSXC-yHmUwfosxAdLBI-0UoqHe8GOCablY,
31
31
  iolanta/facets/cli/sparql/link.sparql,sha256=WtWEfLAvdGc2gP0IhZil6Vglkydc3VO24vk4GwRnR5I,163
32
32
  iolanta/facets/cli/sparql/record.sparql,sha256=GBWkmNelvaSDbvl7v0-LsJHdjFPbsSAW49kNFOUeoGQ,63
33
33
  iolanta/facets/errors.py,sha256=sEBmnzhFcRIgOT18hfNwyMMjry0waa6IB-jC2NVTA50,4124
34
- iolanta/facets/facet.py,sha256=6TAhKxhBLRNII6bjQzOCzqTR1L7zaaB95nliQmVlmJ0,2865
34
+ iolanta/facets/facet.py,sha256=QByUVrvt_XVBoVhfuyk6lKO--pauhMEN79GWmgejdIo,2822
35
35
  iolanta/facets/foaf_person_title/__init__.py,sha256=oj4MNVQvv8Dysb27xiWjtZCii8-nT7-WFa3WMWUwbtU,67
36
36
  iolanta/facets/foaf_person_title/facet.py,sha256=Q9TajjGp1v729quDzAM_Lfzawls3yujp1Z_6jXrG3gY,632
37
37
  iolanta/facets/foaf_person_title/sparql/names.sparql,sha256=p_2hHZXhEaJ8IwGlvLoN0vb0vhGqo44uAVRpDyTzflU,107
@@ -45,7 +45,7 @@ iolanta/facets/html/base.py,sha256=JcpK7YM_QQE9vwH5w5F_EgPkPv-XRzOrcZlVSy4LhIs,1
45
45
  iolanta/facets/html/code_literal.py,sha256=qCddzBrg6Y5XMIKohFQ52Tf9GPOcU7bj83tfAU1FLLU,394
46
46
  iolanta/facets/html/default.py,sha256=Dmf_kYiL2M954iigyYsiWrMstwZ1nvxKetuR6aW3xYY,647
47
47
  iolanta/facets/icon.py,sha256=ddZcolx1Q5_wo3w0jqiCzcc5Lsru6-jA7s7oAd1T8Og,494
48
- iolanta/facets/locator.py,sha256=tFwxGT4ujwEjwkgTevK6gwWfj3_1lU9Q305IU7a-I3Y,7697
48
+ iolanta/facets/locator.py,sha256=0FNISVG_Q4Mk8j6ZX0tmIM-YfnewdKJSf7BYQIwb-lc,8636
49
49
  iolanta/facets/page_title.py,sha256=TwgZK2g_e5UoWYjKNgDzzkmq1EI3cY58680iC8N9kZI,1407
50
50
  iolanta/facets/qname.py,sha256=ztyBbjjcW8dNZzuiNMqhcWfAUxk-gSjbseVGrQE7kVY,531
51
51
  iolanta/facets/textual_browser/__init__.py,sha256=sKgDvXOwib9n9d63kdtKCEv26-FoL0VN6zxDmfcheZ8,104
@@ -86,6 +86,7 @@ iolanta/facets/textual_ontology/__init__.py,sha256=3H6bfYaEbXFr90C6ZpGWC4O-24uaO
86
86
  iolanta/facets/textual_ontology/facets.py,sha256=60g8ANmePb9_i_-d4ui-FdtNwg9aktrOKXHkTQtLp1w,3338
87
87
  iolanta/facets/textual_ontology/sparql/terms.sparql,sha256=oVLxN452nqog_95qRaTWnvar6rxPNxPrRonSo7oFFTg,324
88
88
  iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql,sha256=q9TmU15deL0da28mpo_8W8fgMSEcENfYeqLyM0zVbTg,65
89
+ iolanta/facets/textual_property_pairs_table.py,sha256=Drqc_G_6QhzNmrrfDU170eKTGrVmvQ6JMYu4ir--iUk,4176
89
90
  iolanta/facets/textual_provenance/__init__.py,sha256=k5-_iK8Lrdwr5ZEJaDxq-UhGYe4G_adXVqGfOA5DAP8,114
90
91
  iolanta/facets/textual_provenance/facets.py,sha256=vv3UQsI2duB36DW5Zkw3sqgAXBPmK_xAo7cU0O7jF8g,3767
91
92
  iolanta/facets/textual_provenance/sparql/graphs.sparql,sha256=B45uKFd-1vrBuMDSbTURjUUEjHt51vAbqdL4tUcgMvk,103
@@ -96,7 +97,7 @@ iolanta/facets/title/sparql/title.sparql,sha256=4rz47tjwX2OJavWMzftaYKil1-ZHB76Z
96
97
  iolanta/facets/wikibase_statement_title/__init__.py,sha256=_yk1akxgSJOiUBJIc8QGrD2vovvmx_iw_vJDuv1rD7M,91
97
98
  iolanta/facets/wikibase_statement_title/facets.py,sha256=mUH7twlAgoeX7DgLQuRBQv4ORT6GWbN-0eJ1aliSfiQ,724
98
99
  iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql,sha256=n07DQWxKqB5c3CA4kacq2HSN0R0dLgnMnLP1AxMo5YA,320
99
- iolanta/iolanta.py,sha256=3oxkE3nG8zBE7v46zuO3ftga623D2Nl_M-nMLtVC8Bk,12852
100
+ iolanta/iolanta.py,sha256=ZZ5hBXuVci4v-36xYLeaqDjgo85kEnnYCA07eJMo9ME,14230
100
101
  iolanta/loaders/__init__.py,sha256=QTiKCsQc1BTS-IlY2CQsN9iVpEIPqYFvI9ERMYVZCbU,99
101
102
  iolanta/loaders/base.py,sha256=-DxYwqG1bfDXB2p_S-mKpkc_3Sh14OHhePbe65Iq3-s,3381
102
103
  iolanta/loaders/data_type_choice.py,sha256=zRUXBIzjvuW28P_dhMDVevE9C8EFEIx2_X39WydWrtM,1982
@@ -118,6 +119,7 @@ iolanta/parsers/json.py,sha256=3VrDiz-SaVXP9_B03Gl62VSIk2fKeRrrb2hpTLxqBk4,967
118
119
  iolanta/parsers/markdown.py,sha256=wv-PhszgoDOzWdisykIeZyYBD1edCVmw7UQ8jEn4siw,1669
119
120
  iolanta/parsers/yaml.py,sha256=MeTe5_OaDK27XB38AzxjqWfCxybAm4tqgqn7LLh-YB0,1244
120
121
  iolanta/plugin.py,sha256=MSxpuOIx93AgBahfS8bYh31MEgcwtUSQhj4Js7fgdSI,1096
122
+ iolanta/query_result.py,sha256=VLLBkewUEymtzfB0jeIeRE3Np6pAgo959RPgNsEmiq8,1545
121
123
  iolanta/reformat_blank_nodes.py,sha256=MAVcXusUioKzAoTEHAMume5Gt9vBEpxJGrngqFzmkJI,712
122
124
  iolanta/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
125
  iolanta/resolvers/base.py,sha256=cc9bcrVZ0wTwn85I-IYCwcIRU5Lwaph8D00C0dwSOm8,302
@@ -125,9 +127,7 @@ iolanta/resolvers/python_import.py,sha256=kDOhApBDFfxnrDgK9M7ztMkqXfcny81Zo86FgP
125
127
  iolanta/shortcuts.py,sha256=j8b0E_yeoas8GumsPOfxO2v2jO0noqpmKJ6LFxFfsu4,1892
126
128
  iolanta/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
129
  iolanta/widgets/mixin.py,sha256=nDRCOc-gizCf1a5DAcYs4hW8eZEd6pHBPFsfm0ncv7E,251
128
- ldflex/__init__.py,sha256=8IELqR55CQXuI0BafjobXSK7_kOc-qKVTrEtEwXnZRA,33
129
- ldflex/ldflex.py,sha256=omKmOo5PUyn8Evw4q_lqKCJOq6yqVOcLAYxnYkdwOUs,3332
130
- iolanta-2.0.0.dist-info/METADATA,sha256=Lg09YX2eoU7Dr4AaYDKUl5-978C-m0TeFX53zE0W9l4,2230
131
- iolanta-2.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
132
- iolanta-2.0.0.dist-info/entry_points.txt,sha256=fIp9g4kzjSNcTsTSjUCk4BIG3laHd3b3hUZlkjgFAGU,179
133
- iolanta-2.0.0.dist-info/RECORD,,
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,,
ldflex/__init__.py DELETED
@@ -1 +0,0 @@
1
- from ldflex.ldflex import LDFlex
ldflex/ldflex.py DELETED
@@ -1,138 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Dict, List, Mapping, Optional, Union
3
-
4
- from documented import DocumentedError
5
- from frozendict import frozendict
6
- from pyparsing import ParseException
7
- from rdflib import Graph
8
- from rdflib.plugins.sparql.processor import SPARQLResult
9
- from rdflib.term import Identifier, Node, Variable
10
-
11
- from iolanta.cyberspace.processor import GlobalSPARQLProcessor
12
-
13
- SelectRow = Mapping[str, Node]
14
-
15
-
16
- class SelectResult(List[SelectRow]):
17
- """Result of a SPARQL SELECT."""
18
-
19
- @property
20
- def first(self) -> Optional[SelectRow]:
21
- """Return first element of the list."""
22
- return self[0] if self else None
23
-
24
-
25
- SPARQLQueryArgument = Optional[Union[Node, str, int, float]]
26
-
27
-
28
- QueryResult = Union[
29
- SelectResult, # SELECT
30
- Graph, # CONSTRUCT
31
- bool, # ASK
32
- ]
33
-
34
-
35
- def _format_query_bindings(
36
- bindings: List[Dict[Variable, Identifier]],
37
- ) -> SelectResult:
38
- """
39
- Format bindings before returning them.
40
-
41
- Converts Variable to str for ease of addressing.
42
- """
43
- return SelectResult([
44
- frozendict({
45
- str(variable_name): rdf_value
46
- for variable_name, rdf_value
47
- in row.items()
48
- })
49
- for row in bindings
50
- ])
51
-
52
-
53
- @dataclass
54
- class SPARQLParseException(DocumentedError):
55
- """
56
- SPARQL query is invalid.
57
-
58
- Error:
59
-
60
- ```
61
- {self.error}
62
- ```
63
-
64
- Query:
65
- ```sparql hl_lines="{self.highlight_code}"
66
- {self.query}
67
- ```
68
- """
69
-
70
- error: ParseException
71
- query: str
72
-
73
- @property
74
- def highlight_code(self):
75
- """Define lines to highlight."""
76
- return self.error.lineno
77
-
78
-
79
- @dataclass
80
- class LDFlex:
81
- """Fluent interface to a semantic graph."""
82
-
83
- graph: Graph
84
-
85
- def query(
86
- self,
87
- query_text: str,
88
- **kwargs: SPARQLQueryArgument,
89
- ) -> QueryResult:
90
- """
91
- Run a SPARQL `SELECT`, `CONSTRUCT`, or `ASK` query.
92
-
93
- Args:
94
- query_text: The SPARQL text;
95
- **kwargs: bind variables in the query to values if necessary. For
96
- example:
97
-
98
- ```python
99
- ldflex.query(
100
- 'SELECT ?title WHERE { ?page rdfs:label ?title }',
101
- ?page=page_iri,
102
- )
103
- ```
104
-
105
- Returns:
106
- Results of the query:
107
-
108
- - a graph for `CONSTRUCT`,
109
- - a list of dicts for `SELECT`,
110
- - or a boolean for `ASK`.
111
- """
112
- try:
113
- sparql_result: SPARQLResult = self.graph.query(
114
- query_text,
115
- processor='cyberspace',
116
- initBindings=kwargs,
117
- )
118
- except ParseException as err:
119
- raise SPARQLParseException(
120
- error=err,
121
- query=query_text,
122
- ) from err
123
-
124
- if sparql_result.askAnswer is not None:
125
- return sparql_result.askAnswer
126
-
127
- if sparql_result.graph is not None:
128
- graph: Graph = sparql_result.graph
129
- for prefix, namespace in self.graph.namespaces():
130
- graph.bind(prefix, namespace)
131
-
132
- return graph
133
-
134
- return _format_query_bindings(sparql_result.bindings)
135
-
136
- def update(self, sparql_query: str):
137
- """Apply the given SPARQL INSERT query."""
138
- self.graph.update(sparql_query)