iolanta 2.0.7__py3-none-any.whl → 2.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
iolanta/cli/main.py CHANGED
@@ -136,4 +136,4 @@ def render_command( # noqa: WPS231, WPS238, WPS210, C901
136
136
  raise Exit(1)
137
137
 
138
138
  else:
139
- Console().print(renderable)
139
+ print(renderable)
@@ -1,6 +1,6 @@
1
1
  "@context": context.yaml
2
- $id: python://iolanta.facets.textual_graph.GraphFacet
2
+ $id: pkg:pypi/iolanta#textual-graph
3
3
  $: Graph Triples
4
4
  →: https://iolanta.tech/cli/textual
5
- ⪯: python://iolanta.facets.textual_default.InverseProperties
5
+ ⪯: pkg:pypi/iolanta#textual-inverse-properties
6
6
  ↦: ASK WHERE { GRAPH $this { ?s ?p ?o } }
@@ -30,6 +30,8 @@
30
30
  "@id": iolanta:is-preferred-over
31
31
 
32
32
  ↦: iolanta:matches
33
+ iolanta:hasDefaultFacet:
34
+ "@type": "@id"
33
35
 
34
36
  "@included":
35
37
  - $id: iolanta:icon
@@ -42,26 +44,25 @@
42
44
  - $id: https://iolanta.tech/datatypes/icon
43
45
  $: Icon
44
46
  iolanta:hasDefaultFacet:
45
- $id: python://iolanta.facets.icon.IconFacet
47
+ $id: pkg:pypi/iolanta#icon
46
48
  →: https://iolanta.tech/datatypes/icon
47
49
 
48
50
  - $id: np:Nanopublication
49
51
  $: Nanopublication
50
52
  iolanta:hasInstanceFacet:
51
- $id: python://iolanta.facets.textual_nanopublication.NanopublicationFacet
53
+ $id: pkg:pypi/iolanta#textual-nanopublication
52
54
  $: Nanopublication
53
55
  →: https://iolanta.tech/cli/textual
54
56
  ⪯:
55
- - python://iolanta.facets.textual_default.TextualDefaultFacet
56
- - python://iolanta.facets.textual_graph.GraphFacet
57
+ - pkg:pypi/iolanta#textual-properties
58
+ - pkg:pypi/iolanta#textual-graph
57
59
 
58
60
  - $id: https://iolanta.tech/cli/interactive
59
- iolanta:hasDefaultFacet:
60
- $id: python://iolanta.facets.textual_browser.TextualBrowserFacet
61
+ iolanta:hasDefaultFacet: pkg:pypi/iolanta#textual-browser
61
62
 
62
- - $id: python://iolanta.facets.textual_default.TextualDefaultFacet
63
+ - $id: pkg:pypi/iolanta#textual-properties
63
64
  $: Properties
64
- ⪯: python://iolanta.facets.textual_default.InverseProperties
65
+ ⪯: pkg:pypi/iolanta#textual-inverse-properties
65
66
  →: https://iolanta.tech/cli/textual
66
67
  ↦: |
67
68
  ASK WHERE {
@@ -69,7 +70,7 @@
69
70
  FILTER (?graph != <iolanta://_meta>)
70
71
  }
71
72
 
72
- - $id: python://iolanta.facets.textual_default.InverseProperties
73
+ - $id: pkg:pypi/iolanta#textual-inverse-properties
73
74
  $: Inverse Properties
74
75
  →: https://iolanta.tech/cli/textual
75
76
  ↦: |
@@ -80,54 +81,54 @@
80
81
 
81
82
  - $id: "urn:"
82
83
  iolanta:hasFacetByPrefix:
83
- $id: python://iolanta.facets.textual_provenance.TextualProvenanceFacet
84
+ $id: pkg:pypi/iolanta#textual-provenance
84
85
  →: https://iolanta.tech/cli/textual
85
86
 
86
87
  - $id: https://iolanta.tech/cli/link
87
88
  iolanta:hasDefaultFacet:
88
- $id: python://iolanta.facets.textual_link.TextualLinkFacet
89
+ $id: pkg:pypi/iolanta#textual-link
89
90
 
90
91
  - $id: https://wikiba.se/ontology#Statement
91
92
  iolanta:hasInstanceFacet:
92
- $id: python://iolanta.facets.wikibase_statement_title.WikibaseStatementTitle
93
+ $id: pkg:pypi/iolanta#wikibase-statement-title
93
94
  $: Title
94
95
  →: https://iolanta.tech/datatypes/title
95
96
 
96
97
  - $id: rdfs:Class
97
98
  iolanta:hasInstanceFacet:
98
- $id: python://iolanta.facets.textual_class.Class
99
+ $id: pkg:pypi/iolanta#textual-class
99
100
  $: Instances
100
101
  →: https://iolanta.tech/cli/textual
101
102
  ⪯:
102
- - python://iolanta.facets.textual_browser.TextualBrowserFacet
103
- - python://iolanta.facets.textual_default.InverseProperties
103
+ - pkg:pypi/iolanta#textual-browser
104
+ - pkg:pypi/iolanta#textual-inverse-properties
104
105
 
105
106
  - $id: owl:Ontology
106
107
  iolanta:hasInstanceFacet:
107
- $id: python://iolanta.facets.textual_ontology.OntologyFacet
108
+ $id: pkg:pypi/iolanta#textual-ontology
108
109
  $: Ontology
109
110
  →: https://iolanta.tech/cli/textual
110
- ⪯: python://iolanta.facets.textual_default.TextualDefaultFacet
111
+ ⪯: pkg:pypi/iolanta#textual-properties
111
112
 
112
113
  - $id: https://iolanta.tech/datatypes/textual-graph-triples
113
114
  $: Graph Triples
114
115
  iolanta:hasDefaultFacet:
115
- $id: python://iolanta.facets.textual_graph_triples.GraphTriplesFacet
116
+ $id: pkg:pypi/iolanta#textual-graph-triples
116
117
  →: https://iolanta.tech/datatypes/textual-graph-triples
117
118
 
118
119
  - $id: https://iolanta.tech/qname
119
120
  iolanta:hasDefaultFacet:
120
- $id: python://iolanta.facets.qname.QNameFacet
121
+ $id: pkg:pypi/iolanta#qname
121
122
 
122
- - $id: python://iolanta.facets.textual_property_pairs_table.TextualPropertyPairsTableFacet
123
+ - $id: pkg:pypi/iolanta#textual-property-pairs
123
124
  $: Subjects → Objects
124
125
  →: https://iolanta.tech/cli/textual
125
126
  ↦: ASK WHERE { ?subject $this ?object }
126
127
 
127
- - $id: python://iolanta.facets.textual_class.Class
128
+ - $id: pkg:pypi/iolanta#textual-class
128
129
  ↦:
129
130
  - ASK WHERE { ?instance a $this }
130
131
  - ASK WHERE { $this rdfs:subClassOf ?superclass }
131
132
 
132
133
  - $id: https://iolanta.tech/cli/textual
133
- iolanta:when-no-facet-found: python://iolanta.facets.textual_no_facet_found.TextualNoFacetFound
134
+ iolanta:when-no-facet-found: pkg:pypi/iolanta#textual-no-facet-found
@@ -136,7 +136,7 @@ class PageSwitcher(IolantaWidgetMixin, ContentSwitcher): # noqa: WPS214
136
136
  for facet in other_facets
137
137
  ]
138
138
 
139
- facet_class = iolanta.facet_resolver[facet_iri]
139
+ facet_class = iolanta.facet_resolver.resolve(facet_iri)
140
140
 
141
141
  facet = facet_class(
142
142
  iri=self.iri,
@@ -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.value),
294
+ str(property_value),
295
295
  style='#696969',
296
296
  )
