qlever 0.5.15__py3-none-any.whl → 0.5.18__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/commands/query.py CHANGED
@@ -15,7 +15,21 @@ class QueryCommand(QleverCommand):
15
15
  """
16
16
 
17
17
  def __init__(self):
18
- pass
18
+ self.predefined_queries = {
19
+ "all-predicates": (
20
+ "SELECT (?p AS ?predicate) (COUNT(?p) AS ?count) "
21
+ "WHERE { ?s ?p ?o } "
22
+ "GROUP BY ?p ORDER BY DESC(?count)"
23
+ ),
24
+ "all-graphs": (
25
+ "SELECT ?g (COUNT(?g) AS ?count) "
26
+ "WHERE { GRAPH ?g { ?s ?p ?o } } "
27
+ "GROUP BY ?g ORDER BY DESC(?count)"
28
+ ),
29
+ "ten-random-triples": (
30
+ "SELECT * WHERE { ?s ?p ?o } ORDER BY RAND() LIMIT 10"
31
+ ),
32
+ }
19
33
 
20
34
  def description(self) -> str:
21
35
  return "Send a query to a SPARQL endpoint"
@@ -34,6 +48,12 @@ class QueryCommand(QleverCommand):
34
48
  default="SELECT * WHERE { ?s ?p ?o } LIMIT 10",
35
49
  help="SPARQL query to send",
36
50
  )
51
+ subparser.add_argument(
52
+ "--predefined-query",
53
+ type=str,
54
+ choices=self.predefined_queries.keys(),
55
+ help="Use a predefined query",
56
+ )
37
57
  subparser.add_argument(
38
58
  "--pin-to-cache",
39
59
  action="store_true",
@@ -64,6 +84,10 @@ class QueryCommand(QleverCommand):
64
84
  )
65
85
 
66
86
  def execute(self, args) -> bool:
87
+ # Use a predefined query if requested.
88
+ if args.predefined_query:
89
+ args.query = self.predefined_queries[args.predefined_query]
90
+
67
91
  # When pinning to the cache, set `send=0` and request media type
68
92
  # `application/qlever-results+json` so that we get the result size.
69
93
  # Also, we need to provide the access token.
@@ -83,7 +107,9 @@ class QueryCommand(QleverCommand):
83
107
 
84
108
  # Show what the command will do.
85
109
  sparql_endpoint = (
86
- args.sparql_endpoint if args.sparql_endpoint else f"localhost:{args.port}"
110
+ args.sparql_endpoint
111
+ if args.sparql_endpoint
112
+ else f"localhost:{args.port}"
87
113
  )
88
114
  curl_cmd = (
89
115
  f"curl -s {sparql_endpoint}"
@@ -102,7 +128,10 @@ class QueryCommand(QleverCommand):
102
128
  time_msecs = round(1000 * (time.time() - start_time))
103
129
  if not args.no_time and args.log_level != "NO_LOG":
104
130
  log.info("")
105
- log.info(f"Query processing time (end-to-end):" f" {time_msecs:,d} ms")
131
+ log.info(
132
+ f"Query processing time (end-to-end):"
133
+ f" {time_msecs:,d} ms"
134
+ )
106
135
  except Exception as e:
107
136
  if args.log_level == "DEBUG":
108
137
  traceback.print_exc()
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from termcolor import colored
6
+
7
+ from qlever.command import QleverCommand
8
+ from qlever.log import log
9
+ from qlever.util import run_command
10
+
11
+
12
+ class SettingsCommand(QleverCommand):
13
+ """
14
+ Class for executing the `settings` command.
15
+ """
16
+
17
+ def __init__(self):
18
+ pass
19
+
20
+ def description(self) -> str:
21
+ return "Show or set server settings (after `qlever start`)"
22
+
23
+ def should_have_qleverfile(self) -> bool:
24
+ return True
25
+
26
+ def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
27
+ return {"server": ["port", "host_name", "access_token"]}
28
+
29
+ def additional_arguments(self, subparser) -> None:
30
+ all_keys = [
31
+ "always-multiply-unions",
32
+ "cache-max-num-entries",
33
+ "cache-max-size",
34
+ "cache-max-size-single-entry",
35
+ "default-query-timeout",
36
+ "group-by-disable-index-scan-optimizations",
37
+ "group-by-hash-map-enabled",
38
+ "lazy-index-scan-max-size-materialization",
39
+ "lazy-index-scan-num-threads",
40
+ "lazy-index-scan-queue-size",
41
+ "lazy-result-max-cache-size",
42
+ "query-planning-budget",
43
+ "service-max-value-rows",
44
+ "sort-estimate-cancellation-factor",
45
+ "throw-on-unbound-variables",
46
+ "use-binsearch-transitive-path",
47
+ ]
48
+ subparser.add_argument(
49
+ "runtime_parameter",
50
+ nargs="?",
51
+ help="Set the given runtime parameter (key=value)"
52
+ "; if no argument is given, show all settings",
53
+ ).completer = lambda **kwargs: [f"{key}=" for key in all_keys]
54
+ subparser.add_argument(
55
+ "--endpoint_url",
56
+ type=str,
57
+ help="An arbitrary endpoint URL "
58
+ "(overriding the one in the Qleverfile)",
59
+ )
60
+
61
+ def execute(self, args) -> bool:
62
+ # Get endpoint URL from command line or Qleverfile.
63
+ if args.endpoint_url:
64
+ endpoint_url = args.endpoint_url
65
+ else:
66
+ endpoint_url = f"http://{args.host_name}:{args.port}"
67
+
68
+ # Construct the `curl` command for getting or setting.
69
+ if args.runtime_parameter:
70
+ try:
71
+ parameter_key, parameter_value = args.runtime_parameter.split(
72
+ "="
73
+ )
74
+ except ValueError:
75
+ log.error("Runtime parameter must be given as `key=value`")
76
+ return False
77
+
78
+ curl_cmd = (
79
+ f"curl -s {endpoint_url}"
80
+ f' --data-urlencode "{parameter_key}={parameter_value}"'
81
+ f' --data-urlencode "access-token={args.access_token}"'
82
+ )
83
+ else:
84
+ curl_cmd = (
85
+ f"curl -s {endpoint_url}" f" --data-urlencode cmd=get-settings"
86
+ )
87
+ parameter_key, parameter_value = None, None
88
+ self.show(curl_cmd, only_show=args.show)
89
+ if args.show:
90
+ return True
91
+
92
+ # Execute the `curl` command. Note that the `get-settings` command
93
+ # returns all settings in both scencarios (that is, also when setting a
94
+ # parameter).
95
+ try:
96
+ settings_json = run_command(curl_cmd, return_output=True)
97
+ settings_dict = json.loads(settings_json)
98
+ except Exception as e:
99
+ log.error(f"setting command failed: {e}")
100
+ return False
101
+ for key, value in settings_dict.items():
102
+ print(
103
+ colored(
104
+ f"{key:<45}: {value}",
105
+ "blue"
106
+ if parameter_key and key == parameter_key
107
+ else None,
108
+ )
109
+ )
110
+ return True
qlever/commands/start.py CHANGED
@@ -13,6 +13,110 @@ from qlever.log import log
13
13
  from qlever.util import is_qlever_server_alive, run_command
14
14
 
15
15
 
16
+ # Construct the command line based on the config file.
17
+ def construct_command(args) -> str:
18
+ start_cmd = (
19
+ f"{args.server_binary}"
20
+ f" -i {args.name}"
21
+ f" -j {args.num_threads}"
22
+ f" -p {args.port}"
23
+ f" -m {args.memory_for_queries}"
24
+ f" -c {args.cache_max_size}"
25
+ f" -e {args.cache_max_size_single_entry}"
26
+ f" -k {args.cache_max_num_entries}"
27
+ )
28
+
29
+ if args.timeout:
30
+ start_cmd += f" -s {args.timeout}"
31
+ if args.access_token:
32
+ start_cmd += f" -a {args.access_token}"
33
+ if args.only_pso_and_pos_permutations:
34
+ start_cmd += " --only-pso-and-pos-permutations"
35
+ if not args.use_patterns:
36
+ start_cmd += " --no-patterns"
37
+ if args.use_text_index == "yes":
38
+ start_cmd += " -t"
39
+ start_cmd += f" > {args.name}.server-log.txt 2>&1"
40
+ return start_cmd
41
+
42
+
43
+ # Kill existing server on the same port. Trust that StopCommand() works?
44
+ # Maybe return StopCommand().execute(args) and handle it with a try except?
45
+ def kill_existing_server(args) -> bool:
46
+ args.cmdline_regex = f"^ServerMain.* -p {args.port}"
47
+ args.no_containers = True
48
+ if not StopCommand().execute(args):
49
+ log.error("Stopping the existing server failed")
50
+ return False
51
+ log.info("")
52
+ return True
53
+
54
+
55
+ # Run the command in a container
56
+ def wrap_command_in_container(args, start_cmd) -> str:
57
+ if not args.server_container:
58
+ args.server_container = f"qlever.server.{args.name}"
59
+ start_cmd = Containerize().containerize_command(
60
+ start_cmd,
61
+ args.system,
62
+ "run -d --restart=unless-stopped",
63
+ args.image,
64
+ args.server_container,
65
+ volumes=[("$(pwd)", "/index")],
66
+ ports=[(args.port, args.port)],
67
+ working_directory="/index",
68
+ )
69
+ return start_cmd
70
+
71
+
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
+ # Set the index description.
89
+ def set_index_description(access_arg, port, desc) -> bool:
90
+ curl_cmd = (
91
+ f"curl -Gs http://localhost:{port}/api"
92
+ f' --data-urlencode "index-description={desc}"'
93
+ f" {access_arg} > /dev/null"
94
+ )
95
+ log.debug(curl_cmd)
96
+ try:
97
+ run_command(curl_cmd)
98
+ except Exception as e:
99
+ log.error(f"Setting the index description failed ({e})")
100
+ return False
101
+ return True
102
+
103
+
104
+ # Set the text description.
105
+ def set_text_description(access_arg, port, text_desc) -> bool:
106
+ curl_cmd = (
107
+ f"curl -Gs http://localhost:{port}/api"
108
+ f' --data-urlencode "text-description={text_desc}"'
109
+ f" {access_arg} > /dev/null"
110
+ )
111
+ log.debug(curl_cmd)
112
+ try:
113
+ run_command(curl_cmd)
114
+ except Exception as e:
115
+ log.error(f"Setting the text description failed ({e})")
116
+ return False
117
+ return True
118
+
119
+
16
120
  class StartCommand(QleverCommand):
17
121
  """
