qlever 0.4.1__py3-none-any.whl → 0.4.2__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.

@@ -4,13 +4,15 @@ import re
4
4
  import shlex
5
5
  import subprocess
6
6
  import time
7
+ import traceback
8
+ from pathlib import Path
7
9
 
8
10
  from termcolor import colored
9
11
 
10
12
  from qlever.command import QleverCommand
11
13
  from qlever.commands.clear_cache import ClearCacheCommand
12
14
  from qlever.log import log, mute_log
13
- from qlever.util import run_command
15
+ from qlever.util import run_command, run_curl_command
14
16
 
15
17
 
16
18
  class ExampleQueriesCommand(QleverCommand):
@@ -57,12 +59,27 @@ class ExampleQueriesCommand(QleverCommand):
57
59
  "or just compute the size of the result")
58
60
  subparser.add_argument("--limit", type=int,
59
61
  help="Limit on the number of results")
62
+ subparser.add_argument("--accept", type=str,
63
+ choices=["text/tab-separated-values",
64
+ "application/sparql-results+json"],
65
+ default="text/tab-separated-values",
66
+ help="Accept header for the SPARQL query")
60
67
  subparser.add_argument("--clear-cache",
61
68
  choices=["yes", "no"],
62
69
  default="yes",
63
70
  help="Clear the cache before each query")
64
71
 
65
72
  def execute(self, args) -> bool:
73
+ # If `args.accept` is `application/sparql-results+json`, we need `jq`.
74
+ if args.accept == "application/sparql-results+json":
75
+ try:
76
+ subprocess.run("jq --version", shell=True, check=True,
77
+ stdout=subprocess.DEVNULL,
78
+ stderr=subprocess.DEVNULL)
79
+ except Exception as e:
80
+ log.error(f"Please install `jq` for {args.accept} ({e})")
81
+ return False
82
+
66
83
  # Handle shotcuts for SPARQL endpoint.
67
84
  if args.sparql_endpoint_preset in self.presets:
68
85
  args.sparql_endpoint = self.presets[args.sparql_endpoint_preset]
@@ -92,6 +109,7 @@ class ExampleQueriesCommand(QleverCommand):
92
109
  else f"localhost:{args.port}")
