qlever 0.5.8__py3-none-any.whl → 0.5.10__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/qleverfile.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- import socket
5
4
  import subprocess
6
5
  from configparser import ConfigParser, ExtendedInterpolation
7
6
 
@@ -41,162 +40,277 @@ class Qleverfile:
41
40
  ui_args = all_args["ui"] = {}
42
41
 
43
42
  data_args["name"] = arg(
44
- "--name", type=str, required=True,
45
- help="The name of the dataset")
43
+ "--name", type=str, required=True, help="The name of the dataset"
44
+ )
46
45
  data_args["get_data_cmd"] = arg(
47
- "--get-data-cmd", type=str, required=True,
48
- help="The command to get the data")
46
+ "--get-data-cmd",
47
+ type=str,
48
+ required=True,
49
+ help="The command to get the data",
50
+ )
49
51
  data_args["description"] = arg(
50
- "--description", type=str, required=True,
51
- help="A concise description of the dataset")
52
+ "--description",
53
+ type=str,
54
+ required=True,
55
+ help="A concise description of the dataset",
56
+ )
52
57
  data_args["text_description"] = arg(
53
- "--text-description", type=str, default=None,
54
- help="A concise description of the additional text data"
55
- " if any")
58
+ "--text-description",
59
+ type=str,
60
+ default=None,
61
+ help="A concise description of the additional text data" " if any",
62
+ )
56
63
  data_args["format"] = arg(
57
- "--format", type=str, default="ttl",
58
- choices=["ttl", "nt", "nq"],
59
- help="The format of the data")
64
+ "--format",
65
+ type=str,
66
+ default="ttl",
67
+ choices=["ttl", "nt", "nq"],
68
+ help="The format of the data",
69
+ )
60
70
 
