pyomnigraph 0.0.3__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.
- omnigraph/__init__.py +1 -0
- omnigraph/blazegraph.py +229 -0
- omnigraph/jena.py +100 -0
- omnigraph/ominigraph_paths.py +22 -0
- omnigraph/omnigraph_cmd.py +220 -0
- omnigraph/omniserver.py +72 -0
- omnigraph/persistent_log.py +133 -0
- omnigraph/qlever.py +177 -0
- omnigraph/resources/__init__.py +0 -0
- omnigraph/resources/examples/royals.ttl +69 -0
- omnigraph/resources/examples/servers.yaml +28 -0
- omnigraph/shell.py +239 -0
- omnigraph/sparql_server.py +335 -0
- omnigraph/version.py +40 -0
- omnigraph/yamlable.py +318 -0
- pyomnigraph-0.0.3.dist-info/METADATA +56 -0
- pyomnigraph-0.0.3.dist-info/RECORD +20 -0
- pyomnigraph-0.0.3.dist-info/WHEEL +4 -0
- pyomnigraph-0.0.3.dist-info/entry_points.txt +2 -0
- pyomnigraph-0.0.3.dist-info/licenses/LICENSE +201 -0
omnigraph/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.3"
|
omnigraph/blazegraph.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import glob
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from omnigraph.persistent_log import Log
|
|
7
|
+
from omnigraph.shell import Shell
|
|
8
|
+
from omnigraph.sparql_server import ServerConfig, SparqlServer, ServerEnv
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class BlazegraphConfig(ServerConfig):
|
|
14
|
+
"""
|
|
15
|
+
Blazegraph configuration
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __post_init__(self):
|
|
19
|
+
super().__post_init__()
|
|
20
|
+
blazegraph_base = f"{self.base_url}/bigdata"
|
|
21
|
+
self.status_url = f"{blazegraph_base}/status"
|
|
22
|
+
self.sparql_url = f"{blazegraph_base}/namespace/kb/sparql"
|
|
23
|
+
self.dataloader_url = f"{blazegraph_base}/dataloader"
|
|
24
|
+
self.docker_run_command = f"docker run -d --name {self.container_name} -p {self.port}:8080 {self.image}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Blazegraph(SparqlServer):
|
|
28
|
+
"""
|
|
29
|
+
Dockerized Blazegraph SPARQL server
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
config: ServerConfig,
|
|
35
|
+
env: ServerEnv
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the Blazegraph manager.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config: Server configuration
|
|
42
|
+
env: Server environment (includes log, shell, debug, verbose)
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(config=config, env=env)
|
|
45
|
+
self.dataloader_url = f"{self.config.base_url}/dataloader"
|
|
46
|
+
|
|
47
|
+
def status(self) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
Get Blazegraph status information.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dictionary with status information, empty dict if error
|
|
53
|
+
"""
|
|
54
|
+
status_dict = {}
|
|
55
|
+
|
|
56
|
+
result = self._make_request("GET", self.config.status_url, timeout=2)
|
|
57
|
+
|
|
58
|
+
if result["success"]:
|
|
59
|
+
status_dict["status"] = "ready"
|
|
60
|
+
html_content = result["content"]
|
|
61
|
+
name_value_pattern = r'(?:<span id="(?P<name1>[^"]+)">(?P<value1>[^<]+)</span[^>]*>|/(?P<name2>[^=]+)=(?P<value2>[^\s&#]+))'
|
|
62
|
+
matches = re.finditer(name_value_pattern, html_content, re.DOTALL)
|
|
63
|
+
|
|
64
|
+
for match in matches:
|
|
65
|
+
for name_group, value_group in {
|
|
66
|
+
"name1": "value1",
|
|
67
|
+
"name2": "value2",
|
|
68
|
+
}.items():
|
|
69
|
+
name = match.group(name_group)
|
|
70
|
+
if name:
|
|
71
|
+
value = match.group(value_group)
|
|
72
|
+
sanitized_value = value.replace("</p", "").replace("/", "/")
|
|
73
|
+
sanitized_name = name.replace("-", "_").replace("/", "_")
|
|
74
|
+
sanitized_name = sanitized_name.replace("/", "/")
|
|
75
|
+
if not sanitized_name.startswith("/"):
|
|
76
|
+
status_dict[sanitized_name] = sanitized_value
|
|
77
|
+
break
|
|
78
|
+
else:
|
|
79
|
+
if result.get("error"):
|
|
80
|
+
status_dict["status"] = f"error: {result['error']}"
|
|
81
|
+
else:
|
|
82
|
+
status_dict["status"] = f"status_code: {result['status_code']}"
|
|
83
|
+
|
|
84
|
+
return status_dict
|
|
85
|
+
|
|
86
|
+
def load_file(self, filepath: str) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Load a single RDF file into Blazegraph.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
filepath: Path to RDF file
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if loaded successfully
|
|
95
|
+
"""
|
|
96
|
+
load_success = False
|
|
97
|
+
try:
|
|
98
|
+
with open(filepath, "rb") as f:
|
|
99
|
+
result = self._make_request(
|
|
100
|
+
"POST",
|
|
101
|
+
self.config.sparql_url,
|
|
102
|
+
headers={"Content-Type": "text/turtle"},
|
|
103
|
+
data=f.read(),
|
|
104
|
+
timeout=300,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if result["success"]:
|
|
108
|
+
self.log.log("✅", self.name, f"Loaded {filepath}")
|
|
109
|
+
load_success = True
|
|
110
|
+
else:
|
|
111
|
+
error_msg = result.get("error", f"HTTP {result['status_code']}")
|
|
112
|
+
self.log.log("❌", self.name, f"Failed to load {filepath}: {error_msg}")
|
|
113
|
+
load_success = False
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
self.log.log("❌", self.name, f"Exception loading {filepath}: {e}")
|
|
117
|
+
load_success = False
|
|
118
|
+
|
|
119
|
+
return load_success
|
|
120
|
+
|
|
121
|
+
def load_files_bulk(self, file_list: list) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Load multiple files using Blazegraph's bulk loader REST API.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
file_list: List of file paths to load
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if loaded successfully
|
|
130
|
+
"""
|
|
131
|
+
bulk_load_success = False
|
|
132
|
+
|
|
133
|
+
if not file_list:
|
|
134
|
+
bulk_load_success = False
|
|
135
|
+
else:
|
|
136
|
+
# Convert to absolute paths
|
|
137
|
+
abs_paths = [str(Path(f).absolute()) for f in file_list]
|
|
138
|
+
|
|
139
|
+
properties = f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
140
|
+
<properties>
|
|
141
|
+
<entry key="format">turtle</entry>
|
|
142
|
+
<entry key="quiet">false</entry>
|
|
143
|
+
<entry key="verbose">1</entry>
|
|
144
|
+
<entry key="namespace">kb</entry>
|
|
145
|
+
<entry key="fileOrDirs">{','.join(abs_paths)}</entry>
|
|
146
|
+
</properties>"""
|
|
147
|
+
|
|
148
|
+
result = self._make_request(
|
|
149
|
+
"POST",
|
|
150
|
+
self.dataloader_url,
|
|
151
|
+
headers={"Content-Type": "application/xml"},
|
|
152
|
+
data=properties,
|
|
153
|
+
timeout=3600,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if result["success"]:
|
|
157
|
+
self.log.log("✅", self.container_name, f"Bulk loaded {len(file_list)} files")
|
|
158
|
+
bulk_load_success = True
|
|
159
|
+
else:
|
|
160
|
+
error_msg = result.get("error", f"HTTP {result['status_code']}")
|
|
161
|
+
self.log.log("❌", self.container_name, f"Bulk load failed: {error_msg}")
|
|
162
|
+
bulk_load_success = False
|
|
163
|
+
|
|
164
|
+
return bulk_load_success
|
|
165
|
+
|
|
166
|
+
def load_dump_files(self, file_pattern: str = "dump_*.ttl", use_bulk: bool = True) -> int:
|
|
167
|
+
"""
|
|
168
|
+
Load all dump files matching pattern.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
file_pattern: Glob pattern for dump files
|
|
172
|
+
use_bulk: Use bulk loader if True, individual files if False
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Number of files loaded successfully
|
|
176
|
+
"""
|
|
177
|
+
files = sorted(glob.glob(file_pattern))
|
|
178
|
+
loaded_count = 0
|
|
179
|
+
|
|
180
|
+
if not files:
|
|
181
|
+
self.log.log(
|
|
182
|
+
"⚠️",
|
|
183
|
+
self.container_name,
|
|
184
|
+
f"No files found matching pattern: {file_pattern}",
|
|
185
|
+
)
|
|
186
|
+
loaded_count = 0
|
|
187
|
+
else:
|
|
188
|
+
self.log.log("✅", self.container_name, f"Found {len(files)} files to load")
|
|
189
|
+
|
|
190
|
+
if use_bulk:
|
|
191
|
+
bulk_result = self.load_files_bulk(files)
|
|
192
|
+
loaded_count = len(files) if bulk_result else 0
|
|
193
|
+
else:
|
|
194
|
+
loaded_count = 0
|
|
195
|
+
for filepath in tqdm(files, desc="Loading files"):
|
|
196
|
+
file_result = self.load_file(filepath)
|
|
197
|
+
if file_result:
|
|
198
|
+
loaded_count += 1
|
|
199
|
+
else:
|
|
200
|
+
self.log.log("❌", self.container_name, f"Failed to load: {filepath}")
|
|
201
|
+
|
|
202
|
+
return loaded_count
|
|
203
|
+
|
|
204
|
+
def test_geosparql(self) -> bool:
|
|
205
|
+
"""
|
|
206
|
+
Test if GeoSPARQL functions work.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
True if GeoSPARQL is available
|
|
210
|
+
"""
|
|
211
|
+
test_query = """
|
|
212
|
+
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
|
|
213
|
+
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
|
|
214
|
+
|
|
215
|
+
SELECT * WHERE {
|
|
216
|
+
BIND(geof:distance("POINT(0 0)"^^geo:wktLiteral, "POINT(1 1)"^^geo:wktLiteral) AS ?dist)
|
|
217
|
+
} LIMIT 1
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
result = self._make_request(
|
|
221
|
+
"POST",
|
|
222
|
+
self.sparql_url,
|
|
223
|
+
data={"query": test_query},
|
|
224
|
+
headers={"Accept": "application/sparql-results+json"},
|
|
225
|
+
timeout=10,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
geosparql_available = result["success"]
|
|
229
|
+
return geosparql_available
|
omnigraph/jena.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2025-05-28
|
|
3
|
+
|
|
4
|
+
Apache Jena SPARQL support
|
|
5
|
+
|
|
6
|
+
@author: wf
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from omnigraph.persistent_log import Log
|
|
12
|
+
from omnigraph.shell import Shell
|
|
13
|
+
from omnigraph.sparql_server import ServerConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class JenaConfig(ServerConfig):
|
|
18
|
+
"""
|
|
19
|
+
Jena Fuseki configuration
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
dataset: str = "ds"
|
|
23
|
+
|
|
24
|
+
def __post_init__(self):
|
|
25
|
+
super().__post_init__()
|
|
26
|
+
self.status_url = f"{self.base_url}/$/ping"
|
|
27
|
+
self.sparql_url = f"{self.base_url}/{self.dataset}/sparql"
|
|
28
|
+
self.update_url = f"{self.base_url}/{self.dataset}/update"
|
|
29
|
+
self.upload_url = f"{self.base_url}/{self.dataset}/data"
|
|
30
|
+
self.docker_run_command = (
|
|
31
|
+
f"docker run -d --name {self.container_name} -p {self.port}:3030 -e ADMIN_PASSWORD=admin {self.image}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
config: ServerConfig,
|
|
38
|
+
log: Log = None,
|
|
39
|
+
shell: Shell = None,
|
|
40
|
+
debug: bool = False,
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Initialize the Jena Fuseki manager.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
config: Jena server configuration
|
|
47
|
+
log: Log instance for logging
|
|
48
|
+
shell: Shell instance for Docker commands
|
|
49
|
+
debug: Enable debug output
|
|
50
|
+
"""
|
|
51
|
+
super().__init__(config=config, log=log, shell=shell, debug=debug)
|
|
52
|
+
self.dataset = getattr(config, "dataset", "ds")
|
|
53
|
+
self.update_url = f"{self.config.base_url}/{self.dataset}/update"
|
|
54
|
+
self.upload_url = f"{self.config.base_url}/{self.dataset}/data"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
from omnigraph.sparql_server import SparqlServer
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Jena(SparqlServer):
|
|
61
|
+
"""
|
|
62
|
+
Dockerized Apache Jena Fuseki SPARQL server
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
config: ServerConfig,
|
|
68
|
+
log: Log = None,
|
|
69
|
+
shell: Shell = None,
|
|
70
|
+
debug: bool = False,
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
Initialize the Jena Fuseki manager.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
config: Jena server configuration
|
|
77
|
+
log: Log instance for logging
|
|
78
|
+
shell: Shell instance for Docker commands
|
|
79
|
+
debug: Enable debug output
|
|
80
|
+
"""
|
|
81
|
+
super().__init__(config=config, log=log, shell=shell, debug=debug)
|
|
82
|
+
self.dataset = getattr(config, "dataset", "ds")
|
|
83
|
+
self.update_url = f"{self.config.base_url}/{self.dataset}/update"
|
|
84
|
+
self.upload_url = f"{self.config.base_url}/{self.dataset}/data"
|
|
85
|
+
|
|
86
|
+
def status(self) -> dict:
|
|
87
|
+
"""
|
|
88
|
+
Get Jena Fuseki status information.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary with status information, empty dict if error
|
|
92
|
+
"""
|
|
93
|
+
result = self._make_request("GET", self.config.status_url, timeout=2)
|
|
94
|
+
|
|
95
|
+
if result["success"]:
|
|
96
|
+
status = {"status": "ready"}
|
|
97
|
+
else:
|
|
98
|
+
error_msg = result.get("error", f"status_code: {result['status_code']}")
|
|
99
|
+
status = {"status": f"error: {error_msg}"}
|
|
100
|
+
return status
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 27.05.2025
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OmnigraphPaths:
|
|
11
|
+
"""
|
|
12
|
+
Omnigraph Default Paths
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""
|
|
17
|
+
constructor
|
|
18
|
+
"""
|
|
19
|
+
self.home_dir = Path.home()
|
|
20
|
+
self.omnigraph_dir = self.home_dir / ".omnigraph"
|
|
21
|
+
self.omnigraph_dir.mkdir(exist_ok=True)
|
|
22
|
+
self.examples_dir = (Path(__file__).parent / "resources" / "examples").resolve()
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2025-05-28
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
from argparse import ArgumentParser, RawDescriptionHelpFormatter, Namespace
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Callable, Dict, List
|
|
10
|
+
import webbrowser
|
|
11
|
+
|
|
12
|
+
from omnigraph.ominigraph_paths import OmnigraphPaths
|
|
13
|
+
from omnigraph.omniserver import OmniServer
|
|
14
|
+
from omnigraph.sparql_server import SparqlServer, ServerEnv
|
|
15
|
+
from omnigraph.version import Version
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ServerCmd:
|
|
20
|
+
"""
|
|
21
|
+
Command wrapper for server operations.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, title: str, func: Callable):
|
|
25
|
+
"""
|
|
26
|
+
Initialize server command.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
title: Description of the command
|
|
30
|
+
func: Function to execute
|
|
31
|
+
"""
|
|
32
|
+
self.title = title
|
|
33
|
+
self.func = func
|
|
34
|
+
|
|
35
|
+
def run(self, verbose: bool = True) -> any:
|
|
36
|
+
"""
|
|
37
|
+
Execute the server command.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
verbose: Whether to print result
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Result from function execution
|
|
44
|
+
"""
|
|
45
|
+
result = self.func()
|
|
46
|
+
if verbose:
|
|
47
|
+
print(f"{self.title}: {result}")
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
class OmnigraphCmd:
|
|
51
|
+
"""
|
|
52
|
+
Command line interface for omnigraph.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
"""
|
|
57
|
+
Initialize command line interface.
|
|
58
|
+
"""
|
|
59
|
+
self.ogp = OmnigraphPaths()
|
|
60
|
+
self.default_yaml_path = self.ogp.examples_dir / "servers.yaml"
|
|
61
|
+
self.version = Version()
|
|
62
|
+
self.program_version_message = f"{self.version.name} {self.version.version}"
|
|
63
|
+
self.parser = self.getArgParser()
|
|
64
|
+
|
|
65
|
+
def getArgParser(self, description: str = None, version_msg=None) -> ArgumentParser:
|
|
66
|
+
"""
|
|
67
|
+
Setup command line argument parser
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
description(str): the description
|
|
71
|
+
version_msg(str): the version message
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
ArgumentParser: the argument parser
|
|
75
|
+
"""
|
|
76
|
+
if description is None:
|
|
77
|
+
description = self.version.description
|
|
78
|
+
if version_msg is None:
|
|
79
|
+
version_msg = self.program_version_message
|
|
80
|
+
|
|
81
|
+
parser = ArgumentParser(
|
|
82
|
+
description=description, formatter_class=RawDescriptionHelpFormatter
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"-a",
|
|
86
|
+
"--about",
|
|
87
|
+
help="show about info [default: %(default)s]",
|
|
88
|
+
action="store_true",
|
|
89
|
+
)
|
|
90
|
+
parser.add_argument(
|
|
91
|
+
"-c","--config",
|
|
92
|
+
type=str,
|
|
93
|
+
default=str(self.default_yaml_path),
|
|
94
|
+
help="Path to server configuration YAML file [default: %(default)s]"
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"--cmd",
|
|
98
|
+
nargs="+",
|
|
99
|
+
help="commands to execute on servers: start, stop, status"
|
|
100
|
+
)
|
|
101
|
+
parser.add_argument(
|
|
102
|
+
"-d",
|
|
103
|
+
"--debug",
|
|
104
|
+
action="store_true",
|
|
105
|
+
help="show debug info [default: %(default)s]",
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"-l",
|
|
109
|
+
"--list-servers",
|
|
110
|
+
action="store_true",
|
|
111
|
+
help="List available servers [default: %(default)s]"
|
|
112
|
+
)
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"-s","--servers",
|
|
115
|
+
nargs="+",
|
|
116
|
+
default=["blazegraph"],
|
|
117
|
+
help="servers: servers to work with - all is an alias for all servers [default: %(default)s]"
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"-v",
|
|
121
|
+
"--verbose",
|
|
122
|
+
action="store_true",
|
|
123
|
+
help="show verbose output [default: %(default)s]",
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument("-V", "--version", action="version", version=version_msg)
|
|
126
|
+
return parser
|
|
127
|
+
|
|
128
|
+
def getServers(self) -> Dict[str, SparqlServer]:
|
|
129
|
+
"""
|
|
130
|
+
Get the servers to work with.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary of active servers
|
|
134
|
+
"""
|
|
135
|
+
servers = {}
|
|
136
|
+
server_names = self.args.servers
|
|
137
|
+
if "all" in server_names:
|
|
138
|
+
server_names = list(self.all_servers.keys())
|
|
139
|
+
for server_name in server_names:
|
|
140
|
+
server = self.all_servers.get(server_name)
|
|
141
|
+
if server:
|
|
142
|
+
servers[server_name] = server
|
|
143
|
+
return servers
|
|
144
|
+
|
|
145
|
+
def run_cmds(self, server: SparqlServer, cmds: List[str]) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Run commands on a server.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
server: Server instance
|
|
151
|
+
cmds: List of commands to execute
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
True if any commands were handled
|
|
155
|
+
"""
|
|
156
|
+
handled=False
|
|
157
|
+
s_cmds = {
|
|
158
|
+
"start": ServerCmd(title=f"start {server.name}", func=server.start),
|
|
159
|
+
"stop": ServerCmd(title=f"stop {server.name}", func=server.stop),
|
|
160
|
+
"status": ServerCmd(title=f"status {server.name}", func=server.status)
|
|
161
|
+
}
|
|
162
|
+
if cmds:
|
|
163
|
+
for cmd in cmds:
|
|
164
|
+
s_cmd=s_cmds.get(cmd)
|
|
165
|
+
if s_cmd:
|
|
166
|
+
s_cmd.run()
|
|
167
|
+
handled=True
|
|
168
|
+
else:
|
|
169
|
+
print(f"unsupported command {cmd}")
|
|
170
|
+
return handled
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def handle_args(self,args:Namespace) -> bool:
|
|
174
|
+
"""
|
|
175
|
+
Handle command line arguments.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
bool: True if arguments were handled, False otherwise
|
|
179
|
+
args: Namespace
|
|
180
|
+
"""
|
|
181
|
+
self.args=args
|
|
182
|
+
handled = False
|
|
183
|
+
self.all_servers={}
|
|
184
|
+
if Path(self.args.config).exists():
|
|
185
|
+
env=ServerEnv(debug=args.debug,verbose=args.verbose)
|
|
186
|
+
omni_server=OmniServer(env=env)
|
|
187
|
+
self.all_servers = omni_server.servers(args.config)
|
|
188
|
+
else:
|
|
189
|
+
print(f"Config file not found: {args.config}")
|
|
190
|
+
|
|
191
|
+
if args.about:
|
|
192
|
+
print(self.program_version_message)
|
|
193
|
+
print(f"{len(self.servers)} servers configured")
|
|
194
|
+
print(f"see {self.version.doc_url}")
|
|
195
|
+
webbrowser.open(self.version.doc_url)
|
|
196
|
+
handled = True
|
|
197
|
+
|
|
198
|
+
if self.args.list_servers:
|
|
199
|
+
print("Available servers:")
|
|
200
|
+
for name, server in self.servers.items():
|
|
201
|
+
print(f" {name}: {server.config.server}")
|
|
202
|
+
handled = True
|
|
203
|
+
|
|
204
|
+
self.servers=self.getServers()
|
|
205
|
+
for server in self.servers.values():
|
|
206
|
+
handled=handled or self.run_cmds(server,cmds=args.cmd)
|
|
207
|
+
|
|
208
|
+
return handled
|
|
209
|
+
|
|
210
|
+
def main():
|
|
211
|
+
"""
|
|
212
|
+
Main entry point for command line interface.
|
|
213
|
+
"""
|
|
214
|
+
cmd = OmnigraphCmd()
|
|
215
|
+
args = cmd.parser.parse_args()
|
|
216
|
+
cmd.handle_args(args)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|
omnigraph/omniserver.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Created on 2025-05-28
|
|
3
|
+
|
|
4
|
+
@author: wf
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import asdict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict
|
|
10
|
+
|
|
11
|
+
from omnigraph.blazegraph import Blazegraph, BlazegraphConfig
|
|
12
|
+
from omnigraph.jena import Jena, JenaConfig
|
|
13
|
+
from omnigraph.qlever import QLever, QLeverConfig
|
|
14
|
+
from omnigraph.sparql_server import ServerEnv,ServerConfig, ServerConfigs, SparqlServer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OmniServer:
|
|
18
|
+
"""
|
|
19
|
+
Factory class for creating and managing SPARQL server instances.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self,env:ServerEnv):
|
|
23
|
+
"""
|
|
24
|
+
constructor
|
|
25
|
+
"""
|
|
26
|
+
self.env=env
|
|
27
|
+
|
|
28
|
+
def server4Config(self, config: ServerConfig) -> SparqlServer:
|
|
29
|
+
"""
|
|
30
|
+
Create a SparqlServer instance based on server type in config.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
config: ServerConfig with server type and settings
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
SparqlServer instance of appropriate type
|
|
37
|
+
"""
|
|
38
|
+
server_instance = None
|
|
39
|
+
config_dict = asdict(config)
|
|
40
|
+
|
|
41
|
+
if config.server == "blazegraph":
|
|
42
|
+
blazegraph_config = BlazegraphConfig(**config_dict)
|
|
43
|
+
server_instance = Blazegraph(config=blazegraph_config,env=self.env)
|
|
44
|
+
elif config.server == "qlever":
|
|
45
|
+
qlever_config = QLeverConfig(**config_dict)
|
|
46
|
+
server_instance = QLever(config=qlever_config,env=self.env)
|
|
47
|
+
elif config.server == "jena":
|
|
48
|
+
jena_config = JenaConfig(**config_dict)
|
|
49
|
+
server_instance = Jena(config=jena_config,env=self.env)
|
|
50
|
+
|
|
51
|
+
return server_instance
|
|
52
|
+
|
|
53
|
+
def servers(self, yaml_path: Path) -> Dict[str, SparqlServer]:
|
|
54
|
+
"""
|
|
55
|
+
Load active servers from YAML configuration.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
yaml_path: Path to YAML configuration file
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dictionary mapping server names to SparqlServer instances
|
|
62
|
+
"""
|
|
63
|
+
server_configs = ServerConfigs.ofYaml(yaml_path)
|
|
64
|
+
servers_dict = {}
|
|
65
|
+
|
|
66
|
+
for server_name, config in server_configs.servers.items():
|
|
67
|
+
if config.active:
|
|
68
|
+
server_instance = self.server4Config(config)
|
|
69
|
+
if server_instance:
|
|
70
|
+
servers_dict[server_name] = server_instance
|
|
71
|
+
|
|
72
|
+
return servers_dict
|