93
110
  self.show(f"Obtain queries via: {get_queries_cmd}\n"
94
111
  f"SPARQL endpoint: {sparql_endpoint}\n"
112
+ f"Accept header: {args.accept}\n"
95
113
  f"Clear cache before each query:"
96
114
  f" {args.clear_cache.upper()}\n"
97
115
  f"Download result for each query or just count:"
@@ -103,7 +121,8 @@ class ExampleQueriesCommand(QleverCommand):
103
121
 
104
122
  # Get the example queries.
105
123
  try:
106
- example_query_lines = run_command(get_queries_cmd, return_output=True)
124
+ example_query_lines = run_command(get_queries_cmd,
125
+ return_output=True)
107
126
  if len(example_query_lines) == 0:
108
127
  log.error("No example queries matching the criteria found")
109
128
  return False
@@ -114,9 +133,10 @@ class ExampleQueriesCommand(QleverCommand):
114
133
 
115
134
  # Launch the queries one after the other and for each print: the
116
135
  # description, the result size, and the query processing time.
117
- count = 0
118
136
  total_time_seconds = 0.0
119
137
  total_result_size = 0
138
+ count_succeeded = 0
139
+ count_failed = 0
120
140
  for example_query_line in example_query_lines:
121
141
  # Parse description and query.
122
142
  description, query = example_query_line.split("\t")
@@ -155,44 +175,93 @@ class ExampleQueriesCommand(QleverCommand):
155
175
  + f" }} LIMIT {args.limit}"
156
176
 
157
177
  # Launch query.
158
- query_cmd = (f"curl -sv {sparql_endpoint}"
159
- f" -H \"Accept: text/tab-separated-values\""
160
- f" --data-urlencode query={shlex.quote(query)}")
161
- if args.download_or_count == "count":
162
- query_cmd += " | sed 1d"
163
- else:
164
- query_cmd += " | sed 1d | wc -l"
165
178
  try:
166
- log.debug(query_cmd)
179
+ curl_cmd = (f"curl -s {sparql_endpoint}"
180
+ f" -w \"HTTP code: %{{http_code}}\\n\""
181
+ f" -H \"Accept: {args.accept}\""
182
+ f" --data-urlencode query={shlex.quote(query)}")
183
+ log.debug(curl_cmd)
184
+ result_file = (f"qlever.example_queries.result."
185
+ f"{abs(hash(curl_cmd))}.tmp")
167
186
  start_time = time.time()
168
- result_size = run_command(query_cmd, return_output=True)
169
- result_size = int(result_size.strip())
187
+ http_code = run_curl_command(sparql_endpoint,
188
+ headers={"Accept": args.accept},
189
+ params={"query": query},
190
+ result_file=result_file).strip()
191
+ if http_code != "200":
192
+ raise Exception(f"HTTP code {http_code}"
193
+ f" {Path(result_file).read_text()}")
170
194
  time_seconds = time.time() - start_time
171
- time_string = f"{time_seconds:.2f}"
172
- result_string = f"{result_size:>14,}"
195
+ error_msg = None
173
196
  except Exception as e:
174
- time_seconds = 0.0
175
- time_string = "---"
176
- result_size = 0
177
- result_string = colored(f" FAILED {e}", "red")
197
+ if args.log_level == "DEBUG":
198
+ traceback.print_exc()
199
+ error_msg = re.sub(r"\s+", " ", str(e))
200
+
201
+ # Get result size (via the command line, in order to avoid loading
202
+ # a potentially large JSON file into Python, which is slow).
203
+ if error_msg is None:
204
+ try:
205
+ if args.download_or_count == "count":
206
+ if args.accept == "text/tab-separated-values":
207
+ result_size = run_command(
208
+ f"sed 1d {result_file}",
209
+ return_output=True)
210
+ else:
211
+ result_size = run_command(
212
+ f"jq -r \".results.bindings[0]"
213
+ f" | to_entries[0].value.value"
214
+ f" | tonumber\" {result_file}",
215
+ return_output=True)
216
+ else:
217
+ if args.accept == "text/tab-separated-values":
218
+ result_size = run_command(
219
+ f"sed 1d {result_file} | wc -l",
220
+ return_output=True)
221
+ else:
222
+ result_size = run_command(
223
+ f"jq -r \".results.bindings | length\""
224
+ f" {result_file}",
225
+ return_output=True)
226
+ result_size = int(result_size)
227
+ except Exception as e:
228
+ error_msg = str(e)
178
229
 
179
230
  # Print description, time, result in tabular form.
180
231
  if (len(description) > 60):
181
232
  description = description[:57] + "..."
182
- log.info(f"{description:<60} {time_string:>6} s "
183
- f"{result_string}")
184
- count += 1
185
- total_time_seconds += time_seconds
186
- total_result_size += result_size
233
+ if error_msg is None:
234
+ log.info(f"{description:<60} {time_seconds:6.2f} s "
235
+ f"{result_size:14,}")
236
+ count_succeeded += 1
237
+ total_time_seconds += time_seconds
238
+ total_result_size += result_size
239
+ else:
240
+ count_failed += 1
241
+ if (len(error_msg) > 60) and args.log_level != "DEBUG":
242
+ error_msg = error_msg[:57] + "..."
243
+ log.error(f"{description:<60} failed "
244
+ f"{colored(error_msg, 'red')}")
187
245
 
188
246
  # Print total time.
189
247
  log.info("")
190
- description = (f"TOTAL for {count} "
191
- f"{'query' if count == 1 else 'queries'}")
192
- log.info(f"{description:<60} {total_time_seconds:6.2f} s "
193
- f"{total_result_size:>14,}")
194
- description = (f"AVERAGE for {count} "
195
- f"{'query' if count == 1 else 'queries'}")
196
- log.info(f"{description:<60} {total_time_seconds / count:6.2f} s "
197
- f"{round(total_result_size / count):>14,}")
248
+ if count_succeeded > 0:
249
+ query_or_queries = "query" if count_succeeded == 1 else "queries"
250
+ description = (f"TOTAL for {count_succeeded} {query_or_queries}")
251
+ log.info(f"{description:<60} "
252
+ f"{total_time_seconds:6.2f} s "
253
+ f"{total_result_size:>14,}")
254
+ description = (f"AVERAGE for {count_succeeded} {query_or_queries}")
255
+ log.info(f"{description:<60} "
256
+ f"{total_time_seconds / count_succeeded:6.2f} s "
257
+ f"{round(total_result_size / count_succeeded):>14,}")
258
+ else:
259
+ if count_failed == 1:
260
+ log.info(colored("One query failed", "red"))
261
+ elif count_failed > 1:
262
+ log.info(colored("All queries failed", "red"))
263
+
264
+ # Return success (has nothing to do with how many queries failed).
265
+ if args.log_level != "DEBUG":
266
+ Path(result_file).unlink(missing_ok=True)
198
267
  return True
@@ -71,14 +71,17 @@ class IndexStatsCommand(QleverCommand):
71
71
 
72
72
  # Helper function that finds the next line matching the given `regex`,
73
73
  # starting from `current_line`, and extracts the time. Returns a tuple
74
- # of the time and the regex match object. If a match is found,
75
- # `current_line` is updated to the line after the match. Otherwise,
76
- # `current_line` will be one beyond the last line, unless
77
- # `line_is_optional` is true, in which case it will be the same as when
78
- # the function was entered.
74
+ # of the time and the regex match object.
75
+ #
76
+ # If `update_current_line` is `False`, then `current_line` will not be
77
+ # updated by this call.
78
+ #
79
+ # Otherwise, and this is the default behavior, `current_line` will be
80
+ # updated to the line after the first match, or one beyond the last
81
+ # line if no match is found.
79
82
  current_line = 0
80
83
 
81
- def find_next_line(regex, line_is_optional=False):
84
+ def find_next_line(regex, update_current_line=True):
82
85
  nonlocal lines
83
86
  nonlocal current_line
84
87
  current_line_backup = current_line
@@ -99,7 +102,7 @@ class IndexStatsCommand(QleverCommand):
99
102
  f"\"{timestamp_regex}\" from line "
100
103
  f" \"{line.rstrip()}\" ({e})")
