qlever 0.2.5__py3-none-any.whl → 0.5.41__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.
Files changed (68) hide show
  1. qlever/Qleverfiles/Qleverfile.dblp +36 -0
  2. qlever/Qleverfiles/Qleverfile.dblp-plus +33 -0
  3. qlever/Qleverfiles/Qleverfile.dbpedia +30 -0
  4. qlever/Qleverfiles/Qleverfile.default +51 -0
  5. qlever/Qleverfiles/Qleverfile.dnb +40 -0
  6. qlever/Qleverfiles/Qleverfile.fbeasy +29 -0
  7. qlever/Qleverfiles/Qleverfile.freebase +28 -0
  8. qlever/Qleverfiles/Qleverfile.imdb +36 -0
  9. qlever/Qleverfiles/Qleverfile.ohm-planet +41 -0
  10. qlever/Qleverfiles/Qleverfile.olympics +31 -0
  11. qlever/Qleverfiles/Qleverfile.orkg +30 -0
  12. qlever/Qleverfiles/Qleverfile.osm-country +39 -0
  13. qlever/Qleverfiles/Qleverfile.osm-planet +39 -0
  14. qlever/Qleverfiles/Qleverfile.osm-planet-from-pbf +42 -0
  15. qlever/Qleverfiles/Qleverfile.pubchem +131 -0
  16. qlever/Qleverfiles/Qleverfile.scientists +29 -0
  17. qlever/Qleverfiles/Qleverfile.uniprot +74 -0
  18. qlever/Qleverfiles/Qleverfile.vvz +31 -0
  19. qlever/Qleverfiles/Qleverfile.wikidata +42 -0
  20. qlever/Qleverfiles/Qleverfile.wikipathways +40 -0
  21. qlever/Qleverfiles/Qleverfile.yago-4 +33 -0
  22. qlever/__init__.py +44 -1380
  23. qlever/command.py +87 -0
  24. qlever/commands/__init__.py +0 -0
  25. qlever/commands/add_text_index.py +115 -0
  26. qlever/commands/benchmark_queries.py +1019 -0
  27. qlever/commands/cache_stats.py +125 -0
  28. qlever/commands/clear_cache.py +88 -0
  29. qlever/commands/extract_queries.py +120 -0
  30. qlever/commands/get_data.py +48 -0
  31. qlever/commands/index.py +333 -0
  32. qlever/commands/index_stats.py +306 -0
  33. qlever/commands/log.py +66 -0
  34. qlever/commands/materialized_view.py +110 -0
  35. qlever/commands/query.py +142 -0
  36. qlever/commands/rebuild_index.py +176 -0
  37. qlever/commands/reset_updates.py +59 -0
  38. qlever/commands/settings.py +115 -0
  39. qlever/commands/setup_config.py +97 -0
  40. qlever/commands/start.py +336 -0
  41. qlever/commands/status.py +50 -0
  42. qlever/commands/stop.py +90 -0
  43. qlever/commands/system_info.py +130 -0
  44. qlever/commands/ui.py +271 -0
  45. qlever/commands/update.py +90 -0
  46. qlever/commands/update_wikidata.py +1204 -0
  47. qlever/commands/warmup.py +41 -0
  48. qlever/config.py +223 -0
  49. qlever/containerize.py +167 -0
  50. qlever/log.py +55 -0
  51. qlever/qlever_main.py +79 -0
  52. qlever/qleverfile.py +530 -0
  53. qlever/util.py +330 -0
  54. qlever-0.5.41.dist-info/METADATA +127 -0
  55. qlever-0.5.41.dist-info/RECORD +59 -0
  56. {qlever-0.2.5.dist-info → qlever-0.5.41.dist-info}/WHEEL +1 -1
  57. qlever-0.5.41.dist-info/entry_points.txt +2 -0
  58. qlever-0.5.41.dist-info/top_level.txt +1 -0
  59. build/lib/qlever/__init__.py +0 -1383
  60. build/lib/qlever/__main__.py +0 -4
  61. qlever/__main__.py +0 -4
  62. qlever-0.2.5.dist-info/METADATA +0 -277
  63. qlever-0.2.5.dist-info/RECORD +0 -12
  64. qlever-0.2.5.dist-info/entry_points.txt +0 -2
  65. qlever-0.2.5.dist-info/top_level.txt +0 -4
  66. src/qlever/__init__.py +0 -1383
  67. src/qlever/__main__.py +0 -4
  68. {qlever-0.2.5.dist-info → qlever-0.5.41.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import platform
4
+ from importlib.metadata import version
5
+ from pathlib import Path
6
+
7
+ import psutil
8
+
9
+ from qlever.command import QleverCommand
10
+ from qlever.containerize import Containerize
11
+ from qlever.log import log
12
+ from qlever.util import format_size, run_command
13
+
14
+
15
+ def show_heading(text: str) -> str:
16
+ log.info(text)
17
+ log.info("-" * len(text))
18
+ log.info("")
19
+
20
+
21
+ def get_partition(dir: Path):
22
+ """
23
+ Returns the partition on which `dir` resides. May return None.
24
+ """
25
+ # The first partition that whose mountpoint is a parent of `dir` is
26
+ # returned. Sort the partitions by the length of the mountpoint to ensure
27
+ # that the result is correct. Assume there are partitions with mountpoint
28
+ # `/` and `/home`. This ensures that `/home/foo` is detected as being in
29
+ # the partition with mountpoint `/home`.
30
+ partitions = sorted(
31
+ psutil.disk_partitions(),
32
+ key=lambda part: len(part.mountpoint),
33
+ reverse=True,
34
+ )
35
+ for partition in partitions:
36
+ if dir.is_relative_to(partition.mountpoint):
37
+ return partition
38
+ return None
39
+
40
+
41
+ class SystemInfoCommand(QleverCommand):
42
+ def __init__(self):
43
+ pass
44
+
45
+ def description(self) -> str:
46
+ return "Gather some system info to help with troubleshooting"
47
+
48
+ def should_have_qleverfile(self) -> bool:
49
+ return True
50
+
51
+ def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
52
+ return {"runtime": ["system", "image", "server_container"]}
53
+
54
+ def additional_arguments(self, subparser) -> None:
55
+ pass
56
+
57
+ def execute(self, args) -> bool:
58
+ # Say what the command is doing.
59
+ self.show(
60
+ "Show system information and Qleverfile", only_show=args.show
61
+ )
62
+ if args.show:
63
+ return True
64
+
65
+ # Show system information.
66
+ show_heading("System Information")
67
+ system = platform.system()
68
+ is_linux = system == "Linux"
69
+ is_mac = system == "Darwin"
70
+ is_windows = system == "Windows"
71
+ if is_windows:
72
+ log.warn("Only limited information is gathered on Windows.")
73
+ log.info(f"Version: {version('qlever')} (qlever --version)")
74
+ if is_linux:
75
+ info = platform.freedesktop_os_release()
76
+ log.info(f"OS: {platform.system()} ({info['PRETTY_NAME']})")
77
+ else:
78
+ log.info(f"OS: {platform.system()}")
79
+ log.info(f"Arch: {platform.machine()}")
80
+ log.info(f"Host: {platform.node()}")
81
+ psutil.virtual_memory().total / (1000**3)
82
+ memory_total = psutil.virtual_memory().total / (1024.0**3)
83
+ memory_available = psutil.virtual_memory().available / (1024.0**3)
84
+ log.info(
85
+ f"RAM: {memory_total:.1f} GB total, "
86
+ f"{memory_available:.1f} GB available"
87
+ )
88
+ num_cores = psutil.cpu_count(logical=False)
89
+ num_threads = psutil.cpu_count(logical=True)
90
+ cpu_freq = psutil.cpu_freq().max / 1000
91
+ log.info(
92
+ f"CPU: {num_cores} Cores, "
93
+ f"{num_threads} Threads @ {cpu_freq:.2f} GHz"
94
+ )
95
+
96
+ cwd = Path.cwd()
97
+ log.info(f"CWD: {cwd}")
98
+ # Free and total size of the partition on which the current working
99
+ # directory resides.
100
+ disk_usage = psutil.disk_usage(str(cwd))
101
+ partition = get_partition(cwd)
102
+ partition_description = f"{partition.device} @ {partition.mountpoint}"
103
+ fs_type = partition.fstype
104
+ fs_free = format_size(disk_usage.free)
105
+ fs_total = format_size(disk_usage.total)
106
+ log.info(
107
+ f"Disk space in {partition_description} is "
108
+ f"({fs_type}): {fs_free} free / {fs_total} total"
109
+ )
110
+ # User/Group on host and in container
111
+ if is_linux or is_mac:
112
+ user_info = run_command("id", return_output=True).strip()
113
+ log.info(f"User and group on host: {user_info}")
114
+ elif is_windows:
115
+ user_info = run_command("whoami /all", return_output=True).strip()
116
+ log.info(f"User and group on host: {user_info}")
117
+ if args.system in Containerize.supported_systems():
118
+ user_info = Containerize.run_in_container("id", args).strip()
119
+ log.info(f"User and group in container: {user_info}")
120
+
121
+ # Show Qleverfile.
122
+ log.info("")
123
+ show_heading("Contents of Qleverfile")
124
+ qleverfile = cwd / "Qleverfile"
125
+ if qleverfile.exists():
126
+ # TODO: output the effective qlever file using primites from #57
127
+ log.info(qleverfile.read_text())
128
+ else:
129
+ log.info("No Qleverfile found")
130
+ return True
qlever/commands/ui.py ADDED
@@ -0,0 +1,271 @@
1
+ from __future__ import annotations
2
+
3
+ from os import environ
4
+ from pathlib import Path
5
+
6
+ import yaml
7
+
8
+ from qlever.command import QleverCommand
9
+ from qlever.containerize import Containerize
10
+ from qlever.log import log
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: dict) -> str:
17
+ """
18
+ Custom representer for yaml, which uses the "|" style only for
19
+ multiline strings.
20
+
21
+ NOTE: We replace all `\r\n` with `\n` because otherwise the `|` style
22
+ does not work as expected.
23
+ """
24
+
25
+ class MultiLineDumper(yaml.SafeDumper):
26
+ def represent_scalar(self, tag, value, style=None):
27
+ value = value.replace("\r\n", "\n")
28
+ if isinstance(value, str) and "\n" in value:
29
+ style = "|"
30
+ return super().represent_scalar(tag, value, style)
31
+
32
+ # Dump as yaml.
33
+ return yaml.dump(
34
+ dictionary,
35
+ sort_keys=False,
36
+ allow_unicode=True,
37
+ Dumper=MultiLineDumper,
38
+ )
39
+
40
+
41
+ class UiCommand(QleverCommand):
42
+ """
43
+ Class for launching the QLever UI web application.
44
+ """
45
+
46
+ def __init__(self):
47
+ pass
48
+
49
+ def description(self) -> str:
50
+ return "Launch the QLever UI web application"
51
+
52
+ def should_have_qleverfile(self) -> bool:
53
+ return True
54
+
55
+ def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
56
+ return {
57
+ "data": ["name"],
58
+ "server": ["host_name", "port"],
59
+ "ui": [
60
+ "ui_port",
61
+ "ui_config",
62
+ "ui_system",
63
+ "ui_image",
64
+ "ui_container",
65
+ ],
66
+ }
67
+
68
+ def additional_arguments(self, subparser) -> None:
69
+ subparser.add_argument(
70
+ "--ui-config-file",
71
+ default="Qleverfile-ui.yml",
72
+ help="Name of the config file for the QLever UI "
73
+ "(default: Qleverfile-ui.yml)",
74
+ )
75
+ subparser.add_argument(
76
+ "--ui-db-file",
77
+ help="Name of the database file for the QLever UI "
78
+ "(default: {name}.ui-db.sqlite3)",
79
+ )
80
+ subparser.add_argument(
81
+ "--no-pull-latest",
82
+ action="store_true",
83
+ default=False,
84
+ help="Do not pull the latest image for the QLever UI "
85
+ "(default: pull the latest image if image name contains '/')",
86
+ )
87
+ subparser.add_argument(
88
+ "--stop",
89
+ action="store_true",
90
+ default=False,
91
+ help="Stop the running container",
92
+ )
93
+
94
+ def execute(self, args) -> bool:
95
+ # If QLEVER_OVERRIDE_DISABLE_UI is set, this command is disabled.
96
+ qlever_is_running_in_container = environ.get(
97
+ "QLEVER_IS_RUNNING_IN_CONTAINER"
98
+ )
99
+ if qlever_is_running_in_container:
100
+ log.error(
101
+ "The environment variable `QLEVER_OVERRIDE_DISABLE_UI` is set, "
102
+ "therefore `qlever ui` is not available (it should not be called "
103
+ "from inside a container)"
104
+ )
105
+ log.info("")
106
+ if not args.show:
107
+ log.info(
108
+ "For your information, showing the commands that are "
109
+ "executed when `qlever ui` is available:"
110
+ )
111
+ log.info("")
112
+
113
+ # Construct commands and show them.
114
+ pull_latest_image = "/" in args.ui_image and not args.no_pull_latest
115
+ ui_config_name = args.ui_config
116
+ ui_db_file = args.ui_db_file or f"{args.name}.ui-db.sqlite3"
117
+ ui_db_file_from_image = "qleverui.sqlite3"
118
+ ui_config_file = args.ui_config_file
119
+ sparql_endpoint = f"http://{args.host_name}:{args.port}"
120
+ ui_url = f"http://{args.host_name}:{args.ui_port}"
121
+ pull_cmd = f"{args.ui_system} pull -q {args.ui_image}"
122
+ get_db_cmd = (
123
+ f"{args.ui_system} create "
124
+ f"--name {args.ui_container} "
125
+ f"{args.ui_image} "
126
+ f"&& {args.ui_system} cp "
127
+ f"{args.ui_container}:/app/db/{ui_db_file_from_image} {ui_db_file} "
128
+ f"&& {args.ui_system} rm -f {args.ui_container}"
129
+ )
130
+ start_ui_cmd = (
131
+ f"{args.ui_system} run -d "
132
+ f"--volume $(pwd):/app/db "
133
+ f"--env QLEVERUI_DATABASE_URL=sqlite:////app/db/{ui_db_file} "
134
+ f"--publish {args.ui_port}:7000 "
135
+ f"--name {args.ui_container} "
136
+ f"{args.ui_image}"
137
+ )
138
+ get_config_cmd = (
139
+ f"{args.ui_system} exec -i "
140
+ f"{args.ui_container} "
141
+ f'bash -c "python manage.py config {ui_config_name}"'
142
+ )
143
+ set_config_cmd = (
144
+ f"{args.ui_system} exec -i "
145
+ f"{args.ui_container} "
146
+ f'bash -c "python manage.py config {ui_config_name} '
147
+ f'/app/db/{ui_config_file} --hide-all-other-backends"'
148
+ )
149
+ commands_to_show = []
150
+ if not args.stop:
151
+ if pull_latest_image:
152
+ commands_to_show.append(pull_cmd)
153
+ if not Path(ui_db_file).exists():
154
+ commands_to_show.append(get_db_cmd)
155
+ commands_to_show.append(start_ui_cmd)
156
+ if not Path(ui_config_file).exists():
157
+ commands_to_show.append(get_config_cmd)
158
+ else:
159
+ commands_to_show.append(set_config_cmd)
160
+ self.show("\n".join(commands_to_show), only_show=args.show)
161
+ if qlever_is_running_in_container:
162
+ return False
163
+ if args.show:
164
+ return True
165
+
166
+ # Stop running containers.
167
+ was_found_and_stopped = False
168
+ for container_system in Containerize.supported_systems():
169
+ was_found_and_stopped |= Containerize.stop_and_remove_container(
170
+ container_system, args.ui_container
171
+ )
172
+ if was_found_and_stopped:
173
+ log.debug(f"Stopped and removed container `{args.ui_container}`")
174
+ else:
175
+ log.debug(f"No container with name `{args.ui_container}` found")
176
+ if args.stop:
177
+ return True
178
+
179
+ # Pull the latest image.
180
+ if pull_latest_image:
181
+ log.debug(f"Pulling image `{args.ui_image}` for QLever UI")
182
+ run_command(pull_cmd)
183
+
184
+ # Check if the UI port is already being used.
185
+ if is_port_used(args.ui_port):
186
+ log.warning(
187
+ f"It looks like port {args.ui_port} for the QLever UI "
188
+ f"is already in use. You can set another port in the "
189
+ f" Qleverfile in the [ui] section with the UI_PORT variable."
190
+ )
191
+
192
+ # Get the QLever UI database from the image, unless it already exists.
193
+ if Path(ui_db_file).exists():
194
+ log.debug(f"Found QLever UI database `{ui_db_file}`, reusing it")
195
+ else:
196
+ log.debug(f"Getting QLever UI database `{ui_db_file}` from image")
197
+ try:
198
+ run_command(get_db_cmd)
199
+ except Exception as e:
200
+ log.error(
201
+ f"Failed to get {ui_db_file} from {args.ui_image} "
202
+ f"({e})"
203
+ )
204
+ return False
205
+
206
+ # Start the QLever UI.
207
+ try:
208
+ log.debug(
209
+ f"Starting new container with name `{args.ui_container}`"
210
+ )
211
+ run_command(start_ui_cmd)
212
+ except Exception as e:
213
+ log.error(f"Failed to start container `{args.ui_container}` ({e})")
214
+ return False
215
+
216
+ # Check if config file with name `ui_config_file` exists. If not, try
217
+ # to obtain it via `get_config_cmd` and set it as default.
218
+ if Path(ui_config_file).exists():
219
+ log.info(f"Found config file `{ui_config_file}` and reusing it")
220
+ else:
221
+ try:
222
+ log.info(
223
+ f"Get default config file `{ui_config_file}` from image "
224
+ f"`{args.ui_image}` and set endpoint to `{sparql_endpoint}`"
225
+ )
226
+ config_yaml = run_command(get_config_cmd, return_output=True)
227
+ config_dict = yaml.safe_load(config_yaml)
228
+ except Exception as e:
229
+ log.error("")
230
+ log.error(
231
+ f"An error occured while getting and parsing the "
232
+ f"config file ({e})"
233
+ )
234
+ return False
235
+ try:
236
+ config_dict["config"]["backend"]["isDefault"] = True
237
+ config_dict["config"]["backend"]["baseUrl"] = sparql_endpoint
238
+ config_dict["config"]["backend"]["sortKey"] = 1
239
+ config_yaml = dict_to_yaml(config_dict)
240
+ with open(ui_config_file, "w") as f:
241
+ f.write(config_yaml)
242
+ except Exception as e:
243
+ log.error("")
244
+ log.error(
245
+ f"An error occured while modifying and writing the "
246
+ f"config file ({e})"
247
+ )
248
+ return False
249
+
250
+ # Configure the QLever UI.
251
+ try:
252
+ run_command(set_config_cmd)
253
+ except Exception as e:
254
+ log.error(f"Failed to configure the QLever UI ({e})")
255
+ return False
256
+
257
+ # If we come this far, everything should work.
258
+ log.info("")
259
+ log.info(
260
+ f"The QLever UI should now be up at {ui_url}/{ui_config_name}"
261
+ )
262
+ log.info("")
263
+ log.debug(
264
+ "If you must, you can log in as QLever UI admin with "
265
+ 'username and password "demo"'
266
+ )
267
+ log.info(
268
+ f"You can modify the config file at `{ui_config_file}` "
269
+ f"and then just run `qlever ui` again"
270
+ )
271
+ return True
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+ import time
5
+ import traceback
6
+
7
+ from qlever.command import QleverCommand
8
+ from qlever.log import log
9
+ from qlever.util import run_command
10
+
11
+
12
+ class UpdateCommand(QleverCommand):
13
+ """
14
+ Class for executing a SPARQL UPDATE against a SPARQL endpoint.
15
+
16
+ The command accepts the update either directly on the command line or
17
+ via a file path provided with --update-file.
18
+ """
19
+
20
+ def __init__(self):
21
+ pass
22
+
23
+ def description(self) -> str:
24
+ return "Send an update to a SPARQL endpoint"
25
+
26
+ def should_have_qleverfile(self) -> bool:
27
+ return False
28
+
29
+ def relevant_qleverfile_arguments(self) -> dict[str,list[str]]:
30
+ return {"server": ["host_name", "port", "access_token"]}
31
+
32
+ def additional_arguments(self, subparser) -> None:
33
+ subparser.add_argument(
34
+ "update",
35
+ type=str,
36
+ nargs="?",
37
+ default=None,
38
+ help="SPARQL UPDATE to send (use --update-file to send from a file)",
39
+ )
40
+ subparser.add_argument(
41
+ "--update-file",
42
+ type=str,
43
+ help="Path to a file containing the SPARQL UPDATE to send",
44
+ )
45
+ subparser.add_argument(
46
+ "--sparql-endpoint", type=str, help="URL of the SPARQL endpoint"
47
+ )
48
+
49
+ def execute(self, args) -> bool:
50
+ sparql_endpoint = (
51
+ args.sparql_endpoint if args.sparql_endpoint else f"{args.host_name}:{args.port}"
52
+ )
53
+
54
+ curl_cmd = (
55
+ f"curl -s {sparql_endpoint} -X POST "
56
+ f"-H 'Authorization: Bearer {args.access_token}' "
57
+ f"-H 'Content-Type: application/sparql-update' "
58
+ )
59
+
60
+ if args.update:
61
+ curl_cmd += f"--data-binary {shlex.quote(args.update)}"
62
+ elif args.update_file:
63
+ curl_cmd += f"--data-binary @{shlex.quote(args.update_file)}"
64
+ else:
65
+ log.error("No SPARQL UPDATE provided. Pass it as an argument or via --update-file.")
66
+ return False
67
+
68
+ # Show and exit if requested
69
+ self.show(curl_cmd, only_show=args.show)
70
+ if args.show:
71
+ return True
72
+
73
+ # Execute update
74
+ try:
75
+ start_time = time.time()
76
+ run_command(curl_cmd)
77
+ time_msecs = round(1000 * (time.time() - start_time))
78
+ if args.log_level != "NO_LOG":
79
+ log.info("")
80
+ log.info(
81
+ f"Update processing time (end-to-end): {time_msecs:,d} ms"
82
+ )
83
+ except Exception as e:
84
+ if args.log_level == "DEBUG":
85
+ traceback.print_exc()
86
+ log.error(e)
87
+ return False
88
+
89
+ return True
90
+