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 CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
+ from importlib import import_module
4
5
  from pathlib import Path
5
6
 
6
7
 
@@ -10,9 +11,19 @@ def snake_to_camel(str):
10
11
  return "".join([w.capitalize() for w in str.replace("-", "_").split("_")])
11
12
 
12
13
 
14
+ # Get the name of the script (without the path and without the extension).
15
+ script_name = Path(sys.argv[0]).stem
16
+
17
+ ENGINE_NAMES = {
18
+ "qlever": "QLever",
19
+ "qmdb": "MillenniumDB",
20
+ }
21
+ # Default engine_name = script_name without starting 'q' and capitalized
22
+ engine_name = ENGINE_NAMES.get(script_name, script_name[1:].capitalize())
23
+
13
24
  # Each module in `qlever/commands` corresponds to a command. The name
14
25
  # of the command is the base name of the module file.
15
- package_path = Path(__file__).parent
26
+ package_path = Path(__file__).parent.parent / script_name
16
27
  command_names = [
17
28
  Path(p).stem
18
29
  for p in package_path.glob("commands/*.py")
@@ -22,19 +33,15 @@ command_names = [
22
33
  # Dynamically load all the command classes and create an object for each.
23
34
  command_objects = {}
24
35
  for command_name in command_names:
25
- module_path = f"qlever.commands.{command_name}"
26
- class_name = snake_to_camel(command_name) + "Command"
36
+ module_path = f"{script_name}.commands.{command_name}"
27
37
  try:
28
- module = __import__(module_path, fromlist=[class_name])
38
+ module = import_module(module_path)
29
39
  except ImportError as e:
30
40
  raise Exception(
31
- f"Could not import class {class_name} from module "
32
- f"{module_path} for command {command_name}: {e}"
33
- )
41
+ f"Could not import module {module_path} for {script_name}: {e}"
42
+ ) from e
34
43
  # Create an object of the class and store it in the dictionary. For the
35
44
  # commands, take - instead of _.
45
+ class_name = snake_to_camel(command_name) + "Command"
36
46
  command_class = getattr(module, class_name)
37
47
  command_objects[command_name.replace("_", "-")] = command_class()
38
-
39
- # Get the name of the script (without the path and without the extension).
40
- script_name = Path(sys.argv[0]).stem
qlever/command.py CHANGED
@@ -81,7 +81,7 @@ class QleverCommand(ABC):
81
81
  log.info("")
82
82
  if only_show:
83
83
  log.info(
84
- 'You called "qlever ... --show", therefore the command '
84
+ 'You passed the argument "--show", therefore the command '
85
85
  'is only shown, but not executed (omit the "--show" to '
86
86
  "execute it)"
87
87
  )
@@ -28,7 +28,7 @@ class CacheStatsCommand(QleverCommand):
28
28
  def additional_arguments(self, subparser) -> None:
29
29
  subparser.add_argument("--server-url",
30
30
  help="URL of the QLever server, default is "
31
- "localhost:{port}")
31
+ "{host_name}:{port}")
32
32
  subparser.add_argument("--detailed",
33
33
  action="store_true",
34
34
  default=False,
@@ -37,7 +37,7 @@ class CacheStatsCommand(QleverCommand):
37
37
  def execute(self, args) -> bool:
38
38
  # Construct the two curl commands.
39
39
  server_url = (args.server_url if args.server_url
40
- else f"localhost:{args.port}")
40
+ else f"{args.host_name}:{args.port}")
41
41
  cache_stats_cmd = (f"curl -s {server_url} "
42
42
  f"--data-urlencode \"cmd=cache-stats\"")
