iolanta 2.0.1__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.
@@ -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
@@ -20,6 +20,11 @@ class Facet(Generic[FacetOutput]):
20
20
  iolanta: 'iolanta.Iolanta' = field(repr=False)
21
21
  as_datatype: Optional[NotLiteralNode] = None
22
22
 
23
+ @property
24
+ def this(self) -> NotLiteralNode:
25
+ """This node."""
26
+ return self.iri
27
+
23
28
  @property
24
29
  def stored_queries_path(self) -> Path:
25
30
  """Construct directory for stored queries for this facet."""
iolanta/facets/locator.py CHANGED
@@ -15,7 +15,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
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -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=jDbtdcu-qhT9zrXVLw_wMkApTlk0xKm2aCLRpd7cfNA,2720
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
@@ -126,7 +127,7 @@ iolanta/resolvers/python_import.py,sha256=kDOhApBDFfxnrDgK9M7ztMkqXfcny81Zo86FgP
126
127
  iolanta/shortcuts.py,sha256=j8b0E_yeoas8GumsPOfxO2v2jO0noqpmKJ6LFxFfsu4,1892
127
128
  iolanta/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
129
  iolanta/widgets/mixin.py,sha256=nDRCOc-gizCf1a5DAcYs4hW8eZEd6pHBPFsfm0ncv7E,251
129
- iolanta-2.0.1.dist-info/METADATA,sha256=pcU438uX93qgFW-t4I6fQwDMJcM62G1wXO8FtLxg9zM,2230
130
- iolanta-2.0.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
131
- iolanta-2.0.1.dist-info/entry_points.txt,sha256=fIp9g4kzjSNcTsTSjUCk4BIG3laHd3b3hUZlkjgFAGU,179
132
- iolanta-2.0.1.dist-info/RECORD,,
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,,