18
122
  Class for executing the `start` command.
@@ -22,22 +126,35 @@ class StartCommand(QleverCommand):
22
126
  pass
23
127
 
24
128
  def description(self) -> str:
25
- return ("Start the QLever server (requires that you have built "
26
- "an index with `qlever index` before)")
129
+ return (
130
+ "Start the QLever server (requires that you have built "
131
+ "an index with `qlever index` before)"
132
+ )
27
133
 
28
134
  def should_have_qleverfile(self) -> bool:
29
135
  return True
30
136
 
31
- def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
32
- return {"data": ["name", "description", "text_description"],
33
- "server": ["server_binary", "host_name", "port",
34
- "access_token", "memory_for_queries",
35
- "cache_max_size", "cache_max_size_single_entry",
36
- "cache_max_num_entries", "num_threads",
37
- "timeout", "only_pso_and_pos_permutations",
38
- "use_patterns", "use_text_index",
39
- "warmup_cmd"],
40
- "runtime": ["system", "image", "server_container"]}
137
+ def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
138
+ return {
139
+ "data": ["name", "description", "text_description"],
140
+ "server": [
141
+ "server_binary",
142
+ "host_name",
143
+ "port",
144
+ "access_token",
145
+ "memory_for_queries",
146
+ "cache_max_size",
147
+ "cache_max_size_single_entry",
148
+ "cache_max_num_entries",
149
+ "num_threads",
150
+ "timeout",
151
+ "only_pso_and_pos_permutations",
152
+ "use_patterns",
153
+ "use_text_index",
154
+ "warmup_cmd",
155
+ ],
156
+ "runtime": ["system", "image", "server_container"],
157
+ }
41
158
 
