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

@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import subprocess
4
+ from os import environ
4
5
  from pathlib import Path
5
6
 
6
7
  from qlever.command import QleverCommand
@@ -15,9 +16,9 @@ class SetupConfigCommand(QleverCommand):
15
16
 
16
17
  def __init__(self):
17
18
  self.qleverfiles_path = Path(__file__).parent.parent / "Qleverfiles"
18
- self.qleverfile_names = \
19
- [p.name.split(".")[1]
20
- for p in self.qleverfiles_path.glob("Qleverfile.*")]
19
+ self.qleverfile_names = [
20
+ p.name.split(".")[1] for p in self.qleverfiles_path.glob("Qleverfile.*")
21
+ ]
21
22
 
22
23
  def description(self) -> str:
23
24
  return "Get a pre-configured Qleverfile"
@@ -25,23 +26,38 @@ class SetupConfigCommand(QleverCommand):
25
26
  def should_have_qleverfile(self) -> bool:
26
27
  return False
27
28
 
28
- def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
29
+ def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
29
30
  return {}
30
31
 
31
32
  def additional_arguments(self, subparser) -> None:
32
33
  subparser.add_argument(
33
- "config_name", type=str,
34
- choices=self.qleverfile_names,
35
- help="The name of the pre-configured Qleverfile to create")
34
+ "config_name",
35
+ type=str,
36
+ choices=self.qleverfile_names,
37
+ help="The name of the pre-configured Qleverfile to create",
38
+ )
36
39
 
37
40
  def execute(self, args) -> bool:
41
+ # Show a warning if `QLEVER_OVERRIDE_SYSTEM_NATIVE` is set.
42
+ qlever_is_running_in_container = environ.get("QLEVER_IS_RUNNING_IN_CONTAINER")
43
+ if qlever_is_running_in_container:
44
+ log.warning(
45
+ "The environment variable `QLEVER_IS_RUNNING_IN_CONTAINER` is set, "
46
+ "therefore the Qleverfile is modified to use `SYSTEM = native` "
47
+ "(since inside the container, QLever should run natively)"
48
+ )
49
+ log.info("")
38
50
  # Construct the command line and show it.
39
- qleverfile_path = (self.qleverfiles_path
40
- / f"Qleverfile.{args.config_name}")
51
+ qleverfile_path = self.qleverfiles_path / f"Qleverfile.{args.config_name}"
41
52
  setup_config_cmd = (
42
- f"cat {qleverfile_path}"
43
- f" | sed -E 's/(^ACCESS_TOKEN.*)/\\1_{get_random_string(12)}/'"
44
- f"> Qleverfile")
53
+ f"cat {qleverfile_path}"
54
+ f" | sed -E 's/(^ACCESS_TOKEN.*)/\\1_{get_random_string(12)}/'"
55
+ )
56
+ if qlever_is_running_in_container:
57
+ setup_config_cmd += (
58
+ " | sed -E 's/(^SYSTEM[[:space:]]*=[[:space:]]*).*/\\1native/'"
59
+ )
60
+ setup_config_cmd += "> Qleverfile"
45
61
  self.show(setup_config_cmd, only_show=args.show)
46
62
  if args.show:
47
63
  return False
@@ -51,31 +67,31 @@ class SetupConfigCommand(QleverCommand):
51
67
  if qleverfile_path.exists():
52
68
  log.error("`Qleverfile` already exists in current directory")
53
69
  log.info("")
54
- log.info("If you want to create a new Qleverfile using "
55
- "`qlever setup-config`, delete the existing Qleverfile "
56
- "first")
70
+ log.info(
71
+ "If you want to create a new Qleverfile using "
72
+ "`qlever setup-config`, delete the existing Qleverfile "
73
+ "first"
74
+ )
57
75
  return False
58
76
 
59
77
  # Copy the Qleverfile to the current directory.
60
78
  try:
61
- subprocess.run(setup_config_cmd, shell=True, check=True,
62
- stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
79
+ subprocess.run(
80
+ setup_config_cmd,
81
+ shell=True,
82
+ check=True,
83
+ stdin=subprocess.DEVNULL,
84
+ stdout=subprocess.DEVNULL,
85
+ )
63
86
  except Exception as e:
64
- log.error(f"Could not copy \"{qleverfile_path}\""
65
- f" to current directory: {e}")
87
+ log.error(
88
+ f'Could not copy "{qleverfile_path}"' f" to current directory: {e}"
89
+ )
66
90
  return False
67
91
 
68
92
  # If we get here, everything went well.
69
- log.info(f"Created Qleverfile for config \"{args.config_name}\""
70
- f" in current directory")
93
+ log.info(
94
+ f'Created Qleverfile for config "{args.config_name}"'
95
+ f" in current directory"
96
+ )
71
97
  return True
72
-
73
- # if config_name == "default":
74
- # log.info("Since this is the default Qleverfile, you need to "
75
- # "edit it before you can continue")
76
- # log.info("")
77
- # log.info("Afterwards, run `qlever` without arguments to see "
78
- # "which actions are available")
79
- # else:
80
- # show_available_action_names()
81
- # log.info("")
@@ -0,0 +1,126 @@
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("Show system information and Qleverfile", only_show=args.show)
60
+ if args.show:
61
+ return False
62
+
63
+ # Show system information.
64
+ show_heading("System Information")
65
+ system = platform.system()
66
+ is_linux = system == "Linux"
67
+ is_mac = system == "Darwin"
68
+ is_windows = system == "Windows"
69
+ if is_windows:
70
+ log.warn("Only limited information is gathered on Windows.")
71
+ log.info(f"Version: {version('qlever')} (qlever --version)")
72
+ if is_linux:
73
+ info = platform.freedesktop_os_release()
74
+ log.info(f"OS: {platform.system()} ({info['PRETTY_NAME']})")
75
+ else:
76
+ log.info(f"OS: {platform.system()}")
77
+ log.info(f"Arch: {platform.machine()}")
78
+ log.info(f"Host: {platform.node()}")
79
+ psutil.virtual_memory().total / (1000**3)
80
+ memory_total = psutil.virtual_memory().total / (1024.0**3)
81
+ memory_available = psutil.virtual_memory().available / (1024.0**3)
82
+ log.info(
83
+ f"RAM: {memory_total:.1f} GB total, " f"{memory_available:.1f} GB available"
84
+ )
85
+ num_cores = psutil.cpu_count(logical=False)
86
+ num_threads = psutil.cpu_count(logical=True)
87
+ cpu_freq = psutil.cpu_freq().max / 1000
88
+ log.info(
89
+ f"CPU: {num_cores} Cores, " f"{num_threads} Threads @ {cpu_freq:.2f} GHz"
90
+ )
91
+
92
+ cwd = Path.cwd()
93
+ log.info(f"CWD: {cwd}")
94
+ # Free and total size of the partition on which the current working
95
+ # directory resides.
96
+ disk_usage = psutil.disk_usage(str(cwd))
97
+ partition = get_partition(cwd)
98
+ partition_description = f"{partition.device} @ {partition.mountpoint}"
99
+ fs_type = partition.fstype
100
+ fs_free = format_size(disk_usage.free)
101
+ fs_total = format_size(disk_usage.total)
102
+ log.info(
103
+ f"Disk space in {partition_description} is "
104
+ f"({fs_type}): {fs_free} free / {fs_total} total"
105
+ )
106
+ # User/Group on host and in container
107
+ if is_linux or is_mac:
108
+ user_info = run_command("id", return_output=True).strip()
109
+ log.info(f"User and group on host: {user_info}")
110
+ elif is_windows:
111
+ user_info = run_command("whoami /all", return_output=True).strip()
112
+ log.info(f"User and group on host: {user_info}")
113
+ if args.system in Containerize.supported_systems():
114
+ user_info = Containerize.run_in_container("id", args).strip()
115
+ log.info(f"User and group in container: {user_info}")
116
+
117
+ # Show Qleverfile.
118
+ log.info("")
119
+ show_heading("Contents of Qleverfile")
120
+ qleverfile = cwd / "Qleverfile"
121
+ if qleverfile.exists():
122
+ # TODO: output the effective qlever file using primites from #57
123
+ log.info(qleverfile.read_text())
124
+ else:
125
+ log.info("No Qleverfile found")
126
+ return True
qlever/commands/ui.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import subprocess
4
+ from os import environ
4
5
 
5
6
  from qlever.command import QleverCommand
6
7
  from qlever.containerize import Containerize
@@ -17,46 +18,70 @@ class UiCommand(QleverCommand):
17
18
  pass
18
19
 
19
20
  def description(self) -> str:
20
- return ("Launch the QLever UI web application")
21
+ return "Launch the QLever UI web application"
21
22
 
22
23
  def should_have_qleverfile(self) -> bool:
23
24
  return True
24
25
 
25
- def relevant_qleverfile_arguments(self) -> dict[str: list[str]]:
26
- return {"data": ["name"],
27
- "server": ["host_name", "port"],
28
- "ui": ["ui_port", "ui_config",
29
- "ui_system", "ui_image", "ui_container"]}
26
+ def relevant_qleverfile_arguments(self) -> dict[str : list[str]]:
27
+ return {
28
+ "data": ["name"],
29
+ "server": ["host_name", "port"],
30
+ "ui": ["ui_port", "ui_config", "ui_system", "ui_image", "ui_container"],
31
+ }
30
32
 
31
33
  def additional_arguments(self, subparser) -> None:
32
34
  pass
33
35
 
34
36
  def execute(self, args) -> bool:
37
+ # If QLEVER_OVERRIDE_DISABLE_UI is set, this command is disabled.
38
+ qlever_is_running_in_container = environ.get("QLEVER_IS_RUNNING_IN_CONTAINER")
39
+ if qlever_is_running_in_container:
40
+ log.error(
41
+ "The environment variable `QLEVER_OVERRIDE_DISABLE_UI` is set, "
42
+ "therefore `qlever ui` is not available (it should not be called "
43
+ "from inside a container)"
44
+ )
45
+ log.info("")
46
+ if not args.show:
47
+ log.info(
48
+ "For your information, showing the commands that are "
49
+ "executed when `qlever ui` is available:"
50
+ )
51
+ log.info("")
52
+
35
53
  # Construct commands and show them.
36
54
  server_url = f"http://{args.host_name}:{args.port}"
37
55
  ui_url = f"http://{args.host_name}:{args.ui_port}"
38
56
  pull_cmd = f"{args.ui_system} pull -q {args.ui_image}"
39
- run_cmd = f"{args.ui_system} run -d " \
40
- f"--publish {args.ui_port}:7000 " \
41
- f"--name {args.ui_container} " \
42
- f"{args.ui_image}"
43
- exec_cmd = f"{args.ui_system} exec -it " \
44
- f"{args.ui_container} " \
45
- f"bash -c \"python manage.py configure " \
46
- f"{args.ui_config} {server_url}\""
47
- self.show("\n".join(["Stop running containers",
48
- pull_cmd, run_cmd, exec_cmd]), only_show=args.show)
49
- if args.show:
57
+ run_cmd = (
58
+ f"{args.ui_system} run -d "
59
+ f"--publish {args.ui_port}:7000 "
60
+ f"--name {args.ui_container} "
61
+ f"{args.ui_image}"
62
+ )
63
+ exec_cmd = (
64
+ f"{args.ui_system} exec -it "
65
+ f"{args.ui_container} "
66
+ f'bash -c "python manage.py configure '
67
+ f'{args.ui_config} {server_url}"'
68
+ )
69
+ self.show(
70
+ "\n".join(["Stop running containers", pull_cmd, run_cmd, exec_cmd]),
71
+ only_show=args.show,
72
+ )
73
+ if qlever_is_running_in_container or args.show:
50
74
  return False
51
75
 
52
76
  # Stop running containers.
53
77
  for container_system in Containerize.supported_systems():
54
- Containerize.stop_and_remove_container(
55
- container_system, args.ui_container)
78
+ Containerize.stop_and_remove_container(container_system, args.ui_container)
56
79
 
57
80
  # Check if the UI port is already being used.
58
81
  if is_port_used(args.ui_port):
59
- log.warning(f"It looks like the specified port for the UI ({args.ui_port}) is already in use. You can set another port in the Qleverfile in the [ui] section with the UI_PORT variable.")
82
+ log.warning(
83
+ f"It looks like the specified port for the UI ({args.ui_port}) is already in use. You can set another port in the Qleverfile in the [ui] section with the UI_PORT variable."
84
+ )
60
85
 
61
86
  # Try to start the QLever UI.
62
87
  try:
@@ -68,7 +93,9 @@ class UiCommand(QleverCommand):
68
93
  return False
69
94
 
70
95
  # Success.
71
- log.info(f"The QLever UI should now be up at {ui_url} ..."
72
- f"You can log in as QLever UI admin with username and "
73
- f"password \"demo\"")
96
+ log.info(
97
+ f"The QLever UI should now be up at {ui_url} ..."
98
+ f"You can log in as QLever UI admin with username and "
99
+ f'password "demo"'
100
+ )
74
101
  return True
qlever/containerize.py CHANGED
@@ -9,7 +9,7 @@ import subprocess
9
9
  from typing import Optional
10
10
 
11
11
  from qlever.log import log
12
- from qlever.util import run_command
12
+ from qlever.util import run_command, get_random_string
13
13
 
14
14
 
15
15
  class ContainerizeException(Exception):
@@ -31,12 +31,16 @@ class Containerize:
31
31
  return ["docker", "podman"]
32
32
 
33
33
  @staticmethod
34
- def containerize_command(cmd: str, container_system: str,
35
- run_subcommand: str,
36
- image_name: str, container_name: str,
37
- volumes: list[tuple[str, str]] = [],
38
- ports: list[tuple[int, int]] = [],
39
- working_directory: Optional[str] = None) -> str:
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
+ ) -> str:
40
44
  """
41
45
  Get the command to run `cmd` with the given `container_system` and the
42
46
  given options.
@@ -45,8 +49,9 @@ class Containerize:
45
49
  # Check that `container_system` is supported.
46
50
  if container_system not in Containerize.supported_systems():
47
51
  return ContainerizeException(
48
- f"Invalid container system \"{container_system}\""
49
- f" (must be one of {Containerize.supported_systems()})")
52
+ f'Invalid container system "{container_system}"'
53
+ f" (must be one of {Containerize.supported_systems()})"
54
+ )
50
55
 
51
56
  # Set user and group ids. This is important so that the files created
52
57
  # by the containerized command are owned by the user running the
@@ -62,37 +67,40 @@ class Containerize:
62
67
  # dir.
63
68
  volume_options = "".join([f" -v {v1}:{v2}" for v1, v2 in volumes])
64
69
  port_options = "".join([f" -p {p1}:{p2}" for p1, p2 in ports])
65
- working_directory_option = (f" -w {working_directory}"
66
- if working_directory is not None else "")
70
+ working_directory_option = (
71
+ f" -w {working_directory}" if working_directory is not None else ""
72
+ )
67
73
 
68
74
  # Construct the command that runs `cmd` with the given container
69
75
  # system.
70
- containerized_cmd = (f"{container_system} {run_subcommand}"
71
- f"{user_option}"
72
- f" -v /etc/localtime:/etc/localtime:ro"
73
- f"{volume_options}"
74
- f"{port_options}"
75
- f"{working_directory_option}"
76
- f" --init"
77
- f" --entrypoint bash"
78
- f" --name {container_name} {image_name}"
79
- f" -c {shlex.quote(cmd)}")
76
+ containerized_cmd = (
77
+ f"{container_system} {run_subcommand}"
78
+ f"{user_option}"
79
+ f" -v /etc/localtime:/etc/localtime:ro"
80
+ f"{volume_options}"
81
+ f"{port_options}"
82
+ f"{working_directory_option}"
83
+ f" --init"
84
+ f" --entrypoint bash"
85
+ f" --name {container_name} {image_name}"
86
+ f" -c {shlex.quote(cmd)}"
87
+ )
80
88
  return containerized_cmd
81
89
 
82
90
  @staticmethod
83
91
  def is_running(container_system: str, container_name: str) -> bool:
84
92
  # Note: the `{{{{` and `}}}}` result in `{{` and `}}`, respectively.
85
93
  containers = (
86
- run_command(f"{container_system} ps --format=\"{{{{.Names}}}}\"",
87
- return_output=True)
94
+ run_command(
95
+ f'{container_system} ps --format="{{{{.Names}}}}"', return_output=True
96
+ )
88
97
  .strip()
89
98
  .splitlines()
90
99
  )
91
100
  return container_name in containers
92
101
 
93
102
  @staticmethod
94
- def stop_and_remove_container(container_system: str,
95
- container_name: str) -> bool:
103
+ def stop_and_remove_container(container_system: str, container_name: str) -> bool:
96
104
  """