61
71
  index_args["input_files"] = arg(
62
- "--input-files", type=str, required=True,
63
- help="A space-separated list of patterns that match "
64
- "all the files of the dataset")
72
+ "--input-files",
73
+ type=str,
74
+ required=True,
75
+ help="A space-separated list of patterns that match "
76
+ "all the files of the dataset",
77
+ )
65
78
  index_args["cat_input_files"] = arg(
66
- "--cat-input-files", type=str, required=True,
67
- help="The command that produces the input")
79
+ "--cat-input-files", type=str, help="The command that produces the input"
80
+ )
81
+ index_args["multi_input_json"] = arg(
82
+ "--multi-input-json",
83
+ type=str,
84
+ default=None,
85
+ help="JSON to specify multiple input files, each with a "
86
+ "`cmd` (command that writes the triples to stdout), "
87
+ "`format` (format like for the `--format` option), "
88
+ "`graph` (name of the graph, use `-` for the default graph), "
89
+ "`parallel` (parallel parsing for large files, where all "
90
+ "prefix declaration are at the beginning)",
91
+ )
92
+ index_args["parallel_parsing"] = arg(
93
+ "--parallel-parsing",
94
+ type=str,
95
+ choices=["true", "false"],
96
+ help="Use parallel parsing (recommended for large files, "
97
+ "but it requires that all prefix declarations are at the "
98
+ "beginning of the file)",
99
+ )
68
100
  index_args["settings_json"] = arg(
69
- "--settings-json", type=str, default="{}",
70
- help="The `.settings.json` file for the index")
101
+ "--settings-json",
102
+ type=str,
103
+ default="{}",
104
+ help="The `.settings.json` file for the index",
105
+ )
71
106
  index_args["index_binary"] = arg(
72
- "--index-binary", type=str, default="IndexBuilderMain",
73
- help="The binary for building the index (this requires "
74
- "that you have compiled QLever on your machine)")
107
+ "--index-binary",
108
+ type=str,
109
+ default="IndexBuilderMain",
110
+ help="The binary for building the index (this requires "
111
+ "that you have compiled QLever on your machine)",
112
+ )
75
113
  index_args["stxxl_memory"] = arg(
76
- "--stxxl-memory", type=str, default="5G",
77
- help="The amount of memory to use for the index build "
78
- "(the name of the option has historical reasons)")
114
+ "--stxxl-memory",
115
+ type=str,
116
+ default="5G",
117
+ help="The amount of memory to use for the index build "
118
+ "(the name of the option has historical reasons)",
119
+ )
79
120
  index_args["only_pso_and_pos_permutations"] = arg(
80
- "--only-pso-and-pos-permutations", action="store_true",
81
- default=False,
82
- help="Only create the PSO and POS permutations")
121
+ "--only-pso-and-pos-permutations",
122
+ action="store_true",
123
+ default=False,
124
+ help="Only create the PSO and POS permutations",
125
+ )
83
126
  index_args["use_patterns"] = arg(
84
- "--use-patterns", action="store_true", default=True,
85
- help="Precompute so-called patterns needed for fast processing"
86
- " of queries like SELECT ?p (COUNT(DISTINCT ?s) AS ?c) "
87
- "WHERE { ?s ?p [] ... } GROUP BY ?p")
127
+ "--use-patterns",
128
+ action="store_true",
129
+ default=True,
130
+ help="Precompute so-called patterns needed for fast processing"
131
+ " of queries like SELECT ?p (COUNT(DISTINCT ?s) AS ?c) "
132
+ "WHERE { ?s ?p [] ... } GROUP BY ?p",
133
+ )
88
134
  index_args["text_index"] = arg(
89
- "--text-index",
90
- choices=["none", "from_text_records", "from_literals",
91
- "from_text_records_and_literals"],
92
- default="none",
93
- help="Whether to also build an index for text search"
94
- "and for which texts")
135
+ "--text-index",
136
+ choices=[
137
+ "none",
138
+ "from_text_records",
139
+ "from_literals",
140
+ "from_text_records_and_literals",
141
+ ],
142
+ default="none",
143
+ help="Whether to also build an index for text search" "and for which texts",
144
+ )
95
145
  index_args["text_words_file"] = arg(
96
- "--text-words-file", type=str, default=None,
97
- help="File with the words for the text index (one line "
98
- "per word, format: `word or IRI\t0 or 1\tdoc id\t1`)")
146
+ "--text-words-file",
147
+ type=str,
148
+ default=None,
149
+ help="File with the words for the text index (one line "
150
+ "per word, format: `word or IRI\t0 or 1\tdoc id\t1`)",
151
+ )
99
152
  index_args["text_docs_file"] = arg(
100
- "--text-docs-file", type=str, default=None,
101
- help="File with the documents for the text index (one line "
102
- "per document, format: `id\tdocument text`)")
153
+ "--text-docs-file",
154
+ type=str,
155
+ default=None,
156
+ help="File with the documents for the text index (one line "
157
+ "per document, format: `id\tdocument text`)",
158
+ )
103
159
 