42
159
  def additional_arguments(self, subparser) -> None:
43
160
  # subparser.add_argument("--kill-existing-with-same-name",
@@ -46,16 +163,27 @@ class StartCommand(QleverCommand):
46
163
  # help="If a QLever server is already running "
47
164
  # "with the same name, kill it before "
48
165
  # "starting a new server")
49
- subparser.add_argument("--kill-existing-with-same-port",
50
- action="store_true",
51
- default=False,
52
- help="If a QLever server is already running "
53
- "on the same port, kill it before "
54
- "starting a new server")
55
- subparser.add_argument("--no-warmup",
56
- action="store_true",
57
- default=False,
58
- help="Do not execute the warmup command")
166
+ subparser.add_argument(
167
+ "--kill-existing-with-same-port",
168
+ action="store_true",
169
+ default=False,
170
+ help="If a QLever server is already running "
171
+ "on the same port, kill it before "
172
+ "starting a new server",
173
+ )
174
+ subparser.add_argument(
175
+ "--no-warmup",
176
+ action="store_true",
177
+ default=False,
178
+ help="Do not execute the warmup command",
179
+ )
180
+ subparser.add_argument(
181
+ "--run-in-foreground",
182
+ action="store_true",
183
+ default=False,
184
+ help="Run the server in the foreground "
185
+ "(default: run in the background with `nohup`)",
186
+ )
59
187
 
