qlever 0.5.20__py3-none-any.whl → 0.5.22__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.
Potentially problematic release.
This version of qlever might be problematic. Click here for more details.
- qlever/__init__.py +17 -10
- qlever/command.py +1 -1
- qlever/commands/cache_stats.py +2 -2
- qlever/commands/clear_cache.py +30 -20
- qlever/commands/example_queries.py +40 -13
- qlever/commands/index.py +2 -10
- qlever/commands/query.py +2 -2
- qlever/commands/settings.py +1 -0
- qlever/commands/start.py +6 -26
- qlever/commands/stop.py +17 -41
- qlever/commands/ui.py +178 -26
- qlever/config.py +13 -6
- qlever/containerize.py +9 -4
- qlever/qlever_main.py +9 -4
- qlever/qleverfile.py +59 -5
- qlever/util.py +89 -4
- {qlever-0.5.20.dist-info → qlever-0.5.22.dist-info}/METADATA +6 -4
- {qlever-0.5.20.dist-info → qlever-0.5.22.dist-info}/RECORD +22 -23
- {qlever-0.5.20.dist-info → qlever-0.5.22.dist-info}/WHEEL +1 -1
- {qlever-0.5.20.dist-info → qlever-0.5.22.dist-info}/entry_points.txt +0 -1
- qlever/qlever_old.py +0 -1715
- {qlever-0.5.20.dist-info → qlever-0.5.22.dist-info/licenses}/LICENSE +0 -0
- {qlever-0.5.20.dist-info → qlever-0.5.22.dist-info}/top_level.txt +0 -0
qlever/commands/ui.py
CHANGED
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import subprocess
|
|
4
3
|
from os import environ
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
5
7
|
|
|
6
8
|
from qlever.command import QleverCommand
|
|
7
9
|
from qlever.containerize import Containerize
|
|
8
10
|
from qlever.log import log
|
|
9
|
-
from qlever.util import is_port_used
|
|
11
|
+
from qlever.util import is_port_used, run_command
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Return a YAML string for the given dictionary. Format values with
|
|
15
|
+
# newlines using the "|" style.
|
|
16
|
+
def dict_to_yaml(dictionary):
|
|
17
|
+
# Custom representer for yaml, which uses the "|" style only for
|
|
18
|
+
# multiline strings.
|
|
19
|
+
#
|
|
20
|
+
# NOTE: We replace all `\r\n` with `\n` because otherwise the `|` style
|
|
21
|
+
# does not work as expected.
|
|
22
|
+
class MultiLineDumper(yaml.Dumper):
|
|
23
|
+
def represent_scalar(self, tag, value, style=None):
|
|
24
|
+
value = value.replace("\r\n", "\n")
|
|
25
|
+
if isinstance(value, str) and "\n" in value:
|
|
26
|
+
style = "|"
|
|
27
|
+
return super().represent_scalar(tag, value, style)
|
|
28
|
+
|
|
29
|
+
# Dump as yaml.
|
|
30
|
+
return yaml.dump(
|
|
31
|
+
dictionary,
|
|
32
|
+
sort_keys=False,
|
|
33
|
+
Dumper=MultiLineDumper,
|
|
34
|
+
)
|
|
10
35
|
|
|
11
36
|
|
|
12
37
|
class UiCommand(QleverCommand):
|
|
@@ -37,7 +62,30 @@ class UiCommand(QleverCommand):
|
|
|
37
62
|
}
|
|
38
63
|
|
|
39
64
|
def additional_arguments(self, subparser) -> None:
|
|
40
|
-
|
|
65
|
+
subparser.add_argument(
|
|
66
|
+
"--ui-config-file",
|
|
67
|
+
default="Qleverfile-ui.yml",
|
|
68
|
+
help="Name of the config file for the QLever UI "
|
|
69
|
+
"(default: Qleverfile-ui.yml)",
|
|
70
|
+
)
|
|
71
|
+
subparser.add_argument(
|
|
72
|
+
"--ui-db-file",
|
|
73
|
+
help="Name of the database file for the QLever UI "
|
|
74
|
+
"(default: {name}.ui-db.sqlite3)",
|
|
75
|
+
)
|
|
76
|
+
subparser.add_argument(
|
|
77
|
+
"--no-pull-latest",
|
|
78
|
+
action="store_true",
|
|
79
|
+
default=False,
|
|
80
|
+
help="Do not pull the latest image for the QLever UI "
|
|
81
|
+
"(default: pull the latest image if image name contains '/')",
|
|
82
|
+
)
|
|
83
|
+
subparser.add_argument(
|
|
84
|
+
"--stop",
|
|
85
|
+
action="store_true",
|
|
86
|
+
default=False,
|
|
87
|
+
help="Stop the running container",
|
|
88
|
+
)
|
|
41
89
|
|
|
42
90
|
def execute(self, args) -> bool:
|
|
43
91
|
# If QLEVER_OVERRIDE_DISABLE_UI is set, this command is disabled.
|
|
@@ -59,57 +107,161 @@ class UiCommand(QleverCommand):
|
|
|
59
107
|
log.info("")
|
|
60
108
|
|
|
61
109
|
# Construct commands and show them.
|
|
62
|
-
|
|
110
|
+
pull_latest_image = "/" in args.ui_image and not args.no_pull_latest
|
|
111
|
+
ui_config_name = args.name
|
|
112
|
+
ui_db_file = args.ui_db_file or f"{args.name}.ui-db.sqlite3"
|
|
113
|
+
ui_db_file_from_image = "qleverui.sqlite3"
|
|
114
|
+
ui_config_file = args.ui_config_file
|
|
115
|
+
sparql_endpoint = f"http://{args.host_name}:{args.port}"
|
|
63
116
|
ui_url = f"http://{args.host_name}:{args.ui_port}"
|
|
64
117
|
pull_cmd = f"{args.ui_system} pull -q {args.ui_image}"
|
|
65
|
-
|
|
118
|
+
get_db_cmd = (
|
|
119
|
+
f"{args.ui_system} create "
|
|
120
|
+
f"--name {args.ui_container} "
|
|
121
|
+
f"{args.ui_image} "
|
|
122
|
+
f"&& {args.ui_system} cp "
|
|
123
|
+
f"{args.ui_container}:/app/db/{ui_db_file_from_image} {ui_db_file} "
|
|
124
|
+
f"&& {args.ui_system} rm -f {args.ui_container}"
|
|
125
|
+
)
|
|
126
|
+
start_ui_cmd = (
|
|
66
127
|
f"{args.ui_system} run -d "
|
|
128
|
+
f"--volume $(pwd):/app/db "
|
|
129
|
+
f"--env QLEVERUI_DATABASE_URL=sqlite:////app/db/{ui_db_file} "
|
|
67
130
|
f"--publish {args.ui_port}:7000 "
|
|
68
131
|
f"--name {args.ui_container} "
|
|
69
132
|
f"{args.ui_image}"
|
|
70
133
|
)
|
|
71
|
-
|
|
72
|
-
f"{args.ui_system} exec -
|
|
134
|
+
get_config_cmd = (
|
|
135
|
+
f"{args.ui_system} exec -i "
|
|
73
136
|
f"{args.ui_container} "
|
|
74
|
-
f'bash -c "python manage.py
|
|
75
|
-
f'{args.ui_config} {server_url}"'
|
|
137
|
+
f'bash -c "python manage.py config {ui_config_name}"'
|
|
76
138
|
)
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
139
|
+
set_config_cmd = (
|
|
140
|
+
f"{args.ui_system} exec -i "
|
|
141
|
+
f"{args.ui_container} "
|
|
142
|
+
f'bash -c "python manage.py config {ui_config_name} '
|
|
143
|
+
f'/app/db/{ui_config_file} --hide-all-other-backends"'
|
|
82
144
|
)
|
|
145
|
+
commands_to_show = []
|
|
146
|
+
if not args.stop:
|
|
147
|
+
if pull_latest_image:
|
|
148
|
+
commands_to_show.append(pull_cmd)
|
|
149
|
+
if not Path(ui_db_file).exists():
|
|
150
|
+
commands_to_show.append(get_db_cmd)
|
|
151
|
+
commands_to_show.append(start_ui_cmd)
|
|
152
|
+
if not Path(ui_config_file).exists():
|
|
153
|
+
commands_to_show.append(get_config_cmd)
|
|
154
|
+
else:
|
|
155
|
+
commands_to_show.append(set_config_cmd)
|
|
156
|
+
self.show("\n".join(commands_to_show), only_show=args.show)
|
|
83
157
|
if qlever_is_running_in_container:
|
|
84
158
|
return False
|
|
85
159
|
if args.show:
|
|
86
160
|
return True
|
|
87
161
|
|
|
88
162
|
# Stop running containers.
|
|
163
|
+
was_found_and_stopped = False
|
|
89
164
|
for container_system in Containerize.supported_systems():
|
|
90
|
-
Containerize.stop_and_remove_container(
|
|
165
|
+
was_found_and_stopped |= Containerize.stop_and_remove_container(
|
|
91
166
|
container_system, args.ui_container
|
|
92
167
|
)
|
|
168
|
+
if was_found_and_stopped:
|
|
169
|
+
log.debug(f"Stopped and removed container `{args.ui_container}`")
|
|
170
|
+
else:
|
|
171
|
+
log.debug(f"No container with name `{args.ui_container}` found")
|
|
172
|
+
if args.stop:
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
# Pull the latest image.
|
|
176
|
+
if pull_latest_image:
|
|
177
|
+
log.debug(f"Pulling image `{args.ui_image}` for QLever UI")
|
|
178
|
+
run_command(pull_cmd)
|
|
93
179
|
|
|
94
180
|
# Check if the UI port is already being used.
|
|
95
181
|
if is_port_used(args.ui_port):
|
|
96
182
|
log.warning(
|
|
97
|
-
f"It looks like
|
|
183
|
+
f"It looks like port {args.ui_port} for the QLever UI "
|
|
184
|
+
f"is already in use. You can set another port in the "
|
|
185
|
+
f" Qleverfile in the [ui] section with the UI_PORT variable."
|
|
98
186
|
)
|
|
99
187
|
|
|
100
|
-
#
|
|
188
|
+
# Get the QLever UI database from the image, unless it already exists.
|
|
189
|
+
if Path(ui_db_file).exists():
|
|
190
|
+
log.debug(f"Found QLever UI database `{ui_db_file}`, reusing it")
|
|
191
|
+
else:
|
|
192
|
+
log.debug(f"Getting QLever UI database `{ui_db_file}` from image")
|
|
193
|
+
try:
|
|
194
|
+
run_command(get_db_cmd)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
log.error(
|
|
197
|
+
f"Failed to get {ui_db_file} from {args.ui_image} "
|
|
198
|
+
f"({e})"
|
|
199
|
+
)
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
# Start the QLever UI.
|
|
203
|
+
try:
|
|
204
|
+
log.debug(
|
|
205
|
+
f"Starting new container with name `{args.ui_container}`"
|
|
206
|
+
)
|
|
207
|
+
run_command(start_ui_cmd)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
log.error(f"Failed to start container `{args.ui_container}` ({e})")
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
# Check if config file with name `ui_config_file` exists. If not, try
|
|
213
|
+
# to obtain it via `get_config_cmd` and set it as default.
|
|
214
|
+
if Path(ui_config_file).exists():
|
|
215
|
+
log.info(f"Found config file `{ui_config_file}` and reusing it")
|
|
216
|
+
else:
|
|
217
|
+
try:
|
|
218
|
+
log.info(
|
|
219
|
+
f"Get default config file `{ui_config_file}` from image "
|
|
220
|
+
f"`{args.ui_image}` and set endpoint to `{sparql_endpoint}`"
|
|
221
|
+
)
|
|
222
|
+
config_yaml = run_command(get_config_cmd, return_output=True)
|
|
223
|
+
config_dict = yaml.safe_load(config_yaml)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
log.error("")
|
|
226
|
+
log.error(
|
|
227
|
+
f"An error occured while getting and parsing the "
|
|
228
|
+
f"config file ({e})"
|
|
229
|
+
)
|
|
230
|
+
return False
|
|
231
|
+
try:
|
|
232
|
+
config_dict["config"]["backend"]["isDefault"] = True
|
|
233
|
+
config_dict["config"]["backend"]["baseUrl"] = sparql_endpoint
|
|
234
|
+
config_dict["config"]["backend"]["sortKey"] = 1
|
|
235
|
+
config_yaml = dict_to_yaml(config_dict)
|
|
236
|
+
with open(ui_config_file, "w") as f:
|
|
237
|
+
f.write(config_yaml)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
log.error("")
|
|
240
|
+
log.error(
|
|
241
|
+
f"An error occured while modifying and writing the "
|
|
242
|
+
f"config file ({e})"
|
|
243
|
+
)
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
# Configure the QLever UI.
|
|
101
247
|
try:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
except subprocess.CalledProcessError as e:
|
|
106
|
-
log.error(f"Failed to start the QLever UI ({e})")
|
|
248
|
+
run_command(set_config_cmd)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
log.error(f"Failed to configure the QLever UI ({e})")
|
|
107
251
|
return False
|
|
108
252
|
|
|
109
|
-
#
|
|
253
|
+
# If we come this far, everything should work.
|
|
254
|
+
log.info("")
|
|
255
|
+
log.info(
|
|
256
|
+
f"The QLever UI should now be up at {ui_url}/{ui_config_name}"
|
|
257
|
+
)
|
|
258
|
+
log.info("")
|
|
259
|
+
log.debug(
|
|
260
|
+
"If you must, you can log in as QLever UI admin with "
|
|
261
|
+
'username and password "demo"'
|
|
262
|
+
)
|
|
110
263
|
log.info(
|
|
111
|
-
f"
|
|
112
|
-
f"
|
|
113
|
-
f'password "demo"'
|
|
264
|
+
f"You can modify the config file at `{ui_config_file}` "
|
|
265
|
+
f"and then just run `qlever ui` again"
|
|
114
266
|
)
|
|
115
267
|
return True
|
qlever/config.py
CHANGED
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
import argcomplete
|
|
10
10
|
from termcolor import colored
|
|
11
11
|
|
|
12
|
-
from qlever import command_objects, script_name
|
|
12
|
+
from qlever import command_objects, engine_name, script_name
|
|
13
13
|
from qlever.log import log, log_levels
|
|
14
14
|
from qlever.qleverfile import Qleverfile
|
|
15
15
|
|
|
@@ -176,11 +176,18 @@ class QleverConfig:
|
|
|
176
176
|
# are defined in the modules in `qlever/commands`. In `__init__.py`
|
|
177
177
|
# an object of each class is created and stored in `command_objects`.
|
|
178
178
|
parser = argparse.ArgumentParser(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
description=colored(
|
|
180
|
+
f"This is the {script_name} command line tool, "
|
|
181
|
+
f"it's all you need to work with {engine_name}",
|
|
182
|
+
attrs=["bold"],
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
if script_name == "qlever":
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--version",
|
|
188
|
+
action="version",
|
|
189
|
+
version=f"%(prog)s {version('qlever')}",
|
|
190
|
+
)
|
|
184
191
|
add_qleverfile_option(parser)
|
|
185
192
|
subparsers = parser.add_subparsers(dest='command')
|
|
186
193
|
subparsers.required = True
|
qlever/containerize.py
CHANGED
|
@@ -9,7 +9,7 @@ import subprocess
|
|
|
9
9
|
from typing import Optional
|
|
10
10
|
|
|
11
11
|
from qlever.log import log
|
|
12
|
-
from qlever.util import
|
|
12
|
+
from qlever.util import get_random_string, run_command
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class ContainerizeException(Exception):
|
|
@@ -40,6 +40,7 @@ class Containerize:
|
|
|
40
40
|
volumes: list[tuple[str, str]] = [],
|
|
41
41
|
ports: list[tuple[int, int]] = [],
|
|
42
42
|
working_directory: Optional[str] = None,
|
|
43
|
+
use_bash: bool = True,
|
|
43
44
|
) -> str:
|
|
44
45
|
"""
|
|
45
46
|
Get the command to run `cmd` with the given `container_system` and the
|
|
@@ -80,11 +81,15 @@ class Containerize:
|
|
|
80
81
|
f"{volume_options}"
|
|
81
82
|
f"{port_options}"
|
|
82
83
|
f"{working_directory_option}"
|
|
84
|
+
f" --name {container_name}"
|
|
83
85
|
f" --init"
|
|
84
|
-
f" --entrypoint bash"
|
|
85
|
-
f" --name {container_name} {image_name}"
|
|
86
|
-
f" -c {shlex.quote(cmd)}"
|
|
87
86
|
)
|
|
87
|
+
if use_bash:
|
|
88
|
+
containerized_cmd += (
|
|
89
|
+
f" --entrypoint bash {image_name} -c {shlex.quote(cmd)}"
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
containerized_cmd += f" {image_name} {cmd}"
|
|
88
93
|
return containerized_cmd
|
|
89
94
|
|
|
90
95
|
@staticmethod
|
qlever/qlever_main.py
CHANGED
|
@@ -12,7 +12,7 @@ import traceback
|
|
|
12
12
|
|
|
13
13
|
from termcolor import colored
|
|
14
14
|
|
|
15
|
-
from qlever import command_objects
|
|
15
|
+
from qlever import command_objects, script_name
|
|
16
16
|
from qlever.config import ConfigException, QleverConfig
|
|
17
17
|
from qlever.log import log, log_levels
|
|
18
18
|
|
|
@@ -35,9 +35,9 @@ def main():
|
|
|
35
35
|
log.info("")
|
|
36
36
|
log.info(colored(f"Command: {args.command}", attrs=["bold"]))
|
|
37
37
|
log.info("")
|
|
38
|
-
|
|
38
|
+
command_successful = command_object.execute(args)
|
|
39
39
|
log.info("")
|
|
40
|
-
if not
|
|
40
|
+
if not command_successful:
|
|
41
41
|
exit(1)
|
|
42
42
|
except KeyboardInterrupt:
|
|
43
43
|
log.info("")
|
|
@@ -47,9 +47,14 @@ def main():
|
|
|
47
47
|
except Exception as e:
|
|
48
48
|
# Check if it's a certain kind of `AttributeError` and give a hint in
|
|
49
49
|
# that case.
|
|
50
|
+
log.debug(
|
|
51
|
+
"Command failed with exception, full traceback: "
|
|
52
|
+
f"{traceback.format_exc()}"
|
|
53
|
+
)
|
|
50
54
|
match_error = re.search(r"object has no attribute '(.+)'", str(e))
|
|
51
55
|
match_trace = re.search(
|
|
52
|
-
|
|
56
|
+
rf"({script_name}/commands/.+\.py)\", line (\d+)",
|
|
57
|
+
traceback.format_exc(),
|
|
53
58
|
)
|
|
54
59
|
if isinstance(e, AttributeError) and match_error and match_trace:
|
|
55
60
|
attribute = match_error.group(1)
|
qlever/qleverfile.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
import socket
|
|
4
5
|
import subprocess
|
|
5
|
-
from configparser import ConfigParser, ExtendedInterpolation
|
|
6
|
+
from configparser import ConfigParser, ExtendedInterpolation, RawConfigParser
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
|
|
9
|
+
from qlever import script_name
|
|
7
10
|
from qlever.containerize import Containerize
|
|
8
11
|
from qlever.log import log
|
|
9
12
|
|
|
@@ -242,6 +245,13 @@ class Qleverfile:
|
|
|
242
245
|
default=8,
|
|
243
246
|
help="The number of threads used for query processing",
|
|
244
247
|
)
|
|
248
|
+
server_args["persist_updates"] = arg(
|
|
249
|
+
"--persist-updates",
|
|
250
|
+
action="store_true",
|
|
251
|
+
default=False,
|
|
252
|
+
help="Persist updates to the index (write updates to disk and "
|
|
253
|
+
"read them back in when restarting the server)",
|
|
254
|
+
)
|
|
245
255
|
server_args["only_pso_and_pos_permutations"] = arg(
|
|
246
256
|
"--only-pso-and-pos-permutations",
|
|
247
257
|
action="store_true",
|
|
@@ -291,12 +301,12 @@ class Qleverfile:
|
|
|
291
301
|
runtime_args["index_container"] = arg(
|
|
292
302
|
"--index-container",
|
|
293
303
|
type=str,
|
|
294
|
-
help="The name of the container used by `
|
|
304
|
+
help=f"The name of the container used by `{script_name} index`",
|
|
295
305
|
)
|
|
296
306
|
runtime_args["server_container"] = arg(
|
|
297
307
|
"--server-container",
|
|
298
308
|
type=str,
|
|
299
|
-
help="The name of the container used by `
|
|
309
|
+
help=f"The name of the container used by `{script_name} start`",
|
|
300
310
|
)
|
|
301
311
|
|
|
302
312
|
ui_args["ui_port"] = arg(
|
|
@@ -393,9 +403,9 @@ class Qleverfile:
|
|
|
393
403
|
name = config["data"]["name"]
|
|
394
404
|
runtime = config["runtime"]
|
|
395
405
|
if "server_container" not in runtime:
|
|
396
|
-
runtime["server_container"] = f"
|
|
406
|
+
runtime["server_container"] = f"{script_name}.server.{name}"
|
|
397
407
|
if "index_container" not in runtime:
|
|
398
|
-
runtime["index_container"] = f"
|
|
408
|
+
runtime["index_container"] = f"{script_name}.index.{name}"
|
|
399
409
|
if "ui_container" not in config["ui"]:
|
|
400
410
|
config["ui"]["ui_container"] = f"qlever.ui.{name}"
|
|
401
411
|
index = config["index"]
|
|
@@ -407,5 +417,49 @@ class Qleverfile:
|
|
|
407
417
|
if index.get("text_index", "none") != "none":
|
|
408
418
|
server["use_text_index"] = "yes"
|
|
409
419
|
|
|
420
|
+
# Add other non-trivial default values.
|
|
421
|
+
try:
|
|
422
|
+
config["server"]["host_name"] = socket.gethostname()
|
|
423
|
+
except Exception:
|
|
424
|
+
log.warning(
|
|
425
|
+
"Could not get the hostname, using `localhost` as default"
|
|
426
|
+
)
|
|
427
|
+
pass
|
|
428
|
+
|
|
410
429
|
# Return the parsed Qleverfile with the added inherited values.
|
|
411
430
|
return config
|
|
431
|
+
|
|
432
|
+
@staticmethod
|
|
433
|
+
def filter(
|
|
434
|
+
qleverfile_path: Path, options_included: dict[str, list[str]]
|
|
435
|
+
) -> RawConfigParser:
|
|
436
|
+
"""
|
|
437
|
+
Given a filter criteria (key: section_header, value: list[options]),
|
|
438
|
+
return a RawConfigParser object to create a new filtered Qleverfile
|
|
439
|
+
with only the specified sections and options (selects all options if
|
|
440
|
+
list[options] is empty). Mainly to be used by non-qlever scripts for
|
|
441
|
+
the setup-config command
|
|
442
|
+
"""
|
|
443
|
+
# Read the Qleverfile.
|
|
444
|
+
config = RawConfigParser()
|
|
445
|
+
config.optionxform = str # Preserve case sensitivity of keys
|
|
446
|
+
config.read(qleverfile_path)
|
|
447
|
+
|
|
448
|
+
filtered_config = RawConfigParser()
|
|
449
|
+
filtered_config.optionxform = str
|
|
450
|
+
|
|
451
|
+
for section, desired_fields in options_included.items():
|
|
452
|
+
if config.has_section(section):
|
|
453
|
+
filtered_config.add_section(section)
|
|
454
|
+
|
|
455
|
+
# If the list is empty, copy all fields
|
|
456
|
+
if not desired_fields:
|
|
457
|
+
for field, value in config.items(section):
|
|
458
|
+
filtered_config.set(section, field, value)
|
|
459
|
+
else:
|
|
460
|
+
for desired_field in desired_fields:
|
|
461
|
+
if config.has_option(section, desired_field):
|
|
462
|
+
value = config.get(section, desired_field)
|
|
463
|
+
filtered_config.set(section, desired_field, value)
|
|
464
|
+
|
|
465
|
+
return filtered_config
|
qlever/util.py
CHANGED
|
@@ -10,7 +10,9 @@ import string
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from datetime import date, datetime
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Optional
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
import psutil
|
|
14
16
|
|
|
15
17
|
from qlever.log import log
|
|
16
18
|
|
|
@@ -33,6 +35,7 @@ def run_command(
|
|
|
33
35
|
cmd: str,
|
|
34
36
|
return_output: bool = False,
|
|
35
37
|
show_output: bool = False,
|
|
38
|
+
show_stderr: bool = False,
|
|
36
39
|
use_popen: bool = False,
|
|
37
40
|
) -> Optional[str | subprocess.Popen]:
|
|
38
41
|
"""
|
|
@@ -50,7 +53,7 @@ def run_command(
|
|
|
50
53
|
"shell": True,
|
|
51
54
|
"text": True,
|
|
52
55
|
"stdout": None if show_output else subprocess.PIPE,
|
|
53
|
-
"stderr": subprocess.PIPE,
|
|
56
|
+
"stderr": None if show_stderr else subprocess.PIPE,
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
# With `Popen`, the command runs in the current shell and a process object
|
|
@@ -72,8 +75,8 @@ def run_command(
|
|
|
72
75
|
raise Exception(result.stderr.replace("\n", " ").strip())
|
|
73
76
|
else:
|
|
74
77
|
raise Exception(
|
|
75
|
-
f"Command failed with exit code {result.returncode}"
|
|
76
|
-
f"
|
|
78
|
+
f"Command failed with exit code {result.returncode}, "
|
|
79
|
+
f" nothing written to stderr"
|
|
77
80
|
)
|
|
78
81
|
# Optionally, return what was written to `stdout`.
|
|
79
82
|
if return_output:
|
|
@@ -242,3 +245,85 @@ def format_size(bytes, suffix="B"):
|
|
|
242
245
|
if bytes < factor:
|
|
243
246
|
return f"{bytes:.2f} {unit}{suffix}"
|
|
244
247
|
bytes /= factor
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def stop_process(proc: psutil.Process, pinfo: dict[str, Any]) -> bool:
|
|
251
|
+
"""
|
|
252
|
+
Try to kill the given process, return True iff it was killed
|
|
253
|
+
successfully. The process_info is used for logging.
|
|
254
|
+
"""
|
|
255
|
+
try:
|
|
256
|
+
proc.kill()
|
|
257
|
+
log.info(f"Killed process {pinfo['pid']}")
|
|
258
|
+
return True
|
|
259
|
+
except Exception as e:
|
|
260
|
+
log.error(
|
|
261
|
+
f"Could not kill process with PID "
|
|
262
|
+
f"{pinfo['pid']} ({e}) ... try to kill it "
|
|
263
|
+
f"manually"
|
|
264
|
+
)
|
|
265
|
+
log.info("")
|
|
266
|
+
show_process_info(proc, "", show_heading=True)
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def stop_process_with_regex(cmdline_regex: str) -> list[bool] | None:
|
|
271
|
+
"""
|
|
272
|
+
Given a cmdline_regex for a native process, try to kill the processes that
|
|
273
|
+
match the regex and return a list of their stopped status (bool).
|
|
274
|
+
Show the matched processes as log info.
|
|
275
|
+
"""
|
|
276
|
+
stop_process_results = []
|
|
277
|
+
for proc in psutil.process_iter():
|
|
278
|
+
try:
|
|
279
|
+
pinfo = proc.as_dict(
|
|
280
|
+
attrs=[
|
|
281
|
+
"pid",
|
|
282
|
+
"username",
|
|
283
|
+
"create_time",
|
|
284
|
+
"memory_info",
|
|
285
|
+
"cmdline",
|
|
286
|
+
]
|
|
287
|
+
)
|
|
288
|
+
cmdline = " ".join(pinfo["cmdline"])
|
|
289
|
+
except Exception as e:
|
|
290
|
+
log.debug(f"Error getting process info: {e}")
|
|
291
|
+
return None
|
|
292
|
+
if re.search(cmdline_regex, cmdline):
|
|
293
|
+
log.info(
|
|
294
|
+
f"Found process {pinfo['pid']} from user "
|
|
295
|
+
f"{pinfo['username']} with command line: {cmdline}"
|
|
296
|
+
)
|
|
297
|
+
log.info("")
|
|
298
|
+
stop_process_results.append(stop_process(proc, pinfo))
|
|
299
|
+
return stop_process_results
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def binary_exists(binary: str, cmd_arg: str) -> bool:
|
|
303
|
+
"""
|
|
304
|
+
When a command is run natively, check if the binary exists on the system
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
run_command(f"{binary} --help")
|
|
308
|
+
return True
|
|
309
|
+
except Exception as e:
|
|
310
|
+
log.error(
|
|
311
|
+
f'Running "{binary}" failed, '
|
|
312
|
+
f"set `--{cmd_arg}` to a different binary or "
|
|
313
|
+
f"set `--system to a container system`"
|
|
314
|
+
)
|
|
315
|
+
log.info("")
|
|
316
|
+
log.info(f"The error message was: {e}")
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def is_server_alive(url: str) -> bool:
|
|
321
|
+
"""
|
|
322
|
+
Check if the server is already alive at the given endpoint url
|
|
323
|
+
"""
|
|
324
|
+
check_server_cmd = f"curl -s {url}"
|
|
325
|
+
try:
|
|
326
|
+
run_command(check_server_cmd)
|
|
327
|
+
return True
|
|
328
|
+
except Exception:
|
|
329
|
+
return False
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qlever
|
|
3
|
-
Version: 0.5.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.22
|
|
4
|
+
Summary: Command-line tool for using the QLever graph database
|
|
5
5
|
Author-email: Hannah Bast <bast@cs.uni-freiburg.de>
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: Github, https://github.com/ad-freiburg/qlever
|
|
8
|
-
Keywords:
|
|
8
|
+
Keywords: Graph database,Triplestore,Knowledge graphs,SPARQL,RDF
|
|
9
9
|
Classifier: Topic :: Database :: Database Engines/Servers
|
|
10
10
|
Classifier: Topic :: Database :: Front-Ends
|
|
11
11
|
Requires-Python: >=3.8
|
|
@@ -14,6 +14,8 @@ License-File: LICENSE
|
|
|
14
14
|
Requires-Dist: psutil
|
|
15
15
|
Requires-Dist: termcolor
|
|
16
16
|
Requires-Dist: argcomplete
|
|
17
|
+
Requires-Dist: pyyaml
|
|
18
|
+
Dynamic: license-file
|
|
17
19
|
|
|
18
20
|
# QLever
|
|
19
21
|
|