104
160
  server_args["server_binary"] = arg(
105
- "--server-binary", type=str, default="ServerMain",
106
- help="The binary for starting the server (this requires "
107
- "that you have compiled QLever on your machine)")
161
+ "--server-binary",
162
+ type=str,
163
+ default="ServerMain",
164
+ help="The binary for starting the server (this requires "
165
+ "that you have compiled QLever on your machine)",
166
+ )
108
167
  server_args["host_name"] = arg(
109
- "--host-name", type=str, default=f"localhost",
110
- help="The name of the host on which the server listens for "
111
- "requests")
168
+ "--host-name",
169
+ type=str,
170
+ default="localhost",
171
+ help="The name of the host on which the server listens for " "requests",
172
+ )
112
173
  server_args["port"] = arg(
113
- "--port", type=int,
114
- help="The port on which the server listens for requests")
174
+ "--port", type=int, help="The port on which the server listens for requests"
175
+ )
115
176
  server_args["access_token"] = arg(
116
- "--access-token", type=str, default=None,
117
- help="The access token for privileged operations")
177
+ "--access-token",
178
+ type=str,
179
+ default=None,
180
+ help="The access token for privileged operations",
181
+ )
118
182
  server_args["memory_for_queries"] = arg(
119
- "--memory-for-queries", type=str, default="5G",
120
- help="The maximal amount of memory used for query processing"
121
- " (if a query needs more than what is available, the "
122
- "query will not be processed)")
183
+ "--memory-for-queries",
184
+ type=str,
185
+ default="5G",
186
+ help="The maximal amount of memory used for query processing"
187
+ " (if a query needs more than what is available, the "
188
+ "query will not be processed)",
189
+ )
123
190
  server_args["cache_max_size"] = arg(
124
- "--cache-max-size", type=str, default="2G",
125
- help="The maximal amount of memory used for caching")
191
+ "--cache-max-size",
192
+ type=str,
193
+ default="2G",
194
+ help="The maximal amount of memory used for caching",
195
+ )
126
196
  server_args["cache_max_size_single_entry"] = arg(
127
- "--cache-max-size-single-entry", type=str, default="1G",
128
- help="The maximal amount of memory used for caching a single "
129
- "query result")
197
+ "--cache-max-size-single-entry",
198
+ type=str,
199
+ default="1G",
200
+ help="The maximal amount of memory used for caching a single "
201
+ "query result",
202
+ )
130
203
  server_args["cache_max_num_entries"] = arg(
131
- "--cache-max-num-entries", type=int, default=200,
132
- help="The maximal number of entries in the cache"
133
- " (the eviction policy when the cache is full is LRU)")
204
+ "--cache-max-num-entries",
205
+ type=int,
206
+ default=200,
207
+ help="The maximal number of entries in the cache"
208
+ " (the eviction policy when the cache is full is LRU)",
209
+ )
134
210
  server_args["timeout"] = arg(
135
- "--timeout", type=str, default="30s",
136
- help="The maximal time in seconds a query is allowed to run"
137
- " (can be increased per query with the URL parameters "
138
- "`timeout` and `access_token`)")
211
+ "--timeout",
212
+ type=str,
213
+ default="30s",
214
+ help="The maximal time in seconds a query is allowed to run"
215
+ " (can be increased per query with the URL parameters "
216
+ "`timeout` and `access_token`)",
217
+ )
139
218
  server_args["num_threads"] = arg(
140
- "--num-threads", type=int, default=8,
141
- help="The number of threads used for query processing")
219
+ "--num-threads",
220
+ type=int,
221
+ default=8,
222
+ help="The number of threads used for query processing",
223
+ )
142
224
  server_args["only_pso_and_pos_permutations"] = arg(
143
- "--only-pso-and-pos-permutations", action="store_true",
144
- default=False,
145
- help="Only use the PSO and POS permutations (then each "
146
- "triple pattern must have a fixed predicate)")
225
+ "--only-pso-and-pos-permutations",
226
+ action="store_true",
227
+ default=False,
228
+ help="Only use the PSO and POS permutations (then each "
229
+ "triple pattern must have a fixed predicate)",
230
+ )
147
231
  server_args["use_patterns"] = arg(
148
- "--use-patterns", action="store_true", default=True,
149
- help="Use the patterns precomputed during the index build"
150
- " (see `qlever index --help` for their utility)")
232
+ "--use-patterns",
233
+ action="store_true",
234
+ default=True,
235
+ help="Use the patterns precomputed during the index build"
236
+ " (see `qlever index --help` for their utility)",
237
+ )
151
238
  server_args["use_text_index"] = arg(
152
- "--use-text-index", choices=["yes", "no"], default="no",
153
- help="Whether to use the text index (requires that one was "
154
- "built, see `qlever index`)")
239
+ "--use-text-index",
240
+ choices=["yes", "no"],
241
+ default="no",
242
+ help="Whether to use the text index (requires that one was "
243
+ "built, see `qlever index`)",
244
+ )
155
245
  server_args["warmup_cmd"] = arg(
156
- "--warmup-cmd", type=str,
157
- help="Command executed after the server has started "
158
- " (executed as part of `qlever start` unless "
159
- " `--no-warmup` is specified, or with `qlever warmup`)")
246
+ "--warmup-cmd",
247
+ type=str,
248
+ help="Command executed after the server has started "
249
+ " (executed as part of `qlever start` unless "
250
+ " `--no-warmup` is specified, or with `qlever warmup`)",
251
+ )
160
252
 