60
188
  def execute(self, args) -> bool:
61
189
  # Kill existing server with the same name if so desired.
@@ -70,47 +198,21 @@ class StartCommand(QleverCommand):
70
198
 
71
199
  # Kill existing server on the same port if so desired.
72
200
  if args.kill_existing_with_same_port:
73
- args.cmdline_regex = f"^ServerMain.* -p {args.port}"
74
- args.no_containers = True
75
- if not StopCommand().execute(args):
76
- log.error("Stopping the existing server failed")
201
+ if args.kill_existing_with_same_port and not kill_existing_server(
202
+ args
203
+ ):
77
204
  return False
78
- log.info("")
79
205
 
80
206
  # Construct the command line based on the config file.
81
- start_cmd = (f"{args.server_binary}"
82
- f" -i {args.name}"
83
- f" -j {args.num_threads}"
84
- f" -p {args.port}"
85
- f" -m {args.memory_for_queries}"
86
- f" -c {args.cache_max_size}"
87
- f" -e {args.cache_max_size_single_entry}"
88
- f" -k {args.cache_max_num_entries}")
89
- if args.timeout:
90
- start_cmd += f" -s {args.timeout}"
91
- if args.access_token:
92
- start_cmd += f" -a {args.access_token}"
93
- if args.only_pso_and_pos_permutations:
94
- start_cmd += " --only-pso-and-pos-permutations"
95
- if not args.use_patterns:
96
- start_cmd += " --no-patterns"
97
- if args.use_text_index == "yes":
98
- start_cmd += " -t"
99
- start_cmd += f" > {args.name}.server-log.txt 2>&1"
207
+ start_cmd = construct_command(args)
100
208
 
101
209
  # Run the command in a container (if so desired). Otherwise run with
102
- # `nohup` so that it keeps running after the shell is closed.
210
+ # `nohup` so that it keeps running after the shell is closed. With
211
+ # `--run-in-foreground`, run the server in the foreground.
103
212
  if args.system in Containerize.supported_systems():
104
- if not args.server_container:
105
- args.server_container = f"qlever.server.{args.name}"
106
- start_cmd = Containerize().containerize_command(
107
- start_cmd,
108
- args.system, "run -d --restart=unless-stopped",
109
- args.image,
110
- args.server_container,
111
- volumes=[("$(pwd)", "/index")],
112
- ports=[(args.port, args.port)],
113
- working_directory="/index")
213
+ start_cmd = wrap_command_in_container(args, start_cmd)
214
+ elif args.run_in_foreground:
215
+ start_cmd = f"{start_cmd}"
114
216
  else:
115
217
  start_cmd = f"nohup {start_cmd} &"
116
218
 
@@ -121,35 +223,32 @@ class StartCommand(QleverCommand):
121
223
 
122
224
  # When running natively, check if the binary exists and works.
123
225
  if args.system == "native":
124
- try:
125
- run_command(f"{args.server_binary} --help")
126
- except Exception as e:
127
- log.error(f"Running \"{args.server_binary}\" failed, "
128
- f"set `--server-binary` to a different binary or "
129
- f"set `--system to a container system`")
130
- log.info("")
131
- log.info(f"The error message was: {e}")
226
+ ret = check_binary(args.server_binary)
227
+ if not ret:
132
228
  return False
133
229
 
134
230
  # Check if a QLever server is already running on this port.
135
- port = args.port
136
- if is_qlever_server_alive(port):
137
- log.error(f"QLever server already running on port {port}")
231
+ endpoint_url = f"http://localhost:{args.port}"
232
+ if is_qlever_server_alive(endpoint_url):
233
+ log.error(f"QLever server already running on {endpoint_url}")
138
234
  log.info("")
139
- log.info("To kill the existing server, use `qlever stop` "
140
- "or `qlever start` with option "
141
- "--kill-existing-with-same-port`")
235
+ log.info(
236
+ "To kill the existing server, use `qlever stop` "
237
+ "or `qlever start` with option "
238
+ "--kill-existing-with-same-port`"
239
+ )
142
240
 
143
241
  # Show output of status command.
144
- args.cmdline_regex = f"^ServerMain.* -p *{port}"
242
+ args.cmdline_regex = f"^ServerMain.* -p *{args.port}"
145
243
  log.info("")