297
297
 
@@ -9,6 +9,8 @@ PRIORITIES = [ # noqa: WPS407
9
9
  'schema_name',
10
10
  'rdfs_label',
11
11
  'foaf_name',
12
+ 'literal_form',
13
+ 'preferred_label',
12
14
  ]
13
15
 
14
16
 
@@ -19,7 +21,7 @@ class TitleFacet(Facet[str]):
19
21
  """Render title of a thing."""
20
22
  choices = self.stored_query(
21
23
  'title.sparql',
22
- iri=self.iri,
24
+ iri=self.this,
23
25
  language=self.iolanta.language,
24
26
  )
25
27
 
@@ -23,4 +23,14 @@ 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
+
32
+ OPTIONAL {
33
+ $iri <https://www.w3.org/2004/02/skos/core#prefLabel> ?preferred_label .
34
+ FILTER (!lang(?preferred_label) || lang(?preferred_label) = $language)
35
+ }
36
+ }
iolanta/iolanta.py CHANGED
@@ -39,23 +39,17 @@ from iolanta.query_result import (
39
39
  SPARQLQueryArgument,
40
40
  format_query_bindings,
41
41
  )
42
+ from iolanta.resolvers.base import Resolver
43
+ from iolanta.resolvers.dispatch import SchemeDispatchResolver
44
+ from iolanta.resolvers.pypi import PyPIResolver
42
45
  from iolanta.resolvers.python_import import PythonImportResolver
43
46
  from iolanta.sparqlspace.processor import normalize_term
44
47
 
45
48
 
46
49
  class LoggerProtocol(Protocol):
47
- """
48
- Abstract Logger interface.
50
+ """Abstract Logger interface that unites `loguru` & standard `logging`."""
49
51
 
50
- Unites `loguru` & standard `logging`.
51
- """
52
-
53
- def info( # noqa: WPS110
54
- self,
55
- message: str,
56
- *args: Any,
57
- **kwargs: Any,
58
- ) -> None:
52
+ def info(self, message: str, *args: Any, **kwargs: Any) -> None:
59
53
  """Log an INFO message."""
60
54
 
61
55
  def error(self, message: str, *args: Any, **kwargs: Any) -> None:
@@ -85,8 +79,12 @@ class Iolanta: # noqa: WPS214
85
79
  graph: ConjunctiveGraph = field(default_factory=_create_default_graph)
86
80
  force_plugins: List[Type[Plugin]] = field(default_factory=list)
87
81
 
88
- facet_resolver: Mapping[URIRef, Type[Facet]] = field(
89
- default_factory=PythonImportResolver,
82
+ facet_resolver: Resolver = field(
83
+ default_factory=functools.partial(
84
+ SchemeDispatchResolver,
85
+ python=PythonImportResolver,
86
+ pkg=PyPIResolver,
87
+ ),
90
88
  )
91
89
 
92
90
  logger: LoggerProtocol = loguru.logger
@@ -103,11 +101,7 @@ class Iolanta: # noqa: WPS214
103
101
 
104
102
  @functools.cached_property
105
103
  def plugins(self) -> List[Plugin]:
106
- """
107
- Construct a list of installed plugin instances.
108
-
109
- # FIXME: Get rid of those.
110
- """
104
+ """Construct a list of installed plugin instances."""
111
105
  return [
112
106
  plugin_class(iolanta=self)
113
107
  for plugin_class in self.plugin_classes
@@ -118,28 +112,7 @@ class Iolanta: # noqa: WPS214
118
112
  query_text: str,
119
113
  **kwargs: SPARQLQueryArgument,
120
114
  ) -> QueryResult:
121
- """
122
- Run a SPARQL `SELECT`, `CONSTRUCT`, or `ASK` query.
123
-
124
- Args:
125
- query_text: The SPARQL text;
126
- **kwargs: bind variables in the query to values if necessary. For
127
- example:
128
-
129
- ```python
130
- iolanta.query(
131
- 'SELECT ?title WHERE { ?page rdfs:label ?title }',
132
- ?page=page_iri,
133
- )
134
- ```
135
-
136
- Returns:
137
- Results of the query:
138
-
139
- - a graph for `CONSTRUCT`,
140
- - a list of dicts for `SELECT`,
141
- - or a boolean for `ASK`.
142
- """
115
+ """Run a SPARQL `SELECT`, `CONSTRUCT`, or `ASK` query."""
143
116
  try:
144
117
  sparql_result: SPARQLResult = self.graph.query(
145
118
  query_text,
@@ -231,11 +204,7 @@ class Iolanta: # noqa: WPS214
231
204
  return self
232
205
 
233
206
  def infer(self, closure_class=None) -> 'Iolanta':
234
- """
235
- Apply inference.
236
-
237
- TODO Remove this. Or use `reasonable`. Not sure.
238
- """
207
+ """Apply inference."""
239
208
  return self
240
209
 
241
210
  def bind_namespaces(self):
@@ -258,11 +227,7 @@ class Iolanta: # noqa: WPS214
258
227
 
259
228
  @functools.cached_property
260
229
  def context_paths(self) -> Iterable[Path]:
261
- """
262
- Compile list of context files.
263
-
264
- FIXME: Get rid of those.
265
- """
230
+ """Compile list of context files."""
266
231
  directory = Path(__file__).parent / 'data'
267
232
 
268
233
  yield directory / 'context.yaml'
@@ -272,11 +237,7 @@ class Iolanta: # noqa: WPS214
272
237
  yield path
273
238
 
274
239
  def add_files_from_plugins(self):
275
- """
276
- Load files from plugins.
277
-
278
- FIXME: Get rid of plugins.
279
- """
240
+ """Load files from plugins."""
280
241
  for plugin in self.plugins:
281
242
  try:
282
243
  self.add(plugin.data_files)
@@ -285,14 +246,24 @@ class Iolanta: # noqa: WPS214
285
246
  f'Cannot load {plugin} plugin data files: {error}',
286
247
  )
287
248
 
288
- def __post_init__(self):
289
- """
290
- Load stuff from plugins.
249
+ @property
250
+ def facet_classes(self):
251
+ """Get all registered facet classes."""
252
+ return entry_points.plugins('iolanta.facets')
291
253
 
292
- FIXME: Get rid of plugins.
293
- """
254
+ def add_files_from_facets(self):
255
+ """Add files from all registered facets to the graph."""
256
+ for facet_class in self.facet_classes:
257
+ try:
258
+ self.add(facet_class.META)
259
+ except AttributeError:
260
+ pass
261
+
262
+ def __post_init__(self):
263
+ """Initialize after instance creation."""
294
264
  self.bind_namespaces()
295
265
  self.add_files_from_plugins()
266
+ self.add_files_from_facets()
296
267
  if self.project_root:
297
268
  self.add(self.project_root)
298
269
 
@@ -318,7 +289,7 @@ class Iolanta: # noqa: WPS214
318
289
  as_datatype=as_datatype,
319
290
  ).facet_and_output_datatype