161
253
  runtime_args["system"] = arg(
162
- "--system", type=str,
163
- choices=Containerize.supported_systems() + ["native"],
164
- default="docker",
165
- help=("Whether to run commands like `index` or `start` "
166
- "natively or in a container, and if in a container, "
167
- "which system to use"))
254
+ "--system",
255
+ type=str,
256
+ choices=Containerize.supported_systems() + ["native"],
257
+ default="docker",
258
+ help=(
259
+ "Whether to run commands like `index` or `start` "
260
+ "natively or in a container, and if in a container, "
261
+ "which system to use"
262
+ ),
263
+ )
168
264
  runtime_args["image"] = arg(
169
- "--image", type=str,
170
- default="docker.io/adfreiburg/qlever",
171
- help="The name of the image when running in a container")
265
+ "--image",
266
+ type=str,
267
+ default="docker.io/adfreiburg/qlever",
268
+ help="The name of the image when running in a container",
269
+ )
172
270
  runtime_args["index_container"] = arg(
173
- "--index-container", type=str,
174
- help="The name of the container used by `qlever index`")
271
+ "--index-container",
272
+ type=str,
273
+ help="The name of the container used by `qlever index`",
274
+ )
175
275
  runtime_args["server_container"] = arg(
176
- "--server-container", type=str,
177
- help="The name of the container used by `qlever start`")
276
+ "--server-container",
277
+ type=str,
278
+ help="The name of the container used by `qlever start`",
279
+ )
178
280
 
179
281
  ui_args["ui_port"] = arg(
180
- "--ui-port", type=int, default=8176,
181
- help="The port of the Qlever UI when running `qlever ui`")
282
+ "--ui-port",
283
+ type=int,
284
+ default=8176,
285
+ help="The port of the Qlever UI when running `qlever ui`",
286
+ )
182
287
  ui_args["ui_config"] = arg(
183
- "--ui-config", type=str, default="default",
184
- help="The name of the backend configuration for the QLever UI"
185
- " (this determines AC queries and example queries)")
288
+ "--ui-config",
289
+ type=str,
290
+ default="default",
291
+ help="The name of the backend configuration for the QLever UI"
292
+ " (this determines AC queries and example queries)",
293
+ )
186
294
  ui_args["ui_system"] = arg(
187
- "--ui-system", type=str,
188
- choices=Containerize.supported_systems(),
189
- default="docker",
190
- help="Which container system to use for `qlever ui`"
191
- " (unlike for `qlever index` and `qlever start`, "
192
- " \"native\" is not yet supported here)")
295
+ "--ui-system",
296
+ type=str,
297
+ choices=Containerize.supported_systems(),
298
+ default="docker",
299
+ help="Which container system to use for `qlever ui`"
300
+ " (unlike for `qlever index` and `qlever start`, "
301
+ ' "native" is not yet supported here)',
302
+ )
193
303
  ui_args["ui_image"] = arg(
194
- "--ui-image", type=str,
195
- default="docker.io/adfreiburg/qlever-ui",
196
- help="The name of the image used for `qlever ui`")
304
+ "--ui-image",
305
+ type=str,
306
+ default="docker.io/adfreiburg/qlever-ui",
307
+ help="The name of the image used for `qlever ui`",
308
+ )
197
309
  ui_args["ui_container"] = arg(
198
- "--ui-container", type=str,
199
- help="The name of the container used for `qlever ui`")
310
+ "--ui-container",
311
+ type=str,
312
+ help="The name of the container used for `qlever ui`",
313
+ )
200
314
 
201
315
  return all_args
202
316
 
@@ -214,8 +328,7 @@ class Qleverfile:
214
328
 
215
329
  # Read the Qleverfile.
216
330
  defaults = {"random": "83724324hztz", "version": "01.01.01"}
217
- config = ConfigParser(interpolation=ExtendedInterpolation(),
218
- defaults=defaults)
331
+ config = ConfigParser(interpolation=ExtendedInterpolation(), defaults=defaults)
219
332
  try:
220
333
  config.read(qleverfile_path)
221
334
  except Exception as e:
@@ -230,13 +343,18 @@ class Qleverfile:
230
343
  if match:
231
344
  try:
232
345
  value = subprocess.check_output(
233
- match.group(1), shell=True, text=True,
234
- stderr=subprocess.STDOUT).strip()
346
+ match.group(1),
347
+ shell=True,
348
+ text=True,
349
+ stderr=subprocess.STDOUT,
350
+ ).strip()
235
351
  except Exception as e:
236
352
  log.info("")
237
- log.error(f"Error evaluating {value} for option "
238
- f"{section}.{option.upper()} in "
239
- f"{qleverfile_path}:")
353
+ log.error(
354
+ f"Error evaluating {value} for option "
355
+ f"{section}.{option.upper()} in "
356
+ f"{qleverfile_path}:"
357
+ )
240
358
  log.info("")
241
359
  log.info(e.output if hasattr(e, "output") else e)
242
360
  exit(1)
@@ -263,8 +381,8 @@ class Qleverfile:
263
381
  if "text_docs_file" not in index:
264
382
  index["text_docs_file"] = f"{name}.docsfile.tsv"
265
383
  server = config["server"]
266
- if index.get("text_index", "none") != "none":
267
- server["use_text_index"] = "yes"
384
+ if index.get("text_index", "none") != "none":
385
+ server["use_text_index"] = "yes"
268
386
 
269
387
  # Return the parsed Qleverfile with the added inherited values.
270
388
  return config
qlever/util.py CHANGED
@@ -29,8 +29,9 @@ def get_total_file_size(patterns: list[str]) -> int:
29
29
  return total_size
30
30
 
31
31
 
32
- def run_command(cmd: str, return_output: bool = False,
33
- show_output: bool = False) -> Optional[str]:
32
+ def run_command(
33
+ cmd: str, return_output: bool = False, show_output: bool = False
34
+ ) -> Optional[str]:
34
35
  """
35
36
  Run the given command and throw an exception if the exit code is non-zero.
36
37
  If `return_output` is `True`, return what the command wrote to `stdout`.
@@ -45,7 +46,7 @@ def run_command(cmd: str, return_output: bool = False,
45
46
  "shell": True,
46
47
  "text": True,
47
48
  "stdout": None if show_output else subprocess.PIPE,
48
- "stderr": subprocess.PIPE
49
+ "stderr": subprocess.PIPE,
49
50
  }
50
51
  result = subprocess.run(f"set -o pipefail; {cmd}", **subprocess_args)
51
52
  # If the exit code is non-zero, throw an exception. If something was
@@ -63,17 +64,20 @@ def run_command(cmd: str, return_output: bool = False,
63
64
  raise Exception(result.stderr.replace("\n", " ").strip())
64
65
  else:
65
66
  raise Exception(
66
- f"Command failed with exit code {result.returncode}"
67
- f" but nothing written to stderr")
67
+ f"Command failed with exit code {result.returncode}"
68
+ f" but nothing written to stderr"
69
+ )
68
70
  # Optionally, return what was written to `stdout`.
69
71
  if return_output:
70
72
  return result.stdout
71
73
 
72
74
 
73
- def run_curl_command(url: str,
74
- headers: dict[str, str] = {},
75
- params: dict[str, str] = {},
76
- result_file: Optional[str] = None) -> str:
75
+ def run_curl_command(
76
+ url: str,
77
+ headers: dict[str, str] = {},
78
+ params: dict[str, str] = {},
79
+ result_file: Optional[str] = None,
80
+ ) -> str:
77
81
  """
78
82
  Run `curl` with the given `url`, `headers`, and `params`. If `result_file`
79
83
  is `None`, return the output, otherwise, write the output to the given file