101
104
  # If we get here, we did not find a matching line.
102
- if line_is_optional:
105
+ if not update_current_line:
103
106
  current_line = current_line_backup
104
107
  return None, None
105
108
 
@@ -110,24 +113,34 @@ class IndexStatsCommand(QleverCommand):
110
113
  convert_begin, _ = find_next_line(r"INFO:\s*Converting triples")
111
114
  perm_begin_and_info = []
112
115
  while True:
113
- perm_begin, _ = find_next_line(r"INFO:\s*Creating a pair", True)
116
+ # Find the next line that starts a permutation.
117
+ #
118
+ # NOTE: Should work for the old and new format of the index log
119
+ # file (old format: "Creating a pair" + names of permutations in
120
+ # line "Writing meta data for ..."; new format: name of
121
+ # permutations already in line "Creating permutations ...").
122
+ perm_begin, _ = find_next_line(r"INFO:\s*Creating a pair",
123
+ update_current_line=False)
114
124
  if perm_begin is None:
125
+ perm_begin, perm_info = find_next_line(
126
+ r"INFO:\s*Creating permutations ([A-Z]+ and [A-Z]+)",
127
+ update_current_line=False)
128
+ else:
129
+ _, perm_info = find_next_line(
130
+ r"INFO:\s*Writing meta data for ([A-Z]+ and [A-Z]+)",
131
+ update_current_line=False)
132
+ if perm_info is None:
115
133
  break
