pyomnigraph 0.2.0__tar.gz → 0.2.2__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.
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/PKG-INFO +11 -10
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/README.md +10 -9
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/docs/index.md +1 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/mkdocs.yml +1 -1
- pyomnigraph-0.2.2/omnigraph/__init__.py +1 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/omnigraph_cmd.py +6 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/omniserver.py +1 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/rdf_dataset.py +61 -16
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/rdfdump_cmd.py +14 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/examples/datasets.yaml +37 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/examples/servers.yaml +1 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/server_config.py +1 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/blazegraph.py +65 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/graphdb.py +1 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/jena.py +63 -3
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/millenniumdb.py +2 -2
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/oxigraph.py +1 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/qlever.py +66 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/stardog.py +1 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/virtuoso.py +1 -1
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/sparql_server.py +35 -4
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/version.py +1 -1
- pyomnigraph-0.2.2/scripts/gov-quickstart +11 -0
- pyomnigraph-0.2.2/scripts/quickstart +213 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/test_omniserver.py +54 -0
- pyomnigraph-0.2.2/tests/test_rdf_dataset_params.py +58 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/test_rdfdump.py +51 -1
- pyomnigraph-0.2.2/tests/test_upload.py +98 -0
- pyomnigraph-0.2.0/omnigraph/__init__.py +0 -1
- pyomnigraph-0.2.0/scripts/build-stardog-free +0 -35
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/.github/workflows/build.yml +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/.github/workflows/upload-to-pypi.yml +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/.gitignore +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/.project +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/.pydevproject +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/AGENTS.md +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/LICENSE +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/basecmd.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/ominigraph_paths.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/rdfdump.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/__init__.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/examples/endpoints.yaml +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/examples/prefixes.yaml +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/examples/queries.yaml +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/resources/examples/royals.ttl +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/servers/__init__.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/omnigraph/software.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/pyproject.toml +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/scripts/blackisort +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/scripts/doc +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/scripts/install +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/scripts/release +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/scripts/test +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/__init__.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/basetest.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/test_qlever.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/test_software_list.py +0 -0
- {pyomnigraph-0.2.0 → pyomnigraph-0.2.2}/tests/test_sparql_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyomnigraph
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: pyomnigraph: Unified Python interface for multiple graph databases (SPARQL, Neo4j, Weaviate)
|
|
5
5
|
Project-URL: Home, https://github.com/WolfgangFahl/omnigraph
|
|
6
6
|
Project-URL: Documentation, https://wiki.bitplan.com/index.php/omnigraph
|
|
@@ -83,16 +83,16 @@ This leads to:
|
|
|
83
83
|
|
|
84
84
|
### Supported Triple Stores
|
|
85
85
|
|
|
86
|
-
| Database | Status |
|
|
87
|
-
|
|
86
|
+
| Database | Status | Notes |
|
|
87
|
+
|----------|--------|-------|
|
|
88
88
|
| **Apache Jena** | 🟢 Working | Robust, standards compliant |
|
|
89
89
|
| **Blazegraph** | 🟢 Working | High performance, easy setup |
|
|
90
|
-
| **
|
|
90
|
+
| **Oxigraph** | 🟢 Working | Rust-based, embedded, fast |
|
|
91
91
|
| **QLever** | 🟢 Working | Extremely fast queries |
|
|
92
|
-
| **
|
|
93
|
-
| **MillenniumDB** | 🛑
|
|
94
|
-
| **
|
|
95
|
-
| **Stardog** | 🛑 Planned |
|
|
92
|
+
| **GraphDB** | 🛑 Disabled | License required (free version available) |
|
|
93
|
+
| **MillenniumDB** | 🛑 Disabled | Import-first workflow, Property Graph + RDF |
|
|
94
|
+
| **Virtuoso** | 🛑 Disabled | Permissions issue (#30) |
|
|
95
|
+
| **Stardog** | 🛑 Planned | License required |
|
|
96
96
|
|
|
97
97
|
Whether you're building a semantic web application, conducting research, or evaluating different triple stores, pyomnigraph eliminates the complexity of working with multiple graph database systems.
|
|
98
98
|
|
|
@@ -103,10 +103,11 @@ omnigraph omnigraph --list --include-inactive --doc-format github
|
|
|
103
103
|
| Active | Name | Container Name | Wikidata | Image | Port | Test Port | Dataset | User |
|
|
104
104
|
|----------|-------------------------------------|----------------------|--------------------------------------------------------|--------------------------------------------------------------------------------------------------|--------|-------------|-----------|--------|
|
|
105
105
|
| 🟢️ | [blazegraph](http://localhost:9898) | blazegraph-omnigraph | [Q20127748](https://www.wikidata.org/wiki/Q20127748) | [lyrasis/blazegraph:2.1.5](https://hub.docker.com/r/lyrasis/blazegraph) | 9898 | 7898 | kb | |
|
|
106
|
-
| 🟢 | [graphdb](http://localhost:7200) | graphdb-omnigraph | [Q58425577](https://www.wikidata.org/wiki/Q58425577) | [ontotext/graphdb:9.11.2-se](https://hub.docker.com/r/ontotext/graphdb) | 7200 | 7700 | repo1 | |
|
|
107
106
|
| 🟢️ | [jena](http://localhost:3030) | jena-omnigraph | [Q109376461](https://www.wikidata.org/wiki/Q109376461) | [stain/jena-fuseki:latest](https://hub.docker.com/r/stain/jena-fuseki) | 3030 | 7030 | ds | admin |
|
|
108
|
-
|
|
|
107
|
+
| 🟢️ | [oxigraph](http://localhost:7878) | oxigraph-omnigraph | [Q118980507](https://www.wikidata.org/wiki/Q118980507) | [oxigraph/oxigraph:latest](https://hub.docker.com/r/oxigraph/oxigraph) | 7878 | 7378 | default | |
|
|
109
108
|
| 🟢️ | [qlever](http://localhost:7019) | qlever-omnigraph | [Q111016295](https://www.wikidata.org/wiki/Q111016295) | [adfreiburg/qlever:latest](https://hub.docker.com/r/adfreiburg/qlever) | 7019 | 7819 | olympics | |
|
|
109
|
+
| 🛑 | [graphdb](http://localhost:7200) | graphdb-omnigraph | [Q58425577](https://www.wikidata.org/wiki/Q58425577) | [ontotext/graphdb:11.2.0](https://hub.docker.com/r/ontotext/graphdb) | 7200 | 7700 | repo1 | |
|
|
110
|
+
| 🛑 | [millenniumdb](http://localhost:1234)| millenniumdb-omnigraph| [Q118954995](https://www.wikidata.org/wiki/Q118954995) | [imfd/millenniumdb](https://hub.docker.com/r/imfd/millenniumdb) | 1234 | 1734 | default | |
|
|
110
111
|
| 🛑 | [stardog](http://localhost:5820) | stardog-omnigraph | [Q91147741](https://www.wikidata.org/wiki/Q91147741) | [stardog/stardog:latest](https://hub.docker.com/r/stardog/stardog) | 5820 | 5320 | mydb | admin |
|
|
111
112
|
| 🛑 | [virtuoso](http://localhost:8890) | virtuoso-omnigraph | [Q7935239](https://www.wikidata.org/wiki/Q7935239) | [openlink/virtuoso-opensource-7:latest](https://hub.docker.com/r/openlink/virtuoso-opensource-7) | 8890 | 8390 | KB | dba |
|
|
112
113
|
|
|
@@ -37,16 +37,16 @@ This leads to:
|
|
|
37
37
|
|
|
38
38
|
### Supported Triple Stores
|
|
39
39
|
|
|
40
|
-
| Database | Status |
|
|
41
|
-
|
|
40
|
+
| Database | Status | Notes |
|
|
41
|
+
|----------|--------|-------|
|
|
42
42
|
| **Apache Jena** | 🟢 Working | Robust, standards compliant |
|
|
43
43
|
| **Blazegraph** | 🟢 Working | High performance, easy setup |
|
|
44
|
-
| **
|
|
44
|
+
| **Oxigraph** | 🟢 Working | Rust-based, embedded, fast |
|
|
45
45
|
| **QLever** | 🟢 Working | Extremely fast queries |
|
|
46
|
-
| **
|
|
47
|
-
| **MillenniumDB** | 🛑
|
|
48
|
-
| **
|
|
49
|
-
| **Stardog** | 🛑 Planned |
|
|
46
|
+
| **GraphDB** | 🛑 Disabled | License required (free version available) |
|
|
47
|
+
| **MillenniumDB** | 🛑 Disabled | Import-first workflow, Property Graph + RDF |
|
|
48
|
+
| **Virtuoso** | 🛑 Disabled | Permissions issue (#30) |
|
|
49
|
+
| **Stardog** | 🛑 Planned | License required |
|
|
50
50
|
|
|
51
51
|
Whether you're building a semantic web application, conducting research, or evaluating different triple stores, pyomnigraph eliminates the complexity of working with multiple graph database systems.
|
|
52
52
|
|
|
@@ -57,10 +57,11 @@ omnigraph omnigraph --list --include-inactive --doc-format github
|
|
|
57
57
|
| Active | Name | Container Name | Wikidata | Image | Port | Test Port | Dataset | User |
|
|
58
58
|
|----------|-------------------------------------|----------------------|--------------------------------------------------------|--------------------------------------------------------------------------------------------------|--------|-------------|-----------|--------|
|
|
59
59
|
| 🟢️ | [blazegraph](http://localhost:9898) | blazegraph-omnigraph | [Q20127748](https://www.wikidata.org/wiki/Q20127748) | [lyrasis/blazegraph:2.1.5](https://hub.docker.com/r/lyrasis/blazegraph) | 9898 | 7898 | kb | |
|
|
60
|
-
| 🟢 | [graphdb](http://localhost:7200) | graphdb-omnigraph | [Q58425577](https://www.wikidata.org/wiki/Q58425577) | [ontotext/graphdb:9.11.2-se](https://hub.docker.com/r/ontotext/graphdb) | 7200 | 7700 | repo1 | |
|
|
61
60
|
| 🟢️ | [jena](http://localhost:3030) | jena-omnigraph | [Q109376461](https://www.wikidata.org/wiki/Q109376461) | [stain/jena-fuseki:latest](https://hub.docker.com/r/stain/jena-fuseki) | 3030 | 7030 | ds | admin |
|
|
62
|
-
|
|
|
61
|
+
| 🟢️ | [oxigraph](http://localhost:7878) | oxigraph-omnigraph | [Q118980507](https://www.wikidata.org/wiki/Q118980507) | [oxigraph/oxigraph:latest](https://hub.docker.com/r/oxigraph/oxigraph) | 7878 | 7378 | default | |
|
|
63
62
|
| 🟢️ | [qlever](http://localhost:7019) | qlever-omnigraph | [Q111016295](https://www.wikidata.org/wiki/Q111016295) | [adfreiburg/qlever:latest](https://hub.docker.com/r/adfreiburg/qlever) | 7019 | 7819 | olympics | |
|
|
63
|
+
| 🛑 | [graphdb](http://localhost:7200) | graphdb-omnigraph | [Q58425577](https://www.wikidata.org/wiki/Q58425577) | [ontotext/graphdb:11.2.0](https://hub.docker.com/r/ontotext/graphdb) | 7200 | 7700 | repo1 | |
|
|
64
|
+
| 🛑 | [millenniumdb](http://localhost:1234)| millenniumdb-omnigraph| [Q118954995](https://www.wikidata.org/wiki/Q118954995) | [imfd/millenniumdb](https://hub.docker.com/r/imfd/millenniumdb) | 1234 | 1734 | default | |
|
|
64
65
|
| 🛑 | [stardog](http://localhost:5820) | stardog-omnigraph | [Q91147741](https://www.wikidata.org/wiki/Q91147741) | [stardog/stardog:latest](https://hub.docker.com/r/stardog/stardog) | 5820 | 5320 | mydb | admin |
|
|
65
66
|
| 🛑 | [virtuoso](http://localhost:8890) | virtuoso-omnigraph | [Q7935239](https://www.wikidata.org/wiki/Q7935239) | [openlink/virtuoso-opensource-7:latest](https://hub.docker.com/r/openlink/virtuoso-opensource-7) | 8890 | 8390 | KB | dba |
|
|
66
67
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.2"
|
|
@@ -123,6 +123,10 @@ class OmnigraphCmd(BaseCmd):
|
|
|
123
123
|
"""
|
|
124
124
|
if dataset is None:
|
|
125
125
|
server.config.dumps_dir = self.ogp.examples_dir
|
|
126
|
+
elif dataset.rdf_file:
|
|
127
|
+
# If rdf_file is specified, set dumps_dir to the parent directory of the file
|
|
128
|
+
rdf_file_path = Path(dataset.rdf_file).expanduser().resolve()
|
|
129
|
+
server.config.dumps_dir = rdf_file_path.parent
|
|
126
130
|
else:
|
|
127
131
|
server.config.dumps_dir = self.ogp.dumps_dir / dataset.id
|
|
128
132
|
|
|
@@ -167,7 +171,8 @@ class OmnigraphCmd(BaseCmd):
|
|
|
167
171
|
handled = False
|
|
168
172
|
if cmds:
|
|
169
173
|
for cmd in cmds:
|
|
170
|
-
|
|
174
|
+
# issue #38: upload needs the per-dataset dumps_dir just like load
|
|
175
|
+
if cmd in ("load", "upload"):
|
|
171
176
|
cmd_iterator = self.load_iterator(server)
|
|
172
177
|
else:
|
|
173
178
|
cmd_iterator = iter([None]) # Single iteration
|
|
@@ -70,6 +70,7 @@ class OmniServer:
|
|
|
70
70
|
"start": lambda s: ServerCmd(title("start", s), s.start),
|
|
71
71
|
"status": lambda s: ServerCmd(title("status", s), s.status_info),
|
|
72
72
|
"stop": lambda s: ServerCmd(title("stop", s), s.stop),
|
|
73
|
+
"upload": lambda s: ServerCmd(title("native bulk upload dumps", s), s.upload_dump_files),
|
|
73
74
|
"webui": lambda s: ServerCmd(title("webui", s), s.webui),
|
|
74
75
|
}
|
|
75
76
|
return server_cmds
|
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
|
|
|
8
8
|
from typing import Dict, Optional
|
|
9
9
|
|
|
10
10
|
from basemkit.yamlable import lod_storable
|
|
11
|
+
from lodstorage.params import Params
|
|
11
12
|
from lodstorage.query import Query
|
|
12
13
|
from lodstorage.sparql import SPARQL
|
|
13
14
|
|
|
@@ -19,38 +20,80 @@ class RdfDataset:
|
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
name: str # Human-readable dataset name
|
|
22
|
-
base_url: str # Base URL e.g. for tryit
|
|
23
|
-
endpoint_url: str # SPARQL endpoint URL
|
|
23
|
+
base_url: Optional[str] = None # Base URL e.g. for tryit
|
|
24
|
+
endpoint_url: Optional[str] = None # SPARQL endpoint URL
|
|
24
25
|
description: Optional[str] = None # Optional dataset description
|
|
25
26
|
database: Optional[str] = "jena" # the database type of the endpoint
|
|
26
27
|
expected_solutions: Optional[int] = None # Expected number of solutions
|
|
27
28
|
select_pattern: str = "?s ?p ?o" # Basic Graph Pattern for queries
|
|
28
29
|
construct_template: Optional[str] = field(default="?s ?p ?o")
|
|
30
|
+
params: Optional[Dict[str, str]] = None # npq parameters for {{ name }} templates (issue #36)
|
|
29
31
|
prefix_sets: Optional[list] = field(default_factory=list)
|
|
30
32
|
active: Optional[bool] = False
|
|
33
|
+
rdf_file: Optional[str] = None # Path to local RDF file (alternative to endpoint_url)
|
|
31
34
|
# fields to be configured by post_init
|
|
32
35
|
id: Optional[str] = field(default=None)
|
|
33
36
|
count_query: Optional[Query] = field(default=None)
|
|
34
37
|
select_query: Optional[Query] = field(default=None)
|
|
35
38
|
sparql: Optional[SPARQL] = field(default=None)
|
|
36
39
|
|
|
40
|
+
def apply_params(self, params_dict: Optional[Dict[str, str]] = None) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Apply npq parameters to select_pattern and construct_template.
|
|
43
|
+
|
|
44
|
+
{{ name }} templates are replaced with the values of the merged
|
|
45
|
+
params: the dataset's own params overridden by the given ones.
|
|
46
|
+
Substitution always starts from the raw templates, so CLI overrides
|
|
47
|
+
can be re-applied after YAML defaults. The blanket Params audit is
|
|
48
|
+
off - it rejects quoted VALUES lists (see issue #36); values stem
|
|
49
|
+
from local configuration and CLI.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
params_dict: additional parameter values overriding self.params
|
|
53
|
+
"""
|
|
54
|
+
# preserve the raw templates on first call
|
|
55
|
+
if not hasattr(self, "raw_templates"):
|
|
56
|
+
self.raw_templates = {
|
|
57
|
+
"select_pattern": self.select_pattern,
|
|
58
|
+
"construct_template": self.construct_template,
|
|
59
|
+
}
|
|
60
|
+
merged = dict(self.params) if self.params else {}
|
|
61
|
+
if params_dict:
|
|
62
|
+
merged.update(params_dict)
|
|
63
|
+
if merged:
|
|
64
|
+
for attr, raw_template in self.raw_templates.items():
|
|
65
|
+
if raw_template:
|
|
66
|
+
params = Params(raw_template, with_audit=False)
|
|
67
|
+
if params.has_params:
|
|
68
|
+
params.set(merged)
|
|
69
|
+
setattr(self, attr, params.apply_parameters())
|
|
70
|
+
|
|
71
|
+
def build_queries(self) -> None:
|
|
72
|
+
"""
|
|
73
|
+
(Re)build count_query and select_query from the current select_pattern.
|
|
74
|
+
Only initializes SPARQL-related fields if endpoint_url is provided.
|
|
75
|
+
"""
|
|
76
|
+
if self.endpoint_url:
|
|
77
|
+
self.count_query = Query(
|
|
78
|
+
name=f"{self.name}_count",
|
|
79
|
+
query=f"SELECT (COUNT(*) AS ?count) WHERE {{ {self.select_pattern} }}",
|
|
80
|
+
endpoint=self.endpoint_url,
|
|
81
|
+
description=f"Count query for {self.name}",
|
|
82
|
+
)
|
|
83
|
+
self.select_query = Query(
|
|
84
|
+
name=f"{self.name}_select",
|
|
85
|
+
query=f"SELECT * WHERE {{ {self.select_pattern} }}",
|
|
86
|
+
endpoint=self.endpoint_url,
|
|
87
|
+
description=f"Select query for {self.name}",
|
|
88
|
+
)
|
|
89
|
+
self.sparql = SPARQL(self.endpoint_url)
|
|
90
|
+
|
|
37
91
|
def __post_init__(self):
|
|
38
92
|
"""
|
|
39
|
-
|
|
93
|
+
Apply npq params and generate the queries from select_pattern.
|
|
40
94
|
"""
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
query=f"SELECT (COUNT(*) AS ?count) WHERE {{ {self.select_pattern} }}",
|
|
44
|
-
endpoint=self.endpoint_url,
|
|
45
|
-
description=f"Count query for {self.name}",
|
|
46
|
-
)
|
|
47
|
-
self.select_query = Query(
|
|
48
|
-
name=f"{self.name}_select",
|
|
49
|
-
query=f"SELECT * WHERE {{ {self.select_pattern} }}",
|
|
50
|
-
endpoint=self.endpoint_url,
|
|
51
|
-
description=f"Select query for {self.name}",
|
|
52
|
-
)
|
|
53
|
-
self.sparql = SPARQL(self.endpoint_url)
|
|
95
|
+
self.apply_params()
|
|
96
|
+
self.build_queries()
|
|
54
97
|
|
|
55
98
|
@property
|
|
56
99
|
def full_name(self):
|
|
@@ -66,6 +109,8 @@ class RdfDataset:
|
|
|
66
109
|
Number of solutions available from the count query
|
|
67
110
|
"""
|
|
68
111
|
count = self.sparql.getValue(self.count_query.query, "count")
|
|
112
|
+
# issue #37: getValue returns the raw literal which may be a str
|
|
113
|
+
count = int(count)
|
|
69
114
|
return count
|
|
70
115
|
|
|
71
116
|
def getTryItUrl(self, database: str = "blazegraph") -> str:
|
|
@@ -10,6 +10,8 @@ import os
|
|
|
10
10
|
import webbrowser
|
|
11
11
|
from argparse import ArgumentParser, Namespace
|
|
12
12
|
|
|
13
|
+
from basemkit.argparse_action import StoreDictKeyPair
|
|
14
|
+
|
|
13
15
|
from omnigraph.basecmd import BaseCmd
|
|
14
16
|
from omnigraph.rdf_dataset import RdfDataset, RdfDatasets
|
|
15
17
|
from omnigraph.rdfdump import RdfDumpDownloader
|
|
@@ -61,6 +63,13 @@ class RdfDumpCmd(BaseCmd):
|
|
|
61
63
|
default=None,
|
|
62
64
|
help="Maximum number of solutions/triples to download (uses dataset expected_solutions if not specified)",
|
|
63
65
|
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--param",
|
|
68
|
+
action=StoreDictKeyPair,
|
|
69
|
+
dest="params",
|
|
70
|
+
metavar="KEY=VALUE,KEY=VALUE...",
|
|
71
|
+
help="npq parameter values overriding the dataset's params (issue #36)",
|
|
72
|
+
)
|
|
64
73
|
parser.add_argument("--no-progress", action="store_true", help="Disable progress bar")
|
|
65
74
|
parser.add_argument("--output-path", default=".", help="Path for dump files")
|
|
66
75
|
parser.add_argument("--tryit", action="store_true", help="open the try it! URL [default: %(default)s]")
|
|
@@ -97,6 +106,11 @@ class RdfDumpCmd(BaseCmd):
|
|
|
97
106
|
"""
|
|
98
107
|
super().handle_args(args)
|
|
99
108
|
datasets = self.datasets
|
|
109
|
+
if getattr(args, "params", None):
|
|
110
|
+
# apply CLI npq parameter overrides and rebuild the queries
|
|
111
|
+
for dataset in datasets.values():
|
|
112
|
+
dataset.apply_params(args.params)
|
|
113
|
+
dataset.build_queries()
|
|
100
114
|
if self.args.about:
|
|
101
115
|
self.about()
|
|
102
116
|
|
|
@@ -65,4 +65,40 @@ datasets:
|
|
|
65
65
|
?pName ?p4Name ?o4Name
|
|
66
66
|
# Optional dataset description
|
|
67
67
|
description: "W2306 postal code coordinates"
|
|
68
|
-
active: false
|
|
68
|
+
active: false
|
|
69
|
+
papers_of_my_network_wikidata:
|
|
70
|
+
# npq (named parameterized query) example - issue #36
|
|
71
|
+
# ORCID is the join key; {{ orcids }} is the VALUES scaling knob;
|
|
72
|
+
# endpoint is late-bound - own copy answers completely and fastest,
|
|
73
|
+
# see the endpoint matrix in the issue
|
|
74
|
+
name: "Papers of my network (wikidata)"
|
|
75
|
+
base_url: "https://qlever-api.wikidata.dbis.rwth-aachen.de"
|
|
76
|
+
endpoint_url: "https://qlever-api.wikidata.dbis.rwth-aachen.de/"
|
|
77
|
+
database: "qlever"
|
|
78
|
+
select_pattern: |
|
|
79
|
+
VALUES ?orcid { {{ orcids }} }
|
|
80
|
+
?author <http://www.wikidata.org/prop/direct/P496> ?orcid .
|
|
81
|
+
?work <http://www.wikidata.org/prop/direct/P50> ?author .
|
|
82
|
+
?work ?p ?o
|
|
83
|
+
construct_template: "?work ?p ?o"
|
|
84
|
+
params:
|
|
85
|
+
orcids: '"0000-0002-0821-6995" "0000-0003-2533-6363" "0000-0001-9879-3827" "0000-0001-6324-7164"'
|
|
86
|
+
description: "Papers authored by the co-author network, seeded by ORCIDs"
|
|
87
|
+
active: false
|
|
88
|
+
papers_of_my_network_dblp:
|
|
89
|
+
# npq companion - same {{ orcids }} param drives the dblp graph
|
|
90
|
+
name: "Papers of my network (dblp)"
|
|
91
|
+
base_url: "https://sparql.dblp.org"
|
|
92
|
+
endpoint_url: "https://sparql.dblp.org/sparql"
|
|
93
|
+
database: "qlever"
|
|
94
|
+
select_pattern: |
|
|
95
|
+
VALUES ?orcid { {{ orcids }} }
|
|
96
|
+
BIND(IRI(CONCAT("https://orcid.org/", ?orcid)) AS ?orcid_uri)
|
|
97
|
+
?author <https://dblp.org/rdf/schema#orcid> ?orcid_uri .
|
|
98
|
+
?paper <https://dblp.org/rdf/schema#authoredBy> ?author .
|
|
99
|
+
?paper ?p ?o
|
|
100
|
+
construct_template: "?paper ?p ?o"
|
|
101
|
+
params:
|
|
102
|
+
orcids: '"0000-0002-0821-6995" "0000-0003-2533-6363" "0000-0001-9879-3827" "0000-0001-6324-7164"'
|
|
103
|
+
description: "Papers authored by the co-author network in dblp, same ORCID seed"
|
|
104
|
+
active: false
|
|
@@ -14,6 +14,7 @@ servers:
|
|
|
14
14
|
port: 9898
|
|
15
15
|
test_port: 7898
|
|
16
16
|
host: "localhost"
|
|
17
|
+
# docker_bind: "0.0.0.0" # Optional: Uncomment to allow external access (default: "127.0.0.1" for localhost-only, recommended for security)
|
|
17
18
|
prefix_sets: ["rdf", "blazegraph","gov","wikidata"]
|
|
18
19
|
active: true
|
|
19
20
|
graphdb:
|
|
@@ -161,6 +161,7 @@ class ServerConfig:
|
|
|
161
161
|
has_license: bool = False
|
|
162
162
|
protocol: str = "http"
|
|
163
163
|
host: str = "localhost"
|
|
164
|
+
docker_bind: str = "127.0.0.1" # Docker port bind address (default: localhost-only for security)
|
|
164
165
|
rdf_format: str = "turtle"
|
|
165
166
|
auth_user: Optional[str] = None
|
|
166
167
|
auth_password: Optional[str] = None
|
|
@@ -8,6 +8,8 @@ from dataclasses import dataclass
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
import re
|
|
10
10
|
import os
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
11
13
|
from omnigraph.server_config import ServerLifecycleState, ServerStatus
|
|
12
14
|
from omnigraph.sparql_server import ServerConfig, ServerEnv, SparqlServer
|
|
13
15
|
|
|
@@ -42,7 +44,7 @@ class BlazegraphConfig(ServerConfig):
|
|
|
42
44
|
f"docker run -d --name {self.container_name} "
|
|
43
45
|
f"-e BLAZEGRAPH_UID={os.getuid()} "
|
|
44
46
|
f"-e BLAZEGRAPH_GID={os.getgid()} "
|
|
45
|
-
f"-p {self.port}:8080 "
|
|
47
|
+
f"-p {self.docker_bind}:{self.port}:8080 "
|
|
46
48
|
f"-v {data_dir}/RWStore.properties:/RWStore.properties "
|
|
47
49
|
f"-v {data_dir}:/data "
|
|
48
50
|
f"{self.image}"
|
|
@@ -94,6 +96,68 @@ class Blazegraph(SparqlServer):
|
|
|
94
96
|
rwstore_path.write_text(props)
|
|
95
97
|
|
|
96
98
|
|
|
99
|
+
def get_dataloader_xml(self, container_path: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Build the DataLoader servlet properties XML for the given
|
|
102
|
+
container-side file or directory path.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
container_path: path as seen inside the container (mounted /data)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
the properties XML document
|
|
109
|
+
"""
|
|
110
|
+
xml = f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
111
|
+
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
|
|
112
|
+
<properties>
|
|
113
|
+
<entry key="namespace">{self.config.dataset}</entry>
|
|
114
|
+
<entry key="propertyFile">/RWStore.properties</entry>
|
|
115
|
+
<entry key="fileOrDirs">{container_path}</entry>
|
|
116
|
+
<entry key="-durableQueues">true</entry>
|
|
117
|
+
</properties>"""
|
|
118
|
+
return xml
|
|
119
|
+
|
|
120
|
+
def upload_dump_files(self, file_pattern: str = None) -> int:
|
|
121
|
+
"""
|
|
122
|
+
Bulk-load dump files via Blazegraph's REST DataLoader servlet, which
|
|
123
|
+
reads the files from the server's own filesystem (the mounted /data
|
|
124
|
+
directory) instead of pushing them through HTTP request bodies.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
file_pattern: Glob pattern for dump files
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Number of files loaded successfully
|
|
131
|
+
"""
|
|
132
|
+
container_name = self.config.container_name
|
|
133
|
+
files = self.get_dump_files(file_pattern)
|
|
134
|
+
loaded_count = 0
|
|
135
|
+
if not files:
|
|
136
|
+
self.log.log("⚠️", container_name, f"No dump files found for pattern: {file_pattern}")
|
|
137
|
+
else:
|
|
138
|
+
# stage the dumps under data_dir so the container sees them at /data/dumps
|
|
139
|
+
stage_dir = Path(self.config.base_data_dir) / "dumps"
|
|
140
|
+
stage_dir.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
for file in files:
|
|
142
|
+
target = stage_dir / file.name
|
|
143
|
+
if not target.exists():
|
|
144
|
+
shutil.copy2(file, target)
|
|
145
|
+
xml = self.get_dataloader_xml("/data/dumps")
|
|
146
|
+
response = self.make_request(
|
|
147
|
+
"POST",
|
|
148
|
+
self.config.dataloader_url,
|
|
149
|
+
headers={"Content-Type": "application/xml"},
|
|
150
|
+
data=xml,
|
|
151
|
+
timeout=self.config.upload_timeout,
|
|
152
|
+
)
|
|
153
|
+
if response.success:
|
|
154
|
+
loaded_count = len(files)
|
|
155
|
+
self.log.log("✅", container_name, f"DataLoader loaded {loaded_count} file(s)")
|
|
156
|
+
else:
|
|
157
|
+
error_msg = str(response.error) if response.error else f"HTTP {response.response.status_code}"
|
|
158
|
+
self.log.log("❌", container_name, f"DataLoader failed: {error_msg}")
|
|
159
|
+
return loaded_count
|
|
160
|
+
|
|
97
161
|
def status(self) -> ServerStatus:
|
|
98
162
|
"""
|
|
99
163
|
Get server status information.
|
|
@@ -60,7 +60,7 @@ class GraphDBConfig(ServerConfig):
|
|
|
60
60
|
# 4. Construct Command
|
|
61
61
|
docker_run_command = (
|
|
62
62
|
f"docker run {env_str} -d --name {self.container_name} "
|
|
63
|
-
f"-p
|
|
63
|
+
f"-p {self.docker_bind}:{self.port}:7200 "
|
|
64
64
|
f"-v {data_dir}:/opt/graphdb/home "
|
|
65
65
|
f"{target_image}"
|
|
66
66
|
)
|
|
@@ -7,6 +7,8 @@ Apache Jena SPARQL support
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List
|
|
10
12
|
|
|
11
13
|
from omnigraph.server_config import ServerLifecycleState, ServerStatus
|
|
12
14
|
from omnigraph.sparql_server import ServerConfig, ServerEnv, SparqlServer
|
|
@@ -48,7 +50,7 @@ class JenaConfig(ServerConfig):
|
|
|
48
50
|
env = f"{env} -e ADMIN_PASSWORD={self.auth_password}"
|
|
49
51
|
docker_run_command = (
|
|
50
52
|
f"docker run {self.docker_user_flag} {env} -d --name {self.container_name} "
|
|
51
|
-
f"-p {self.port}:3030 "
|
|
53
|
+
f"-p {self.docker_bind}:{self.port}:3030 "
|
|
52
54
|
f"-v {data_dir}:/fuseki "
|
|
53
55
|
f"{self.image}"
|
|
54
56
|
)
|
|
@@ -96,7 +98,65 @@ class Jena(SparqlServer):
|
|
|
96
98
|
Returns:
|
|
97
99
|
Tuple of (response, exception)
|
|
98
100
|
"""
|
|
99
|
-
result,error=self.execute_update_query_with_post(update_query)
|
|
100
|
-
return result,error
|
|
101
|
+
result, error = self.execute_update_query_with_post(update_query)
|
|
102
|
+
return result, error
|
|
103
|
+
|
|
104
|
+
def get_tdbloader_command(self, files: List[Path]) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Build the tdb2.tdbloader docker command for the given dump files.
|
|
107
|
+
|
|
108
|
+
The loader classes ship inside fuseki-server.jar, so the server's own
|
|
109
|
+
image is reused with an overridden entrypoint. The TDB2 dataset is
|
|
110
|
+
single-writer - the server must be stopped while the loader runs.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
files: dump files to load (from the dumps directory)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
the docker run command string
|
|
117
|
+
"""
|
|
118
|
+
loc = f"/fuseki/databases/{self.config.dataset}"
|
|
119
|
+
dumps_dir = Path(self.config.dumps_dir)
|
|
120
|
+
file_args = " ".join(f"/dumps/{file.name}" for file in files)
|
|
121
|
+
command = (
|
|
122
|
+
f"docker run --rm {self.config.docker_user_flag} --entrypoint java "
|
|
123
|
+
f"-v {self.config.base_data_dir}:/fuseki "
|
|
124
|
+
f"-v {dumps_dir}:/dumps "
|
|
125
|
+
f"{self.config.image} "
|
|
126
|
+
f"-cp /jena-fuseki/fuseki-server.jar tdb2.tdbloader "
|
|
127
|
+
f"--loc {loc} {file_args}"
|
|
128
|
+
)
|
|
129
|
+
return command
|
|
130
|
+
|
|
131
|
+
def upload_dump_files(self, file_pattern: str = None) -> int:
|
|
132
|
+
"""
|
|
133
|
+
Bulk-load dump files with tdb2.tdbloader directly into the TDB2
|
|
134
|
+
database files - the native path for the file-backed store, avoiding
|
|
135
|
+
the HTTP single-POST transaction that faults the mmap'd node table
|
|
136
|
+
(issue #25).
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
file_pattern: Glob pattern for dump files
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Number of files loaded successfully
|
|
143
|
+
"""
|
|
144
|
+
container_name = self.config.container_name
|
|
145
|
+
files = self.get_dump_files(file_pattern)
|
|
146
|
+
loaded_count = 0
|
|
147
|
+
if not files:
|
|
148
|
+
self.log.log("⚠️", container_name, f"No dump files found for pattern: {file_pattern}")
|
|
149
|
+
else:
|
|
150
|
+
self.stop()
|
|
151
|
+
loader_cmd = self.get_tdbloader_command(files)
|
|
152
|
+
shell_result = self.run_shell_command(
|
|
153
|
+
loader_cmd,
|
|
154
|
+
success_msg=f"tdb2.tdbloader loaded {len(files)} file(s)",
|
|
155
|
+
error_msg="tdb2.tdbloader failed",
|
|
156
|
+
)
|
|
157
|
+
if shell_result.success:
|
|
158
|
+
loaded_count = len(files)
|
|
159
|
+
self.start()
|
|
160
|
+
return loaded_count
|
|
101
161
|
|
|
102
162
|
|
|
@@ -62,8 +62,8 @@ class MillenniumDBConfig(ServerConfig):
|
|
|
62
62
|
|
|
63
63
|
docker_run_command = (
|
|
64
64
|
f"docker run -d --name {self.container_name} "
|
|
65
|
-
f"-p {self.sparql_port}:1234 "
|
|
66
|
-
f"-p {self.web_port}:4321 "
|
|
65
|
+
f"-p {self.docker_bind}:{self.sparql_port}:1234 "
|
|
66
|
+
f"-p {self.docker_bind}:{self.web_port}:4321 "
|
|
67
67
|
f"-v {data_dir}:/data "
|
|
68
68
|
f"{self.image} "
|
|
69
69
|
f"mdb-server {db_path}"
|
|
@@ -47,7 +47,7 @@ class OxigraphConfig(ServerConfig):
|
|
|
47
47
|
"""
|
|
48
48
|
docker_run_command = (
|
|
49
49
|
f"docker run {self.docker_user_flag} -d --name {self.container_name} "
|
|
50
|
-
f"-p {self.port}:7878 "
|
|
50
|
+
f"-p {self.docker_bind}:{self.port}:7878 "
|
|
51
51
|
f"-v {data_dir}:/data "
|
|
52
52
|
f"{self.image} serve --bind 0.0.0.0:7878 --location /data"
|
|
53
53
|
)
|
|
@@ -5,6 +5,7 @@ Created on 2025-05-28
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import shutil
|
|
8
9
|
from configparser import ConfigParser, ExtendedInterpolation
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from pathlib import Path
|
|
@@ -238,6 +239,71 @@ class QLever(SparqlServer):
|
|
|
238
239
|
|
|
239
240
|
return None
|
|
240
241
|
|
|
242
|
+
def get_index_commands(self, files: List[Path]) -> List[str]:
|
|
243
|
+
"""
|
|
244
|
+
Build the qlever CLI commands to (re)index the given dump files.
|
|
245
|
+
|
|
246
|
+
QLever loads bulk data by building its index from files - there is no
|
|
247
|
+
native HTTP bulk-write path; the SPARQL INSERT route is only suitable
|
|
248
|
+
for small increments.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
files: dump files staged in the data directory
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
list of shell commands to run in the data directory
|
|
255
|
+
"""
|
|
256
|
+
commands = [
|
|
257
|
+
"qlever stop",
|
|
258
|
+
"qlever index --overwrite-existing",
|
|
259
|
+
f"qlever start --server-container {self.config.container_name}",
|
|
260
|
+
]
|
|
261
|
+
return commands
|
|
262
|
+
|
|
263
|
+
def upload_dump_files(self, file_pattern: str = None) -> int:
|
|
264
|
+
"""
|
|
265
|
+
Bulk-load dump files by rebuilding the QLever index from them -
|
|
266
|
+
the native path; the turtle-to-INSERT conversion of upload_request
|
|
267
|
+
is unsuitable for bulk data.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
file_pattern: Glob pattern for dump files
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Number of files loaded successfully
|
|
274
|
+
"""
|
|
275
|
+
container_name = self.config.container_name
|
|
276
|
+
files = self.get_dump_files(file_pattern)
|
|
277
|
+
loaded_count = 0
|
|
278
|
+
if not files:
|
|
279
|
+
self.log.log("⚠️", container_name, f"No dump files found for pattern: {file_pattern}")
|
|
280
|
+
return loaded_count
|
|
281
|
+
data_dir = Path(self.config.data_dir)
|
|
282
|
+
qleverfile_path = data_dir / "Qleverfile"
|
|
283
|
+
qlever_file = QLeverfile.ofFile(qleverfile_path)
|
|
284
|
+
if qlever_file is None:
|
|
285
|
+
self.log.log("❌", container_name, f"no Qleverfile in {data_dir} - run start (setup-config) first")
|
|
286
|
+
return loaded_count
|
|
287
|
+
# stage the dumps in the data directory and register them as input files
|
|
288
|
+
for file in files:
|
|
289
|
+
target = data_dir / file.name
|
|
290
|
+
if not target.exists():
|
|
291
|
+
shutil.copy2(file, target)
|
|
292
|
+
input_files = " ".join(file.name for file in files)
|
|
293
|
+
qlever_file.set("index", "INPUT_FILES", input_files)
|
|
294
|
+
qlever_file.save()
|
|
295
|
+
ok = True
|
|
296
|
+
for command in self.get_index_commands(files):
|
|
297
|
+
shell_result = self.run_shell_command(f"cd {data_dir};{command}")
|
|
298
|
+
if not shell_result.success:
|
|
299
|
+
self.log.log("❌", container_name, f"failed: {command}")
|
|
300
|
+
ok = False
|
|
301
|
+
break
|
|
302
|
+
if ok:
|
|
303
|
+
loaded_count = len(files)
|
|
304
|
+
self.log.log("✅", container_name, f"index rebuilt from {loaded_count} file(s)")
|
|
305
|
+
return loaded_count
|
|
306
|
+
|
|
241
307
|
def upload_request(self, file_content: bytes) -> Response:
|
|
242
308
|
"""Upload request for QLever using SPARQL INSERT statements."""
|
|
243
309
|
turtle_data = file_content.decode("utf-8")
|
|
@@ -62,7 +62,7 @@ class StardogConfig(ServerConfig):
|
|
|
62
62
|
# 4. Construct Command using target_image
|
|
63
63
|
docker_run_command = (
|
|
64
64
|
f"docker run {self.docker_user_flag}{env_str} -d --name {self.container_name} "
|
|
65
|
-
f"-p {self.port}:5820 "
|
|
65
|
+
f"-p {self.docker_bind}:{self.port}:5820 "
|
|
66
66
|
f"-v {data_dir}:/var/opt/stardog "
|
|
67
67
|
f"{target_image}"
|
|
68
68
|
)
|