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.
- qlever/Qleverfiles/Qleverfile.dblp +36 -0
- qlever/Qleverfiles/Qleverfile.dblp-plus +33 -0
- qlever/Qleverfiles/Qleverfile.dbpedia +30 -0
- qlever/Qleverfiles/Qleverfile.default +51 -0
- qlever/Qleverfiles/Qleverfile.dnb +40 -0
- qlever/Qleverfiles/Qleverfile.fbeasy +29 -0
- qlever/Qleverfiles/Qleverfile.freebase +28 -0
- qlever/Qleverfiles/Qleverfile.imdb +36 -0
- qlever/Qleverfiles/Qleverfile.ohm-planet +41 -0
- qlever/Qleverfiles/Qleverfile.olympics +31 -0
- qlever/Qleverfiles/Qleverfile.orkg +30 -0
- qlever/Qleverfiles/Qleverfile.osm-country +39 -0
- qlever/Qleverfiles/Qleverfile.osm-planet +39 -0
- qlever/Qleverfiles/Qleverfile.osm-planet-from-pbf +42 -0
- qlever/Qleverfiles/Qleverfile.pubchem +131 -0
- qlever/Qleverfiles/Qleverfile.scientists +29 -0
- qlever/Qleverfiles/Qleverfile.uniprot +74 -0
- qlever/Qleverfiles/Qleverfile.vvz +31 -0
- qlever/Qleverfiles/Qleverfile.wikidata +42 -0
- qlever/Qleverfiles/Qleverfile.wikipathways +40 -0
- qlever/Qleverfiles/Qleverfile.yago-4 +33 -0
- qlever/__init__.py +44 -1380
- qlever/command.py +87 -0
- qlever/commands/__init__.py +0 -0
- qlever/commands/add_text_index.py +115 -0
- qlever/commands/benchmark_queries.py +1019 -0
- qlever/commands/cache_stats.py +125 -0
- qlever/commands/clear_cache.py +88 -0
- qlever/commands/extract_queries.py +120 -0
- qlever/commands/get_data.py +48 -0
- qlever/commands/index.py +333 -0
- qlever/commands/index_stats.py +306 -0
- qlever/commands/log.py +66 -0
- qlever/commands/materialized_view.py +110 -0
- qlever/commands/query.py +142 -0
- qlever/commands/rebuild_index.py +176 -0
- qlever/commands/reset_updates.py +59 -0
- qlever/commands/settings.py +115 -0
- qlever/commands/setup_config.py +97 -0
- qlever/commands/start.py +336 -0
- qlever/commands/status.py +50 -0
- qlever/commands/stop.py +90 -0
- qlever/commands/system_info.py +130 -0
- qlever/commands/ui.py +271 -0
- qlever/commands/update.py +90 -0
- qlever/commands/update_wikidata.py +1204 -0
- qlever/commands/warmup.py +41 -0
- qlever/config.py +223 -0
- qlever/containerize.py +167 -0
- qlever/log.py +55 -0
- qlever/qlever_main.py +79 -0
- qlever/qleverfile.py +530 -0
- qlever/util.py +330 -0
- qlever-0.5.41.dist-info/METADATA +127 -0
- qlever-0.5.41.dist-info/RECORD +59 -0
- {qlever-0.2.5.dist-info → qlever-0.5.41.dist-info}/WHEEL +1 -1
- qlever-0.5.41.dist-info/entry_points.txt +2 -0
- qlever-0.5.41.dist-info/top_level.txt +1 -0
- build/lib/qlever/__init__.py +0 -1383
- build/lib/qlever/__main__.py +0 -4
- qlever/__main__.py +0 -4
- qlever-0.2.5.dist-info/METADATA +0 -277
- qlever-0.2.5.dist-info/RECORD +0 -12
- qlever-0.2.5.dist-info/entry_points.txt +0 -2
- qlever-0.2.5.dist-info/top_level.txt +0 -4
- src/qlever/__init__.py +0 -1383
- src/qlever/__main__.py +0 -4
- {qlever-0.2.5.dist-info → qlever-0.5.41.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from qlever.command import QleverCommand
|
|
6
|
+
from qlever.log import log
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WarmupCommand(QleverCommand):
|
|
10
|
+
"""
|
|
11
|
+
Class for executing the `warmup` command.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def description(self) -> str:
|
|
18
|
+
return ("Execute WARMUP_CMD")
|
|
19
|
+
|
|
20
|
+
def should_have_qleverfile(self) -> bool:
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
|
|
24
|
+
return {"server": ["port", "warmup_cmd"]}
|
|
25
|
+
|
|
26
|
+
def additional_arguments(self, subparser) -> None:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def execute(self, args) -> bool:
|
|
30
|
+
# Show what the command is doing.
|
|
31
|
+
self.show(args.warmup_cmd, only_show=args.show)
|
|
32
|
+
if args.show:
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
# Execute the command.
|
|
36
|
+
try:
|
|
37
|
+
subprocess.run(args.warmup_cmd, shell=True, check=True)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
log.error(f"{e.output if hasattr(e, 'output') else e}")
|
|
40
|
+
return False
|
|
41
|
+
return True
|
qlever/config.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import traceback
|
|
6
|
+
from importlib.metadata import version
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import argcomplete
|
|
10
|
+
from termcolor import colored
|
|
11
|
+
|
|
12
|
+
from qlever import command_objects, engine_name, script_name
|
|
13
|
+
from qlever.log import log, log_levels
|
|
14
|
+
from qlever.qleverfile import Qleverfile
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Simple exception class for configuration errors (the class need not do
|
|
18
|
+
# anything, we just want a distinct exception type).
|
|
19
|
+
class ConfigException(Exception):
|
|
20
|
+
def __init__(self, message):
|
|
21
|
+
stack = traceback.extract_stack()[-2] # Caller's frame.
|
|
22
|
+
self.filename = stack.filename
|
|
23
|
+
self.lineno = stack.lineno
|
|
24
|
+
full_message = f"{message} [in {self.filename}:{self.lineno}]"
|
|
25
|
+
super().__init__(full_message)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class QleverConfig:
|
|
29
|
+
"""
|
|
30
|
+
Class that manages all config parameters, and overwrites them with the
|
|
31
|
+
settings from a Qleverfile.
|
|
32
|
+
|
|
33
|
+
IMPORTANT: An instance of this class is created for each execution of
|
|
34
|
+
the `qlever` script, in particular, each time the user triggers
|
|
35
|
+
autocompletion. Therefore, pay attention that no unnecessary work is done
|
|
36
|
+
before the call to `argcomplete.autocomplete(...)`. In particular, avoid
|
|
37
|
+
parsing the Qleverfile before that point, it's not needed for
|
|
38
|
+
autocompletion.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def add_subparser_for_command(self, subparsers, command_name,
|
|
42
|
+
command_object, all_qleverfile_args,
|
|
43
|
+
qleverfile_config=None):
|
|
44
|
+
"""
|
|
45
|
+
Add subparser for the given command. Take the arguments from
|
|
46
|
+
`command_object.relevant_qleverfile_arguments()` and report an error if
|
|
47
|
+
one of them is not contained in `all_qleverfile_args`. Overwrite the
|
|
48
|
+
default values with the values from `qleverfile_config` if specified.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
arg_names = command_object.relevant_qleverfile_arguments()
|
|
52
|
+
|
|
53
|
+
# Helper function that shows a detailed error messahe when an argument
|
|
54
|
+
# from `relevant_qleverfile_arguments` is not contained in
|
|
55
|
+
# `all_qleverfile_args`.
|
|
56
|
+
def argument_error(prefix):
|
|
57
|
+
log.info("")
|
|
58
|
+
log.error(f"{prefix} in `Qleverfile.all_arguments()` for command "
|
|
59
|
+
f"`{command_name}`")
|
|
60
|
+
log.info("")
|
|
61
|
+
log.info(f"Value of `relevant_qleverfile_arguments` for "
|
|
62
|
+
f"command `{command_name}`:")
|
|
63
|
+
log.info("")
|
|
64
|
+
log.info(f"{arg_names}")
|
|
65
|
+
log.info("")
|
|
66
|
+
exit(1)
|
|
67
|
+
|
|
68
|
+
# Add the subparser.
|
|
69
|
+
description = command_object.description()
|
|
70
|
+
subparser = subparsers.add_parser(command_name,
|
|
71
|
+
description=description,
|
|
72
|
+
help=description)
|
|
73
|
+
|
|
74
|
+
# Add the arguments relevant for the command.
|
|
75
|
+
for section in arg_names:
|
|
76
|
+
if section not in all_qleverfile_args:
|
|
77
|
+
argument_error(f"Section `{section}` not found")
|
|
78
|
+
for arg_name in arg_names[section]:
|
|
79
|
+
if arg_name not in all_qleverfile_args[section]:
|
|
80
|
+
argument_error(f"Argument `{arg_name}` of section "
|
|
81
|
+
f"`{section}` not found")
|
|
82
|
+
args, kwargs = all_qleverfile_args[section][arg_name]
|
|
83
|
+
kwargs_copy = kwargs.copy()
|
|
84
|
+
# If `qleverfile_config` is given, add info about default
|
|
85
|
+
# values to the help string.
|
|
86
|
+
if qleverfile_config is not None:
|
|
87
|
+
default_value = kwargs.get("default", None)
|
|
88
|
+
qleverfile_value = qleverfile_config.get(
|
|
89
|
+
section, arg_name, fallback=None)
|
|
90
|
+
if qleverfile_value is not None:
|
|
91
|
+
kwargs_copy["default"] = qleverfile_value
|
|
92
|
+
kwargs_copy["required"] = False
|
|
93
|
+
kwargs_copy["help"] += (f" [default, from Qleverfile:"
|
|
94
|
+
f" {qleverfile_value}]")
|
|
95
|
+
else:
|
|
96
|
+
kwargs_copy["help"] += f" [default: {default_value}]"
|
|
97
|
+
subparser.add_argument(*args, **kwargs_copy)
|
|
98
|
+
|
|
99
|
+
# Additional arguments that are shared by all commands.
|
|
100
|
+
command_object.additional_arguments(subparser)
|
|
101
|
+
subparser.add_argument("--show", action="store_true",
|
|
102
|
+
default=False,
|
|
103
|
+
help="Only show what would be executed"
|
|
104
|
+
", but don't execute it")
|
|
105
|
+
subparser.add_argument("--log-level",
|
|
106
|
+
choices=log_levels.keys(),
|
|
107
|
+
default="INFO",
|
|
108
|
+
help="Set the log level")
|
|
109
|
+
|
|
110
|
+
def parse_args(self):
|
|
111
|
+
# Determine whether we are in autocomplete mode or not.
|
|
112
|
+
autocomplete_mode = "COMP_LINE" in os.environ
|
|
113
|
+
|
|
114
|
+
# Check if the user has registered this script for argcomplete.
|
|
115
|
+
argcomplete_check_off = os.environ.get("QLEVER_ARGCOMPLETE_CHECK_OFF")
|
|
116
|
+
argcomplete_enabled = os.environ.get("QLEVER_ARGCOMPLETE_ENABLED")
|
|
117
|
+
if not argcomplete_enabled and not argcomplete_check_off:
|
|
118
|
+
log.info("")
|
|
119
|
+
log.warn(f"To enable autocompletion, run the following command, "
|
|
120
|
+
f"and consider adding it to your `.bashrc` or `.zshrc`:"
|
|
121
|
+
f"\n\n"
|
|
122
|
+
f"eval \"$(register-python-argcomplete {script_name})\""
|
|
123
|
+
f" && export QLEVER_ARGCOMPLETE_ENABLED=1")
|
|
124
|
+
log.info("")
|
|
125
|
+
|
|
126
|
+
# Create a temporary parser only to parse the `--qleverfile` option, in
|
|
127
|
+
# case it is given, and to determine whether a command was given that
|
|
128
|
+
# requires a Qleverfile. This is because in the actual parser below we
|
|
129
|
+
# want the values from the Qleverfile to be shown in the help strings,
|
|
130
|
+
# but only if this is actually necessary.
|
|
131
|
+
def add_qleverfile_option(parser):
|
|
132
|
+
parser.add_argument("--qleverfile", "-q", type=str,
|
|
133
|
+
default="Qleverfile")
|
|
134
|
+
qleverfile_parser = argparse.ArgumentParser(add_help=False)
|
|
135
|
+
add_qleverfile_option(qleverfile_parser)
|
|
136
|
+
qleverfile_parser.add_argument("command", type=str, nargs="?")
|
|
137
|
+
qleverfile_args, _ = qleverfile_parser.parse_known_args()
|
|
138
|
+
qleverfile_path_name = qleverfile_args.qleverfile
|
|
139
|
+
# command = qleverfile_args.command
|
|
140
|
+
# should_have_qleverfile = command in command_objects \
|
|
141
|
+
# and command_objects[command].should_have_qleverfile()
|
|
142
|
+
|
|
143
|
+
# Check if the Qleverfile exists and if we are using the default name.
|
|
144
|
+
# We need this again further down in the code, so remember it.
|
|
145
|
+
qleverfile_path = Path(qleverfile_path_name)
|
|
146
|
+
qleverfile_exists = qleverfile_path.is_file()
|
|
147
|
+
qleverfile_is_default = qleverfile_path_name \
|
|
148
|
+
== qleverfile_parser.get_default("qleverfile")
|
|
149
|
+
# If a Qleverfile with a non-default name was specified, but it does
|
|
150
|
+
# not exist, that's an error.
|
|
151
|
+
if not qleverfile_exists and not qleverfile_is_default:
|
|
152
|
+
raise ConfigException(f"Qleverfile with non-default name "
|
|
153
|
+
f"`{qleverfile_path_name}` specified, "
|
|
154
|
+
f"but it does not exist")
|
|
155
|
+
# If it exists and we are not in the autocompletion mode, parse it.
|
|
156
|
+
#
|
|
157
|
+
# IMPORTANT: No need to parse the Qleverfile in autocompletion mode and
|
|
158
|
+
# it would be unnecessarily expensive to do so.
|
|
159
|
+
#
|
|
160
|
+
# TODO: What if `command.should_have_qleverfile()` is `False`, should
|
|
161
|
+
# we then parse the Qleverfile or not.
|
|
162
|
+
if qleverfile_exists and not autocomplete_mode:
|
|
163
|
+
try:
|
|
164
|
+
qleverfile_config = Qleverfile.read(qleverfile_path)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
log.info("")
|
|
167
|
+
log.error(f"Error parsing Qleverfile `{qleverfile_path}`"
|
|
168
|
+
f": {e}")
|
|
169
|
+
log.info("")
|
|
170
|
+
exit(1)
|
|
171
|
+
else:
|
|
172
|
+
qleverfile_config = None
|
|
173
|
+
|
|
174
|
+
# Now the regular parser with commands and a subparser for each
|
|
175
|
+
# command. We have a dedicated class for each command. These classes
|
|
176
|
+
# are defined in the modules in `qlever/commands`. In `__init__.py`
|
|
177
|
+
# an object of each class is created and stored in `command_objects`.
|
|
178
|
+
parser = argparse.ArgumentParser(
|
|
179
|
+
description=colored(
|
|
180
|
+
f"This is the {script_name} command line tool, "
|
|
181
|
+
f"it's all you need to work with {engine_name}",
|
|
182
|
+
attrs=["bold"],
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
if script_name == "qlever":
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--version",
|
|
188
|
+
action="version",
|
|
189
|
+
version=f"%(prog)s {version('qlever')}",
|
|
190
|
+
)
|
|
191
|
+
add_qleverfile_option(parser)
|
|
192
|
+
subparsers = parser.add_subparsers(dest='command')
|
|
193
|
+
subparsers.required = True
|
|
194
|
+
all_args = Qleverfile.all_arguments()
|
|
195
|
+
for command_name, command_object in command_objects.items():
|
|
196
|
+
self.add_subparser_for_command(
|
|
197
|
+
subparsers, command_name, command_object,
|
|
198
|
+
all_args, qleverfile_config)
|
|
199
|
+
|
|
200
|
+
# Enable autocompletion for the commands and their options.
|
|
201
|
+
#
|
|
202
|
+
# NOTE: All code executed before this line should be relatively cheap
|
|
203
|
+
# because it is executed whenever the user triggers the autocompletion.
|
|
204
|
+
argcomplete.autocomplete(parser, always_complete_options="long")
|
|
205
|
+
|
|
206
|
+
# If called without arguments, show the help message.
|
|
207
|
+
if len(os.sys.argv) == 1:
|
|
208
|
+
parser.print_help()
|
|
209
|
+
exit(0)
|
|
210
|
+
|
|
211
|
+
# Parse the command line arguments.
|
|
212
|
+
args = parser.parse_args()
|
|
213
|
+
|
|
214
|
+
# If the command says that we should have a Qleverfile, but we don't,
|
|
215
|
+
# issue a warning.
|
|
216
|
+
if command_objects[args.command].should_have_qleverfile():
|
|
217
|
+
if not qleverfile_exists:
|
|
218
|
+
log.warning(f"Invoking command `{args.command}` without a "
|
|
219
|
+
"Qleverfile. You have to specify all required "
|
|
220
|
+
"arguments on the command line. This is possible, "
|
|
221
|
+
"but not recommended.")
|
|
222
|
+
|
|
223
|
+
return args
|
qlever/containerize.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Copyright 2024, University of Freiburg,
|
|
2
|
+
# Chair of Algorithms and Data Structures
|
|
3
|
+
# Author: Hannah Bast <bast@cs.uni-freiburg.de>
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import shlex
|
|
8
|
+
import subprocess
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from qlever.log import log
|
|
12
|
+
from qlever.util import get_random_string, run_command
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContainerizeException(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Containerize:
|
|
20
|
+
"""
|
|
21
|
+
This class contains functions specific for running commands with various
|
|
22
|
+
container engines.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def supported_systems() -> list[str]:
|
|
27
|
+
"""
|
|
28
|
+
Return a list of the supported container systems. Make sure that they
|
|
29
|
+
are all indeed supported by `containerize_command` below.
|
|
30
|
+
"""
|
|
31
|
+
return ["docker", "podman"]
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def containerize_command(
|
|
35
|
+
cmd: str,
|
|
36
|
+
container_system: str,
|
|
37
|
+
run_subcommand: str,
|
|
38
|
+
image_name: str,
|
|
39
|
+
container_name: str,
|
|
40
|
+
volumes: list[tuple[str, str]] = [],
|
|
41
|
+
ports: list[tuple[int, int]] = [],
|
|
42
|
+
working_directory: Optional[str] = None,
|
|
43
|
+
use_bash: bool = True,
|
|
44
|
+
) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Get the command to run `cmd` with the given `container_system` and the
|
|
47
|
+
given options.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Check that `container_system` is supported.
|
|
51
|
+
if container_system not in Containerize.supported_systems():
|
|
52
|
+
return ContainerizeException(
|
|
53
|
+
f'Invalid container system "{container_system}"'
|
|
54
|
+
f" (must be one of {Containerize.supported_systems()})"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Set user and group ids. This is important so that the files created
|
|
58
|
+
# by the containerized command are owned by the user running the
|
|
59
|
+
# command.
|
|
60
|
+
if container_system == "docker":
|
|
61
|
+
user_option = " -u $(id -u):$(id -g)"
|
|
62
|
+
elif container_system == "podman":
|
|
63
|
+
user_option = " -u root"
|
|
64
|
+
else:
|
|
65
|
+
user_option = ""
|
|
66
|
+
|
|
67
|
+
# Options for mounting volumes, setting ports, and setting the working
|
|
68
|
+
# dir.
|
|
69
|
+
volume_options = "".join(
|
|
70
|
+
[
|
|
71
|
+
f' --mount type=bind,src="{v1}",target={v2}'
|
|
72
|
+
for v1, v2 in volumes
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
port_options = "".join([f" -p {p1}:{p2}" for p1, p2 in ports])
|
|
76
|
+
working_directory_option = (
|
|
77
|
+
f" -w {working_directory}" if working_directory is not None else ""
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Construct the command that runs `cmd` with the given container
|
|
81
|
+
# system.
|
|
82
|
+
containerized_cmd = (
|
|
83
|
+
f"{container_system} {run_subcommand}"
|
|
84
|
+
f"{user_option}"
|
|
85
|
+
f" -v /etc/localtime:/etc/localtime:ro"
|
|
86
|
+
f"{volume_options}"
|
|
87
|
+
f"{port_options}"
|
|
88
|
+
f"{working_directory_option}"
|
|
89
|
+
f" --name {container_name}"
|
|
90
|
+
f" --init"
|
|
91
|
+
)
|
|
92
|
+
if use_bash:
|
|
93
|
+
containerized_cmd += (
|
|
94
|
+
f" --entrypoint bash {image_name} -c {shlex.quote(cmd)}"
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
containerized_cmd += f" {image_name} {cmd}"
|
|
98
|
+
return containerized_cmd
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def is_running(container_system: str, container_name: str) -> bool:
|
|
102
|
+
# Note: the `{{{{` and `}}}}` result in `{{` and `}}`, respectively.
|
|
103
|
+
containers = (
|
|
104
|
+
run_command(
|
|
105
|
+
f'{container_system} ps --format="{{{{.Names}}}}"',
|
|
106
|
+
return_output=True,
|
|
107
|
+
)
|
|
108
|
+
.strip()
|
|
109
|
+
.splitlines()
|
|
110
|
+
)
|
|
111
|
+
return container_name in containers
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def stop_and_remove_container(
|
|
115
|
+
container_system: str, container_name: str
|
|
116
|
+
) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Stop the container with the given name using the given system. Return
|
|
119
|
+
`True` if a container with that name was found and stopped, `False`
|
|
120
|
+
otherwise.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# Check that `container_system` is supported.
|
|
124
|
+
if container_system not in Containerize.supported_systems():
|
|
125
|
+
return ContainerizeException(
|
|
126
|
+
f'Invalid container system "{container_system}"'
|
|
127
|
+
f" (must be one of {Containerize.supported_systems()})"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Construct the command that stops the container.
|
|
131
|
+
stop_cmd = (
|
|
132
|
+
f"{container_system} stop {container_name} && "
|
|
133
|
+
f"{container_system} rm {container_name}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Run the command.
|
|
137
|
+
try:
|
|
138
|
+
subprocess.run(
|
|
139
|
+
stop_cmd,
|
|
140
|
+
shell=True,
|
|
141
|
+
check=True,
|
|
142
|
+
stdout=subprocess.DEVNULL,
|
|
143
|
+
stderr=subprocess.DEVNULL,
|
|
144
|
+
)
|
|
145
|
+
return True
|
|
146
|
+
except Exception as e:
|
|
147
|
+
log.debug(f'Error running "{stop_cmd}": {e}')
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def run_in_container(cmd: str, args) -> Optional[str]:
|
|
152
|
+
"""
|
|
153
|
+
Run an arbitrary command in the qlever container and return its output.
|
|
154
|
+
"""
|
|
155
|
+
if args.system in Containerize.supported_systems():
|
|
156
|
+
if not args.server_container:
|
|
157
|
+
args.server_container = get_random_string(20)
|
|
158
|
+
run_cmd = Containerize().containerize_command(
|
|
159
|
+
cmd,
|
|
160
|
+
args.system,
|
|
161
|
+
'run --rm -it --entrypoint "" ',
|
|
162
|
+
args.image,
|
|
163
|
+
args.server_container,
|
|
164
|
+
volumes=[("$(pwd)", "/index")],
|
|
165
|
+
working_directory="/index",
|
|
166
|
+
)
|
|
167
|
+
return run_command(run_cmd, return_output=True)
|
qlever/log.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from termcolor import colored
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class QleverLogFormatter(logging.Formatter):
|
|
10
|
+
"""
|
|
11
|
+
Custom formatter for logging.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def format(self, record):
|
|
15
|
+
message = record.getMessage()
|
|
16
|
+
if record.levelno == logging.DEBUG:
|
|
17
|
+
return colored(f"{message}", "magenta")
|
|
18
|
+
elif record.levelno == logging.WARNING:
|
|
19
|
+
return colored(f"{message}", "yellow")
|
|
20
|
+
elif record.levelno in [logging.CRITICAL, logging.ERROR]:
|
|
21
|
+
return colored(f"{message}", "red")
|
|
22
|
+
else:
|
|
23
|
+
return message
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Custom logger.
|
|
27
|
+
log = logging.getLogger("qlever")
|
|
28
|
+
log.setLevel(logging.INFO)
|
|
29
|
+
handler = logging.StreamHandler()
|
|
30
|
+
handler.setFormatter(QleverLogFormatter())
|
|
31
|
+
log.addHandler(handler)
|
|
32
|
+
log_levels = {
|
|
33
|
+
"DEBUG": logging.DEBUG,
|
|
34
|
+
"INFO": logging.INFO,
|
|
35
|
+
"WARNING": logging.WARNING,
|
|
36
|
+
"ERROR": logging.ERROR,
|
|
37
|
+
"CRITICAL": logging.CRITICAL,
|
|
38
|
+
"NO_LOG": logging.CRITICAL + 1,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@contextmanager
|
|
43
|
+
def mute_log(level=logging.ERROR):
|
|
44
|
+
"""
|
|
45
|
+
Temporarily mute the log, simply works as follows:
|
|
46
|
+
|
|
47
|
+
with mute_log():
|
|
48
|
+
...
|
|
49
|
+
"""
|
|
50
|
+
original_level = log.getEffectiveLevel()
|
|
51
|
+
log.setLevel(level)
|
|
52
|
+
try:
|
|
53
|
+
yield
|
|
54
|
+
finally:
|
|
55
|
+
log.setLevel(original_level)
|
qlever/qlever_main.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
3
|
+
|
|
4
|
+
# Copyright 2024, University of Freiburg,
|
|
5
|
+
# Chair of Algorithms and Data Structures
|
|
6
|
+
# Author: Hannah Bast <bast@cs.uni-freiburg.de>
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import traceback
|
|
12
|
+
|
|
13
|
+
from termcolor import colored
|
|
14
|
+
|
|
15
|
+
from qlever import command_objects, script_name
|
|
16
|
+
from qlever.config import ConfigException, QleverConfig
|
|
17
|
+
from qlever.log import log, log_levels
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
# Parse the command line arguments and read the Qleverfile.
|
|
22
|
+
try:
|
|
23
|
+
qlever_config = QleverConfig()
|
|
24
|
+
args = qlever_config.parse_args()
|
|
25
|
+
except ConfigException as e:
|
|
26
|
+
log.error(e)
|
|
27
|
+
log.info("")
|
|
28
|
+
log.info(traceback.format_exc())
|
|
29
|
+
exit(1)
|
|
30
|
+
|
|
31
|
+
# Execute the command.
|
|
32
|
+
command_object = command_objects[args.command]
|
|
33
|
+
log.setLevel(log_levels[args.log_level])
|
|
34
|
+
try:
|
|
35
|
+
log.info("")
|
|
36
|
+
log.info(colored(f"Command: {args.command}", attrs=["bold"]))
|
|
37
|
+
log.info("")
|
|
38
|
+
command_successful = command_object.execute(args)
|
|
39
|
+
log.info("")
|
|
40
|
+
if not command_successful:
|
|
41
|
+
exit(1)
|
|
42
|
+
except KeyboardInterrupt:
|
|
43
|
+
log.warn("\rCtrl-C pressed, exiting ...")
|
|
44
|
+
log.info("")
|
|
45
|
+
exit(1)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
# Check if it's a certain kind of `AttributeError` and give a hint in
|
|
48
|
+
# that case.
|
|
49
|
+
log.debug(
|
|
50
|
+
"Command failed with exception, full traceback: "
|
|
51
|
+
f"{traceback.format_exc()}"
|
|
52
|
+
)
|
|
53
|
+
match_error = re.search(r"object has no attribute '(.+)'", str(e))
|
|
54
|
+
match_trace = re.search(
|
|
55
|
+
rf"({script_name}/commands/.+\.py)\", line (\d+)",
|
|
56
|
+
traceback.format_exc(),
|
|
57
|
+
)
|
|
58
|
+
if isinstance(e, AttributeError) and match_error and match_trace:
|
|
59
|
+
attribute = match_error.group(1)
|
|
60
|
+
trace_command = match_trace.group(1)
|
|
61
|
+
trace_line = match_trace.group(2)
|
|
62
|
+
log.error(f"{e} in `{trace_command}` at line {trace_line}")
|
|
63
|
+
log.info("")
|
|
64
|
+
log.info(
|
|
65
|
+
f"Likely cause: you used `args.{attribute}`, but it was "
|
|
66
|
+
f"neither defined in `relevant_qleverfile_arguments` "
|
|
67
|
+
f"nor in `additional_arguments`"
|
|
68
|
+
)
|
|
69
|
+
log.info("")
|
|
70
|
+
log.info(
|
|
71
|
+
f"If you did not implement `{trace_command}` yourself, "
|
|
72
|
+
f"please report this issue"
|
|
73
|
+
)
|
|
74
|
+
log.info("")
|
|
75
|
+
else:
|
|
76
|
+
log.error(f"An unexpected error occurred: {e}")
|
|
77
|
+
log.info("")
|
|
78
|
+
log.info(traceback.format_exc())
|
|
79
|
+
exit(1)
|