116
- _, perm_info = find_next_line(r"INFO:\s*Writing meta data for"
117
- r" ([A-Z]+ and [A-Z]+)", True)
118
- # if perm_info is None:
119
- # break
120
134
  perm_begin_and_info.append((perm_begin, perm_info))
121
135
  convert_end = (perm_begin_and_info[0][0] if
122
136
  len(perm_begin_and_info) > 0 else None)
123
137
  normal_end, _ = find_next_line(r"INFO:\s*Index build completed")
124
- text_begin, _ = find_next_line(r"INFO:\s*Adding text index", True)
125
- text_end, _ = find_next_line(r"INFO:\s*Text index build comp", True)
138
+ text_begin, _ = find_next_line(r"INFO:\s*Adding text index",
139
+ update_current_line=False)
140
+ text_end, _ = find_next_line(r"INFO:\s*Text index build comp",
141
+ update_current_line=False)
126
142
  if args.ignore_text_index:
127
143
  text_begin = text_end = None
128
- # print("DEBUG:", len(perm_begin_and_info), perm_begin_and_info)
129
- # print("DEBUG:", overall_begin)
130
- # print("DEBUG:", normal_end)
131
144
 
132
145
  # Check whether at least the first phase is done.
133
146
  if overall_begin is None:
qlever/util.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import secrets
4
3
  import re
4
+ import secrets
5
5
  import shlex
6
6
  import shutil
7
7
  import string
@@ -31,7 +31,7 @@ def run_command(cmd: str, return_output: bool = False,
31
31
  show_output: bool = False) -> Optional[str]:
32
32
  """
33
33
  Run the given command and throw an exception if the exit code is non-zero.
34
- If `get_output` is `True`, return what the command wrote to `stdout`.
34
+ If `return_output` is `True`, return what the command wrote to `stdout`.
35
35
 
36
36
  NOTE: The `set -o pipefail` ensures that the exit code of the command is
37
37
  non-zero if any part of the pipeline fails (not just the last part).
@@ -68,6 +68,45 @@ def run_command(cmd: str, return_output: bool = False,
68
68
  return result.stdout
69
69
 
70
70
 
71
+ def run_curl_command(url: str,
72
+ headers: dict[str, str] = {},
73
+ params: dict[str, str] = {},
74
+ result_file: Optional[str] = None) -> str:
75
+ """
76
+ Run `curl` with the given `url`, `headers`, and `params`. If `result_file`
77
+ is `None`, return the output, otherwise, write the output to the given file
78
+ and return the HTTP code. If the `curl` command fails, throw an exception.
79
+
80
+ """
81
+ # Construct and run the `curl` command.
82
+ default_result_file = "/tmp/qlever.curl.result"
83
+ actual_result_file = result_file if result_file else default_result_file
84
+ curl_cmd = (f"curl -s -o \"{actual_result_file}\""
85
+ f" -w \"%{{http_code}}\n\" {url}"
86
+ + "".join([f" -H \"{key}: {value}\""
87
+ for key, value in headers.items()])
88
+ + "".join([f" --data-urlencode {key}={shlex.quote(value)}"
89
+ for key, value in params.items()]))
90
+ result = subprocess.run(curl_cmd, shell=True, text=True,
91
+ stdout=subprocess.PIPE,
92
+ stderr=subprocess.PIPE)
93
+ # Case 1: An error occurred, raise an exception.
94
+ if result.returncode != 0:
95
+ if len(result.stderr) > 0:
96
+ raise Exception(result.stderr)
97
+ else:
98
+ raise Exception(f"curl command failed with exit code "
99
+ f"{result.returncode}, stderr is empty")
100
+ # Case 2: Return output (read from `default_result_file`).
101
+ if result_file is None:
102
+ result_file_path = Path(default_result_file)
103
+ result = result_file_path.read_text()
104
+ result_file_path.unlink()
105
+ return result
106
+ # Case 3: Return HTTP code.
107
+ return result.stdout
108
+
109
+
71
110
  def is_qlever_server_alive(port: str) -> bool:
72
111
  """