320
291
 
321
- facet_class = self.facet_resolver[found['facet']]
292
+ facet_class = self.facet_resolver.resolve(found['facet'])
322
293
 
323
294
  facet = facet_class(
324
295
  iri=node,
@@ -375,11 +346,7 @@ class Iolanta: # noqa: WPS214
375
346
  ) from err
376
347
 
377
348
  def node_as_qname(self, node: Node):
378
- """
379
- Render node as a QName if possible.
380
-
381
- Return the node as is, if it is not.
382
- """
349
+ """Render node as a QName if possible."""
383
350
  qname = node_to_qname(node, self.graph)
384
351
  return f'{qname.namespace_name}:{qname.term}' if isinstance(
385
352
  qname,
File without changes
@@ -0,0 +1,127 @@
1
+ import functools
2
+ from pathlib import Path
3
+ from typing import Iterable
4
+
5
+ import boltons
6
+ import cachetools
7
+ import funcy
8
+ from boltons.cacheutils import cached, cachedmethod
9
+ from pydantic import AnyUrl
10
+ from rdflib import BNode, Literal, Node, URIRef
11
+
12
+ from iolanta import Facet
13
+ from iolanta.mermaid.models import (
14
+ Diagram,
15
+ MermaidBlankNode,
16
+ MermaidEdge,
17
+ MermaidLiteral,
18
+ MermaidScalar,
19
+ MermaidSubgraph,
20
+ MermaidURINode,
21
+ )
22
+ from iolanta.models import NotLiteralNode
23
+ from iolanta.namespaces import DATATYPES
24
+
25
+
26
+ def filter_edges(edges: Iterable[MermaidEdge]) -> Iterable[MermaidEdge]:
27
+ for edge in edges:
28
+ if isinstance(edge.target, MermaidLiteral) and edge.source.title == edge.target.title:
29
+ continue
30
+
31
+ yield edge
32
+
33
+
34
+ def filter_nodes(edges: Iterable[MermaidEdge], except_uris: Iterable[NotLiteralNode]) -> Iterable[MermaidURINode | MermaidLiteral | MermaidBlankNode]:
35
+ nodes = [
36
+ node
37
+ for edge in edges
38
+ for node in edge.nodes
39
+ ]
40
+
41
+ literals_in_edges = {edge.target for edge in edges if isinstance(edge.target, MermaidLiteral)}
42
+ for node in nodes:
43
+ if isinstance(node, MermaidLiteral) and node not in literals_in_edges:
44
+ continue
45
+
46
+ if isinstance(node, MermaidURINode) and node.uri in except_uris:
47
+ continue
48
+
49
+ if isinstance(node, MermaidBlankNode) and node.node in except_uris:
50
+ continue
51
+
52
+ if isinstance(node, MermaidSubgraph):
53
+ continue
54
+
55
+ yield node
56
+
57
+
58
+ class Mermaid(Facet[str]):
59
+ """Mermaid diagram."""
60
+
61
+ META = Path(__file__).parent / 'mermaid.yamlld'
62
+
63
+ def as_mermaid(self, node: Node):
64
+ match node:
65
+ case URIRef() as uri:
66
+ if uri in self.subgraph_uris:
67
+ return MermaidSubgraph(children=[], uri=uri, title=self.render(uri, as_datatype=DATATYPES.title))
68
+
69
+ return MermaidURINode(uri=uri, url=AnyUrl(uri), title=self.render(uri, as_datatype=DATATYPES.title))
70
+ case Literal() as literal:
71
+ return MermaidLiteral(literal=literal)
72
+ case BNode() as bnode:
73
+ return MermaidBlankNode(node=bnode, title=self.render(bnode, as_datatype=DATATYPES.title))
74
+ case unknown:
75
+ unknown_type = type(unknown)
76
+ raise ValueError(f'Unknown something: {unknown} ({unknown_type})')
77
+
78
+ def construct_mermaid_for_graph(self, graph: URIRef) -> Iterable[MermaidScalar]:
79
+ """Render graph as mermaid."""
80
+ rows = self.stored_query('graph.sparql', this=graph)
81
+ edges = [
82
+ MermaidEdge(
83
+ source=self.as_mermaid(row['s']),
84
+ target=self.as_mermaid(row['o']),
85
+ title=self.render(row['p'], as_datatype=DATATYPES.title),
86
+ predicate=row['p'],
87
+ ) for row in rows
88
+ ]
89
+
90
+ edges = list(filter_edges(edges))
91
+ nodes = list(
92
+ filter_nodes(
93
+ edges=edges,
94
+ except_uris=self.subgraph_uris,
95
+ ),
96
+ )
97
+
98
+ return *nodes, *edges
99
+
100
+ @functools.cached_property
101
+ def subgraph_uris(self) -> set[NotLiteralNode]:
102
+ return set(
103
+ funcy.pluck(
104
+ 'subgraph',
105
+ self.stored_query('subgraphs.sparql', this=self.this),
106
+ ),
107
+ )
108
+
109
+ def construct_mermaid_subgraphs(self) -> Iterable[MermaidSubgraph]:
110
+ for subgraph_uri in self.subgraph_uris:
111
+ children = list(self.construct_mermaid_for_graph(subgraph_uri))
112
+ if children:
113
+ title = self.render(subgraph_uri, as_datatype=DATATYPES.title)
114
+ yield MermaidSubgraph(
115
+ children=children,
116
+ uri=subgraph_uri,
117
+ title=title,
118
+ )
119
+
120
+ def show(self) -> str:
121
+ """Render mermaid diagram."""
122
+ direct_children = self.construct_mermaid_for_graph(self.this)
123
+ subgraphs = self.construct_mermaid_subgraphs()
124
+ return str(Diagram(children=[*direct_children, *subgraphs]))
125
+
126
+
127
+
@@ -0,0 +1,42 @@
1
+ "@context":
2
+ "@import": https://json-ld.org/contexts/dollar-convenience.jsonld
3
+ vann: https://purl.org/vocab/vann/
4
+ foaf: https://xmlns.com/foaf/0.1/
5
+ owl: https://www.w3.org/2002/07/owl#
6
+ iolanta: https://iolanta.tech/
7
+ rdfs: "https://www.w3.org/2000/01/rdf-schema#"
8
+ rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
9
+ dcterms: https://purl.org/dc/terms/
10
+ dcam: https://purl.org/dc/dcam/
11
+
12
+ iolanta:outputs:
13
+ "@type": "@id"
14
+
15
+ iolanta:when-no-facet-found:
16
+ "@type": "@id"
17
+
18
+ $: rdfs:label
19
+ →:
20
+ "@type": "@id"
21
+ "@id": iolanta:outputs
22
+
23
+ ⊆:
24
+ "@type": "@id"
25
+ "@id": rdfs:subClassOf
26
+
27
+ ⪯:
28
+ "@type": "@id"
29
+ "@id": iolanta:is-preferred-over
30
+
31
+ ↦: iolanta:matches
32
+ iolanta:hasDefaultFacet:
33
+ "@type": "@id"
34
+
35
+ $id: pkg:pypi/iolanta#mermaid-graph
36
+ $: Mermaid Graph
37
+ →:
38
+ $id: https://iolanta.tech/datatypes/mermaid
39
+ $: Mermaid
40
+ ↦:
41
+ - ASK WHERE { GRAPH $this { ?s ?p ?o } }
42
+ - ASK WHERE { $this iolanta:has-sub-graph ?subgraph }
@@ -0,0 +1,129 @@
1
+ import enum
2
+ import hashlib
3
+ import textwrap
4
+ import urllib.parse
5
+
6
+ from documented import Documented
7
+ from pydantic import AnyUrl, BaseModel
8
+ from rdflib import BNode, Literal, URIRef
9
+
10
+ from iolanta.models import NotLiteralNode
11
+
12
+
13
+ class Direction(enum.StrEnum):
14
+ """Mermaid diagram direction."""
15
+
16
+ TB = 'TB'
17
+ LR = 'LR'
18
+
19
+
20
+ class MermaidURINode(Documented, BaseModel, arbitrary_types_allowed=True, frozen=True):
21
+ """
22
+ {self.id}{self.maybe_title}
23
+ click {self.id} "{self.url}"
24
+ """
25
+
26
+ uri: URIRef
27
+ url: AnyUrl
28
+ title: str = ''
29
+
30
+ @property
31
+ def maybe_title(self):
32
+ return self.title and f'({self.title})'
33
+
34
+ @property
35
+ def id(self):
36
+ return urllib.parse.unquote(str(self.url)).strip('/').replace(':', '_').replace('/', '_').replace('.', '_').replace('#', '_')
37
+
38
+
39
+ class MermaidLiteral(Documented, BaseModel, arbitrary_types_allowed=True, frozen=True):
40
+ """{self.id}[[{self.title}]]"""
41
+
42
+ literal: Literal
43
+
44
+ @property
45
+ def title(self) -> str:
46
+ return str(self.literal) or 'EMPTY'
47
+
48
+ @property
49
+ def id(self) -> str:
50
+ value_hash = hashlib.md5(str(self.literal.value).encode()).hexdigest()
51
+ return f'Literal-{value_hash}'
52
+
53
+
54
+ class MermaidBlankNode(Documented, BaseModel, arbitrary_types_allowed=True):
55
+ """{self.id}({self.title})"""
56
+
57
+ node: BNode
58
+ title: str
59
+
60
+ @property
61
+ def id(self) -> str:
62
+ return self.node.replace('_:', '')
63
+
64
+
65
+ class MermaidEdge(Documented, BaseModel, arbitrary_types_allowed=True):
66
+ """
67
+ {self.source.id} --- {self.id}(["{self.title}"])--> {self.target.id}
68
+ click {self.id} "{self.predicate}"
69
+ class {self.id} predicate
70
+ """
71
+
72
+ source: 'MermaidURINode | MermaidBlankNode | MermaidSubgraph'
73
+ target: 'MermaidURINode | MermaidLiteral | MermaidBlankNode | MermaidSubgraph'
74
+ predicate: URIRef
75
+ title: str
76
+
77
+ @property
78
+ def id(self) -> str:
79
+ return hashlib.md5(f'{self.source.id}{self.predicate}{self.target.id}'.encode()).hexdigest()
80
+
81
+ @property
82
+ def nodes(self):
83
+ return [self.source, self.target]
84
+
85
+
86
+ MermaidScalar = MermaidLiteral | MermaidBlankNode | MermaidURINode | MermaidEdge
87
+
88
+
89
+ class MermaidSubgraph(Documented, BaseModel, arbitrary_types_allowed=True, frozen=True):
90
+ """
91
+ subgraph {self.id}["{self.title}"]
92
+ direction {self.direction}
93
+ {self.formatted_body}
94
+ end
95
+ """
96
+ children: list[MermaidScalar]
97
+ uri: NotLiteralNode
98
+ title: str
99
+ direction: Direction = Direction.LR
100
+
101
+ @property
102
+ def id(self):
103
+ uri_hash = hashlib.md5(str(self.uri).encode()).hexdigest()
104
+ return f'subgraph_{uri_hash}'
105
+
106
+ @property
107
+ def formatted_body(self):
108
+ return textwrap.indent(
109
+ '\n'.join(map(str, self.children)),
110
+ prefix=' ',
111
+ )
112
+
113
+
114
+ class Diagram(Documented, BaseModel):
115
+ """
116
+ graph {self.direction}
117
+ {self.formatted_body}
118
+ classDef predicate fill:none,stroke:none,stroke-width:0px;
119
+ """
120
+
121
+ children: list[MermaidScalar | MermaidSubgraph]
122
+ direction: Direction = Direction.LR
123
+
124
+ @property
125
+ def formatted_body(self):
126
+ return textwrap.indent(
127
+ '\n'.join(map(str, self.children)),
128
+ prefix=' ',
129
+ )
@@ -0,0 +1,5 @@
1
+ SELECT * WHERE {
2
+ GRAPH $this {
3
+ ?s ?p ?o .
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ SELECT ?subgraph WHERE {
2
+ $this iolanta:has-sub-graph ?subgraph .
3
+ }
iolanta/namespaces.py CHANGED
@@ -3,7 +3,7 @@ import rdflib
3
3
  LOCAL = rdflib.Namespace('local:')
4
4
  IOLANTA = rdflib.Namespace('https://iolanta.tech/')
5
5
  DATATYPES = rdflib.Namespace('https://iolanta.tech/datatypes/')
6
- PYTHON = rdflib.Namespace('python://')
6
+ IOLANTA_FACETS = rdflib.Namespace('pkg:pypi/iolanta#')
7
7
  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
iolanta/parse_quads.py CHANGED
@@ -2,6 +2,7 @@ import dataclasses
2
2
  import hashlib
3
3
  from types import MappingProxyType
4
4
  from typing import Iterable, Optional
5
+ from urllib.parse import unquote
5
6
 
6
7
  from documented import DocumentedError
7
8
  from rdflib import BNode, Literal, URIRef
@@ -29,7 +30,7 @@ def parse_term( # noqa: C901
29
30
  term_value = term['value']
30
31
 
31
32
  if term_type == 'IRI':
32
- return URIRef(term_value)
33
+ return URIRef(unquote(term_value))
33
34
 
34
35
  if term_type == 'literal':
35
36
  language = term.get('language')
iolanta/resolvers/base.py CHANGED
@@ -8,6 +8,7 @@ from iolanta.facets.facet import Facet
8
8
 
9
9
  class Resolver:
10
10
  """Resolve facet IRIs into classes."""
11
+
11
12
  @abstractmethod
12
- def __getitem__(self, facet_iri: URIRef) -> Type[Facet]:
13
+ def resolve(self, uri: URIRef) -> Type[Facet]:
13
14
  """Find a resolver by IRI."""
@@ -0,0 +1,41 @@
1
+ from typing import Type
2
+
3
+ from rdflib import URIRef
4
+ from yarl import URL
5
+
6
+ from iolanta import Facet
7
+ from iolanta.resolvers.base import Resolver
8
+
9
+
10
+ class SchemeDispatchResolver(Resolver):
11
+ """
12
+ A resolver that dispatches to other resolvers based on URI scheme.
13
+
14
+ For example, 'pkg:' URIs are handled by PyPIResolver, while
15
+ 'python:' URIs are handled by PythonImportResolver.
16
+ """
17
+
18
+ def __init__(self, **resolver_by_scheme: Type[Resolver]):
19
+ """
20
+ Initialize with a mapping of URI schemes to resolver classes.
21
+
22
+ Args:
23
+ **resolver_by_scheme: Mapping of URI schemes to resolver classes.
24
+ For example: python=PythonImportResolver.
25
+ """
26
+ self.resolver_by_scheme = resolver_by_scheme
27
+
28
+ def resolve(self, uri: URIRef) -> Type[Facet]:
29
+ """
30
+ Find a resolver by IRI, dispatching to the appropriate scheme handler.
31
+
32
+ Args:
33
+ uri: The URI to resolve.
34
+
35
+ Returns:
36
+ Facet class resolved by the appropriate scheme handler.
37
+ """
38
+ url = URL(uri)
39
+ resolver_class = self.resolver_by_scheme[url.scheme]
40
+ resolver = resolver_class()
41
+ return resolver.resolve(uri)
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from importlib import metadata
5
+ from typing import Type
6
+
7
+ from packageurl import PackageURL
8
+ from rdflib import URIRef
9
+
10
+ from iolanta import Facet
11
+ from iolanta.resolvers.base import Resolver
12
+
13
+ ENTRY_POINT_GROUP = 'iolanta.facets'
14
+
15
+
16
+ def _norm(name: str) -> str:
17
+ """Normalize package name by replacing common separators with dashes."""
18
+ return re.sub('[-_.]+', '-', name).lower()
19
+
20
+
21
+ class MissingFacetError(RuntimeError):
22
+ """Raised when a required facet is not found in the installed packages."""
23
+
24
+
25
+ class PyPIResolver(Resolver):
26
+ """Resolve facet IRIs into classes from PyPI packages."""
27
+
28
+ def resolve(self, uri: URIRef) -> Type[Facet]:
29
+ """Find and load a Facet class from a PyPI package."""
30
+ dist_name, facet_name = self._get_package_info(uri)
31
+ dist = self._get_distribution(dist_name, facet_name)
32
+ entry_point = self._find_facet_entry_point(dist, facet_name)
33
+ facet_class = entry_point.load()
34
+ self._validate_facet_class(
35
+ facet_class=facet_class,
36
+ facet_name=facet_name,
37
+ dist_name=dist.metadata['Name'],
38
+ )
39
+ return facet_class
40
+
41
+ def _get_package_info(self, uri: URIRef) -> tuple[str, str]:
42
+ """Extract and validate package information from URI."""
43
+ package_url = PackageURL.from_string(str(uri))
44
+ if package_url.type != 'pypi':
45
+ raise NotImplementedError(f'{package_url.type} is not supported')
46
+
47
+ dist_name = _norm(package_url.name or '')
48
+ facet_name = package_url.subpath
49
+
50
+ if not dist_name or not facet_name:
51
+ raise ValueError('PURL must be pkg:pypi/<dist>#<FacetName>')
52
+
53
+ return dist_name, facet_name
54
+
55
+ def _get_distribution(
56
+ self,
57
+ dist_name: str,
58
+ facet_name: str,
59
+ ) -> metadata.Distribution:
60
+ """Get package distribution and verify it exists."""
61
+ try:
62
+ return metadata.distribution(dist_name)
63
+ except metadata.PackageNotFoundError as exc:
64
+ raise MissingFacetError(
65
+ f'This page requires `{dist_name}` (facet `{facet_name}`). '
66
+ f'Install with: pip install "{dist_name}"',
67
+ ) from exc
68
+
69
+ def _find_facet_entry_point(
70
+ self,
71
+ dist: metadata.Distribution,
72
+ facet_name: str,
73
+ ) -> metadata.EntryPoint:
74
+ """Find the facet entry point in the distribution."""
75
+ matching_entry_points = [
76
+ entry_point
77
+ for entry_point in dist.entry_points
78
+ if (
79
+ entry_point.group == ENTRY_POINT_GROUP
80
+ and entry_point.name == facet_name
81
+ )
82
+ ]
83
+
84
+ if not matching_entry_points:
85
+ dist_name = dist.metadata['Name']
86
+ msg = (
87
+ f'Facet `{facet_name}` not found in `{dist_name}` '
88
+ f'{dist.version}. Ensure it exposes entry point '
89
+ f'`{ENTRY_POINT_GROUP}` named `{facet_name}`.'
90
+ )
91
+ raise MissingFacetError(msg)
92
+
93
+ return matching_entry_points[0]
94
+
95
+ def _validate_facet_class(
96
+ self,
97
+ facet_class: Type[Facet],
98
+ facet_name: str,
99
+ dist_name: str,
100
+ ) -> None:
101
+ """Validate that the loaded class is a Facet subclass."""
102
+ if not issubclass(facet_class, Facet):
103
+ raise TypeError(
104
+ f'Entry point `{facet_name}` in `{dist_name}` '
105
+ 'is not a subclass of iolanta.Facet',
106
+ )
@@ -1,40 +1,17 @@
1
- import pydoc
2
- from typing import Type, cast
1
+ from typing import Type
3
2
 
4
3
  from rdflib import URIRef
5
4
 
6
- from iolanta.facets.errors import FacetNotCallable
7
5
  from iolanta.facets.facet import Facet
8
6
  from iolanta.resolvers.base import Resolver
9
7
 
10
8
 
11
9
  class PythonImportResolver(Resolver):
12
- def __getitem__(self, item: URIRef) -> Type[Facet]:
13
- url = str(item)
14
-
15
- if not url.startswith('python://'):
16
- raise Exception(
17
- 'Iolanta only supports facets which are importable Python '
18
- 'callables. The URLs of such facets must start with '
19
- '`python://`, '
20
- 'which {url} does not comply to.'.format(
21
- url=url,
22
- ),
23
- )
24
-
25
- # It is impossible to use `yarl` for this operation because it (or,
26
- # rather, one of upper classes from `urllib` that `yarl` depends
27
- # upon)
28
- # will lowercase the URL when parsing it - which means, irreversibly. We
29
- # have to resort to plain string manipulation.
30
- import_path = url.replace('python://', '').strip('/')
31
-
32
- facet = cast(Type['iolanta.Facet'], pydoc.locate(import_path))
33
-
34
- if not callable(facet):
35
- raise FacetNotCallable(
36
- path=import_path,
37
- facet=facet,
38
- )
39
-
40
- return facet
10
+ """Resolve facet IRIs into classes by importing Python modules."""
11
+
12
+ def resolve(self, uri: URIRef) -> Type[Facet]:
13
+ """Find a resolver by IRI in python:<module>.<class> format."""
14
+ raise NotImplementedError(
15
+ f'{uri} URL for facet import is unsupported anymore '
16
+ f'due to security reasons.',
17
+ )
@@ -72,6 +72,10 @@ REDIRECTS = MappingProxyType({
72
72
  'https://nanopub.net/nschema#',
73
73
  ),
74
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',
75
79
  })
76
80
 
77
81
 
@@ -281,11 +285,30 @@ def _extract_nanopublication_uris(
281
285
  )
282
286
 
283
287
 
284
- def apply_redirect(source: URIRef) -> URIRef:
285
- """Rewrite the URL."""
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
+
286
298
  for pattern, destination in REDIRECTS.items():
287
- if source.startswith(pattern):
288
- return destination
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)
289
312
 
290
313
  return source
291
314
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iolanta
3
- Version: 2.0.7
3
+ Version: 2.1.1
4
4
  Summary: Semantic Web browser
5
5
  License: MIT
6
6
  Author: Anatoly Scherbakov
@@ -24,6 +24,7 @@ Requires-Dist: more-itertools (>=9.0.0)
24
24
  Requires-Dist: nanopub (>=2.0.1)
25
25
  Requires-Dist: owlrl (>=6.0.2)
26
26
  Requires-Dist: oxrdflib (>=0.4.0)
27
+ Requires-Dist: packageurl-python (>=0.17.5)
27
28
  Requires-Dist: python-frontmatter (>=0.5.0)
28
29
  Requires-Dist: rdflib (<8.0)
29
30
  Requires-Dist: reasonable (>=0.2.6)
@@ -31,8 +32,8 @@ Requires-Dist: requests (>=2.25.1)
31
32
  Requires-Dist: rich (>=13.3.1)
32
33
  Requires-Dist: textual (>=0.83.0)
33
34
  Requires-Dist: typer (>=0.9.0)
34
- Requires-Dist: watchfiles (>=1.0.4,<2.0.0)
35
- Requires-Dist: yaml-ld (>=1.1.9)
35
+ Requires-Dist: watchfiles (>=1.0.4)
36
+ Requires-Dist: yaml-ld (>=1.1.10)
36
37
  Requires-Dist: yarl (>=1.9.4)
37
38
  Description-Content-Type: text/markdown
38
39
 
@@ -6,17 +6,15 @@ iolanta/cli/formatters/choose.py,sha256=LWzsO_9IBSSgYNIyLlItkp8TNvpW3v6YCQ8-6kbI
6
6
  iolanta/cli/formatters/csv.py,sha256=ceJ_DTz0beqeK-d6FPBQqqjXrziEfF0FRSLoGZCt_fs,760
7
7
  iolanta/cli/formatters/json.py,sha256=Og5B9UrSM_0NWqW5Afpsy6WH8ZfYgPMVXYvT3i-43Jc,748
8
8
  iolanta/cli/formatters/pretty.py,sha256=IypZRAr2vNqcXFY6NOIc75mpyfpFWh56aCBlOPDDieQ,2901
9
- iolanta/cli/main.py,sha256=W1SoQK9kncaSUsy4X5yMrzu8lQUlu-eE6YyIu4dNnec,3167
9
+ iolanta/cli/main.py,sha256=GVtdBQjAOs6VlsE_uztTXHbmRXUk_S6gN4LWqACJGS8,3157
10
10
  iolanta/cli/models.py,sha256=cjbpowdzI4wAP0DUk3qoVHyimk6AZwlXi9CGmusZTuM,159
11
11
  iolanta/cli/pretty_print.py,sha256=M6E3TmhzA6JY5GeUVmDZLmOh5u70-393PVit4voFKDI,977
12
12
  iolanta/context.py,sha256=bZR-tbZIrDQ-Vby01PMDZ6ifxM-0YMK68RJvAsyqCTs,507
13
13
  iolanta/conversions.py,sha256=hbLwRF1bAbOxy17eMWLHhYksbdCWN-v4-0y0wn3XSSg,1185
14
- iolanta/data/cli.yaml,sha256=TsnldYXoY5GIzoNuPDvwBKGw8eAEForZW1FCKqKI0Kg,1029
15
14
  iolanta/data/context.yaml,sha256=LUBasiBKgQeGAYjYV_T5XvgPlrdzACeKaZwY_rKzjtI,1636
16
- iolanta/data/graph-triples.yamlld,sha256=oACKRwqWcMp6TBobXMG6gbbnaauyd6wlK-gH2vVkPps,241
17
- iolanta/data/html.yaml,sha256=hVFdLWLy8FMY8xpOrJMYc-tE3S0Nq83xuxVkjRW_7rI,517
15
+ iolanta/data/graph-triples.yamlld,sha256=rtn-HfbijaqbmjCrKv-2pVV_aaJhB_9_OqXA_yLznCs,209
18
16
  iolanta/data/iolanta.yaml,sha256=xubIFBNU02lmFXhgOSuyQwUcZD3xCqVfeVAZMvOxKbI,1433
19
- iolanta/data/textual-browser.yaml,sha256=SajFDOdqISVLsBVneS3uh3s8vuYAUIuC0nWvVqIWMZ0,4141
17
+ iolanta/data/textual-browser.yaml,sha256=nJbDS0B3G-emM9vCu2DIlWZBhzmWbO3zrWkt_Rmv5uA,3700
20
18
  iolanta/ensure_is_context.py,sha256=9aok8asyEx7KPesOR28VBDb3Ch9kfc3eoCpQSJwj07U,717
21
19
  iolanta/entry_points.py,sha256=DZbf-udlEwELFGqeWENj0M2BOUPOWlmGJdqyaEtnot0,504
22
20
  iolanta/errors.py,sha256=t_RltahnoEvcytVa1BOq2MADgHps3JNd_h5-SFu7_wM,1250
@@ -53,7 +51,7 @@ iolanta/facets/textual_browser/home.py,sha256=GfDD1G2HiwdLkPkNdcYRqVIxDl5tWH9few
53
51
  iolanta/facets/textual_browser/location.py,sha256=w0La8bVTpJiVo1_hFTDQeIUdDdqfhYnoihuZW-f130c,205
54
52
  iolanta/facets/textual_browser/models.py,sha256=DqTBjhkkTt5mNwqr4DzNbPSqzV-QtNqfKj7wpn6T3ao,173
55
53
  iolanta/facets/textual_browser/page.py,sha256=NkcQ5rSKZRbp63C8ozgsR_iVhcKHGv_SytUCQyGa7ss,786
56
- iolanta/facets/textual_browser/page_switcher.py,sha256=WOsFY9hSFEnuf3za1eCUhPaQ6uTaeBuMPIAHjHhnvDY,9950
54
+ iolanta/facets/textual_browser/page_switcher.py,sha256=hZT9FFMXJf7t8V2ztsqsNNCmGY8CPx39mlEKJjhRsPA,9958
57
55
  iolanta/facets/textual_class/__init__.py,sha256=tiL0p-3JspGcBRj4qa3rmoBFAuadk71l2ja2lJN6CEs,75
58
56
  iolanta/facets/textual_class/facets.py,sha256=tascZiue3tbYfpIsnP8yY2dgXQIQzoQ_HPzU7DbVbao,5999
59
57
  iolanta/facets/textual_class/sparql/instances.sparql,sha256=EW4BmuNxiRDA8djuEtvZpLLJlNSIshvId4ExlaYPcZE,106
@@ -66,7 +64,7 @@ iolanta/facets/textual_default/sparql/properties.sparql,sha256=stDbvFP4g6YKYphpN
66
64
  iolanta/facets/textual_default/tcss/default.tcss,sha256=v6k6LvZMndRW4t9Iq-7QF59U_LJTdohRsyavwTY5ruI,69
67
65
  iolanta/facets/textual_default/templates/default.md,sha256=CuD5lISsE2eAVnm2z6kfNff-vEgrNG95Wi5LTgkieWY,21
68
66
  iolanta/facets/textual_default/triple_uri_ref.py,sha256=XfuNPaAe-YxH8IyrdrHQ641aWh5zVMVs0L0WC3D6A4M,1279
69
- iolanta/facets/textual_default/widgets.py,sha256=0f8gJmo447O5e_KwVxyb3hdVNzDJQTUf9GmRnp6hfrA,9183
67
+ iolanta/facets/textual_default/widgets.py,sha256=9qZbJfLvf9yilyQjW6IlCk1AiD8gTmfvaFuLLGQMnGU,9177
70
68
  iolanta/facets/textual_graph/__init__.py,sha256=DWd2gljzL8SiyYKQdBH78HouF1EMqgCH-w0K5OEmL2I,59
71
69
  iolanta/facets/textual_graph/facets.py,sha256=NGg0i1XYmdR8gjMd-D4Ckvu_n7m7s1FA68VSUm7cpj0,873
72
70
  iolanta/facets/textual_graph/sparql/triples.sparql,sha256=5rFVGcvZzEHZj2opQQp0YlxJLpEdl-r1RjkPwo8j7t0,145
@@ -90,32 +88,40 @@ iolanta/facets/textual_provenance/facets.py,sha256=vv3UQsI2duB36DW5Zkw3sqgAXBPmK
90
88
  iolanta/facets/textual_provenance/sparql/graphs.sparql,sha256=B45uKFd-1vrBuMDSbTURjUUEjHt51vAbqdL4tUcgMvk,103
91
89
  iolanta/facets/textual_provenance/sparql/triples.sparql,sha256=V-EdVuWbGHY3MspbJIMpwxPQautLDqJJV-AmihDjSHc,53
92
90
  iolanta/facets/title/__init__.py,sha256=fxpkG-YvHDp6eiVL3o7BbwhPMZZe-1R2Qi6S36QCTf8,77
93
- iolanta/facets/title/facets.py,sha256=v7YifoXqrpTlJscgVM_USfherpoXB_QP_LO5u3AwIwg,792
94
- iolanta/facets/title/sparql/title.sparql,sha256=4rz47tjwX2OJavWMzftaYKil1-ZHB76ZwLGVCwMbn5s,746
91
+ iolanta/facets/title/facets.py,sha256=41cWNdisyb9dPJowSgTIQa-rmFCI4WUb-EYSFDr3GI0,836
92
+ iolanta/facets/title/sparql/title.sparql,sha256=VKHeE9NvV6sFgVGvsLQD9z__J4APj8TDHLT4Rk-Ascc,1102
95
93
  iolanta/facets/wikibase_statement_title/__init__.py,sha256=_yk1akxgSJOiUBJIc8QGrD2vovvmx_iw_vJDuv1rD7M,91
96
94
  iolanta/facets/wikibase_statement_title/facets.py,sha256=mUH7twlAgoeX7DgLQuRBQv4ORT6GWbN-0eJ1aliSfiQ,724
97
95
  iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql,sha256=n07DQWxKqB5c3CA4kacq2HSN0R0dLgnMnLP1AxMo5YA,320
98
- iolanta/iolanta.py,sha256=y95nu0BbllQqWIBv19FUAbS-l4-qNFA4LWcbZdseTyU,11330
96
+ iolanta/iolanta.py,sha256=uM5vmK96Q_GsqXM-bBpQXoxwqs25K-YGYrZvWWFTsDA,11072
97
+ iolanta/mermaid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
+ iolanta/mermaid/facet.py,sha256=0SMzwkjtB3xjCQRW-XwOB5P7zVJdUCDv8c_XmanvqP0,4068
99
+ iolanta/mermaid/mermaid.yamlld,sha256=j5yQ6A1M_Fn4cq5WYISz8MfWw6skYdoB0lvcI68ahyw,968
100
+ iolanta/mermaid/models.py,sha256=v0loPqJYKpylVBaq89yFdk8iNxyzvN9Ih_ozAbhD4pk,3197
101
+ iolanta/mermaid/sparql/graph.sparql,sha256=mDGf05od3CUFhzI6rcqt5ZMVy-gSKDu-WxmV_zpIsVI,62
102
+ iolanta/mermaid/sparql/subgraphs.sparql,sha256=VuoOYr_ZtKXXRrBpAEJek0mBRzR9EV-KnKENgAbkzCs,71
99
103
  iolanta/models.py,sha256=M-1dTxPwjTyUgQ4VCOiH6Q7ltNJiDPG0l1XQH430Omo,3250
100
- iolanta/namespaces.py,sha256=VJ3BLTji3UmqD3N1Om2voAoyYPtF4o-8FXN2Uq_4sPc,1194
104
+ iolanta/namespaces.py,sha256=H_qYyxCqbIGMOczpT9vUBHumTW9QJ-Y6qXHbJgFwFP8,1210
101
105
  iolanta/node_to_qname.py,sha256=a82_qpgT87cbekY_76tTkl4Z-6Rz6am4UGIQChUf9Y0,794
102
- iolanta/parse_quads.py,sha256=w1GlfABG6EYKQ9iFFQHKFp8juPGdsjYmadQE6hH0gC8,4499
106
+ iolanta/parse_quads.py,sha256=nKUGS_OVpCA6PFA5rOd6Qa5ik_eQhCL-Mlv1zDXk_Co,4541
103
107
  iolanta/plugin.py,sha256=MSxpuOIx93AgBahfS8bYh31MEgcwtUSQhj4Js7fgdSI,1096
104
108
  iolanta/query_result.py,sha256=VLLBkewUEymtzfB0jeIeRE3Np6pAgo959RPgNsEmiq8,1545
105
109
  iolanta/reformat_blank_nodes.py,sha256=MAVcXusUioKzAoTEHAMume5Gt9vBEpxJGrngqFzmkJI,712
106
110
  iolanta/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
- iolanta/resolvers/base.py,sha256=cc9bcrVZ0wTwn85I-IYCwcIRU5Lwaph8D00C0dwSOm8,302
108
- iolanta/resolvers/python_import.py,sha256=kDOhApBDFfxnrDgK9M7ztMkqXfcny81Zo86FgPFb-LI,1301
111
+ iolanta/resolvers/base.py,sha256=gkPHP85xRawmvZZtkZzFM0JjDRbZUKaTM_Zg2cdZWHk,293
112
+ iolanta/resolvers/dispatch.py,sha256=L2-ZQfusUgi-VuFdn0UA19ecEG1bZzV9PD2z2QJye1s,1204
113
+ iolanta/resolvers/pypi.py,sha256=BJA7PGPaFFIxisCp-1-pjWZYNTDRVA5am5Nn8O2WddM,3498
114
+ iolanta/resolvers/python_import.py,sha256=hFs2Fi7BDjdKllgrGJhTaqjfcT7Vaj0j1b45CJt21B0,522
109
115
  iolanta/sparqlspace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
110
116
  iolanta/sparqlspace/cli.py,sha256=pb6q03lzrU8OaZ4A3QAQEmaFYcQ_-sHUrPhRs6GE88w,1590
111
117
  iolanta/sparqlspace/inference/wikibase-claim.sparql,sha256=JSawj3VTc9ZPoU9mvh1w1AMYb3cWyZ3x1rEYWUsKZ6A,209
112
118
  iolanta/sparqlspace/inference/wikibase-statement-property.sparql,sha256=SkSHZZlxWVDwBM3aLo0Q7hLuOj9BsIQnXtcuAuwaxqU,240
113
- iolanta/sparqlspace/processor.py,sha256=tYIGIqh-1kbzIfYgirU77QoyFwsF8t6jv5BdHAEXjM0,23191
119
+ iolanta/sparqlspace/processor.py,sha256=0Rk7ZeLWifdnmnKL5EZc39JRuMfsxT2RBUv6vgg3di0,23995
114
120
  iolanta/sparqlspace/sparqlspace.py,sha256=Y8_ZPXwuGEXbEes6XQjaQWA2Zv9y8SWxMPDFdqVBGFo,796
115
121
  iolanta/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
122
  iolanta/widgets/description.py,sha256=98Qd3FwT9r8sYqKjl9ZEptaVX9jJ2ULWf0uy3j52p5o,800
117
123
  iolanta/widgets/mixin.py,sha256=nDRCOc-gizCf1a5DAcYs4hW8eZEd6pHBPFsfm0ncv7E,251
118
- iolanta-2.0.7.dist-info/METADATA,sha256=CjOyQqdHPRnClEWEC0nllINZGGdUznLk_C04P0_B5aI,2230
119
- iolanta-2.0.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
120
- iolanta-2.0.7.dist-info/entry_points.txt,sha256=z9RPLmGuz3tYGPV7Qf4nNjuSJYdTUS9enC4595poe-M,221
121
- iolanta-2.0.7.dist-info/RECORD,,
124
+ iolanta-2.1.1.dist-info/METADATA,sha256=PC0tU9wgfi3BMHGEcI19gFVTUpQaIWxoGz2PcAoVx_8,2268
125
+ iolanta-2.1.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
126
+ iolanta-2.1.1.dist-info/entry_points.txt,sha256=H2bCnnscOwzl0QMoI86ifQ6hm1NhyYnrGQbMd0Kp6dA,1445
127
+ iolanta-2.1.1.dist-info/RECORD,,
@@ -0,0 +1,31 @@
1
+ [console_scripts]
2
+ iolanta=iolanta.cli:app
3
+ sparqlspace=iolanta.sparqlspace.cli:app
4
+
5
+ [iolanta.facets]
6
+ boolean=iolanta.facets.generic:BoolLiteral
7
+ icon=iolanta.facets.icon:IconFacet
8
+ mermaid-graph=iolanta.mermaid.facet:Mermaid
9
+ qname=iolanta.facets.qname:QNameFacet
10
+ textual-browser=iolanta.facets.textual_browser:TextualBrowserFacet
11
+ textual-class=iolanta.facets.textual_class:Class
12
+ textual-graph=iolanta.facets.textual_graph:GraphFacet
13
+ textual-graph-triples=iolanta.facets.textual_graph_triples:GraphTriplesFacet
14
+ textual-inverse-properties=iolanta.facets.textual_default:InverseProperties
15
+ textual-link=iolanta.facets.textual_link:TextualLinkFacet
16
+ textual-nanopublication=iolanta.facets.textual_nanopublication:NanopublicationFacet
17
+ textual-no-facet-found=iolanta.facets.textual_no_facet_found:TextualNoFacetFound
18
+ textual-ontology=iolanta.facets.textual_ontology:OntologyFacet
19
+ textual-properties=iolanta.facets.textual_default:TextualDefaultFacet
20
+ textual-property-pairs=iolanta.facets.textual_property_pairs_table:TextualPropertyPairsTableFacet
21
+ textual-provenance=iolanta.facets.textual_provenance:TextualProvenanceFacet
22
+ title=iolanta.facets.title:TitleFacet
23
+ title-foaf-person=iolanta.facets.foaf_person_title:FOAFPersonTitle
24
+ wikibase-statement-title=iolanta.facets.wikibase_statement_title:WikibaseStatementTitle
25
+
26
+ [iolanta.plugins]
27
+ base=iolanta:IolantaBase
28
+
29
+ [rdf.plugins.queryprocessor]
30
+ sparqlspace=iolanta.sparqlspace.processor:GlobalSPARQLProcessor
31
+
iolanta/data/cli.yaml DELETED
@@ -1,30 +0,0 @@
1
- "@context":
2
- "@import": https://json-ld.org/contexts/dollar-convenience.jsonld
3
- vann: https://purl.org/vocab/vann/
4
- foaf: https://xmlns.com/foaf/0.1/
5
- owl: https://www.w3.org/2002/07/owl#
6
- iolanta: https://iolanta.tech/
7
- rdfs: "https://www.w3.org/2000/01/rdf-schema#"
8
- rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
9
-
10
- $included:
11
- - $id: python://iolanta.facets.cli.Default
12
- rdfs:label: For the CLI, display a link to the node or (at least) its human readable label.
13
-
14
- - $id: iolanta:cli
15
- $type: iolanta:OutputDatatype
16
- iolanta:hasDefaultFacet:
17
- $id: python://iolanta.facets.cli.Record
18
- rdfs:label: Show properties of a node
19
-
20
- - $id: https://iolanta.tech/cli/record/title
21
- iolanta:hasDefaultFacet:
22
- $id: python://iolanta.facets.cli.Default
23
-
24
- - $id: https://iolanta.tech/cli/record/property
25
- iolanta:hasDefaultFacet:
26
- $id: python://iolanta.facets.cli.Default
27
-
28
- - $id: https://iolanta.tech/cli/record/value
29
- iolanta:hasDefaultFacet:
30
- $id: python://iolanta.facets.cli.Default
iolanta/data/html.yaml DELETED
@@ -1,15 +0,0 @@
1
- "@context":
2
- "@import": https://json-ld.org/contexts/dollar-convenience.jsonld
3
- vann: https://purl.org/vocab/vann/
4
- foaf: https://xmlns.com/foaf/0.1/
5
- owl: https://www.w3.org/2002/07/owl#
6
- iolanta: https://iolanta.tech/
7
- rdfs: "https://www.w3.org/2000/01/rdf-schema#"
8
- rdf: https://www.w3.org/1999/02/22-rdf-syntax-ns#
9
-
10
- $included:
11
- - $id: iolanta:html
12
- $type: iolanta:OutputDatatype
13
- owl:sameAs:
14
- $id: https://html.spec.whatwg.org/
15
- iolanta:hasDefaultFacet: python://iolanta.facets.html.Default
@@ -1,10 +0,0 @@
1
- [console_scripts]
2
- iolanta=iolanta.cli:app
3
- sparqlspace=iolanta.sparqlspace.cli:app
4
-
5
- [iolanta.plugins]
6
- base=iolanta:IolantaBase
7
-
8
- [rdf.plugins.queryprocessor]
9
- sparqlspace=iolanta.sparqlspace.processor:GlobalSPARQLProcessor
10
-