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
qlever/commands/log.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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 LogCommand(QleverCommand):
|
|
10
|
+
"""
|
|
11
|
+
Class for executing the `log` command.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def description(self) -> str:
|
|
18
|
+
return ("Show the last lines of the server log file and follow it")
|
|
19
|
+
|
|
20
|
+
def should_have_qleverfile(self) -> bool:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
|
|
24
|
+
return {"data": ["name"]}
|
|
25
|
+
|
|
26
|
+
def additional_arguments(self, subparser) -> None:
|
|
27
|
+
subparser.add_argument("--tail-num-lines", type=int, default=20,
|
|
28
|
+
help="Show this many of the last lines of the "
|
|
29
|
+
"log file")
|
|
30
|
+
subparser.add_argument("--from-beginning", action="store_true",
|
|
31
|
+
default=False,
|
|
32
|
+
help="Show all lines of the log file")
|
|
33
|
+
subparser.add_argument("--no-follow", action="store_true",
|
|
34
|
+
default=False,
|
|
35
|
+
help="Don't follow the log file")
|
|
36
|
+
|
|
37
|
+
def execute(self, args) -> bool:
|
|
38
|
+
# Construct the command and show it.
|
|
39
|
+
log_cmd = "tail"
|
|
40
|
+
if args.from_beginning:
|
|
41
|
+
log_cmd += " -n +1"
|
|
42
|
+
else:
|
|
43
|
+
log_cmd += f" -n {args.tail_num_lines}"
|
|
44
|
+
if not args.no_follow:
|
|
45
|
+
log_cmd += " -f"
|
|
46
|
+
log_file = f"{args.name}.server-log.txt"
|
|
47
|
+
log_cmd += f" {log_file}"
|
|
48
|
+
self.show(log_cmd, only_show=args.show)
|
|
49
|
+
if args.show:
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
# Execute the command.
|
|
53
|
+
log.info(f"Follow log file {log_file}, press Ctrl-C to stop"
|
|
54
|
+
f" following (will not stop the server)")
|
|
55
|
+
log.info("")
|
|
56
|
+
try:
|
|
57
|
+
subprocess.run(log_cmd, shell=True)
|
|
58
|
+
return True
|
|
59
|
+
except Exception as e:
|
|
60
|
+
log.error(e)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import shlex
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from qlever.command import QleverCommand
|
|
9
|
+
from qlever.log import log
|
|
10
|
+
from qlever.util import (
|
|
11
|
+
run_command,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MaterializedViewCommand(QleverCommand):
|
|
16
|
+
"""
|
|
17
|
+
Class for executing the `materialized-view` command.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.materialized_view_name_regex = r"^[A-Za-z0-9-]+$"
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def description(self) -> str:
|
|
25
|
+
return "Create a materialized view from the given query"
|
|
26
|
+
|
|
27
|
+
def should_have_qleverfile(self) -> bool:
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
|
|
31
|
+
return {
|
|
32
|
+
"data": ["name"],
|
|
33
|
+
"server": ["host_name", "port", "access_token"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def additional_arguments(self, subparser) -> None:
|
|
37
|
+
subparser.add_argument(
|
|
38
|
+
"view_name",
|
|
39
|
+
type=str,
|
|
40
|
+
help="Name of the materialized view",
|
|
41
|
+
)
|
|
42
|
+
subparser.add_argument(
|
|
43
|
+
"view_query",
|
|
44
|
+
type=str,
|
|
45
|
+
help="SPARQL query from which to create the materialized view",
|
|
46
|
+
)
|
|
47
|
+
subparser.add_argument(
|
|
48
|
+
"--sparql-endpoint",
|
|
49
|
+
type=str,
|
|
50
|
+
help="URL of the SPARQL endpoint (default: <host_name>:<port>)",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def execute(self, args) -> bool:
|
|
54
|
+
# SPARQL endpoint to use.
|
|
55
|
+
sparql_endpoint = (
|
|
56
|
+
args.sparql_endpoint
|
|
57
|
+
if args.sparql_endpoint is not None
|
|
58
|
+
else f"{args.host_name}:{args.port}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Check that the name of the materialized view is valid.
|
|
62
|
+
if not re.match(self.materialized_view_name_regex, args.view_name):
|
|
63
|
+
log.error(
|
|
64
|
+
f"The name for the materialized view must match "
|
|
65
|
+
f"the regex {self.materialized_view_name_regex}"
|
|
66
|
+
)
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Command for building the materialized view.
|
|
70
|
+
url = (
|
|
71
|
+
f"{sparql_endpoint}"
|
|
72
|
+
f"?cmd=write-materialized-view"
|
|
73
|
+
f"&view-name={args.view_name}"
|
|
74
|
+
)
|
|
75
|
+
materialized_view_cmd = (
|
|
76
|
+
f"curl -s {shlex.quote(url)} "
|
|
77
|
+
f"-H 'Authorization: Bearer {args.access_token}' "
|
|
78
|
+
f"-H 'Content-type: application/sparql-query' "
|
|
79
|
+
f"-d {shlex.quote(args.view_query)}"
|
|
80
|
+
)
|
|
81
|
+
self.show(materialized_view_cmd, only_show=args.show)
|
|
82
|
+
if args.show:
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
# Run the command (and time it).
|
|
86
|
+
time_start = time.monotonic()
|
|
87
|
+
try:
|
|
88
|
+
log.info("Creating the materialized view ... "
|
|
89
|
+
"(this may take a while, depending on the complexity "
|
|
90
|
+
"of the query and the size of the result)")
|
|
91
|
+
log.info("")
|
|
92
|
+
result = run_command(materialized_view_cmd, return_output=True)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
log.error(f"Creating the materialized view failed: {e}")
|
|
95
|
+
return False
|
|
96
|
+
time_end = time.monotonic()
|
|
97
|
+
duration_seconds = round(time_end - time_start)
|
|
98
|
+
|
|
99
|
+
# Try to parse the result (should be JSON).
|
|
100
|
+
try:
|
|
101
|
+
result_json = json.loads(result)
|
|
102
|
+
view_name = result_json.get("materialized-view-written")
|
|
103
|
+
log.info(
|
|
104
|
+
f"Materialized view '{view_name}' created successfully "
|
|
105
|
+
f"in {duration_seconds:,} seconds"
|
|
106
|
+
)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
log.error(f'Failed to parse JSON from "{result}": {e}')
|
|
109
|
+
|
|
110
|
+
return True
|
qlever/commands/query.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
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 QueryCommand(QleverCommand):
|
|
13
|
+
"""
|
|
14
|
+
Class for executing the `query` command.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
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
|
+
}
|
|
33
|
+
|
|
34
|
+
def description(self) -> str:
|
|
35
|
+
return "Send a query to a SPARQL endpoint"
|
|
36
|
+
|
|
37
|
+
def should_have_qleverfile(self) -> bool:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
|
|
41
|
+
return {"server": ["host_name", "port", "access_token"]}
|
|
42
|
+
|
|
43
|
+
def additional_arguments(self, subparser) -> None:
|
|
44
|
+
subparser.add_argument(
|
|
45
|
+
"query",
|
|
46
|
+
type=str,
|
|
47
|
+
nargs="?",
|
|
48
|
+
default="SELECT * WHERE { ?s ?p ?o } LIMIT 10",
|
|
49
|
+
help="SPARQL query to send",
|
|
50
|
+
)
|
|
51
|
+
subparser.add_argument(
|
|
52
|
+
"--predefined-query",
|
|
53
|
+
type=str,
|
|
54
|
+
choices=self.predefined_queries.keys(),
|
|
55
|
+
help="Use a predefined query",
|
|
56
|
+
)
|
|
57
|
+
subparser.add_argument(
|
|
58
|
+
"--pin-to-cache",
|
|
59
|
+
action="store_true",
|
|
60
|
+
default=False,
|
|
61
|
+
help="Pin the query to the cache",
|
|
62
|
+
)
|
|
63
|
+
subparser.add_argument(
|
|
64
|
+
"--sparql-endpoint", type=str, help="URL of the SPARQL endpoint"
|
|
65
|
+
)
|
|
66
|
+
subparser.add_argument(
|
|
67
|
+
"--accept",
|
|
68
|
+
type=str,
|
|
69
|
+
choices=[
|
|
70
|
+
"text/tab-separated-values",
|
|
71
|
+
"text/csv",
|
|
72
|
+
"application/sparql-results+json",
|
|
73
|
+
"application/sparql-results+xml",
|
|
74
|
+
"application/qlever-results+json",
|
|
75
|
+
"application/octet-stream",
|
|
76
|
+
],
|
|
77
|
+
default="text/tab-separated-values",
|
|
78
|
+
help="Accept header for the SPARQL query",
|
|
79
|
+
)
|
|
80
|
+
subparser.add_argument(
|
|
81
|
+
"--no-time",
|
|
82
|
+
action="store_true",
|
|
83
|
+
default=False,
|
|
84
|
+
help="Do not print the (end-to-end) time taken",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def execute(self, args) -> bool:
|
|
88
|
+
# Use a predefined query if requested.
|
|
89
|
+
if args.predefined_query:
|
|
90
|
+
args.query = self.predefined_queries[args.predefined_query]
|
|
91
|
+
|
|
92
|
+
# When pinning to the cache, set `send=0` and request media type
|
|
93
|
+
# `application/qlever-results+json` so that we get the result size.
|
|
94
|
+
# Also, we need to provide the access token.
|
|
95
|
+
if args.pin_to_cache:
|
|
96
|
+
args.accept = "application/qlever-results+json"
|
|
97
|
+
curl_cmd_additions = (
|
|
98
|
+
f" --data pin-result=true --data send=0"
|
|
99
|
+
f" --data access-token="
|
|
100
|
+
f"{shlex.quote(args.access_token)}"
|
|
101
|
+
f" | jq .resultsize | numfmt --grouping"
|
|
102
|
+
f" | xargs -I {{}} printf"
|
|
103
|
+
f' "Result pinned to cache,'
|
|
104
|
+
f' number of rows: {{}}\\n"'
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
curl_cmd_additions = ""
|
|
108
|
+
|
|
109
|
+
# Show what the command will do.
|
|
110
|
+
sparql_endpoint = (
|
|
111
|
+
args.sparql_endpoint
|
|
112
|
+
if args.sparql_endpoint
|
|
113
|
+
else f"{args.host_name}:{args.port}"
|
|
114
|
+
)
|
|
115
|
+
curl_cmd = (
|
|
116
|
+
f"curl -s {sparql_endpoint}"
|
|
117
|
+
f' -H "Accept: {args.accept}"'
|
|
118
|
+
f" --data-urlencode query={shlex.quote(args.query)}"
|
|
119
|
+
f"{curl_cmd_additions}"
|
|
120
|
+
)
|
|
121
|
+
self.show(curl_cmd, only_show=args.show)
|
|
122
|
+
if args.show:
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
# Launch query.
|
|
126
|
+
try:
|
|
127
|
+
start_time = time.time()
|
|
128
|
+
run_command(curl_cmd, show_output=True)
|
|
129
|
+
time_msecs = round(1000 * (time.time() - start_time))
|
|
130
|
+
if not args.no_time and args.log_level != "NO_LOG":
|
|
131
|
+
log.info("")
|
|
132
|
+
log.info(
|
|
133
|
+
f"Query processing time (end-to-end):"
|
|
134
|
+
f" {time_msecs:,d} ms"
|
|
135
|
+
)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
if args.log_level == "DEBUG":
|
|
138
|
+
traceback.print_exc()
|
|
139
|
+
log.error(e)
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
return True
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from termcolor import colored
|
|
8
|
+
|
|
9
|
+
from qlever.command import QleverCommand
|
|
10
|
+
from qlever.log import log
|
|
11
|
+
from qlever.util import (
|
|
12
|
+
run_command,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RebuildIndexCommand(QleverCommand):
|
|
17
|
+
"""
|
|
18
|
+
Class for executing the `rebuild-index` command.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
def description(self) -> str:
|
|
25
|
+
return "Rebuild the index from the current data (including updates)"
|
|
26
|
+
|
|
27
|
+
def should_have_qleverfile(self) -> bool:
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
|
|
31
|
+
return {
|
|
32
|
+
"data": ["name"],
|
|
33
|
+
"server": ["host_name", "port", "access_token"],
|
|
34
|
+
"runtime": ["server_container"],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def additional_arguments(self, subparser) -> None:
|
|
38
|
+
subparser.add_argument(
|
|
39
|
+
"--index-dir",
|
|
40
|
+
type=str,
|
|
41
|
+
help="Directory for the new index (default: subdirectory "
|
|
42
|
+
"`rebuild.YYYY-MM-DDTHH:MM` of the current directory)",
|
|
43
|
+
)
|
|
44
|
+
subparser.add_argument(
|
|
45
|
+
"--index-name",
|
|
46
|
+
type=str,
|
|
47
|
+
help="Base name of the new index (default: use the same as the "
|
|
48
|
+
"current index)",
|
|
49
|
+
)
|
|
50
|
+
subparser.add_argument(
|
|
51
|
+
"--restart-when-finished",
|
|
52
|
+
action="store_true",
|
|
53
|
+
default=False,
|
|
54
|
+
help="When the rebuild is finished, stop the server with the old "
|
|
55
|
+
"index and start it again with the new index",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def execute(self, args) -> bool:
|
|
59
|
+
# Default values for arguments.
|
|
60
|
+
if args.index_name is None:
|
|
61
|
+
args.index_name = args.name
|
|
62
|
+
if args.index_dir is None:
|
|
63
|
+
timestamp = time.strftime("%Y-%m-%dT%H:%M", time.localtime())
|
|
64
|
+
args.index_dir = f"rebuild.{timestamp}"
|
|
65
|
+
if args.index_dir.endswith("/"):
|
|
66
|
+
args.index_dir = args.index_dir[:-1]
|
|
67
|
+
|
|
68
|
+
# Check that the index directory either does not exist or is empty.
|
|
69
|
+
index_path = Path(args.index_dir)
|
|
70
|
+
if index_path.exists() and any(index_path.iterdir()):
|
|
71
|
+
log.error(
|
|
72
|
+
f"The specified index directory '{args.index_dir}' already "
|
|
73
|
+
"exists and is not empty; please specify an empty or "
|
|
74
|
+
"non-existing directory"
|
|
75
|
+
)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# Split `index_dir` into path and dir name. For example, if `index_dir`
|
|
79
|
+
# is `path/to/index`, then the path is `path/to` and the dir name
|
|
80
|
+
# is `index`.
|
|
81
|
+
#
|
|
82
|
+
# NOTE: We keep this separate because we can always create a
|
|
83
|
+
# subdirectory in the current directory (even when running in a
|
|
84
|
+
# container), but not necessarily a directory at an arbitrary path. If
|
|
85
|
+
# a path outside the current directory is desired, we move the index
|
|
86
|
+
# there after it has been built.
|
|
87
|
+
index_dir_path = str(Path(args.index_dir).parent)
|
|
88
|
+
index_dir_name = str(Path(args.index_dir).name)
|
|
89
|
+
log_file_name = f"{args.index_name}.rebuild-index-log.txt"
|
|
90
|
+
|
|
91
|
+
# Command for rebuilding the index.
|
|
92
|
+
mkdir_cmd = (
|
|
93
|
+
f"mkdir -p {index_dir_name} && "
|
|
94
|
+
f"> {index_dir_name}/{log_file_name} && "
|
|
95
|
+
f"cp -a Qleverfile {index_dir_name}"
|
|
96
|
+
)
|
|
97
|
+
rebuild_index_cmd = (
|
|
98
|
+
f"curl -s {args.host_name}:{args.port} "
|
|
99
|
+
f"-d cmd=rebuild-index "
|
|
100
|
+
f"-d index-name={index_dir_name}/{args.index_name} "
|
|
101
|
+
f"-d access-token={args.access_token}"
|
|
102
|
+
)
|
|
103
|
+
move_index_cmd = f"mv {index_dir_name} {index_dir_path}"
|
|
104
|
+
restart_server_cmd = (
|
|
105
|
+
f"cd {args.index_dir} && "
|
|
106
|
+
f"qlever start --kill-existing-with-same-port"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Show the command lines.
|
|
110
|
+
cmds_to_show = [mkdir_cmd, rebuild_index_cmd]
|
|
111
|
+
if index_dir_path != ".":
|
|
112
|
+
cmds_to_show.append(move_index_cmd)
|
|
113
|
+
if args.restart_when_finished:
|
|
114
|
+
cmds_to_show.append(restart_server_cmd)
|
|
115
|
+
self.show("\n".join(cmds_to_show), only_show=args.show)
|
|
116
|
+
if args.show:
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
# Create the index directory and the log file.
|
|
120
|
+
try:
|
|
121
|
+
run_command(mkdir_cmd)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
log.error(f"Creating the index directory failed: {e}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
# Show the server log while rebuilding the index.
|
|
127
|
+
#
|
|
128
|
+
# NOTE: This will only work satisfactorily when no other queries are
|
|
129
|
+
# being processed at the same time. It would be better if QLever
|
|
130
|
+
# logged the rebuild-index output to a separate log file.
|
|
131
|
+
tail_cmd = f"exec tail -n 0 -f {index_dir_name}/{log_file_name}"
|
|
132
|
+
tail_proc = subprocess.Popen(tail_cmd, shell=True)
|
|
133
|
+
|
|
134
|
+
# Run the index rebuild command (and time it).
|
|
135
|
+
try:
|
|
136
|
+
time_start = time.monotonic()
|
|
137
|
+
try:
|
|
138
|
+
run_command(rebuild_index_cmd, show_output=False)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
log.error(f"Rebuilding the index failed: {e}")
|
|
141
|
+
return False
|
|
142
|
+
time_end = time.monotonic()
|
|
143
|
+
duration_seconds = round(time_end - time_start)
|
|
144
|
+
log.info("")
|
|
145
|
+
rebuild_done_msg = f"Rebuilt index in {duration_seconds:,} seconds"
|
|
146
|
+
if index_dir_path == ".":
|
|
147
|
+
rebuild_done_msg += (
|
|
148
|
+
f", in the new directory '{args.index_dir}'"
|
|
149
|
+
)
|
|
150
|
+
log.info(rebuild_done_msg)
|
|
151
|
+
finally:
|
|
152
|
+
tail_proc.terminate()
|
|
153
|
+
tail_proc.wait()
|
|
154
|
+
|
|
155
|
+
# Move the new index to the specified directory, if needed.
|
|
156
|
+
if index_dir_path != ".":
|
|
157
|
+
try:
|
|
158
|
+
log.info(f"Moving the new index to {args.index_dir}")
|
|
159
|
+
run_command(move_index_cmd)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
log.error(f"Moving the new index failed: {e}")
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
# Restart the server with the new index, if requested.
|
|
165
|
+
if args.restart_when_finished:
|
|
166
|
+
try:
|
|
167
|
+
log.info("Restarting the server with the new index ...")
|
|
168
|
+
log.info("")
|
|
169
|
+
log.info(colored("Command: start", attrs=["bold"]))
|
|
170
|
+
log.info("")
|
|
171
|
+
run_command(restart_server_cmd, show_output=True)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
log.error(f"Restarting the server failed: {e}")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
return True
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from qlever.command import QleverCommand
|
|
6
|
+
from qlever.log import log
|
|
7
|
+
from qlever.util import run_command
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ResetUpdatesCommand(QleverCommand):
|
|
11
|
+
"""
|
|
12
|
+
Class for executing the `reset-updates` command.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def description(self) -> str:
|
|
19
|
+
return "Reset the updates on the server"
|
|
20
|
+
|
|
21
|
+
def should_have_qleverfile(self) -> bool:
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
|
|
25
|
+
return {"server": ["host_name", "port", "access_token"]}
|
|
26
|
+
|
|
27
|
+
def additional_arguments(self, subparser) -> None:
|
|
28
|
+
subparser.add_argument(
|
|
29
|
+
"--sparql-endpoint",
|
|
30
|
+
help="URL of the QLever server, default is {host_name}:{port}",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def execute(self, args) -> bool:
|
|
34
|
+
reset_cmd = "curl -s"
|
|
35
|
+
if args.sparql_endpoint:
|
|
36
|
+
reset_cmd += f" {args.sparql_endpoint}"
|
|
37
|
+
else:
|
|
38
|
+
reset_cmd += f" {args.host_name}:{args.port}"
|
|
39
|
+
reset_cmd += f' --data-urlencode "cmd=clear-delta-triples" --data-urlencode "access-token={args.access_token}"'
|
|
40
|
+
self.show(reset_cmd, only_show=args.show)
|
|
41
|
+
if args.show:
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
reset_cmd += ' -w " %{http_code}"'
|
|
46
|
+
result = run_command(reset_cmd, return_output=True)
|
|
47
|
+
match = re.match(r"^(.*) (\d+)$", result, re.DOTALL)
|
|
48
|
+
if not match:
|
|
49
|
+
raise Exception(f"Unexpected output:\n{result}")
|
|
50
|
+
error_message = match.group(1).strip()
|
|
51
|
+
status_code = match.group(2)
|
|
52
|
+
if status_code != "200":
|
|
53
|
+
raise Exception(error_message)
|
|
54
|
+
message = "Updates reset successfully"
|
|
55
|
+
log.info(message)
|
|
56
|
+
return True
|
|
57
|
+
except Exception as e:
|
|
58
|
+
log.error(e)
|
|
59
|
+
return False
|
|
@@ -0,0 +1,115 @@
|
|
|
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.qleverfile import Qleverfile
|
|
10
|
+
from qlever.util import run_command
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SettingsCommand(QleverCommand):
|
|
14
|
+
"""
|
|
15
|
+
Class for executing the `settings` command.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def description(self) -> str:
|
|
22
|
+
return "Show or set server settings (after `qlever start`)"
|
|
23
|
+
|
|
24
|
+
def should_have_qleverfile(self) -> bool:
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
def relevant_qleverfile_arguments(self) -> dict[str, list[str]]:
|
|
28
|
+
return {"server": ["port", "host_name", "access_token"]}
|
|
29
|
+
|
|
30
|
+
def additional_arguments(self, subparser) -> None:
|
|
31
|
+
subparser.add_argument(
|
|
32
|
+
"runtime_parameters",
|
|
33
|
+
nargs="*",
|
|
34
|
+
help="Space-separated list of runtime parameters to set "
|
|
35
|
+
"in the form `key=value`; afterwards shows all settings, "
|
|
36
|
+
"with the changed ones highlighted",
|
|
37
|
+
).completer = lambda **kwargs: [
|
|
38
|
+
f"{key}=" for key in Qleverfile.SERVER_RUNTIME_PARAMETERS
|
|
39
|
+
]
|
|
40
|
+
subparser.add_argument(
|
|
41
|
+
"--endpoint_url",
|
|
42
|
+
type=str,
|
|
43
|
+
help="An arbitrary endpoint URL "
|
|
44
|
+
"(overriding the one in the Qleverfile)",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def execute(self, args) -> bool:
|
|
48
|
+
# Get endpoint URL from command line or Qleverfile.
|
|
49
|
+
if args.endpoint_url:
|
|
50
|
+
endpoint_url = args.endpoint_url
|
|
51
|
+
else:
|
|
52
|
+
endpoint_url = f"http://{args.host_name}:{args.port}"
|
|
53
|
+
|
|
54
|
+
# Construct the `curl` commands for setting and getting.
|
|
55
|
+
curl_cmds_setting = []
|
|
56
|
+
keys_set = set()
|
|
57
|
+
if args.runtime_parameters:
|
|
58
|
+
for key_value_pair in args.runtime_parameters:
|
|
59
|
+
try:
|
|
60
|
+
key, value = key_value_pair.split("=")
|
|
61
|
+
except ValueError:
|
|
62
|
+
log.error("Runtime parameter must be given as `key=value`")
|
|
63
|
+
return False
|
|
64
|
+
curl_cmds_setting.append(
|
|
65
|
+
f"curl -s {endpoint_url} -w %{{http_code}}"
|
|
66
|
+
f' --data-urlencode "{key}={value}"'
|
|
67
|
+
f' --data-urlencode "access-token={args.access_token}"'
|
|
68
|
+
)
|
|
69
|
+
keys_set.add(key)
|
|
70
|
+
curl_cmd_getting = (
|
|
71
|
+
f"curl -s {endpoint_url} -w %{{http_code}}"
|
|
72
|
+
f" --data-urlencode cmd=get-settings"
|
|
73
|
+
)
|
|
74
|
+
self.show(
|
|
75
|
+
"\n".join(curl_cmds_setting + [curl_cmd_getting]),
|
|
76
|
+
only_show=args.show,
|
|
77
|
+
)
|
|
78
|
+
if args.show:
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
# Execute the `curl` commands for setting the key-value pairs if any.
|
|
82
|
+
for curl_cmd in curl_cmds_setting:
|
|
83
|
+
try:
|
|
84
|
+
curl_result = run_command(curl_cmd, return_output=True)
|
|
85
|
+
body, http_code = curl_result[:-3], curl_result[-3:]
|
|
86
|
+
if http_code != "200":
|
|
87
|
+
raise Exception(body)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
log.error(
|
|
90
|
+
f"curl command for setting key-value pair failed: {e}"
|
|
91
|
+
)
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Execute the `curl` commands for getting the settings.
|
|
95
|
+
try:
|
|
96
|
+
curl_result = run_command(curl_cmd_getting, return_output=True)
|
|
97
|
+
body, http_code = curl_result[:-3], curl_result[-3:]
|
|
98
|
+
if http_code != "200":
|
|
99
|
+
raise Exception(body)
|
|
100
|
+
settings_dict = json.loads(body)
|
|
101
|
+
if isinstance(settings_dict, list):
|
|
102
|
+
settings_dict = settings_dict[0]
|
|
103
|
+
except Exception as e:
|
|
104
|
+
log.error(f"curl command for getting settings failed: {e}")
|
|
105
|
+
return False
|
|
106
|
+
for key, value in settings_dict.items():
|
|
107
|
+
print(
|
|
108
|
+
colored(
|
|
109
|
+
f"{key:<45}: {value}",
|
|
110
|
+
"blue" if key in keys_set else None,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# That's it.
|
|
115
|
+
return True
|