146
244
  StatusCommand().execute(args)
147
-
148
245
  return False
149
246
 
150
247
  # Remove already existing container.
151
- if args.system in Containerize.supported_systems() \
152
- and args.kill_existing_with_same_port:
248
+ if (
249
+ args.system in Containerize.supported_systems()
250
+ and args.kill_existing_with_same_port
251
+ ):
153
252
  try:
154
253
  run_command(f"{args.system} rm -f {args.server_container}")
155
254
  except Exception as e:
@@ -166,7 +265,10 @@ class StartCommand(QleverCommand):
166
265
 
167
266
  # Execute the command line.
168
267
  try:
169
- run_command(start_cmd)
268
+ process = run_command(
269
+ start_cmd,
270
+ use_popen=args.run_in_foreground,
271
+ )
170
272
  except Exception as e:
171
273
  log.error(f"Starting the QLever server failed ({e})")
172
274
  return False
@@ -174,41 +276,40 @@ class StartCommand(QleverCommand):
174
276
  # Tail the server log until the server is ready (note that the `exec`
175
277
  # is important to make sure that the tail process is killed and not
176
278
  # just the bash process).
177
- log.info(f"Follow {args.name}.server-log.txt until the server is ready"
178
- f" (Ctrl-C stops following the log, but not the server)")
279
+ if args.run_in_foreground:
280
+ log.info(
281
+ f"Follow {args.name}.server-log.txt as long as the server is"
282
+ f" running (Ctrl-C stops the server)"
283
+ )
284
+ else:
285
+ log.info(
286
+ f"Follow {args.name}.server-log.txt until the server is ready"
287
+ f" (Ctrl-C stops following the log, but NOT the server)"
288
+ )
179
289
  log.info("")
180
290
  tail_cmd = f"exec tail -f {args.name}.server-log.txt"
181
291
  tail_proc = subprocess.Popen(tail_cmd, shell=True)
182
- while not is_qlever_server_alive(port):
292
+ while not is_qlever_server_alive(endpoint_url):
183
293
  time.sleep(1)
184
294
 
185
- # Set the access token if specified.
186
- access_arg = f"--data-urlencode \"access-token={args.access_token}\""
295
+ # Set the description for the index and text.
296
+ access_arg = f'--data-urlencode "access-token={args.access_token}"'
187
297
  if args.description:
188
- desc = args.description
189
- curl_cmd = (f"curl -Gs http://localhost:{port}/api"
190
- f" --data-urlencode \"index-description={desc}\""
191
- f" {access_arg} > /dev/null")
192
- log.debug(curl_cmd)
193
- try:
194
- run_command(curl_cmd)
195
- except Exception as e:
196
- log.error(f"Setting the index description failed ({e})")
298
+ ret = set_index_description(
299
+ access_arg, args.port, args.description
300
+ )
301
+ if not ret:
197
302
  return False
198
303
  if args.text_description:
199
- text_desc = args.text_description
200
- curl_cmd = (f"curl -Gs http://localhost:{port}/api"
201
- f" --data-urlencode \"text-description={text_desc}\""
202
- f" {access_arg} > /dev/null")
203
- log.debug(curl_cmd)
204
- try:
205
- run_command(curl_cmd)
206
- except Exception as e:
207
- log.error(f"Setting the text description failed ({e})")
304
+ ret = set_text_description(
305
+ access_arg, args.port, args.text_description
306
+ )
307
+ if not ret:
208
308
  return False
209
309
 
210
310
  # Kill the tail process. NOTE: `tail_proc.kill()` does not work.
211
- tail_proc.terminate()
311
+ if not args.run_in_foreground:
312
+ tail_proc.terminate()
212
313
 
213
314
  # Execute the warmup command.
214
315
  if args.warmup_cmd and not args.no_warmup:
@@ -218,8 +319,18 @@ class StartCommand(QleverCommand):
218
319
  return False
219
320
 
220
321
  # Show cache stats.
221
- log.info("")
222
- args.detailed = False
223
- args.server_url = None
224
- CacheStatsCommand().execute(args)
322
+ if not args.run_in_foreground:
323
+ log.info("")
324
+ args.detailed = False
325
+ args.server_url = None
326
+ CacheStatsCommand().execute(args)
327
+
328
+ # With `--run-in-foreground`, wait until the server is stopped.
329
+ if args.run_in_foreground:
330
+ try:
331
+ process.wait()
332
+ except KeyboardInterrupt:
333
+ process.terminate()
334
+ tail_proc.terminate()
335
+
225
336
  return True