iolanta 2.1.19__tar.gz
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-2.1.19/PKG-INFO +72 -0
- iolanta-2.1.19/README.md +30 -0
- iolanta-2.1.19/iolanta/__init__.py +3 -0
- iolanta-2.1.19/iolanta/base_plugin.py +5 -0
- iolanta-2.1.19/iolanta/cli/__init__.py +2 -0
- iolanta-2.1.19/iolanta/cli/formatters/__init__.py +0 -0
- iolanta-2.1.19/iolanta/cli/formatters/choose.py +38 -0
- iolanta-2.1.19/iolanta/cli/formatters/csv.py +35 -0
- iolanta-2.1.19/iolanta/cli/formatters/json.py +32 -0
- iolanta-2.1.19/iolanta/cli/formatters/pretty.py +126 -0
- iolanta-2.1.19/iolanta/cli/main.py +301 -0
- iolanta-2.1.19/iolanta/cli/models.py +10 -0
- iolanta-2.1.19/iolanta/cli/pretty_print.py +40 -0
- iolanta-2.1.19/iolanta/context.py +25 -0
- iolanta-2.1.19/iolanta/conversions.py +57 -0
- iolanta-2.1.19/iolanta/data/context.yaml +76 -0
- iolanta-2.1.19/iolanta/data/graph-triples.yamlld +6 -0
- iolanta-2.1.19/iolanta/data/iolanta.yaml +54 -0
- iolanta-2.1.19/iolanta/data/textual-browser.yaml +105 -0
- iolanta-2.1.19/iolanta/declension/__init__.py +0 -0
- iolanta-2.1.19/iolanta/declension/data/declension.yamlld +39 -0
- iolanta-2.1.19/iolanta/declension/facet.py +44 -0
- iolanta-2.1.19/iolanta/declension/sparql/declension.sparql +8 -0
- iolanta-2.1.19/iolanta/ensure_is_context.py +36 -0
- iolanta-2.1.19/iolanta/entry_points.py +15 -0
- iolanta-2.1.19/iolanta/errors.py +61 -0
- iolanta-2.1.19/iolanta/facets/__init__.py +1 -0
- iolanta-2.1.19/iolanta/facets/cli/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/cli/base.py +17 -0
- iolanta-2.1.19/iolanta/facets/cli/default.py +13 -0
- iolanta-2.1.19/iolanta/facets/cli/record.py +48 -0
- iolanta-2.1.19/iolanta/facets/cli/sparql/link.sparql +11 -0
- iolanta-2.1.19/iolanta/facets/cli/sparql/record.sparql +3 -0
- iolanta-2.1.19/iolanta/facets/errors.py +175 -0
- iolanta-2.1.19/iolanta/facets/facet.py +81 -0
- iolanta-2.1.19/iolanta/facets/foaf_person_title/__init__.py +1 -0
- iolanta-2.1.19/iolanta/facets/foaf_person_title/facet.py +24 -0
- iolanta-2.1.19/iolanta/facets/foaf_person_title/sparql/names.sparql +4 -0
- iolanta-2.1.19/iolanta/facets/generic/__init__.py +2 -0
- iolanta-2.1.19/iolanta/facets/generic/bool_literal.py +17 -0
- iolanta-2.1.19/iolanta/facets/generic/date_literal.py +24 -0
- iolanta-2.1.19/iolanta/facets/generic/default.py +73 -0
- iolanta-2.1.19/iolanta/facets/generic/sparql/default.sparql +26 -0
- iolanta-2.1.19/iolanta/facets/html/__init__.py +1 -0
- iolanta-2.1.19/iolanta/facets/html/base.py +9 -0
- iolanta-2.1.19/iolanta/facets/html/code_literal.py +17 -0
- iolanta-2.1.19/iolanta/facets/html/default.py +27 -0
- iolanta-2.1.19/iolanta/facets/icon.py +25 -0
- iolanta-2.1.19/iolanta/facets/locator/sparql/get-query-to-facet.sparql +5 -0
- iolanta-2.1.19/iolanta/facets/locator.py +305 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/__init__.py +0 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/facet.py +133 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/inference/blocks.sparql +13 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/inference/has-task-default-type.sparql +16 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/inference/task.sparql +26 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/inference/unblocked.sparql +21 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/mermaid_roadmap.yamlld +59 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/sparql/edges.sparql +25 -0
- iolanta-2.1.19/iolanta/facets/mermaid_roadmap/sparql/nodes.sparql +17 -0
- iolanta-2.1.19/iolanta/facets/mkdocs_material_insiders_markdown/__init__.py +6 -0
- iolanta-2.1.19/iolanta/facets/mkdocs_material_insiders_markdown/data/mkdocs_material_insiders_markdown.yamlld +20 -0
- iolanta-2.1.19/iolanta/facets/mkdocs_material_insiders_markdown/facet.py +86 -0
- iolanta-2.1.19/iolanta/facets/mkdocs_material_insiders_markdown/templates/datatype.jinja2.md +24 -0
- iolanta-2.1.19/iolanta/facets/page_title.py +51 -0
- iolanta-2.1.19/iolanta/facets/qname.py +19 -0
- iolanta-2.1.19/iolanta/facets/query/__init__.py +0 -0
- iolanta-2.1.19/iolanta/facets/query/ask_result_csv.py +23 -0
- iolanta-2.1.19/iolanta/facets/query/ask_result_json.py +24 -0
- iolanta-2.1.19/iolanta/facets/query/ask_result_table.py +23 -0
- iolanta-2.1.19/iolanta/facets/query/construct_result_csv.py +34 -0
- iolanta-2.1.19/iolanta/facets/query/construct_result_json.py +32 -0
- iolanta-2.1.19/iolanta/facets/query/construct_result_table.py +55 -0
- iolanta-2.1.19/iolanta/facets/query/data/query_result.yamlld +102 -0
- iolanta-2.1.19/iolanta/facets/query/select_result_csv.py +36 -0
- iolanta-2.1.19/iolanta/facets/query/select_result_json.py +24 -0
- iolanta-2.1.19/iolanta/facets/query/select_result_table.py +48 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/app.py +118 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/facet.py +28 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/history.py +39 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/home.py +14 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/location.py +14 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/models.py +11 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/page.py +26 -0
- iolanta-2.1.19/iolanta/facets/textual_browser/page_switcher.py +352 -0
- iolanta-2.1.19/iolanta/facets/textual_class/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/textual_class/facets.py +218 -0
- iolanta-2.1.19/iolanta/facets/textual_class/sparql/instances.sparql +6 -0
- iolanta-2.1.19/iolanta/facets/textual_class/textual-class.yamlld +25 -0
- iolanta-2.1.19/iolanta/facets/textual_default/__init__.py +6 -0
- iolanta-2.1.19/iolanta/facets/textual_default/facets.py +192 -0
- iolanta-2.1.19/iolanta/facets/textual_default/sparql/inverse-properties.sparql +3 -0
- iolanta-2.1.19/iolanta/facets/textual_default/sparql/label.sparql +7 -0
- iolanta-2.1.19/iolanta/facets/textual_default/sparql/nodes-for-property.sparql +3 -0
- iolanta-2.1.19/iolanta/facets/textual_default/sparql/properties.sparql +17 -0
- iolanta-2.1.19/iolanta/facets/textual_default/tcss/default.tcss +7 -0
- iolanta-2.1.19/iolanta/facets/textual_default/templates/default.md +3 -0
- iolanta-2.1.19/iolanta/facets/textual_default/textual-inverse-properties.yamlld +25 -0
- iolanta-2.1.19/iolanta/facets/textual_default/triple_uri_ref.py +51 -0
- iolanta-2.1.19/iolanta/facets/textual_default/widgets.py +374 -0
- iolanta-2.1.19/iolanta/facets/textual_graph/__init__.py +1 -0
- iolanta-2.1.19/iolanta/facets/textual_graph/facets.py +30 -0
- iolanta-2.1.19/iolanta/facets/textual_graph/sparql/triples.sparql +5 -0
- iolanta-2.1.19/iolanta/facets/textual_graph_triples.py +153 -0
- iolanta-2.1.19/iolanta/facets/textual_graphs/__init__.py +6 -0
- iolanta-2.1.19/iolanta/facets/textual_graphs/data/textual_graphs.yamlld +23 -0
- iolanta-2.1.19/iolanta/facets/textual_graphs/facets.py +138 -0
- iolanta-2.1.19/iolanta/facets/textual_graphs/sparql/graphs.sparql +5 -0
- iolanta-2.1.19/iolanta/facets/textual_link/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/textual_link/facet.py +23 -0
- iolanta-2.1.19/iolanta/facets/textual_nanopublication/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/textual_nanopublication/facet.py +14 -0
- iolanta-2.1.19/iolanta/facets/textual_nanopublication/models.py +9 -0
- iolanta-2.1.19/iolanta/facets/textual_nanopublication/nanopublication_widget.py +85 -0
- iolanta-2.1.19/iolanta/facets/textual_nanopublication/term_list_widget.py +39 -0
- iolanta-2.1.19/iolanta/facets/textual_nanopublication/term_widget.py +124 -0
- iolanta-2.1.19/iolanta/facets/textual_no_facet_found.py +128 -0
- iolanta-2.1.19/iolanta/facets/textual_ontology/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/textual_ontology/facets.py +127 -0
- iolanta-2.1.19/iolanta/facets/textual_ontology/sparql/terms.sparql +15 -0
- iolanta-2.1.19/iolanta/facets/textual_ontology/sparql/visualization-vocab.sparql +3 -0
- iolanta-2.1.19/iolanta/facets/textual_property_pairs_table.py +133 -0
- iolanta-2.1.19/iolanta/facets/textual_provenance/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/textual_provenance/facets.py +139 -0
- iolanta-2.1.19/iolanta/facets/textual_provenance/sparql/graphs.sparql +5 -0
- iolanta-2.1.19/iolanta/facets/textual_provenance/sparql/triples.sparql +3 -0
- iolanta-2.1.19/iolanta/facets/title/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/title/facets.py +38 -0
- iolanta-2.1.19/iolanta/facets/title/sparql/title.sparql +36 -0
- iolanta-2.1.19/iolanta/facets/title/title.yamlld +33 -0
- iolanta-2.1.19/iolanta/facets/wikibase_statement_title/__init__.py +3 -0
- iolanta-2.1.19/iolanta/facets/wikibase_statement_title/facets.py +29 -0
- iolanta-2.1.19/iolanta/facets/wikibase_statement_title/sparql/statement-title.sparql +13 -0
- iolanta-2.1.19/iolanta/iolanta.py +430 -0
- iolanta-2.1.19/iolanta/labeled_triple_set/__init__.py +0 -0
- iolanta-2.1.19/iolanta/labeled_triple_set/data/labeled_triple_set.yamlld +42 -0
- iolanta-2.1.19/iolanta/labeled_triple_set/labeled_triple_set.py +137 -0
- iolanta-2.1.19/iolanta/labeled_triple_set/sparql/triples.sparql +5 -0
- iolanta-2.1.19/iolanta/mcp/__init__.py +0 -0
- iolanta-2.1.19/iolanta/mcp/cli.py +37 -0
- iolanta-2.1.19/iolanta/mermaid/__init__.py +0 -0
- iolanta-2.1.19/iolanta/mermaid/facet.py +124 -0
- iolanta-2.1.19/iolanta/mermaid/mermaid.yamlld +25 -0
- iolanta-2.1.19/iolanta/mermaid/models.py +192 -0
- iolanta-2.1.19/iolanta/mermaid/sparql/ask-has-triples.sparql +3 -0
- iolanta-2.1.19/iolanta/mermaid/sparql/graph.sparql +5 -0
- iolanta-2.1.19/iolanta/mermaid/sparql/subgraphs.sparql +3 -0
- iolanta-2.1.19/iolanta/models.py +128 -0
- iolanta-2.1.19/iolanta/namespaces.py +47 -0
- iolanta-2.1.19/iolanta/node_to_qname.py +37 -0
- iolanta-2.1.19/iolanta/parse_quads.py +175 -0
- iolanta-2.1.19/iolanta/plugin.py +44 -0
- iolanta-2.1.19/iolanta/query_result.py +73 -0
- iolanta-2.1.19/iolanta/reformat_blank_nodes.py +25 -0
- iolanta-2.1.19/iolanta/resolvers/__init__.py +0 -0
- iolanta-2.1.19/iolanta/resolvers/base.py +14 -0
- iolanta-2.1.19/iolanta/resolvers/dispatch.py +41 -0
- iolanta-2.1.19/iolanta/resolvers/pypi.py +106 -0
- iolanta-2.1.19/iolanta/resolvers/python_import.py +17 -0
- iolanta-2.1.19/iolanta/sparqlspace/__init__.py +0 -0
- iolanta-2.1.19/iolanta/sparqlspace/cli.py +64 -0
- iolanta-2.1.19/iolanta/sparqlspace/inference/wikidata-prop-label.sparql +10 -0
- iolanta-2.1.19/iolanta/sparqlspace/inference/wikidata-statement-label.sparql +27 -0
- iolanta-2.1.19/iolanta/sparqlspace/processor.py +761 -0
- iolanta-2.1.19/iolanta/sparqlspace/redirects.py +58 -0
- iolanta-2.1.19/iolanta/sparqlspace/sparqlspace.py +32 -0
- iolanta-2.1.19/iolanta/widgets/__init__.py +0 -0
- iolanta-2.1.19/iolanta/widgets/description.py +31 -0
- iolanta-2.1.19/iolanta/widgets/mixin.py +10 -0
- iolanta-2.1.19/pyproject.toml +98 -0
iolanta-2.1.19/PKG-INFO
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iolanta
|
|
3
|
+
Version: 2.1.19
|
|
4
|
+
Summary: Semantic Web browser
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Anatoly Scherbakov
|
|
7
|
+
Author-email: altaisoft@gmail.com
|
|
8
|
+
Requires-Python: >=3.12,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Provides-Extra: all
|
|
15
|
+
Requires-Dist: boltons (>=24.0.0)
|
|
16
|
+
Requires-Dist: classes (>=0.4.0)
|
|
17
|
+
Requires-Dist: deepmerge (>=0.1.1)
|
|
18
|
+
Requires-Dist: diskcache (>=5.6.3)
|
|
19
|
+
Requires-Dist: documented (>=0.1.1)
|
|
20
|
+
Requires-Dist: dominate (>=2.6.0)
|
|
21
|
+
Requires-Dist: fastmcp (>=2.12.4,<3.0.0)
|
|
22
|
+
Requires-Dist: funcy (>=2.0)
|
|
23
|
+
Requires-Dist: jinja2 (>=3.1.0)
|
|
24
|
+
Requires-Dist: loguru (>=0.7.3)
|
|
25
|
+
Requires-Dist: more-itertools (>=9.0.0)
|
|
26
|
+
Requires-Dist: nanopub (>=2.1.0,<3.0.0)
|
|
27
|
+
Requires-Dist: owlrl (>=6.0.2)
|
|
28
|
+
Requires-Dist: oxrdflib (>=0.4.0)
|
|
29
|
+
Requires-Dist: packageurl-python (>=0.17.5)
|
|
30
|
+
Requires-Dist: python-frontmatter (>=0.5.0)
|
|
31
|
+
Requires-Dist: rdflib (<8.0)
|
|
32
|
+
Requires-Dist: reasonable (>=0.2.6)
|
|
33
|
+
Requires-Dist: requests (>=2.25.1)
|
|
34
|
+
Requires-Dist: rich (>=13.3.1)
|
|
35
|
+
Requires-Dist: textual (>=0.83.0)
|
|
36
|
+
Requires-Dist: typer (>=0.9.0)
|
|
37
|
+
Requires-Dist: watchfiles (>=1.0.4)
|
|
38
|
+
Requires-Dist: yaml-ld (>=1.1.16)
|
|
39
|
+
Requires-Dist: yarl (>=1.9.4)
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# iolanta | Linked Data browser
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
[](https://github.com/iolanta-tech/iolanta/actions?query=workflow%3Atest)
|
|
47
|
+
[](https://codecov.io/gh/iolanta-tech/iolanta)
|
|
48
|
+
[](https://pypi.org/project/iolanta/)
|
|
49
|
+
[](https://github.com/wemake-services/wemake-python-styleguide)
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
## 📦 Install with `pip`
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
pip install iolanta
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Try it out!
|
|
60
|
+
|
|
61
|
+
Explore a Nanopublication:
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
iolanta https://w3id.org/np/RA7OYmnx-3ln_AY233lElN01wSDJWDOXPz061Ah93EQ2I
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
⇒
|
|
68
|
+
|
|
69
|
+

|
|
70
|
+
|
|
71
|
+
See more [in the docs](https://iolanta.tech).
|
|
72
|
+
|
iolanta-2.1.19/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# iolanta | Linked Data browser
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
[](https://github.com/iolanta-tech/iolanta/actions?query=workflow%3Atest)
|
|
6
|
+
[](https://codecov.io/gh/iolanta-tech/iolanta)
|
|
7
|
+
[](https://pypi.org/project/iolanta/)
|
|
8
|
+
[](https://github.com/wemake-services/wemake-python-styleguide)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## 📦 Install with `pip`
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
pip install iolanta
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Try it out!
|
|
19
|
+
|
|
20
|
+
Explore a Nanopublication:
|
|
21
|
+
|
|
22
|
+
```shell
|
|
23
|
+
iolanta https://w3id.org/np/RA7OYmnx-3ln_AY233lElN01wSDJWDOXPz061Ah93EQ2I
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
⇒
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
See more [in the docs](https://iolanta.tech).
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from rdflib import Graph
|
|
4
|
+
|
|
5
|
+
from iolanta.cli.formatters.csv import csv_print
|
|
6
|
+
from iolanta.cli.formatters.json import print_json
|
|
7
|
+
from iolanta.cli.formatters.pretty import pretty_print
|
|
8
|
+
from iolanta.models import QueryResultsFormat
|
|
9
|
+
from iolanta.node_to_qname import node_to_qname
|
|
10
|
+
from iolanta.query_result import SelectResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# @cli_print.instance(SelectResult)
|
|
14
|
+
def cli_print(
|
|
15
|
+
query_result: SelectResult,
|
|
16
|
+
output_format: QueryResultsFormat,
|
|
17
|
+
display_iri_as_qname: bool = True,
|
|
18
|
+
graph: Optional[Graph] = None,
|
|
19
|
+
):
|
|
20
|
+
if display_iri_as_qname:
|
|
21
|
+
if graph is None:
|
|
22
|
+
raise NotImplementedError(
|
|
23
|
+
'Cannot compute QNames if graph is not provided.',
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
query_result = SelectResult([
|
|
27
|
+
{
|
|
28
|
+
key: node_to_qname(node, graph)
|
|
29
|
+
for key, node in row.items()
|
|
30
|
+
}
|
|
31
|
+
for row in query_result
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
QueryResultsFormat.CSV: csv_print,
|
|
36
|
+
QueryResultsFormat.PRETTY: pretty_print,
|
|
37
|
+
QueryResultsFormat.JSON: print_json,
|
|
38
|
+
}[output_format](query_result) # type: ignore
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from classes import typeclass
|
|
5
|
+
from rdflib import Graph
|
|
6
|
+
|
|
7
|
+
from iolanta.query_result import QueryResult, SelectResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@typeclass
|
|
11
|
+
def csv_print(query_result: QueryResult) -> None:
|
|
12
|
+
"""Print as CSV."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@csv_print.instance(SelectResult)
|
|
16
|
+
def _csv_select(select_result: SelectResult):
|
|
17
|
+
if not select_result:
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
first_row = select_result[0]
|
|
21
|
+
fieldnames = first_row.keys()
|
|
22
|
+
|
|
23
|
+
writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
|
|
24
|
+
writer.writeheader()
|
|
25
|
+
writer.writerows(select_result)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@csv_print.instance(Graph)
|
|
29
|
+
def _construct_print(graph: Graph):
|
|
30
|
+
if not graph:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
writer = csv.writer(sys.stdout)
|
|
34
|
+
writer.writerow(('subject', 'predicate', 'object'))
|
|
35
|
+
writer.writerows(graph)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from classes import typeclass
|
|
5
|
+
from rdflib import Graph
|
|
6
|
+
|
|
7
|
+
from iolanta.query_result import QueryResult, SelectResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@typeclass
|
|
11
|
+
def print_json(query_result: QueryResult) -> None:
|
|
12
|
+
"""Print query result as JSON."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@print_json.instance(SelectResult)
|
|
16
|
+
def _print_select_as_json(select_result: SelectResult):
|
|
17
|
+
sys.stdout.write(json.dumps(select_result, indent=2, default=str))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@print_json.instance(Graph)
|
|
21
|
+
def _print_construct_as_json(graph: Graph):
|
|
22
|
+
fieldnames = ('subject', 'predicate', 'object')
|
|
23
|
+
sys.stdout.write(
|
|
24
|
+
json.dumps(
|
|
25
|
+
[
|
|
26
|
+
dict(zip(fieldnames, triple))
|
|
27
|
+
for triple in graph
|
|
28
|
+
],
|
|
29
|
+
indent=2,
|
|
30
|
+
default=str,
|
|
31
|
+
),
|
|
32
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from classes import typeclass
|
|
5
|
+
from more_itertools import consume, first
|
|
6
|
+
from rdflib import BNode, Graph, Literal, URIRef
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from iolanta.cli.pretty_print import render_literal_value
|
|
11
|
+
from iolanta.models import ComputedQName
|
|
12
|
+
from iolanta.query_result import QueryResult, SelectResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@typeclass
|
|
16
|
+
def pretty_print_value(rdflib_value: Union[URIRef, Literal, BNode]) -> str:
|
|
17
|
+
"""Pretty print a value for a table in CLI."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pretty_print_value.instance(type(None))
|
|
21
|
+
def _pretty_print_none(none_value):
|
|
22
|
+
"""Format None."""
|
|
23
|
+
return f'∅ {none_value}'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pretty_print_value.instance(URIRef)
|
|
27
|
+
def _pretty_print_value_uri_ref(uriref: URIRef):
|
|
28
|
+
"""Format URI Ref."""
|
|
29
|
+
return f'🔗 {uriref}'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pretty_print_value.instance(ComputedQName)
|
|
33
|
+
def _pretty_print_value_uri_ref(qname: ComputedQName):
|
|
34
|
+
"""Format QName."""
|
|
35
|
+
return (
|
|
36
|
+
f'🔗 [link={qname.namespace_url}]'
|
|
37
|
+
f'[blue]{qname.namespace_name}[/blue][/link]:'
|
|
38
|
+
f'{qname.term}'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pretty_print_value.instance(Literal)
|
|
43
|
+
def _pretty_print_literal(literal: Literal):
|
|
44
|
+
"""Render a literal."""
|
|
45
|
+
rendered_value = render_literal_value(literal.toPython())
|
|
46
|
+
|
|
47
|
+
if literal.language:
|
|
48
|
+
formatted_language = {
|
|
49
|
+
'ru': '🇷🇺',
|
|
50
|
+
'en': '🇺🇸',
|
|
51
|
+
'ua': '🇺🇦',
|
|
52
|
+
}[literal.language]
|
|
53
|
+
|
|
54
|
+
return f'{formatted_language} {rendered_value}'
|
|
55
|
+
|
|
56
|
+
return rendered_value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pretty_print_value.instance(BNode)
|
|
60
|
+
def _pretty_print_bnode(bnode: BNode):
|
|
61
|
+
"""Print a blank node."""
|
|
62
|
+
return f'😶 {bnode}'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@typeclass
|
|
66
|
+
def pretty_print(query_result: QueryResult):
|
|
67
|
+
"""Pretty print query result."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pretty_print.instance(SelectResult)
|
|
71
|
+
def _pretty_print_select_result(select_result: SelectResult):
|
|
72
|
+
"""Print a SPARQL query result in style."""
|
|
73
|
+
if not select_result:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
columns = first(select_result).keys()
|
|
77
|
+
|
|
78
|
+
table = Table(
|
|
79
|
+
*columns,
|
|
80
|
+
show_header=True,
|
|
81
|
+
header_style="bold magenta",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
consume(
|
|
85
|
+
itertools.starmap(
|
|
86
|
+
table.add_row,
|
|
87
|
+
[
|
|
88
|
+
map(
|
|
89
|
+
pretty_print_value,
|
|
90
|
+
row.values(),
|
|
91
|
+
)
|
|
92
|
+
for row in select_result
|
|
93
|
+
],
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
Console().print(table)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pretty_print.instance(Graph)
|
|
101
|
+
def _pretty_construct(graph: Graph):
|
|
102
|
+
if not graph:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
table = Table(
|
|
106
|
+
'Subject',
|
|
107
|
+
'Predicate',
|
|
108
|
+
'Object',
|
|
109
|
+
show_header=True,
|
|
110
|
+
header_style="bold magenta",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
consume(
|
|
114
|
+
itertools.starmap(
|
|
115
|
+
table.add_row,
|
|
116
|
+
[
|
|
117
|
+
map(
|
|
118
|
+
pretty_print_value,
|
|
119
|
+
triple,
|
|
120
|
+
)
|
|
121
|
+
for triple in graph
|
|
122
|
+
],
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
Console().print(table)
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import locale
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import loguru
|
|
8
|
+
import platformdirs
|
|
9
|
+
from documented import DocumentedError
|
|
10
|
+
from rdflib import Graph, Literal, URIRef
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.markdown import Markdown
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from typer import Argument, Exit, Option, Typer
|
|
15
|
+
from yarl import URL
|
|
16
|
+
|
|
17
|
+
from iolanta.cli.models import LogLevel
|
|
18
|
+
from iolanta.facets.errors import FacetNotFound
|
|
19
|
+
from iolanta.iolanta import Iolanta
|
|
20
|
+
from iolanta.models import NotLiteralNode
|
|
21
|
+
from iolanta.namespaces import DATATYPES
|
|
22
|
+
from iolanta.query_result import (
|
|
23
|
+
QueryResult,
|
|
24
|
+
SPARQLParseException,
|
|
25
|
+
SelectResult,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
DEFAULT_LANGUAGE = locale.getlocale()[0].split("_")[0]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = Typer(no_args_is_help=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def string_to_node(name: str) -> NotLiteralNode:
|
|
38
|
+
"""
|
|
39
|
+
Parse a string into a node identifier.
|
|
40
|
+
|
|
41
|
+
String might be:
|
|
42
|
+
* a URL,
|
|
43
|
+
* or a local disk path.
|
|
44
|
+
"""
|
|
45
|
+
url = URL(name)
|
|
46
|
+
if url.scheme:
|
|
47
|
+
return URIRef(name)
|
|
48
|
+
|
|
49
|
+
path = Path(name).absolute()
|
|
50
|
+
return URIRef(f"file://{path}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def decode_datatype(datatype: str) -> URIRef:
|
|
54
|
+
if datatype.startswith("http"):
|
|
55
|
+
return URIRef(datatype)
|
|
56
|
+
|
|
57
|
+
return URIRef(f"https://iolanta.tech/datatypes/{datatype}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def setup_logging(log_level: LogLevel):
|
|
61
|
+
"""Configure and return logger."""
|
|
62
|
+
level = {
|
|
63
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
64
|
+
LogLevel.INFO: logging.INFO,
|
|
65
|
+
LogLevel.WARNING: logging.WARNING,
|
|
66
|
+
LogLevel.ERROR: logging.ERROR,
|
|
67
|
+
}[log_level]
|
|
68
|
+
|
|
69
|
+
log_file_path = (
|
|
70
|
+
platformdirs.user_log_path(
|
|
71
|
+
"iolanta",
|
|
72
|
+
ensure_exists=True,
|
|
73
|
+
)
|
|
74
|
+
/ "iolanta.log"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
level_name = {
|
|
78
|
+
logging.DEBUG: "DEBUG",
|
|
79
|
+
logging.INFO: "INFO",
|
|
80
|
+
logging.WARNING: "WARNING",
|
|
81
|
+
logging.ERROR: "ERROR",
|
|
82
|
+
}[level]
|
|
83
|
+
|
|
84
|
+
loguru.logger.remove()
|
|
85
|
+
loguru.logger.add(
|
|
86
|
+
log_file_path,
|
|
87
|
+
level=level_name,
|
|
88
|
+
format="{time} {level} {message}",
|
|
89
|
+
enqueue=True,
|
|
90
|
+
)
|
|
91
|
+
loguru.logger.add(
|
|
92
|
+
sys.stderr,
|
|
93
|
+
level=level_name,
|
|
94
|
+
format="{time} | {level:<8} | {name}:{function}:{line} - {message}",
|
|
95
|
+
)
|
|
96
|
+
loguru.logger.level(level_name)
|
|
97
|
+
|
|
98
|
+
return loguru.logger
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handle_error(
|
|
102
|
+
error: Exception,
|
|
103
|
+
log_level: LogLevel,
|
|
104
|
+
use_markdown: bool = True,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Handle an error by checking log level and printing appropriately.
|
|
108
|
+
|
|
109
|
+
If log level is DEBUG or INFO, re-raise the error.
|
|
110
|
+
Otherwise, print it and exit with code 1.
|
|
111
|
+
"""
|
|
112
|
+
level = {
|
|
113
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
114
|
+
LogLevel.INFO: logging.INFO,
|
|
115
|
+
LogLevel.WARNING: logging.WARNING,
|
|
116
|
+
LogLevel.ERROR: logging.ERROR,
|
|
117
|
+
}[log_level]
|
|
118
|
+
|
|
119
|
+
if level in {logging.DEBUG, logging.INFO}:
|
|
120
|
+
raise error
|
|
121
|
+
|
|
122
|
+
if use_markdown:
|
|
123
|
+
console.print(
|
|
124
|
+
Markdown(
|
|
125
|
+
str(error),
|
|
126
|
+
justify="left",
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
console.print(str(error))
|
|
131
|
+
|
|
132
|
+
raise Exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def create_query_node(query_result: QueryResult) -> Literal:
|
|
136
|
+
"""
|
|
137
|
+
Create a Literal node from a query result.
|
|
138
|
+
|
|
139
|
+
Converts QueryResult (SelectResult, Graph, or bool) into a Literal
|
|
140
|
+
with the appropriate datatype for facet rendering.
|
|
141
|
+
"""
|
|
142
|
+
match query_result:
|
|
143
|
+
case SelectResult():
|
|
144
|
+
return Literal(
|
|
145
|
+
query_result,
|
|
146
|
+
datatype=DATATYPES["sparql-select-result"],
|
|
147
|
+
)
|
|
148
|
+
case Graph():
|
|
149
|
+
return Literal(
|
|
150
|
+
query_result,
|
|
151
|
+
datatype=DATATYPES["sparql-construct-result"],
|
|
152
|
+
)
|
|
153
|
+
case bool():
|
|
154
|
+
return Literal(
|
|
155
|
+
query_result,
|
|
156
|
+
datatype=DATATYPES["sparql-ask-result"],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def render_and_return(
|
|
161
|
+
node: Literal | URIRef,
|
|
162
|
+
as_datatype: str,
|
|
163
|
+
language: str = DEFAULT_LANGUAGE,
|
|
164
|
+
log_level: LogLevel = LogLevel.ERROR,
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Render a node.
|
|
168
|
+
|
|
169
|
+
The node must be either a URIRef (for URLs) or a Literal (for query results).
|
|
170
|
+
"""
|
|
171
|
+
logger = setup_logging(log_level)
|
|
172
|
+
|
|
173
|
+
# Determine Iolanta instance based on node type
|
|
174
|
+
if isinstance(node, Literal):
|
|
175
|
+
# Literal nodes (e.g., from query results) are used directly
|
|
176
|
+
# Use current directory as project_root for query results
|
|
177
|
+
iolanta: Iolanta = Iolanta(
|
|
178
|
+
language=Literal(language),
|
|
179
|
+
logger=logger,
|
|
180
|
+
project_root=Path.cwd(),
|
|
181
|
+
)
|
|
182
|
+
elif isinstance(node, URIRef):
|
|
183
|
+
# URIRef - determine project_root if it's a file:// URI
|
|
184
|
+
if str(node).startswith("file://"):
|
|
185
|
+
path = Path(str(node).replace("file://", ""))
|
|
186
|
+
# Load current directory (like CLI AI agents): use parent if path is a file
|
|
187
|
+
project_root = path.parent if path.is_file() else path
|
|
188
|
+
iolanta: Iolanta = Iolanta(
|
|
189
|
+
language=Literal(language),
|
|
190
|
+
logger=logger,
|
|
191
|
+
project_root=project_root,
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
iolanta: Iolanta = Iolanta(
|
|
195
|
+
language=Literal(language),
|
|
196
|
+
logger=logger,
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
# This should never happen due to type checking, but kept for safety
|
|
200
|
+
raise TypeError(f"Expected Literal or URIRef, got {type(node)}")
|
|
201
|
+
|
|
202
|
+
return iolanta.render(
|
|
203
|
+
node=node,
|
|
204
|
+
as_datatype=decode_datatype(as_datatype),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@app.command(name="render")
|
|
209
|
+
def render_command( # noqa: WPS231, WPS238, WPS210, C901
|
|
210
|
+
url: Annotated[str | None, Argument()] = None,
|
|
211
|
+
query: Annotated[
|
|
212
|
+
str | None,
|
|
213
|
+
Option(
|
|
214
|
+
"--query",
|
|
215
|
+
help="SPARQL query to execute.",
|
|
216
|
+
),
|
|
217
|
+
] = None,
|
|
218
|
+
as_datatype: Annotated[
|
|
219
|
+
str | None,
|
|
220
|
+
Option(
|
|
221
|
+
"--as",
|
|
222
|
+
),
|
|
223
|
+
] = None,
|
|
224
|
+
language: Annotated[
|
|
225
|
+
str,
|
|
226
|
+
Option(
|
|
227
|
+
help="Data language to prefer.",
|
|
228
|
+
),
|
|
229
|
+
] = DEFAULT_LANGUAGE,
|
|
230
|
+
log_level: LogLevel = LogLevel.ERROR,
|
|
231
|
+
):
|
|
232
|
+
"""Render a given URL."""
|
|
233
|
+
if query is not None:
|
|
234
|
+
# For queries, default to 'table' format
|
|
235
|
+
if as_datatype is None:
|
|
236
|
+
as_datatype = "table"
|
|
237
|
+
|
|
238
|
+
# Setup logging and create Iolanta instance (unlikely to raise)
|
|
239
|
+
logger = setup_logging(log_level)
|
|
240
|
+
iolanta: Iolanta = Iolanta(
|
|
241
|
+
language=Literal(language),
|
|
242
|
+
logger=logger,
|
|
243
|
+
project_root=Path.cwd(),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
renderable = render_and_return(
|
|
248
|
+
node=create_query_node(iolanta.query(query)),
|
|
249
|
+
as_datatype=as_datatype,
|
|
250
|
+
language=language,
|
|
251
|
+
log_level=log_level,
|
|
252
|
+
)
|
|
253
|
+
except (SPARQLParseException, DocumentedError, FacetNotFound) as error:
|
|
254
|
+
handle_error(error, log_level, use_markdown=True)
|
|
255
|
+
except Exception as error:
|
|
256
|
+
handle_error(error, log_level, use_markdown=False)
|
|
257
|
+
else:
|
|
258
|
+
# FIXME: An intermediary Literal can be used to dispatch rendering.
|
|
259
|
+
match renderable:
|
|
260
|
+
case Table() as table:
|
|
261
|
+
console.print(table)
|
|
262
|
+
|
|
263
|
+
case unknown:
|
|
264
|
+
console.print(unknown)
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
if url is None:
|
|
268
|
+
console.print("Error: Either URL or --query must be provided")
|
|
269
|
+
raise Exit(1)
|
|
270
|
+
|
|
271
|
+
# For URLs, default to interactive mode
|
|
272
|
+
if as_datatype is None:
|
|
273
|
+
as_datatype = "https://iolanta.tech/cli/interactive"
|
|
274
|
+
|
|
275
|
+
# Parse string URL to URIRef (URL() is permissive and won't raise)
|
|
276
|
+
node_url = URL(url)
|
|
277
|
+
if node_url.scheme and node_url.scheme != "file":
|
|
278
|
+
node = URIRef(url)
|
|
279
|
+
else:
|
|
280
|
+
path = Path(node_url.path).absolute()
|
|
281
|
+
node = URIRef(f"file://{path}")
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
renderable = render_and_return(
|
|
285
|
+
node=node,
|
|
286
|
+
as_datatype=as_datatype,
|
|
287
|
+
language=language,
|
|
288
|
+
log_level=log_level,
|
|
289
|
+
)
|
|
290
|
+
except DocumentedError as error:
|
|
291
|
+
handle_error(error, log_level, use_markdown=True)
|
|
292
|
+
except Exception as error:
|
|
293
|
+
handle_error(error, log_level, use_markdown=False)
|
|
294
|
+
else:
|
|
295
|
+
# FIXME: An intermediary Literal can be used to dispatch rendering.
|
|
296
|
+
match renderable:
|
|
297
|
+
case Table() as table:
|
|
298
|
+
console.print(table)
|
|
299
|
+
|
|
300
|
+
case unknown:
|
|
301
|
+
console.print(unknown)
|