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 ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.3"
@@ -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[^>]*>|&#47;(?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("&#47;", "/")
73
+ sanitized_name = name.replace("-", "_").replace("/", "_")
74
+ sanitized_name = sanitized_name.replace("&#47;", "/")
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()
@@ -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