Plain is headed towards 1.0! Subscribe for development updates →

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