97
105
  Stop the container with the given name using the given system. Return
98
106
  `True` if a container with that name was found and stopped, `False`
@@ -102,19 +110,45 @@ class Containerize:
102
110
  # Check that `container_system` is supported.
103
111
  if container_system not in Containerize.supported_systems():
104
112
  return ContainerizeException(
105
- f"Invalid container system \"{container_system}\""
106
- f" (must be one of {Containerize.supported_systems()})")
113
+ f'Invalid container system "{container_system}"'
114
+ f" (must be one of {Containerize.supported_systems()})"
115
+ )
107
116
 
108
117
  # Construct the command that stops the container.
109
- stop_cmd = f"{container_system} stop {container_name} && " \
110
- f"{container_system} rm {container_name}"
118
+ stop_cmd = (
119
+ f"{container_system} stop {container_name} && "
120
+ f"{container_system} rm {container_name}"
121
+ )
111
122
 
112
123
  # Run the command.
113
124
  try:
114
- subprocess.run(stop_cmd, shell=True, check=True,
115
- stdout=subprocess.DEVNULL,
116
- stderr=subprocess.DEVNULL)
125
+ subprocess.run(
126
+ stop_cmd,
127
+ shell=True,
128
+ check=True,
129
+ stdout=subprocess.DEVNULL,
130
+ stderr=subprocess.DEVNULL,
131
+ )
117
132
  return True