@@ -83,22 +87,29 @@ def run_curl_command(url: str,
83
87
  # Construct and run the `curl` command.
84
88
  default_result_file = "/tmp/qlever.curl.result"
85
89
  actual_result_file = result_file if result_file else default_result_file
86
- curl_cmd = (f"curl -s -o \"{actual_result_file}\""
87
- f" -w \"%{{http_code}}\n\" {url}"
88
- + "".join([f" -H \"{key}: {value}\""
89
- for key, value in headers.items()])
90
- + "".join([f" --data-urlencode {key}={shlex.quote(value)}"
91
- for key, value in params.items()]))
92
- result = subprocess.run(curl_cmd, shell=True, text=True,
93
- stdout=subprocess.PIPE,
94
- stderr=subprocess.PIPE)
90
+ curl_cmd = (
91
+ f'curl -s -o "{actual_result_file}"'
92
+ f' -w "%{{http_code}}\n" {url}'
93
+ + "".join([f' -H "{key}: {value}"' for key, value in headers.items()])
94
+ + "".join(
95
+ [
96
+ f" --data-urlencode {key}={shlex.quote(value)}"
97
+ for key, value in params.items()
98
+ ]
99
+ )
100
+ )
101
+ result = subprocess.run(
102
+ curl_cmd, shell=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
103
+ )
95
104
  # Case 1: An error occurred, raise an exception.
96
105
  if result.returncode != 0:
97
106
  if len(result.stderr) > 0:
98
107
  raise Exception(result.stderr)
99
108
  else:
100
- raise Exception(f"curl command failed with exit code "
101
- f"{result.returncode}, stderr is empty")
109
+ raise Exception(
110
+ f"curl command failed with exit code "
111
+ f"{result.returncode}, stderr is empty"
112
+ )
102
113
  # Case 2: Return output (read from `default_result_file`).
103
114
  if result_file is None:
104
115
  result_file_path = Path(default_result_file)
@@ -117,9 +128,9 @@ def is_qlever_server_alive(port: str) -> bool:
117
128
 
118
129
  message = "from the qlever script".replace(" ", "%20")
119
130
  curl_cmd = f"curl -s http://localhost:{port}/ping?msg={message}"
120
- exit_code = subprocess.call(curl_cmd, shell=True,
121
- stdout=subprocess.DEVNULL,
122
- stderr=subprocess.DEVNULL)
131
+ exit_code = subprocess.call(
132
+ curl_cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
133
+ )
123
134
  return exit_code == 0
124
135
 
125
136
 
@@ -152,15 +163,15 @@ def show_process_info(psutil_process, cmdline_regex, show_heading=True):
152
163
 
153
164
  try:
154
165
  pinfo = psutil_process.as_dict(
155
- attrs=['pid', 'username', 'create_time',
156
- 'memory_info', 'cmdline'])
166
+ attrs=["pid", "username", "create_time", "memory_info", "cmdline"]
167
+ )
157
168
  # Note: pinfo[`cmdline`] is `None` if the process is a zombie.
158
- cmdline = " ".join(pinfo['cmdline'] or [])
169
+ cmdline = " ".join(pinfo["cmdline"] or [])
159
170
  if len(cmdline) == 0 or not re.search(cmdline_regex, cmdline):
160
171
  return False
161
- pid = pinfo['pid']
162
- user = pinfo['username'] if pinfo['username'] else ""
163
- start_time = datetime.fromtimestamp(pinfo['create_time'])
172
+ pid = pinfo["pid"]
173
+ user = pinfo["username"] if pinfo["username"] else ""
174
+ start_time = datetime.fromtimestamp(pinfo["create_time"])
164
175
  if start_time.date() == date.today():
165
176
  start_time = start_time.strftime("%H:%M")
166
177
  else:
@@ -193,10 +204,24 @@ def is_port_used(port: int) -> bool:
193
204
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
194
205
  # Ensure that the port is not blocked after the check.
195
206
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
196
- sock.bind(('', port))
207
+ sock.bind(("", port))
197
208
  sock.close()
198
209
  return False
199
210
  except OSError as err:
200
211
  if err.errno != errno.EADDRINUSE:
201
212
  log.warning(f"Failed to determine if port is used: {err}")
202
213
  return True
214
+
215
+
216
+ def format_size(bytes, suffix="B"):
217
+ """
218
+ Scale bytes to its proper format
219
+ e.g:
220
+ 1253656 => '1.20MB'
221
+ 1253656678 => '1.17GB'
222
+ """
223
+ factor = 1024
224
+ for unit in ["", "K", "M", "G", "T", "P"]:
225
+ if bytes < factor:
226
+ return f"{bytes:.2f} {unit}{suffix}"
227
+ bytes /= factor
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qlever
3
- Version: 0.5.8
3
+ Version: 0.5.10
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-2.0