iolanta 2.0.6__py3-none-any.whl → 2.0.8__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/__init__.py +0 -1
- iolanta/facets/facet.py +0 -7
- iolanta/facets/textual_default/widgets.py +1 -1
- iolanta/facets/textual_no_facet_found.py +33 -1
- iolanta/facets/title/facets.py +1 -0
- iolanta/facets/title/sparql/title.sparql +6 -1
- iolanta/iolanta.py +3 -101
- iolanta/models.py +25 -6
- iolanta/namespaces.py +2 -0
- iolanta/parse_quads.py +91 -23
- iolanta/sparqlspace/processor.py +36 -28
- iolanta/widgets/description.py +22 -3
- {iolanta-2.0.6.dist-info → iolanta-2.0.8.dist-info}/METADATA +2 -2
- {iolanta-2.0.6.dist-info → iolanta-2.0.8.dist-info}/RECORD +16 -33
- iolanta/loaders/__init__.py +0 -2
- iolanta/loaders/base.py +0 -124
- iolanta/loaders/data_type_choice.py +0 -66
- iolanta/loaders/dict_loader.py +0 -57
- iolanta/loaders/errors.py +0 -29
- iolanta/loaders/http.py +0 -127
- iolanta/loaders/local_directory.py +0 -148
- iolanta/loaders/local_file.py +0 -107
- iolanta/loaders/scheme_choice.py +0 -72
- iolanta/parsers/__init__.py +0 -0
- iolanta/parsers/base.py +0 -41
- iolanta/parsers/dict_parser.py +0 -171
- iolanta/parsers/errors.py +0 -35
- iolanta/parsers/json.py +0 -35
- iolanta/parsers/markdown.py +0 -58
- iolanta/parsers/yaml.py +0 -46
- iolanta/shortcuts.py +0 -63
- {iolanta-2.0.6.dist-info → iolanta-2.0.8.dist-info}/WHEEL +0 -0
- {iolanta-2.0.6.dist-info → iolanta-2.0.8.dist-info}/entry_points.txt +0 -0
iolanta/__init__.py
CHANGED
iolanta/facets/facet.py
CHANGED
|
@@ -85,13 +85,6 @@ class Facet(Generic[FacetOutput]):
|
|
|
85
85
|
"""Preferred language for Iolanta output."""
|
|
86
86
|
return self.iolanta.language
|
|
87
87
|
|
|
88
|
-
def find_triple(
|
|
89
|
-
self,
|
|
90
|
-
triple: TripleTemplate,
|
|
91
|
-
) -> Triple | None:
|
|
92
|
-
"""Lightweight procedure to find a triple by template."""
|
|
93
|
-
return self.iolanta.find_triple(triple_template=triple)
|
|
94
|
-
|
|
95
88
|
@cached_property
|
|
96
89
|
def logger(self):
|
|
97
90
|
"""Logger."""
|
|
@@ -291,7 +291,7 @@ class LiteralPropertyValue(Widget, can_focus=True, inherit_bindings=False):
|
|
|
291
291
|
self.property_iri = property_iri
|
|
292
292
|
super().__init__()
|
|
293
293
|
self.renderable = Text( # noqa: WPS601
|
|
294
|
-
str(property_value
|
|
294
|
+
str(property_value),
|
|
295
295
|
style='#696969',
|
|
296
296
|
)
|
|
297
297
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
import funcy
|
|
3
4
|
from rich.markdown import Markdown
|
|
4
5
|
from textual.containers import Vertical
|
|
5
6
|
from yarl import URL
|
|
@@ -15,6 +16,7 @@ TEXT = """
|
|
|
15
16
|
* Or, no edges might exist which involve it;
|
|
16
17
|
* Or maybe Iolanta does not know of such edges.
|
|
17
18
|
{content}
|
|
19
|
+
{subgraphs}
|
|
18
20
|
**What can you do?**
|
|
19
21
|
|
|
20
22
|
* If you feel this might indicate a bug 🐛, please do let us know at GitHub
|
|
@@ -29,6 +31,12 @@ CONTENT_TEMPLATE = """
|
|
|
29
31
|
```
|
|
30
32
|
"""
|
|
31
33
|
|
|
34
|
+
SUBGRAPHS_TEMPLATE = """
|
|
35
|
+
**Subgraphs**
|
|
36
|
+
|
|
37
|
+
{formatted_subgraphs}
|
|
38
|
+
"""
|
|
39
|
+
|
|
32
40
|
|
|
33
41
|
class TextualNoFacetFound(Facet):
|
|
34
42
|
"""Facet to handle the case when no facet is found."""
|
|
@@ -55,14 +63,38 @@ class TextualNoFacetFound(Facet):
|
|
|
55
63
|
content=file_content,
|
|
56
64
|
type={
|
|
57
65
|
'.yamlld': 'yaml',
|
|
66
|
+
'.jsonld': 'json',
|
|
58
67
|
}.get(path.suffix, ''),
|
|
59
68
|
)
|
|
60
69
|
|
|
70
|
+
@property
|
|
71
|
+
def subgraphs_description(self) -> str:
|
|
72
|
+
"""Return a formatted description of subgraphs, if any exist."""
|
|
73
|
+
rows = self.query(
|
|
74
|
+
'SELECT ?subgraph WHERE { $this iolanta:has-sub-graph ?subgraph }',
|
|
75
|
+
this=self.this,
|
|
76
|
+
)
|
|
77
|
+
subgraphs = funcy.lpluck('subgraph', rows)
|
|
78
|
+
if subgraphs:
|
|
79
|
+
return SUBGRAPHS_TEMPLATE.format(
|
|
80
|
+
formatted_subgraphs='\n'.join([
|
|
81
|
+
f'- {subgraph}'
|
|
82
|
+
for subgraph in subgraphs
|
|
83
|
+
]),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return ''
|
|
87
|
+
|
|
61
88
|
def show(self):
|
|
62
89
|
"""Compose the page."""
|
|
63
90
|
return Vertical(
|
|
64
91
|
PageTitle(self.this),
|
|
65
92
|
Description(
|
|
66
|
-
Markdown(
|
|
93
|
+
Markdown(
|
|
94
|
+
TEXT.format(
|
|
95
|
+
content=self.raw_content or '',
|
|
96
|
+
subgraphs=self.subgraphs_description or '',
|
|
97
|
+
),
|
|
98
|
+
),
|
|
67
99
|
),
|
|
68
100
|
)
|
iolanta/facets/title/facets.py
CHANGED
|
@@ -23,4 +23,9 @@ SELECT * WHERE {
|
|
|
23
23
|
$iri <https://xmlns.com/foaf/0.1/name> ?foaf_name .
|
|
24
24
|
FILTER (!lang(?foaf_name) || lang(?foaf_name) = $language)
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
OPTIONAL {
|
|
28
|
+
$iri <https://www.w3.org/2008/05/skos-xl#literalForm> ?literal_form .
|
|
29
|
+
FILTER (!lang(?literal_form) || lang(?literal_form) = $language)
|
|
30
|
+
}
|
|
31
|
+
}
|
iolanta/iolanta.py
CHANGED
|
@@ -4,22 +4,19 @@ from pathlib import Path
|
|
|
4
4
|
from typing import ( # noqa: WPS235
|
|
5
5
|
Annotated,
|
|
6
6
|
Any,
|
|
7
|
-
Dict,
|
|
8
7
|
Iterable,
|
|
9
8
|
List,
|
|
10
9
|
Mapping,
|
|
11
10
|
Optional,
|
|
12
11
|
Protocol,
|
|
13
12
|
Set,
|
|
14
|
-
Tuple,
|
|
15
13
|
Type,
|
|
16
14
|
)
|
|
17
15
|
|
|
18
|
-
import funcy
|
|
19
16
|
import loguru
|
|
20
17
|
import yaml_ld
|
|
21
18
|
from pyparsing import ParseException
|
|
22
|
-
from rdflib import ConjunctiveGraph, Graph, Literal,
|
|
19
|
+
from rdflib import ConjunctiveGraph, Graph, Literal, URIRef
|
|
23
20
|
from rdflib.namespace import NamespaceManager
|
|
24
21
|
from rdflib.plugins.sparql.processor import SPARQLResult
|
|
25
22
|
from rdflib.term import Node
|
|
@@ -32,18 +29,9 @@ from iolanta.errors import UnresolvedIRI
|
|
|
32
29
|
from iolanta.facets.errors import FacetError
|
|
33
30
|
from iolanta.facets.facet import Facet
|
|
34
31
|
from iolanta.facets.locator import FacetFinder
|
|
35
|
-
from iolanta.
|
|
36
|
-
from iolanta.loaders.local_directory import merge_contexts
|
|
37
|
-
from iolanta.models import (
|
|
38
|
-
ComputedQName,
|
|
39
|
-
LDContext,
|
|
40
|
-
NotLiteralNode,
|
|
41
|
-
Triple,
|
|
42
|
-
TripleTemplate,
|
|
43
|
-
)
|
|
32
|
+
from iolanta.models import ComputedQName, LDContext, NotLiteralNode
|
|
44
33
|
from iolanta.node_to_qname import node_to_qname
|
|
45
34
|
from iolanta.parse_quads import parse_quads
|
|
46
|
-
from iolanta.parsers.yaml import YAML
|
|
47
35
|
from iolanta.plugin import Plugin
|
|
48
36
|
from iolanta.query_result import (
|
|
49
37
|
QueryResult,
|
|
@@ -103,12 +91,6 @@ class Iolanta: # noqa: WPS214
|
|
|
103
91
|
|
|
104
92
|
logger: LoggerProtocol = loguru.logger
|
|
105
93
|
|
|
106
|
-
sources_added_not_yet_inferred: list[SourceType] = field(
|
|
107
|
-
default_factory=list,
|
|
108
|
-
init=False,
|
|
109
|
-
repr=False,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
94
|
could_not_retrieve_nodes: Set[Node] = field(
|
|
113
95
|
default_factory=set,
|
|
114
96
|
init=False,
|
|
@@ -182,23 +164,6 @@ class Iolanta: # noqa: WPS214
|
|
|
182
164
|
|
|
183
165
|
return format_query_bindings(sparql_result.bindings)
|
|
184
166
|
|
|
185
|
-
@functools.cached_property
|
|
186
|
-
def namespaces_to_bind(self) -> Dict[str, Namespace]:
|
|
187
|
-
"""
|
|
188
|
-
Namespaces globally specified for the graph.
|
|
189
|
-
|
|
190
|
-
FIXME: Probably get rid of this, I do not know.
|
|
191
|
-
"""
|
|
192
|
-
return {
|
|
193
|
-
key: Namespace(value)
|
|
194
|
-
for key, value in self.default_context['@context'].items() # noqa
|
|
195
|
-
if (
|
|
196
|
-
isinstance(value, str)
|
|
197
|
-
and not value.startswith('@') # noqa: W503
|
|
198
|
-
and not key.startswith('@') # noqa: W503
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
167
|
def reset(self):
|
|
203
168
|
"""Reset Iolanta graph."""
|
|
204
169
|
self.graph = _create_default_graph() # noqa: WPS601
|
|
@@ -212,7 +177,6 @@ class Iolanta: # noqa: WPS214
|
|
|
212
177
|
) -> 'Iolanta':
|
|
213
178
|
"""Parse & load information from given URL into the graph."""
|
|
214
179
|
self.logger.info(f'Adding to graph: {source}')
|
|
215
|
-
self.sources_added_not_yet_inferred.append(source)
|
|
216
180
|
|
|
217
181
|
if not isinstance(source, Path):
|
|
218
182
|
source = Path(source)
|
|
@@ -262,17 +226,7 @@ class Iolanta: # noqa: WPS214
|
|
|
262
226
|
self.logger.info(f'{source_file} | No data found')
|
|
263
227
|
continue
|
|
264
228
|
|
|
265
|
-
|
|
266
|
-
tuple([
|
|
267
|
-
normalize_term(term) for term in replace(
|
|
268
|
-
quad,
|
|
269
|
-
graph=graph,
|
|
270
|
-
).as_tuple()
|
|
271
|
-
])
|
|
272
|
-
for quad in quads
|
|
273
|
-
]
|
|
274
|
-
|
|
275
|
-
self.graph.addN(quad_tuples)
|
|
229
|
+
self.graph.addN(quads)
|
|
276
230
|
|
|
277
231
|
return self
|
|
278
232
|
|
|
@@ -317,20 +271,6 @@ class Iolanta: # noqa: WPS214
|
|
|
317
271
|
if path := plugin.context_path:
|
|
318
272
|
yield path
|
|
319
273
|
|
|
320
|
-
@functools.cached_property
|
|
321
|
-
def default_context(self) -> LDContext:
|
|
322
|
-
"""Construct default context from plugins."""
|
|
323
|
-
context_documents = [
|
|
324
|
-
YAML().as_jsonld_document(path.open('r'))
|
|
325
|
-
for path in self.context_paths
|
|
326
|
-
]
|
|
327
|
-
|
|
328
|
-
for context in context_documents:
|
|
329
|
-
if isinstance(context, list):
|
|
330
|
-
raise ValueError('Context cannot be a list: %s', context)
|
|
331
|
-
|
|
332
|
-
return merge_contexts(*context_documents) # type: ignore
|
|
333
|
-
|
|
334
274
|
def add_files_from_plugins(self):
|
|
335
275
|
"""
|
|
336
276
|
Load files from plugins.
|
|
@@ -434,44 +374,6 @@ class Iolanta: # noqa: WPS214
|
|
|
434
374
|
error=err,
|
|
435
375
|
) from err
|
|
436
376
|
|
|
437
|
-
def retrieve_triple(self, triple_template: TripleTemplate) -> Triple:
|
|
438
|
-
"""Retrieve remote data to project directory."""
|
|
439
|
-
for plugin in self.plugins:
|
|
440
|
-
# FIXME Parallelization?
|
|
441
|
-
plugin.retrieve_triple(triple_template)
|
|
442
|
-
|
|
443
|
-
if not downloaded_files:
|
|
444
|
-
self.could_not_retrieve_nodes.add(node)
|
|
445
|
-
|
|
446
|
-
for path in downloaded_files:
|
|
447
|
-
self.add(path)
|
|
448
|
-
|
|
449
|
-
return self
|
|
450
|
-
|
|
451
|
-
def maybe_infer(self):
|
|
452
|
-
"""
|
|
453
|
-
Apply inference lazily.
|
|
454
|
-
|
|
455
|
-
Only run inference if there are new files added after last inference.
|
|
456
|
-
"""
|
|
457
|
-
if self.sources_added_not_yet_inferred:
|
|
458
|
-
self.infer()
|
|
459
|
-
|
|
460
|
-
def find_triple(
|
|
461
|
-
self,
|
|
462
|
-
triple_template: TripleTemplate,
|
|
463
|
-
) -> Triple | None:
|
|
464
|
-
"""Lightweight procedure to find a triple by template."""
|
|
465
|
-
triples = self.graph.triples(
|
|
466
|
-
(triple_template.subject, triple_template.predicate, triple_template.object),
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
raw_triple = funcy.first(triples)
|
|
470
|
-
if raw_triple:
|
|
471
|
-
return Triple(*raw_triple)
|
|
472
|
-
|
|
473
|
-
return self.retrieve_triple(triple_template)
|
|
474
|
-
|
|
475
377
|
def node_as_qname(self, node: Node):
|
|
476
378
|
"""
|
|
477
379
|
Render node as a QName if possible.
|
iolanta/models.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import re
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import Any, Dict, List, NamedTuple, Union
|
|
4
4
|
|
|
@@ -82,8 +82,14 @@ class TripleTemplate(NamedTuple):
|
|
|
82
82
|
object: Node | None
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
def _normalize_term(term: Node):
|
|
86
|
+
if isinstance(term, URIRef) and term.startswith('http://'):
|
|
87
|
+
return URIRef(re.sub('^http', 'https', term))
|
|
88
|
+
|
|
89
|
+
return term
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Quad(NamedTuple):
|
|
87
93
|
"""Triple assigned to a named graph."""
|
|
88
94
|
|
|
89
95
|
subject: Node
|
|
@@ -107,6 +113,19 @@ class Quad:
|
|
|
107
113
|
f'{rendered_graph})' # noqa: WPS326
|
|
108
114
|
)
|
|
109
115
|
|
|
110
|
-
def
|
|
111
|
-
"""
|
|
112
|
-
|
|
116
|
+
def replace(self, mapping: dict[Node, URIRef]):
|
|
117
|
+
"""Replace variables in the quad."""
|
|
118
|
+
terms = [
|
|
119
|
+
mapping.get(term, term)
|
|
120
|
+
for term in self
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
return Quad(*terms)
|
|
124
|
+
|
|
125
|
+
def normalize(self) -> 'Quad':
|
|
126
|
+
"""Normalize the quad by applying normalization to all its terms."""
|
|
127
|
+
terms = [
|
|
128
|
+
_normalize_term(term)
|
|
129
|
+
for term in self
|
|
130
|
+
]
|
|
131
|
+
return Quad(*terms)
|
iolanta/namespaces.py
CHANGED
|
@@ -8,6 +8,8 @@ NP = rdflib.Namespace('https://www.nanopub.org/nschema#')
|
|
|
8
8
|
RDFG = rdflib.Namespace('https://www.w3.org/2004/03/trix/rdfg-1/')
|
|
9
9
|
SDO = rdflib.SDO
|
|
10
10
|
|
|
11
|
+
META = rdflib.URIRef('iolanta://_meta')
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class DC(rdflib.DC):
|
|
13
15
|
_NS = rdflib.Namespace('https://purl.org/dc/elements/1.1/')
|
iolanta/parse_quads.py
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import hashlib
|
|
3
|
-
from
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
from typing import Iterable, Optional
|
|
5
|
+
from urllib.parse import unquote
|
|
4
6
|
|
|
7
|
+
from documented import DocumentedError
|
|
5
8
|
from rdflib import BNode, Literal, URIRef
|
|
6
9
|
from rdflib.term import Node
|
|
7
10
|
|
|
8
11
|
from iolanta.errors import UnresolvedIRI
|
|
9
12
|
from iolanta.models import Quad
|
|
10
|
-
from iolanta.namespaces import IOLANTA,
|
|
11
|
-
|
|
13
|
+
from iolanta.namespaces import IOLANTA, META
|
|
14
|
+
|
|
15
|
+
NORMALIZE_TERMS_MAP = MappingProxyType({
|
|
16
|
+
URIRef(_url := 'https://www.w3.org/2002/07/owl'): URIRef(f'{_url}#'),
|
|
17
|
+
URIRef(_url := 'https://www.w3.org/2000/01/rdf-schema'): URIRef(f'{_url}#'),
|
|
18
|
+
})
|
|
12
19
|
|
|
13
20
|
|
|
14
21
|
def parse_term( # noqa: C901
|
|
@@ -23,7 +30,7 @@ def parse_term( # noqa: C901
|
|
|
23
30
|
term_value = term['value']
|
|
24
31
|
|
|
25
32
|
if term_type == 'IRI':
|
|
26
|
-
return URIRef(term_value)
|
|
33
|
+
return URIRef(unquote(term_value))
|
|
27
34
|
|
|
28
35
|
if term_type == 'literal':
|
|
29
36
|
language = term.get('language')
|
|
@@ -48,6 +55,40 @@ def parse_term( # noqa: C901
|
|
|
48
55
|
raise ValueError(f'Unknown term: {term}')
|
|
49
56
|
|
|
50
57
|
|
|
58
|
+
def construct_subgraph_name(subgraph_name: str, graph: URIRef) -> URIRef:
|
|
59
|
+
"""
|
|
60
|
+
Construct a proper subgraph name URI from a base name and graph.
|
|
61
|
+
|
|
62
|
+
If the subgraph name already starts with the graph URI, return it as is.
|
|
63
|
+
Otherwise, append the name as a fragment to the graph URI.
|
|
64
|
+
"""
|
|
65
|
+
if subgraph_name.startswith(str(graph)):
|
|
66
|
+
return URIRef(subgraph_name)
|
|
67
|
+
|
|
68
|
+
return URIRef(f'{graph}#{subgraph_name}')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _parse_quads_per_subgraph(
|
|
72
|
+
raw_quads,
|
|
73
|
+
blank_node_prefix: str,
|
|
74
|
+
graph: URIRef,
|
|
75
|
+
subgraph: URIRef,
|
|
76
|
+
) -> Iterable[Quad]:
|
|
77
|
+
for quad in raw_quads:
|
|
78
|
+
try:
|
|
79
|
+
yield Quad(
|
|
80
|
+
subject=parse_term(quad['subject'], blank_node_prefix),
|
|
81
|
+
predicate=parse_term(quad['predicate'], blank_node_prefix),
|
|
82
|
+
object=parse_term(quad['object'], blank_node_prefix),
|
|
83
|
+
graph=subgraph,
|
|
84
|
+
)
|
|
85
|
+
except SpaceInProperty as err:
|
|
86
|
+
raise dataclasses.replace(
|
|
87
|
+
err,
|
|
88
|
+
iri=graph,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
51
92
|
def parse_quads(
|
|
52
93
|
quads_document,
|
|
53
94
|
graph: URIRef,
|
|
@@ -59,33 +100,44 @@ def parse_quads(
|
|
|
59
100
|
).hexdigest()
|
|
60
101
|
blank_node_prefix = f'_:{blank_node_prefix}'
|
|
61
102
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
subgraph_names = {
|
|
104
|
+
URIRef(subgraph_name): construct_subgraph_name(
|
|
105
|
+
subgraph_name,
|
|
106
|
+
graph=graph,
|
|
107
|
+
)
|
|
108
|
+
for subgraph_name in quads_document.keys()
|
|
109
|
+
if subgraph_name != '@default'
|
|
110
|
+
}
|
|
111
|
+
subgraph_names[graph] = graph
|
|
112
|
+
|
|
113
|
+
for subgraph, quads in quads_document.items():
|
|
114
|
+
if subgraph == '@default':
|
|
115
|
+
subgraph = graph # noqa: WPS440
|
|
65
116
|
|
|
66
117
|
else:
|
|
67
|
-
|
|
118
|
+
subgraph = URIRef(subgraph)
|
|
68
119
|
|
|
69
120
|
yield Quad(
|
|
70
121
|
graph,
|
|
71
122
|
IOLANTA['has-sub-graph'],
|
|
72
|
-
|
|
73
|
-
|
|
123
|
+
subgraph_names[subgraph],
|
|
124
|
+
META,
|
|
74
125
|
)
|
|
75
126
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
127
|
+
quads = _parse_quads_per_subgraph(
|
|
128
|
+
quads,
|
|
129
|
+
blank_node_prefix=blank_node_prefix,
|
|
130
|
+
graph=subgraph,
|
|
131
|
+
subgraph=subgraph_names[subgraph],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
for quad in quads: # noqa: WPS526
|
|
135
|
+
yield quad.replace(
|
|
136
|
+
subgraph_names | NORMALIZE_TERMS_MAP | {
|
|
137
|
+
# To enable nanopub rendering
|
|
138
|
+
URIRef('http://purl.org/nanopub/temp/np/'): graph,
|
|
139
|
+
},
|
|
140
|
+
).normalize()
|
|
89
141
|
|
|
90
142
|
|
|
91
143
|
def raise_if_term_is_qname(term_value: str):
|
|
@@ -102,3 +154,19 @@ def raise_if_term_is_qname(term_value: str):
|
|
|
102
154
|
iri=term_value,
|
|
103
155
|
prefix=prefix,
|
|
104
156
|
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclasses.dataclass
|
|
160
|
+
class SpaceInProperty(DocumentedError):
|
|
161
|
+
"""
|
|
162
|
+
Space in property.
|
|
163
|
+
|
|
164
|
+
That impedes JSON-LD parsing.
|
|
165
|
+
|
|
166
|
+
Please do not use spaces in property names in JSON or YAML data; use `title`
|
|
167
|
+
or other methods instead.
|
|
168
|
+
|
|
169
|
+
Document IRI: {self.iri}
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
iri: Optional[URIRef] = None
|
iolanta/sparqlspace/processor.py
CHANGED
|
@@ -24,8 +24,7 @@ from rdflib.plugins.sparql.parserutils import CompValue
|
|
|
24
24
|
from rdflib.plugins.sparql.sparql import Query
|
|
25
25
|
from rdflib.query import Processor
|
|
26
26
|
from rdflib.term import BNode, Literal, Node
|
|
27
|
-
from requests import
|
|
28
|
-
from requests.exceptions import ConnectionError
|
|
27
|
+
from requests.exceptions import ConnectionError, InvalidSchema
|
|
29
28
|
from yaml_ld.document_loaders.content_types import ParserNotFound
|
|
30
29
|
from yaml_ld.errors import NotFound, YAMLLDError
|
|
31
30
|
from yarl import URL
|
|
@@ -36,19 +35,14 @@ from iolanta.namespaces import ( # noqa: WPS235
|
|
|
36
35
|
DCTERMS,
|
|
37
36
|
FOAF,
|
|
38
37
|
IOLANTA,
|
|
38
|
+
META,
|
|
39
39
|
OWL,
|
|
40
40
|
PROV,
|
|
41
41
|
RDF,
|
|
42
42
|
RDFS,
|
|
43
43
|
VANN,
|
|
44
44
|
)
|
|
45
|
-
from iolanta.parse_quads import parse_quads
|
|
46
|
-
|
|
47
|
-
NORMALIZE_TERMS_MAP = MappingProxyType({
|
|
48
|
-
URIRef(_url := 'https://www.w3.org/2002/07/owl'): URIRef(f'{_url}#'),
|
|
49
|
-
URIRef(_url := 'https://www.w3.org/2000/01/rdf-schema'): URIRef(f'{_url}#'),
|
|
50
|
-
})
|
|
51
|
-
|
|
45
|
+
from iolanta.parse_quads import NORMALIZE_TERMS_MAP, parse_quads
|
|
52
46
|
|
|
53
47
|
REASONING_ENABLED = True
|
|
54
48
|
OWL_REASONING_ENABLED = False
|
|
@@ -78,6 +72,10 @@ REDIRECTS = MappingProxyType({
|
|
|
78
72
|
'https://nanopub.net/nschema#',
|
|
79
73
|
),
|
|
80
74
|
URIRef(PROV): URIRef('https://www.w3.org/ns/prov-o'),
|
|
75
|
+
|
|
76
|
+
# Convert lexvo.org/id URLs to lexvo.org/data URLs
|
|
77
|
+
r'https://lexvo\.org/id/(.+)': r'http://lexvo.org/data/\1',
|
|
78
|
+
r'https://www\.lexinfo\.net/(.+)': r'http://www.lexinfo.net/\1',
|
|
81
79
|
})
|
|
82
80
|
|
|
83
81
|
|
|
@@ -94,9 +92,7 @@ def find_retractions_for(nanopublication: URIRef) -> set[URIRef]:
|
|
|
94
92
|
# context of this dirty hack.
|
|
95
93
|
use_server = 'http://grlc.nanopubs.lod.labs.vu.nl/api/local/local/'
|
|
96
94
|
|
|
97
|
-
client = NanopubClient(
|
|
98
|
-
use_server=use_server,
|
|
99
|
-
)
|
|
95
|
+
client = NanopubClient(use_server=use_server)
|
|
100
96
|
client.grlc_urls = [use_server]
|
|
101
97
|
|
|
102
98
|
http_url = str(nanopublication).replace(
|
|
@@ -106,7 +102,7 @@ def find_retractions_for(nanopublication: URIRef) -> set[URIRef]:
|
|
|
106
102
|
|
|
107
103
|
try:
|
|
108
104
|
retractions = client.find_retractions_of(http_url)
|
|
109
|
-
except HTTPError:
|
|
105
|
+
except (requests.HTTPError, InvalidSchema):
|
|
110
106
|
return set()
|
|
111
107
|
|
|
112
108
|
return {URIRef(retraction) for retraction in retractions}
|
|
@@ -289,11 +285,30 @@ def _extract_nanopublication_uris(
|
|
|
289
285
|
)
|
|
290
286
|
|
|
291
287
|
|
|
292
|
-
def apply_redirect(source: URIRef) -> URIRef:
|
|
293
|
-
"""
|
|
288
|
+
def apply_redirect(source: URIRef) -> URIRef: # noqa: WPS210
|
|
289
|
+
"""
|
|
290
|
+
Rewrite the URL using regex patterns and group substitutions.
|
|
291
|
+
|
|
292
|
+
For each pattern in REDIRECTS:
|
|
293
|
+
- If the pattern matches the source URI
|
|
294
|
+
- Replace the source with the destination, substituting any regex groups
|
|
295
|
+
"""
|
|
296
|
+
source_str = str(source)
|
|
297
|
+
|
|
294
298
|
for pattern, destination in REDIRECTS.items():
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
pattern_str = str(pattern)
|
|
300
|
+
destination_str = str(destination)
|
|
301
|
+
|
|
302
|
+
match = re.match(pattern_str, source_str)
|
|
303
|
+
if match:
|
|
304
|
+
# Replace any group references in the destination
|
|
305
|
+
# (like \1, \2, etc.)
|
|
306
|
+
redirected_uri = re.sub(
|
|
307
|
+
pattern_str,
|
|
308
|
+
destination_str,
|
|
309
|
+
source_str,
|
|
310
|
+
)
|
|
311
|
+
return URIRef(redirected_uri)
|
|
297
312
|
|
|
298
313
|
return source
|
|
299
314
|
|
|
@@ -331,7 +346,7 @@ class NanopubQueryPlugin:
|
|
|
331
346
|
if potential_type == original_RDF.type:
|
|
332
347
|
yield potential_class
|
|
333
348
|
|
|
334
|
-
@funcy.retry(errors=HTTPError, tries=3, timeout=3)
|
|
349
|
+
@funcy.retry(errors=requests.HTTPError, tries=3, timeout=3)
|
|
335
350
|
def _load_instances(self, class_uri: URIRef):
|
|
336
351
|
"""
|
|
337
352
|
Load instances from Nanopub Registry.
|
|
@@ -537,7 +552,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
|
|
|
537
552
|
uri,
|
|
538
553
|
IOLANTA['last-loaded-time'],
|
|
539
554
|
None,
|
|
540
|
-
|
|
555
|
+
META,
|
|
541
556
|
)),
|
|
542
557
|
) is not None
|
|
543
558
|
|
|
@@ -546,7 +561,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
|
|
|
546
561
|
uri,
|
|
547
562
|
IOLANTA['last-loaded-time'],
|
|
548
563
|
Literal(datetime.datetime.now()),
|
|
549
|
-
|
|
564
|
+
META,
|
|
550
565
|
))
|
|
551
566
|
|
|
552
567
|
def _follow_is_visualized_with_links(self, uri: URIRef):
|
|
@@ -692,14 +707,7 @@ class GlobalSPARQLProcessor(Processor): # noqa: WPS338, WPS214
|
|
|
692
707
|
self.logger.info('{source} | No data found', source=source)
|
|
693
708
|
return Loaded()
|
|
694
709
|
|
|
695
|
-
|
|
696
|
-
tuple([
|
|
697
|
-
normalize_term(term) for term in quad.as_tuple()
|
|
698
|
-
])
|
|
699
|
-
for quad in quads
|
|
700
|
-
]
|
|
701
|
-
|
|
702
|
-
self.graph.addN(quad_tuples)
|
|
710
|
+
self.graph.addN(quads)
|
|
703
711
|
self.graph.last_not_inferred_source = source
|
|
704
712
|
|
|
705
713
|
into_graphs = ', '.join({
|
iolanta/widgets/description.py
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
|
+
from rich.markdown import Markdown
|
|
2
|
+
from textual.containers import VerticalScroll
|
|
1
3
|
from textual.widgets import Label
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
class Description(
|
|
6
|
+
class Description(VerticalScroll):
|
|
5
7
|
"""Free form textual description."""
|
|
6
8
|
|
|
7
9
|
DEFAULT_CSS = """
|
|
8
10
|
Description {
|
|
9
|
-
padding:
|
|
10
|
-
padding-
|
|
11
|
+
padding: 4;
|
|
12
|
+
padding-top: 1;
|
|
13
|
+
padding-bottom: 1;
|
|
14
|
+
max-height: 100%;
|
|
11
15
|
}
|
|
12
16
|
""" # noqa: WPS115
|
|
17
|
+
|
|
18
|
+
def __init__(self, renderable: str | Markdown):
|
|
19
|
+
"""
|
|
20
|
+
Initialize a Description widget with a renderable content.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
renderable: Content to display, either as a string or
|
|
24
|
+
Markdown object
|
|
25
|
+
"""
|
|
26
|
+
self.renderable = renderable
|
|
27
|
+
super().__init__()
|
|
28
|
+
|
|
29
|
+
def compose(self):
|
|
30
|
+
"""Build and return the widget's component structure."""
|
|
31
|
+
yield Label(self.renderable)
|