Plain is headed towards 1.0! Subscribe for development updates →

 1from __future__ import annotations
 2
 3import signal
 4from typing import Any
 5
 6from plain.models.backends.base.client import BaseDatabaseClient
 7
 8
 9class DatabaseClient(BaseDatabaseClient):
10    executable_name = "mysql"
11
12    @classmethod
13    def settings_to_cmd_args_env(
14        cls, settings_dict: dict[str, Any], parameters: list[str]
15    ) -> tuple[list[str], dict[str, str] | None]:
16        args = [cls.executable_name]
17        env = None
18        database = settings_dict["OPTIONS"].get(
19            "database",
20            settings_dict["OPTIONS"].get("db", settings_dict["NAME"]),
21        )
22        user = settings_dict["OPTIONS"].get("user", settings_dict["USER"])
23        password = settings_dict["OPTIONS"].get(
24            "password",
25            settings_dict["OPTIONS"].get("passwd", settings_dict["PASSWORD"]),
26        )
27        host = settings_dict["OPTIONS"].get("host", settings_dict["HOST"])
28        port = settings_dict["OPTIONS"].get("port", settings_dict["PORT"])
29        server_ca = settings_dict["OPTIONS"].get("ssl", {}).get("ca")
30        client_cert = settings_dict["OPTIONS"].get("ssl", {}).get("cert")
31        client_key = settings_dict["OPTIONS"].get("ssl", {}).get("key")
32        defaults_file = settings_dict["OPTIONS"].get("read_default_file")
33        charset = settings_dict["OPTIONS"].get("charset")
34        # Seems to be no good way to set sql_mode with CLI.
35
36        if defaults_file:
37            args += [f"--defaults-file={defaults_file}"]
38        if user:
39            args += [f"--user={user}"]
40        if password:
41            # The MYSQL_PWD environment variable usage is discouraged per
42            # MySQL's documentation due to the possibility of exposure through
43            # `ps` on old Unix flavors but --password suffers from the same
44            # flaw on even more systems. Usage of an environment variable also
45            # prevents password exposure if the subprocess.run(check=True) call
46            # raises a CalledProcessError since the string representation of
47            # the latter includes all of the provided `args`.
48            env = {"MYSQL_PWD": password}
49        if host:
50            if "/" in host:
51                args += [f"--socket={host}"]
52            else:
53                args += [f"--host={host}"]
54        if port:
55            args += [f"--port={port}"]
56        if server_ca:
57            args += [f"--ssl-ca={server_ca}"]
58        if client_cert:
59            args += [f"--ssl-cert={client_cert}"]
60        if client_key:
61            args += [f"--ssl-key={client_key}"]
62        if charset:
63            args += [f"--default-character-set={charset}"]
64        if database:
65            args += [database]
66        args.extend(parameters)
67        return args, env
68
69    def runshell(self, parameters: list[str]) -> None:
70        sigint_handler = signal.getsignal(signal.SIGINT)
71        try:
72            # Allow SIGINT to pass to mysql to abort queries.
73            signal.signal(signal.SIGINT, signal.SIG_IGN)
74            super().runshell(parameters)
75        finally:
76            # Restore the original SIGINT handler.
77            signal.signal(signal.SIGINT, sigint_handler)