118
133
  except Exception as e:
119
- log.debug(f"Error running \"{stop_cmd}\": {e}")
134
+ log.debug(f'Error running "{stop_cmd}": {e}')
120
135
  return False
136
+
137
+ @staticmethod
138
+ def run_in_container(cmd: str, args) -> Optional[str]:
139
+ """
140
+ Run an arbitrary command in the qlever container and return its output.
141
+ """
142
+ if args.system in Containerize.supported_systems():
143
+ if not args.server_container:
144
+ args.server_container = get_random_string(20)
145
+ run_cmd = Containerize().containerize_command(
146
+ cmd,
147
+ args.system,
148
+ 'run --rm -it --entrypoint "" ',
149
+ args.image,
150
+ args.server_container,
151
+ volumes=[("$(pwd)", "/index")],
152
+ working_directory="/index",
153
+ )
154
+ return run_command(run_cmd, return_output=True)
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
 
@@ -63,8 +62,16 @@ class Qleverfile:
63
62
  help="A space-separated list of patterns that match "
64
63
  "all the files of the dataset")
65
64
  index_args["cat_input_files"] = arg(
66
- "--cat-input-files", type=str, required=True,
65
+ "--cat-input-files", type=str,
67
66
  help="The command that produces the input")
67
+ index_args["multi_input_json"] = arg(
68
+ "--multi-input-json", type=str, default=None,
69
+ help="JSON to specify multiple input files, each with a "
70
+ "`cmd` (command that writes the triples to stdout), "
71
+ "`format` (format like for the `--format` option), "
72
+ "`graph` (name of the graph, use `-` for the default graph), "
73
+ "`parallel` (parallel parsing for large files, where all "
74
+ "prefix declaration are at the beginning)")
68
75
  index_args["settings_json"] = arg(
69
76
  "--settings-json", type=str, default="{}",
70
77
  help="The `.settings.json` file for the index")
@@ -106,7 +113,7 @@ class Qleverfile:
106
113
  help="The binary for starting the server (this requires "
107
114
  "that you have compiled QLever on your machine)")
108
115
  server_args["host_name"] = arg(
109
- "--host-name", type=str, default=f"localhost",
116
+ "--host-name", type=str, default="localhost",
110
117
  help="The name of the host on which the server listens for "
111
118
  "requests")
112
119
  server_args["port"] = arg(