73
112
  Helper function that checks if a QLever server is running on the given
@@ -82,30 +121,6 @@ def is_qlever_server_alive(port: str) -> bool:
82
121
  return exit_code == 0
83
122
 
84
123
 
85
- def get_curl_cmd_for_sparql_query(
86
- query: str, port: int,
87
- host: str = "localhost",
88
- media_type: str = "application/sparql-results+qlever",
89
- verbose: bool = False,
90
- pinresult: bool = False,
91
- access_token: Optional[str] = None,
92
- send: Optional[int] = None) -> str:
93
- """
94
- Get curl command for given SPARQL query.
95
- """
96
- curl_cmd = (f"curl -s http://{host}:{port}"
97
- f" -H \"Accept: {media_type}\" "
98
- f" --data-urlencode query={shlex.quote(query)}")
99
- if pinresult and access_token is not None:
100
- curl_cmd += " --data-urlencode pinresult=true"
101
- curl_cmd += f" --data-urlencode access_token={access_token}"
102
- if send is not None:
103
- curl_cmd += f" --data-urlencode send={send}"
104
- if verbose:
105
- curl_cmd += " --verbose"
106
- return curl_cmd
107
-
108
-
109
124
  def get_existing_index_files(basename: str) -> list[str]:
110
125
  """
111
126
  Helper function that returns a list of all index files for `basename` in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qlever
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Script for using the QLever SPARQL engine.
5
5
  Author-email: Hannah Bast <bast@cs.uni-freiburg.de>
6
6
  License: Apache License
@@ -6,7 +6,7 @@ qlever/containerize.py,sha256=p8g3O3G8a_0XLzSTzl_e5t9dqjbCQ-ippoA8vI2Z9pI,4193
6
6
  qlever/log.py,sha256=k9Mq4hxQ_d2k0e-5ZVgcB2XIRhOsGMO9I3rIR7YQyDA,1376
7
7
  qlever/qlever_main.py,sha256=k8vIQYK7zqObFNet11iLf--nrLdPooL5amprmlySi4k,2300
8
8
  qlever/qleverfile.py,sha256=6Ll81xkzel_s2Ju9ZfBXUGlRfikaAzZM6Do-dTrdo3k,12934
9
- qlever/util.py,sha256=dwqtpY14P3ds_PYx5bgqus_nsx_BhPQzUSa0Z86ONdo,6236
9
+ qlever/util.py,sha256=eepj0SY9JJOUQq5kvtoPnWfoLLV9fbw_sTEWKHet66E,7147
10
10
  qlever/Qleverfiles/Qleverfile.dblp,sha256=SFjBD20aOSWod4mEQnxHSDWdInoE_EFp2nyMw7ev7ZA,1167
11
11
  qlever/Qleverfiles/Qleverfile.dblp-plus,sha256=Dwd9pK1vPcelKfw6sA-IuyhbZ6yIxOh6_84JgPYnB9Q,1332
12
12
  qlever/Qleverfiles/Qleverfile.default,sha256=mljl6I1RCkpIWOqMQwjzPZIsarYQx1R0mIlc583KuqU,1869
@@ -28,10 +28,10 @@ qlever/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  qlever/commands/add_text_index.py,sha256=dkqYtwgOhgnXiei_eyhBWYCtdAiQUEmjWoa3JMlMb4c,3641
29
29
  qlever/commands/cache_stats.py,sha256=6JjueQstAqc8dNfgY8TP2EitFMxdUvCwrcyd7KUEb2o,4157
30
30
  qlever/commands/clear_cache.py,sha256=AnE1MOoj1ZexxrRT8FGeBLlv8rtQIVV4DP8VBn5-X-s,2843
31
- qlever/commands/example_queries.py,sha256=3jlfHyL7pw1OSTuu3fY-23XaRAPIuEdNGW8QnIY2Va8,8644
31
+ qlever/commands/example_queries.py,sha256=2rYTd35t0r7et0i-IBBcCpmVlYZya9kvwSI-gdTpNdE,12326
32
32
  qlever/commands/get_data.py,sha256=0fGuRLDB7YofHtpqk0ctq9_de_xeuliSmSZafGXAo1A,1470
33
33
  qlever/commands/index.py,sha256=lJhDnweknFZQm1czqPzNyz33EvbjIvOrS4j0wDaJ98o,5663
34
- qlever/commands/index_stats.py,sha256=ao7_ySyz8MAjUvCbEp3Kj30PsR5x3MBM3ohgEUWdALM,11083
34
+ qlever/commands/index_stats.py,sha256=_BiUNBhmbYd9RPxrlm4HF0oENO6JmqnRiAkwkyOdN4U,11722
35
35
  qlever/commands/log.py,sha256=8Krt3MsTUDapYqVw1zUu5X15SF8mV97Uj0qKOWK8jXk,1861
36
36
  qlever/commands/setup_config.py,sha256=mFkEtCPZ6oeVfehjVLrcLttYcPDgtwXHrNIWWzvHOfo,2928
37
37
  qlever/commands/start.py,sha256=2rOtk3NmhEs28D5csL_a1BdjSWU9VkcH6AqYT0vdww0,9285
@@ -39,9 +39,9 @@ qlever/commands/status.py,sha256=5S6EdapZEwFKV9cQZtNYcZhMbAXAY-FP6ggjIhfX8ek,163
39
39
  qlever/commands/stop.py,sha256=TZs4bxKHvujlZAU8BZmFjA5eXSZNAa6EeNzvPpEZsuI,4139
40
40
  qlever/commands/ui.py,sha256=rV8u017WLbfz0zVT_c9GC4d9v1WWwrTM3kfGONbeCvQ,2499
41
41
  qlever/commands/warmup.py,sha256=WOZSxeV8U_F6pEEnAb6YybXLQMxZFTRJXs4BPHUhsmc,1030
42
- qlever-0.4.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
43
- qlever-0.4.1.dist-info/METADATA,sha256=GkXf_oneu0Oe02UOPR8OvqVzxDNA-ljS6yPGLi2x_Bk,17076
44
- qlever-0.4.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
45
- qlever-0.4.1.dist-info/entry_points.txt,sha256=s0iWBHKRUzsJ7B6nVGiyMdOJtiOS84IJMSSxgbNU6LU,85
46
- qlever-0.4.1.dist-info/top_level.txt,sha256=kd3zsYqiFd0--Czh5XTVkfEq6XR-XgRFW35X0v0GT-c,7
47
- qlever-0.4.1.dist-info/RECORD,,
42
+ qlever-0.4.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
43
+ qlever-0.4.2.dist-info/METADATA,sha256=tyLaWQtRaXbIaQkJ72mCcRpjxlusFHztHdAWedpZ1QE,17076
44
+ qlever-0.4.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
45
+ qlever-0.4.2.dist-info/entry_points.txt,sha256=s0iWBHKRUzsJ7B6nVGiyMdOJtiOS84IJMSSxgbNU6LU,85
46
+ qlever-0.4.2.dist-info/top_level.txt,sha256=kd3zsYqiFd0--Czh5XTVkfEq6XR-XgRFW35X0v0GT-c,7
47
+ qlever-0.4.2.dist-info/RECORD,,
File without changes