43
43
  cache_settings_cmd = (f"curl -s {server_url} "
@@ -17,22 +17,25 @@ class ClearCacheCommand(QleverCommand):
17
17
  pass
18
18
 
19
19
  def description(self) -> str:
20
- return ("Clear the query processing cache")
20
+ return "Clear the query processing cache"
21
21
 
22
22
  def should_have_qleverfile(self) -> bool:
23
23
  return True
24
24
 
25
- def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
26
- return {"server": ["port", "access_token"]}
25
+ def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
26
+ return {"server": ["host_name", "port", "access_token"]}
27
27
 
28
28
  def additional_arguments(self, subparser) -> None:
29
- subparser.add_argument("--server-url",
30
- help="URL of the QLever server, default is "
31
- "localhost:{port}")
32
- subparser.add_argument("--complete", action="store_true",
33
- default=False,
34
- help="Clear the cache completely, including "
35
- "the pinned queries")
29
+ subparser.add_argument(
30
+ "--server-url",
31
+ help="URL of the QLever server, default is {host_name}:{port}",
32
+ )
33
+ subparser.add_argument(
34
+ "--complete",
35
+ action="store_true",
36
+ default=False,
37
+ help="Clear the cache completely, including the pinned queries",
38
+ )
36
39
 
37
40
  def execute(self, args) -> bool:
38
41
  # Construct command line and show it.
@@ -40,22 +43,27 @@ class ClearCacheCommand(QleverCommand):
40
43
  if args.server_url:
41
44
  clear_cache_cmd += f" {args.server_url}"
42
45
  else:
43
- clear_cache_cmd += f" localhost:{args.port}"
46
+ clear_cache_cmd += f" {args.host_name}:{args.port}"
44
47
  cmd_val = "clear-cache-complete" if args.complete else "clear-cache"
45
- clear_cache_cmd += f" --data-urlencode \"cmd={cmd_val}\""
48
+ clear_cache_cmd += f' --data-urlencode "cmd={cmd_val}"'
46
49
  if args.complete:
47
- clear_cache_cmd += (f" --data-urlencode access-token="
48
- f"\"{args.access_token}\"")
50
+ clear_cache_cmd += (
51
+ f" --data-urlencode access-token=" f'"{args.access_token}"'
52
+ )
49
53
  self.show(clear_cache_cmd, only_show=args.show)
50
54
  if args.show:
51
55
  return True
52
56
 
53
57
  # Execute the command.
54
58
  try:
55
- clear_cache_cmd += " -w \" %{http_code}\""
56
- result = subprocess.run(clear_cache_cmd, shell=True,
57
- capture_output=True, text=True,
58
- check=True).stdout
59
+ clear_cache_cmd += ' -w " %{http_code}"'
60
+ result = subprocess.run(
61
+ clear_cache_cmd,
62
+ shell=True,
63
+ capture_output=True,
64
+ text=True,
65
+ check=True,
66
+ ).stdout
59
67
  match = re.match(r"^(.*) (\d+)$", result, re.DOTALL)
60
68
  if not match:
61
69
  raise Exception(f"Unexpected output:\n{result}")
@@ -77,6 +85,8 @@ class ClearCacheCommand(QleverCommand):
77
85
  log.info("")
78
86
  args.detailed = False
79
87
  if not CacheStatsCommand().execute(args):
80
- log.error("Clearing the cache was successful, but showing the "
81
- "cache stats failed {e}")
88
+ log.error(
89
+ "Clearing the cache was successful, but showing the "
90
+ "cache stats failed {e}"
91
+ )
82
92
  return True
@@ -31,7 +31,7 @@ class ExampleQueriesCommand(QleverCommand):
31
31
  return False
32
32
 
33
33
  def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
34
- return {"server": ["port"], "ui": ["ui_config"]}
34
+ return {"server": ["host_name", "port"], "ui": ["ui_config"]}
35
35
 
36
36
  def additional_arguments(self, subparser) -> None:
37
37
  subparser.add_argument(
@@ -103,8 +103,8 @@ class ExampleQueriesCommand(QleverCommand):
103
103
  subparser.add_argument(
104
104
  "--clear-cache",
105
105
  choices=["yes", "no"],
106
- default="yes",
107
- help="Clear the cache before each query",
106
+ default="no",
107
+ help="Clear the cache before each query (only works for QLever)",
108
108
  )
109
109
  subparser.add_argument(
110
110
  "--width-query-description",
@@ -213,9 +213,18 @@ class ExampleQueriesCommand(QleverCommand):
213
213
  not args.sparql_endpoint
214
214
  or args.sparql_endpoint.startswith("https://qlever")
215
215
  )
216
- if args.clear_cache == "yes" and not is_qlever:
217
- log.warning("Clearing the cache only works for QLever")
218
- args.clear_cache = "no"
216
+ if args.clear_cache == "yes":
217
+ if is_qlever:
218
+ log.warning(
219
+ "Clearing the cache before each query"
220
+ " (only works for QLever)"
221
+ )
222
+ else:
223
+ log.warning(
224
+ "Clearing the cache only works for QLever"
225
+ ", option `--clear-cache` is ignored"
226
+ )
227
+ args.clear_cache = "no"
219
228
 
220
229
  # Show what the command will do.
221
230
  get_queries_cmd = (
@@ -231,14 +240,12 @@ class ExampleQueriesCommand(QleverCommand):
231
240
  sparql_endpoint = (
232
241
  args.sparql_endpoint
233
242
  if args.sparql_endpoint
234
- else f"localhost:{args.port}"
243
+ else f"{args.host_name}:{args.port}"
235
244
  )
236
245
  self.show(
237
246
  f"Obtain queries via: {get_queries_cmd}\n"
238
247
  f"SPARQL endpoint: {sparql_endpoint}\n"
239
248
  f"Accept header: {args.accept}\n"
240
- f"Clear cache before each query:"
241
- f" {args.clear_cache.upper()}\n"
242
249
  f"Download result for each query or just count:"
243
250
  f" {args.download_or_count.upper()}"
244
251
  + (f" with LIMIT {args.limit}" if args.limit else ""),
@@ -404,6 +411,7 @@ class ExampleQueriesCommand(QleverCommand):
404
411
  # Get result size (via the command line, in order to avoid loading
405
412
  # a potentially large JSON file into Python, which is slow).
406
413
  if error_msg is None:
414
+ single_int_result = None
407
415
  # CASE 0: The result is empty despite a 200 HTTP code (not a
408
416
  # problem for CONSTRUCT and DESCRIBE queries).
409
417
  if Path(result_file).stat().st_size == 0 and (
@@ -461,16 +469,29 @@ class ExampleQueriesCommand(QleverCommand):
461
469
  )
462
470
  else:
463
471
  try:
464
- result_size = run_command(
465
- f'jq -r ".results.bindings | length"'
466
- f" {result_file}",
467
- return_output=True,
472
+ result_size = int(
473
+ run_command(
474
+ f'jq -r ".results.bindings | length"'
475
+ f" {result_file}",
476
+ return_output=True,
477
+ ).rstrip()
468
478
  )
469
479
  except Exception as e:
470
480
  error_msg = {
471
481
  "short": "Malformed JSON",
472
482
  "long": re.sub(r"\s+", " ", str(e)),
473
483
  }
484
+ if result_size == 1:
485
+ try:
486
+ single_int_result = int(
487
+ run_command(
488
+ f'jq -e -r ".results.bindings[0][] | .value"'
489
+ f" {result_file}",
490
+ return_output=True,
491
+ ).rstrip()
492
+ )
493
+ except Exception:
494
+ pass
474
495
 
475
496
  # Remove the result file (unless in debug mode).
476
497
  if args.log_level != "DEBUG":
@@ -485,10 +506,16 @@ class ExampleQueriesCommand(QleverCommand):
485
506
  )
486
507
  if error_msg is None:
487
508
  result_size = int(result_size)
509
+ single_int_result = (
510
+ f" [single int result: {single_int_result:,}]"
511
+ if single_int_result is not None
512
+ else ""
513
+ )
488
514
  log.info(
489
515
  f"{description:<{width_query_description}} "
490
516
  f"{time_seconds:6.2f} s "
491
517
  f"{result_size:>{args.width_result_size},}"
518
+ f"{single_int_result}"
492
519
  )
493
520
  query_times.append(time_seconds)
494
521
  result_sizes.append(result_size)
qlever/commands/index.py CHANGED
@@ -9,6 +9,7 @@ from qlever.command import QleverCommand
9
9
  from qlever.containerize import Containerize
10
10
  from qlever.log import log
11
11
  from qlever.util import (
12
+ binary_exists,
12
13
  get_existing_index_files,
13
14
  get_total_file_size,
14
15
  run_command,
@@ -267,16 +268,7 @@ class IndexCommand(QleverCommand):
267
268
 
268
269
  # When running natively, check if the binary exists and works.
269
270
  if args.system == "native":
270
- try:
271
- run_command(f"{args.index_binary} --help")
272
- except Exception as e:
273
- log.error(
274
- f'Running "{args.index_binary}" failed, '
275
- f"set `--index-binary` to a different binary or "
276
- f"set `--system to a container system`"
277
- )
278
- log.info("")
279
- log.info(f"The error message was: {e}")
271
+ if not binary_exists(args.index_binary, "index-binary"):
280
272
  return False
281
273
 
282
274
  # Check if all of the input files exist.
qlever/commands/query.py CHANGED
@@ -38,7 +38,7 @@ class QueryCommand(QleverCommand):
38
38
  return False
39
39
 
40
40
  def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
41
- return {"server": ["port", "access_token"]}
41
+ return {"server": ["host_name", "port", "access_token"]}
42
42
 
43
43
  def additional_arguments(self, subparser) -> None:
44
44
  subparser.add_argument(
@@ -109,7 +109,7 @@ class QueryCommand(QleverCommand):
109
109
  sparql_endpoint = (
110
110
  args.sparql_endpoint
111
111
  if args.sparql_endpoint
112
- else f"localhost:{args.port}"
112
+ else f"{args.host_name}:{args.port}"
113
113
  )
114
114
  curl_cmd = (
115
115
  f"curl -s {sparql_endpoint}"
@@ -32,6 +32,7 @@ class SettingsCommand(QleverCommand):
32
32
  "cache-max-num-entries",
33
33
  "cache-max-size",
34
34
  "cache-max-size-single-entry",
35
+ "cache-service-results",
35
36
  "default-query-timeout",
36
37
  "group-by-disable-index-scan-optimizations",
37
38
  "group-by-hash-map-enabled",
qlever/commands/start.py CHANGED
@@ -10,7 +10,7 @@ from qlever.commands.stop import StopCommand
10
10
  from qlever.commands.warmup import WarmupCommand
11
11
  from qlever.containerize import Containerize
12
12
  from qlever.log import log
13
- from qlever.util import is_qlever_server_alive, run_command
13
+ from qlever.util import binary_exists, is_qlever_server_alive, run_command
14
14
 
15
15
 
16
16
  # Construct the command line based on the config file.
@@ -30,6 +30,8 @@ def construct_command(args) -> str:
30
30
  start_cmd += f" -s {args.timeout}"
31
31
  if args.access_token:
32
32
  start_cmd += f" -a {args.access_token}"
33
+ if args.persist_updates:
34
+ start_cmd += " --persist-updates"
33
35
  if args.only_pso_and_pos_permutations:
34
36
  start_cmd += " --only-pso-and-pos-permutations"
35
37
  if not args.use_patterns:
@@ -69,22 +71,6 @@ def wrap_command_in_container(args, start_cmd) -> str:
69
71
  return start_cmd
70
72
 
71
73
 
72
- # When running natively, check if the binary exists and works.
73
- def check_binary(binary) -> bool:
74
- try:
75
- run_command(f"{binary} --help")
76
- return True
77
- except Exception as e:
78
- log.error(
79
- f'Running "{binary}" failed, '
80
- f"set `--server-binary` to a different binary or "
81
- f"set `--system to a container system`"
82
- )
83
- log.info("")
84
- log.info(f"The error message was: {e}")
85
- return False
86
-
87
-
88
74
  # Set the index description.
89
75
  def set_index_description(access_arg, port, desc) -> bool:
90
76
  curl_cmd = (
@@ -148,6 +134,7 @@ class StartCommand(QleverCommand):
148
134
  "cache_max_num_entries",
149
135
  "num_threads",
150
136
  "timeout",
137
+ "persist_updates",
151
138
  "only_pso_and_pos_permutations",
152
139
  "use_patterns",
153
140
  "use_text_index",
@@ -157,12 +144,6 @@ class StartCommand(QleverCommand):
157
144
  }
158
145
 
159
146
  def additional_arguments(self, subparser) -> None:
160
- # subparser.add_argument("--kill-existing-with-same-name",
161
- # action="store_true",
162
- # default=False,
163
- # help="If a QLever server is already running "
164
- # "with the same name, kill it before "
165
- # "starting a new server")
166
147
  subparser.add_argument(
167
148
  "--kill-existing-with-same-port",
168
149
  action="store_true",
@@ -223,12 +204,11 @@ class StartCommand(QleverCommand):
223
204
 
224
205
  # When running natively, check if the binary exists and works.
225
206
  if args.system == "native":
226
- ret = check_binary(args.server_binary)
227
- if not ret:
207
+ if not binary_exists(args.server_binary, "server-binary"):
228
208
  return False
229
209
 
230
210
  # Check if a QLever server is already running on this port.
231
- endpoint_url = f"http://localhost:{args.port}"
211
+ endpoint_url = f"http://{args.host_name}:{args.port}"
232
212
  if is_qlever_server_alive(endpoint_url):
233
213
  log.error(f"QLever server already running on {endpoint_url}")
234
214
  log.info("")
qlever/commands/stop.py CHANGED
@@ -1,37 +1,25 @@
1
1
  from __future__ import annotations
2
- import re
3
- import psutil
2
+
4
3
  from qlever.command import QleverCommand
5
4
  from qlever.commands.status import StatusCommand
6
5
  from qlever.containerize import Containerize
7
6
  from qlever.log import log
8
- from qlever.util import show_process_info
9
-
10
- # try to kill the given process, return true iff it was killed successfully.
11
- # the process_info is used for logging.
12
- def stop_process(proc, pinfo):
13
- try:
14
- proc.kill()
15
- log.info(f"Killed process {pinfo['pid']}")
16
- return True
17
- except Exception as e:
18
- log.error(f"Could not kill process with PID "
19
- f"{pinfo['pid']} ({e}) ... try to kill it "
20
- f"manually")
21
- log.info("")
22
- show_process_info(proc, "", show_heading=True)
23
- return False
7
+ from qlever.util import stop_process_with_regex
24
8
 
25
9
 
26
- # try to stop and remove container. return True iff it was stopped
27
- # successfully. Gives log info accordingly.
28
- def stop_container(server_container):
10
+ def stop_container(server_container: str) -> bool:
11
+ """
12
+ Try to stop and remove container. return True iff it was stopped
13
+ successfully. Gives log info accordingly.
14
+ """
29
15
  for container_system in Containerize.supported_systems():
30
16
  if Containerize.stop_and_remove_container(
31
- container_system, server_container):
32
- log.info(f"{container_system.capitalize()} container with "
33
- f"name \"{server_container}\" stopped "
34
- f" and removed")
17
+ container_system, server_container
18
+ ):
19
+ log.info(
20
+ f"{container_system.capitalize()} container with "
21
+ f'name "{server_container}" stopped and removed'
22
+ )
35
23
  return True
36
24
  return False
37
25
 
@@ -45,7 +33,7 @@ class StopCommand(QleverCommand):
45
33
  pass
46
34
 
47
35
  def description(self) -> str:
48
- return "Stop QLever server for a given datasedataset or port"
36
+ return "Stop QLever server for a given dataset or port"
49
37
 
50
38
  def should_have_qleverfile(self) -> bool:
51
39
  return True
@@ -85,21 +73,9 @@ class StopCommand(QleverCommand):
85
73
  # Check if there is a process running on the server port using psutil.
86
74
  # NOTE: On MacOS, some of the proc's returned by psutil.process_iter()
87
75
  # no longer exist when we try to access them, so we just skip them.
88
- stop_process_results = []
89
- for proc in psutil.process_iter():
90
- try:
91
- pinfo = proc.as_dict(
92
- attrs=['pid', 'username', 'create_time',
93
- 'memory_info', 'cmdline'])
94
- cmdline = " ".join(pinfo['cmdline'])
95
- except Exception as e:
96
- log.debug(f"Error getting process info: {e}")
97
- return False
98
- if re.search(cmdline_regex, cmdline):
99
- log.info(f"Found process {pinfo['pid']} from user "
100
- f"{pinfo['username']} with command line: {cmdline}")
101
- log.info("")
102
- stop_process_results.append(stop_process(proc, pinfo))
76
+ stop_process_results = stop_process_with_regex(cmdline_regex)
77
+ if stop_process_results is None:
78
+ return False
103
79
  if len(stop_process_results) > 0:
104
80
  return all(stop_process_results)
105
81