mysql-shell-client 0.6.0__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.
- mysql_shell/__init__.py +7 -0
- mysql_shell/builders/__init__.py +7 -0
- mysql_shell/builders/authorization/__init__.py +5 -0
- mysql_shell/builders/authorization/base.py +23 -0
- mysql_shell/builders/authorization/charm.py +213 -0
- mysql_shell/builders/locking/__init__.py +5 -0
- mysql_shell/builders/locking/base.py +28 -0
- mysql_shell/builders/locking/charm.py +97 -0
- mysql_shell/builders/logging/__init__.py +5 -0
- mysql_shell/builders/logging/base.py +14 -0
- mysql_shell/builders/logging/charm.py +36 -0
- mysql_shell/builders/quoting.py +46 -0
- mysql_shell/clients/__init__.py +5 -0
- mysql_shell/clients/cluster.py +414 -0
- mysql_shell/clients/instance.py +524 -0
- mysql_shell/executors/__init__.py +5 -0
- mysql_shell/executors/base.py +36 -0
- mysql_shell/executors/errors/__init__.py +4 -0
- mysql_shell/executors/errors/runtime.py +15 -0
- mysql_shell/executors/local.py +188 -0
- mysql_shell/models/__init__.py +8 -0
- mysql_shell/models/account.py +49 -0
- mysql_shell/models/cluster.py +57 -0
- mysql_shell/models/connection.py +22 -0
- mysql_shell/models/instance.py +27 -0
- mysql_shell/models/statement.py +30 -0
- mysql_shell_client-0.6.0.dist-info/METADATA +143 -0
- mysql_shell_client-0.6.0.dist-info/RECORD +30 -0
- mysql_shell_client-0.6.0.dist-info/WHEEL +4 -0
- mysql_shell_client-0.6.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import Generator
|
|
8
|
+
|
|
9
|
+
from ..models import ConnectionDetails
|
|
10
|
+
from .base import BaseExecutor
|
|
11
|
+
from .errors import ExecutionError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LocalExecutor(BaseExecutor):
|
|
15
|
+
"""Local executor for the MySQL Shell."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, conn_details: ConnectionDetails, shell_path: str):
|
|
18
|
+
"""Initialize the executor."""
|
|
19
|
+
super().__init__(conn_details, shell_path)
|
|
20
|
+
|
|
21
|
+
def _common_args(self) -> list[str]:
|
|
22
|
+
"""Return the list of common arguments."""
|
|
23
|
+
return [
|
|
24
|
+
self._shell_path,
|
|
25
|
+
"--json=raw",
|
|
26
|
+
"--save-passwords=never",
|
|
27
|
+
"--passwords-from-stdin",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
def _connection_args(self) -> list[str]:
|
|
31
|
+
"""Return the list of connection arguments."""
|
|
32
|
+
if self._conn_details.socket:
|
|
33
|
+
return [
|
|
34
|
+
f"--socket={self._conn_details.socket}",
|
|
35
|
+
f"--user={self._conn_details.username}",
|
|
36
|
+
]
|
|
37
|
+
else:
|
|
38
|
+
return [
|
|
39
|
+
f"--host={self._conn_details.host}",
|
|
40
|
+
f"--port={self._conn_details.port}",
|
|
41
|
+
f"--user={self._conn_details.username}",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
def _parse_error(self, output: str) -> dict:
|
|
45
|
+
"""Parse the execution error."""
|
|
46
|
+
error = next(self._iter_output(output, "error"), None)
|
|
47
|
+
if not error:
|
|
48
|
+
error = {}
|
|
49
|
+
|
|
50
|
+
return error
|
|
51
|
+
|
|
52
|
+
def _parse_output_py(self, output: str) -> str:
|
|
53
|
+
"""Parse the Python execution output."""
|
|
54
|
+
result = next(self._iter_output(output, "info"), None)
|
|
55
|
+
if not result:
|
|
56
|
+
result = "{}"
|
|
57
|
+
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
def _parse_output_sql(self, output: str) -> list:
|
|
61
|
+
"""Parse the SQL execution output."""
|
|
62
|
+
result = next(self._iter_output(output, "rows"), None)
|
|
63
|
+
if not result:
|
|
64
|
+
result = []
|
|
65
|
+
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _iter_output(output: str, key: str) -> Generator:
|
|
70
|
+
"""Iterates over the log lines in reversed order."""
|
|
71
|
+
logs = output.split("\n")
|
|
72
|
+
|
|
73
|
+
# MySQL Shell always prints prompts and warnings first
|
|
74
|
+
for log in reversed(logs):
|
|
75
|
+
if not log:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
log = json.loads(log)
|
|
79
|
+
val = log.get(key)
|
|
80
|
+
if not isinstance(val, str) or val.strip():
|
|
81
|
+
yield val
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _strip_password(error: subprocess.SubprocessError):
|
|
85
|
+
"""Strip passwords from SQL scripts."""
|
|
86
|
+
if not hasattr(error, "cmd"):
|
|
87
|
+
return error
|
|
88
|
+
|
|
89
|
+
password_pattern = re.compile("(?<=IDENTIFIED BY ')[^']+(?=')")
|
|
90
|
+
password_replace = "*****"
|
|
91
|
+
|
|
92
|
+
for index, value in enumerate(error.cmd):
|
|
93
|
+
if "IDENTIFIED BY" in value:
|
|
94
|
+
error.cmd[index] = re.sub(password_pattern, password_replace, value)
|
|
95
|
+
|
|
96
|
+
return error
|
|
97
|
+
|
|
98
|
+
def check_connection(self) -> None:
|
|
99
|
+
"""Check the connection."""
|
|
100
|
+
command = [
|
|
101
|
+
*self._common_args(),
|
|
102
|
+
*self._connection_args(),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
subprocess.check_output(
|
|
107
|
+
command,
|
|
108
|
+
input=self._conn_details.password,
|
|
109
|
+
text=True,
|
|
110
|
+
)
|
|
111
|
+
except subprocess.CalledProcessError as exc:
|
|
112
|
+
err = self._parse_error(exc.output)
|
|
113
|
+
raise ExecutionError(err)
|
|
114
|
+
except subprocess.TimeoutExpired:
|
|
115
|
+
raise ExecutionError()
|
|
116
|
+
|
|
117
|
+
def execute_py(self, script: str, *, timeout: int | None = None) -> str:
|
|
118
|
+
"""Execute a Python script.
|
|
119
|
+
|
|
120
|
+
Arguments:
|
|
121
|
+
script: Python script to execute
|
|
122
|
+
timeout: Optional timeout seconds
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
String with the output of the MySQL Shell command.
|
|
126
|
+
The output cannot be parsed to JSON, as the output depends on the script
|
|
127
|
+
"""
|
|
128
|
+
# Prepend every Python command with useWizards=False, to disable interactive mode.
|
|
129
|
+
# Cannot be set on command line as it conflicts with --passwords-from-stdin.
|
|
130
|
+
script = "shell.options.set('useWizards', False)\n" + script
|
|
131
|
+
|
|
132
|
+
command = [
|
|
133
|
+
*self._common_args(),
|
|
134
|
+
*self._connection_args(),
|
|
135
|
+
"--py",
|
|
136
|
+
"--execute",
|
|
137
|
+
script,
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
output = subprocess.check_output(
|
|
142
|
+
command,
|
|
143
|
+
timeout=timeout,
|
|
144
|
+
input=self._conn_details.password,
|
|
145
|
+
text=True,
|
|
146
|
+
)
|
|
147
|
+
except subprocess.CalledProcessError as exc:
|
|
148
|
+
err = self._parse_error(exc.output)
|
|
149
|
+
raise ExecutionError(err)
|
|
150
|
+
except subprocess.TimeoutExpired:
|
|
151
|
+
raise ExecutionError()
|
|
152
|
+
else:
|
|
153
|
+
return self._parse_output_py(output)
|
|
154
|
+
|
|
155
|
+
def execute_sql(self, script: str, *, timeout: int | None = None) -> list[dict]:
|
|
156
|
+
"""Execute a SQL script.
|
|
157
|
+
|
|
158
|
+
Arguments:
|
|
159
|
+
script: SQL script to execute
|
|
160
|
+
timeout: Optional timeout seconds
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of dictionaries, one per returned row
|
|
164
|
+
"""
|
|
165
|
+
command = [
|
|
166
|
+
*self._common_args(),
|
|
167
|
+
*self._connection_args(),
|
|
168
|
+
"--sql",
|
|
169
|
+
"--execute",
|
|
170
|
+
script,
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
output = subprocess.check_output(
|
|
175
|
+
command,
|
|
176
|
+
timeout=timeout,
|
|
177
|
+
input=self._conn_details.password,
|
|
178
|
+
text=True,
|
|
179
|
+
)
|
|
180
|
+
except subprocess.CalledProcessError as exc:
|
|
181
|
+
err = self._parse_error(exc.output)
|
|
182
|
+
exc = self._strip_password(exc)
|
|
183
|
+
raise ExecutionError(err) from exc
|
|
184
|
+
except subprocess.TimeoutExpired as exc:
|
|
185
|
+
exc = self._strip_password(exc)
|
|
186
|
+
raise ExecutionError() from exc
|
|
187
|
+
else:
|
|
188
|
+
return self._parse_output_sql(output)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Role:
|
|
10
|
+
"""MySQL role account."""
|
|
11
|
+
|
|
12
|
+
rolename: str
|
|
13
|
+
hostname: str = "%"
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_row(cls, rolename: str, hostname: str):
|
|
17
|
+
"""Create a role account from a MySQL row."""
|
|
18
|
+
return Role(
|
|
19
|
+
rolename=rolename,
|
|
20
|
+
hostname=hostname,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class User:
|
|
26
|
+
"""MySQL user account."""
|
|
27
|
+
|
|
28
|
+
username: str
|
|
29
|
+
hostname: str = "%"
|
|
30
|
+
attributes: dict | None = None
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_row(cls, username: str, hostname: str, attributes: str | None):
|
|
34
|
+
"""Create a user account from a MySQL row."""
|
|
35
|
+
if not attributes:
|
|
36
|
+
attributes = "{}"
|
|
37
|
+
|
|
38
|
+
return User(
|
|
39
|
+
username=username,
|
|
40
|
+
hostname=hostname,
|
|
41
|
+
attributes=json.loads(attributes),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def serialize_attrs(self) -> str:
|
|
45
|
+
"""Serialize the user attributes."""
|
|
46
|
+
if not self.attributes:
|
|
47
|
+
return "{}"
|
|
48
|
+
|
|
49
|
+
return json.dumps(self.attributes)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ClusterSetStatus(str, Enum):
|
|
8
|
+
"""MySQL cluster-set statuses.
|
|
9
|
+
|
|
10
|
+
https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset-status.html
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
HEALTHY = "HEALTHY"
|
|
14
|
+
AVAILABLE = "AVAILABLE"
|
|
15
|
+
UNAVAILABLE = "UNAVAILABLE"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ClusterGlobalStatus(str, Enum):
|
|
19
|
+
"""MySQL cluster statuses within a cluster-set context.
|
|
20
|
+
|
|
21
|
+
https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset-status.html
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
OK = "OK"
|
|
25
|
+
OK_NOT_REPLICATING = "OK_NOT_REPLICATING"
|
|
26
|
+
OK_NOT_CONSISTENT = "OK_NOT_CONSISTENT"
|
|
27
|
+
OK_MISCONFIGURED = "OK_MISCONFIGURED"
|
|
28
|
+
NOT_OK = "NOT_OK"
|
|
29
|
+
UNKNOWN = "UNKNOWN"
|
|
30
|
+
INVALIDATED = "INVALIDATED"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ClusterRole(str, Enum):
|
|
34
|
+
"""MySQL cluster roles."""
|
|
35
|
+
|
|
36
|
+
PRIMARY = "PRIMARY"
|
|
37
|
+
REPLICA = "REPLICA"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ClusterStatus(str, Enum):
|
|
41
|
+
"""MySQL cluster statuses.
|
|
42
|
+
|
|
43
|
+
There is a slight discrepancy between the possible cluster statuses reported
|
|
44
|
+
on different MySQL documentation pages, this list contains the common ones across them.
|
|
45
|
+
- https://dev.mysql.com/doc/mysql-shell/8.0/en/innodb-clusterset-status.html
|
|
46
|
+
- https://dev.mysql.com/doc/mysql-shell/8.0/en/monitoring-innodb-cluster.html
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
OK = "OK"
|
|
50
|
+
OK_PARTIAL = "OK_PARTIAL"
|
|
51
|
+
OK_NO_TOLERANCE = "OK_NO_TOLERANCE"
|
|
52
|
+
OK_NO_TOLERANCE_PARTIAL = "OK_NO_TOLERANCE_PARTIAL"
|
|
53
|
+
NO_QUORUM = "NO_QUORUM"
|
|
54
|
+
OFFLINE = "OFFLINE"
|
|
55
|
+
ERROR = "ERROR"
|
|
56
|
+
UNREACHABLE = "UNREACHABLE"
|
|
57
|
+
UNKNOWN = "UNKNOWN"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ConnectionDetails:
|
|
9
|
+
"""MySQL connection details."""
|
|
10
|
+
|
|
11
|
+
username: str
|
|
12
|
+
password: str
|
|
13
|
+
host: str = ""
|
|
14
|
+
port: str = ""
|
|
15
|
+
socket: str = ""
|
|
16
|
+
|
|
17
|
+
def __post_init__(self) -> None:
|
|
18
|
+
"""Validates that the connection details are correct."""
|
|
19
|
+
if (not self.host or not self.port) and not self.socket:
|
|
20
|
+
raise ValueError("Connection details must not be empty")
|
|
21
|
+
if (self.host or self.port) and self.socket:
|
|
22
|
+
raise ValueError("Connection details must not be state both TCP and socket values")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InstanceRole(str, Enum):
|
|
8
|
+
"""MySQL instance roles."""
|
|
9
|
+
|
|
10
|
+
PRIMARY = "PRIMARY"
|
|
11
|
+
SECONDARY = "SECONDARY"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InstanceState(str, Enum):
|
|
15
|
+
"""MySQL instance states.
|
|
16
|
+
|
|
17
|
+
There is a slight discrepancy between the possible instance states reported
|
|
18
|
+
by different MySQL mechanisms, this list contains the common ones across them.
|
|
19
|
+
- https://dev.mysql.com/doc/refman/8.0/en/group-replication-server-states.html
|
|
20
|
+
- https://dev.mysql.com/doc/mysql-shell/8.0/en/monitoring-innodb-cluster.html
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
ONLINE = "ONLINE"
|
|
24
|
+
RECOVERING = "RECOVERING"
|
|
25
|
+
OFFLINE = "OFFLINE"
|
|
26
|
+
ERROR = "ERROR"
|
|
27
|
+
UNREACHABLE = "UNREACHABLE"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LogType(str, Enum):
|
|
8
|
+
"""MySQL log types.
|
|
9
|
+
|
|
10
|
+
https://dev.mysql.com/doc/refman/8.0/en/flush.html#flush-logs
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
BINARY = "BINARY"
|
|
14
|
+
ENGINE = "ENGINE"
|
|
15
|
+
ERROR = "ERROR"
|
|
16
|
+
GENERAL = "GENERAL"
|
|
17
|
+
RELAY = "RELAY"
|
|
18
|
+
SLOW = "SLOW"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VariableScope(str, Enum):
|
|
22
|
+
"""MySQL variable scopes.
|
|
23
|
+
|
|
24
|
+
https://dev.mysql.com/doc/refman/8.0/en/set-variable.html
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
GLOBAL = "GLOBAL"
|
|
28
|
+
SESSION = "SESSION"
|
|
29
|
+
PERSIST = "PERSIST"
|
|
30
|
+
PERSIST_ONLY = "PERSIST_ONLY"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mysql-shell-client
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Python client for MySQL Shell
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Sinclert Perez
|
|
8
|
+
Author-email: sinclert.perez@canonical.com
|
|
9
|
+
Maintainer: Canonical Data Platform
|
|
10
|
+
Maintainer-email: data-platform@lists.launchpad.net
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Provides-Extra: format
|
|
19
|
+
Provides-Extra: lint
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: codespell (>=2.3,<3.0) ; extra == "lint"
|
|
22
|
+
Requires-Dist: coverage (>=7.6,<8.0) ; extra == "test"
|
|
23
|
+
Requires-Dist: pytest (>=8.3,<9.0) ; extra == "test"
|
|
24
|
+
Requires-Dist: ruff (>=0.9,<1.0) ; extra == "format"
|
|
25
|
+
Requires-Dist: ruff (>=0.9,<1.0) ; extra == "lint"
|
|
26
|
+
Project-URL: Changelog, https://github.com/canonical/mysql-shell-client/blob/main/CHANGELOG.md
|
|
27
|
+
Project-URL: Homepage, https://github.com/canonical/mysql-shell-client
|
|
28
|
+
Project-URL: Issues, https://github.com/canonical/mysql-shell-client/issues
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# MySQL Shell Python client
|
|
32
|
+
|
|
33
|
+
[![CI/CD Status][ci-status-badge]][ci-status-link]
|
|
34
|
+
[![Coverage Status][cov-status-badge]][cov-status-link]
|
|
35
|
+
[![Apache license][apache-license-badge]][apache-license-link]
|
|
36
|
+
|
|
37
|
+
MySQL Shell is an advanced client for MySQL Server that allow system administrator to perform both
|
|
38
|
+
cluster and instance level operations, using a single binary.
|
|
39
|
+
|
|
40
|
+
This project provides a Python client to perform the most common set of operations,
|
|
41
|
+
in addition to a set of predefined queries to cover most of the common use-cases.
|
|
42
|
+
|
|
43
|
+
## 🧑💻 Usage
|
|
44
|
+
|
|
45
|
+
1. Install the package from PyPi:
|
|
46
|
+
```shell
|
|
47
|
+
pip install mysql-shell-client
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. Import and build the executors:
|
|
51
|
+
```python
|
|
52
|
+
from mysql_shell.executors import LocalExecutor
|
|
53
|
+
from mysql_shell.models import ConnectionDetails
|
|
54
|
+
|
|
55
|
+
cluster_conn = ConnectionDetails(
|
|
56
|
+
username="...",
|
|
57
|
+
password="...",
|
|
58
|
+
host="...",
|
|
59
|
+
port="...",
|
|
60
|
+
)
|
|
61
|
+
instance_conn = ConnectionDetails(
|
|
62
|
+
username="...",
|
|
63
|
+
password="...",
|
|
64
|
+
host="...",
|
|
65
|
+
port="...",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
cluster_executor = LocalExecutor(cluster_conn, "mysqlsh")
|
|
69
|
+
instance_executor = LocalExecutor(instance_conn, "mysqlsh")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. Import and build the query builders **[optional]**:
|
|
73
|
+
```python
|
|
74
|
+
from mysql_shell.builders import CharmLockingQueryBuilder
|
|
75
|
+
|
|
76
|
+
# This is just an example
|
|
77
|
+
builder = CharmLockingQueryBuilder("mysql", "locking")
|
|
78
|
+
query = builder.build_table_creation_query()
|
|
79
|
+
rows = instance_executor.execute_sql(query)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
4. Import and build the clients:
|
|
83
|
+
```python
|
|
84
|
+
from mysql_shell.clients import MySQLClusterClient, MySQLInstanceClient
|
|
85
|
+
|
|
86
|
+
cluster_client = MySQLClusterClient(cluster_executor)
|
|
87
|
+
instance_client = MySQLInstanceClient(instance_executor)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## 🔧 Development
|
|
92
|
+
|
|
93
|
+
### Dependencies
|
|
94
|
+
In order to install all the development packages:
|
|
95
|
+
|
|
96
|
+
```shell
|
|
97
|
+
poetry install --all-extras
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Linting
|
|
101
|
+
All Python files are linted using [Ruff][docs-ruff], to run it:
|
|
102
|
+
|
|
103
|
+
```shell
|
|
104
|
+
tox -e lint
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Testing
|
|
108
|
+
Project testing is performed using [Pytest][docs-pytest], to run them:
|
|
109
|
+
|
|
110
|
+
```shell
|
|
111
|
+
tox -e unit
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```shell
|
|
115
|
+
export MYSQL_DATABASE="test"
|
|
116
|
+
export MYSQL_USERNAME="root"
|
|
117
|
+
export MYSQL_PASSWORD="root_pass"
|
|
118
|
+
export MYSQL_SHELL_PATH="mysqlsh"
|
|
119
|
+
|
|
120
|
+
podman-compose -f compose/mysql-8.0.yaml up --detach && tox -e integration
|
|
121
|
+
podman-compose -f compose/mysql-8.0.yaml down
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Release
|
|
125
|
+
Commits can be tagged to create releases of the package, in order to do so:
|
|
126
|
+
|
|
127
|
+
1. Bump up the version within the `pyproject.toml` file.
|
|
128
|
+
2. Add a new section to the `CHANGELOG.md`.
|
|
129
|
+
3. Commit + push the changes.
|
|
130
|
+
4. Trigger the [release workflow][github-workflows].
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
[apache-license-badge]: https://img.shields.io/badge/License-Apache%202.0-blue.svg
|
|
134
|
+
[apache-license-link]: https://github.com/canonical/mysql-shell-client/blob/main/LICENSE
|
|
135
|
+
[ci-status-badge]: https://github.com/canonical/mysql-shell-client/actions/workflows/ci.yaml/badge.svg?branch=main
|
|
136
|
+
[ci-status-link]: https://github.com/canonical/mysql-shell-client/actions/workflows/ci.yaml?query=branch%3Amain
|
|
137
|
+
[cov-status-badge]: https://codecov.io/gh/canonical/mysql-shell-client/branch/main/graph/badge.svg
|
|
138
|
+
[cov-status-link]: https://codecov.io/gh/canonical/mysql-shell-client
|
|
139
|
+
|
|
140
|
+
[docs-pytest]: https://docs.pytest.org/en/latest/#
|
|
141
|
+
[docs-ruff]: https://docs.astral.sh/ruff/
|
|
142
|
+
[github-workflows]: https://github.com/canonical/mysql-shell-client/actions/workflows/release.yaml
|
|
143
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
mysql_shell/__init__.py,sha256=MfYnXwgdAN0FdILtYZBo-gR6xpsDs9nBBVUI120R4co,169
|
|
2
|
+
mysql_shell/builders/__init__.py,sha256=hw7c-Z3DGnL_DnjU3zzj1wnQ73KmRWCYTQgu090GllU,173
|
|
3
|
+
mysql_shell/builders/authorization/__init__.py,sha256=mKu4sNa1L65d1gpzokxdHmGVGJOlUA8FufTOAus90-w,173
|
|
4
|
+
mysql_shell/builders/authorization/base.py,sha256=ueo_Wo6JsL9UW1JEUit5tJabLxKZok_Jgxdm3sElNEs,768
|
|
5
|
+
mysql_shell/builders/authorization/charm.py,sha256=PRglXU2UKhzo3tWdPLVJbTCBeJaEc8aVNRNndrrgFdU,7336
|
|
6
|
+
mysql_shell/builders/locking/__init__.py,sha256=Qy1Q_YDEDGAs7q9pmCWtZpcIi9P_C_3OIqUyNaroITM,161
|
|
7
|
+
mysql_shell/builders/locking/base.py,sha256=bZj943m_uvmHTtlBS4-q1mDN9u9b0vNXXSV6eYhMsKw,880
|
|
8
|
+
mysql_shell/builders/locking/charm.py,sha256=JaAae3CTxzgnZMbBx2l5BmA_4mxy8HHBggjnlIxTQ2U,3267
|
|
9
|
+
mysql_shell/builders/logging/__init__.py,sha256=zM_LjTJlOTNlgfli1CJFvKiuo0OqSgtUcyEUITgyXJg,161
|
|
10
|
+
mysql_shell/builders/logging/base.py,sha256=mFjOHl3iMah11Pddk95ap7QrozpKLRgEbV3qcUJy0ns,413
|
|
11
|
+
mysql_shell/builders/logging/charm.py,sha256=A6PelFVHxiTDOUnQ-vjlPwCGIlvokKN-3YcD_iAhOWY,1132
|
|
12
|
+
mysql_shell/builders/quoting.py,sha256=mhVo2md38m3ok9kn12lSDjl2-V_SKp-LWyvr3uSOSPo,1934
|
|
13
|
+
mysql_shell/clients/__init__.py,sha256=D4y63dGJUsq7Pklqo_vaijhVqdp640X-6H9eZDS8KCc,122
|
|
14
|
+
mysql_shell/clients/cluster.py,sha256=IIkumRhAyMhxjH7vT5cNHLLQi8djxVz8sxyzrPSQT5w,15024
|
|
15
|
+
mysql_shell/clients/instance.py,sha256=Vy_yhwxJ6T4ChiC4IiP9TVer8mCzC5A7O6ENyzHJuv8,18419
|
|
16
|
+
mysql_shell/executors/__init__.py,sha256=mplYyzgA-22b7GNjVUEmEiI4lN0tgIl9qlgNHe3tRPI,139
|
|
17
|
+
mysql_shell/executors/base.py,sha256=rsvOLSWvlxtYcVDq_FeNoEBphixv-iW0Fa1OdWqgG6M,1089
|
|
18
|
+
mysql_shell/executors/errors/__init__.py,sha256=zAJdgtRRGGcdMJg-5nNy3iS7FcGKyCstbQko0QT4n5k,111
|
|
19
|
+
mysql_shell/executors/errors/runtime.py,sha256=er7m8HECfeAkMgT7UI0TvWK9hh56MxL0meO291GqM0c,382
|
|
20
|
+
mysql_shell/executors/local.py,sha256=9wrL7o_vopZYtTEgwSCaeL949qJSFpo-u6oOUvOuDqU,5858
|
|
21
|
+
mysql_shell/models/__init__.py,sha256=erB_kCQjGfKik32UTcm_GUndZVCefPepKe60SkE99cU,196
|
|
22
|
+
mysql_shell/models/account.py,sha256=u8DGcq2SgxYxZKqmykQaSmcxTiO1yQt9yWMg1f_TtDw,1101
|
|
23
|
+
mysql_shell/models/cluster.py,sha256=rA1V7lohODVaJvvSEQKr325tz_eiafN5LsPO-ZH6ChQ,1546
|
|
24
|
+
mysql_shell/models/connection.py,sha256=YTxJP_49TWxQR6QZjP_u8OxHfPOubCEIHkckU38Fbt4,665
|
|
25
|
+
mysql_shell/models/instance.py,sha256=br2mvraBrMqigsUGb794n5XUybDD5e9eOVJEujyHd2k,745
|
|
26
|
+
mysql_shell/models/statement.py,sha256=rm9nf-qKlRc2snVyw2KgISdFuhdH7Kvext-7KQbhRZI,591
|
|
27
|
+
mysql_shell_client-0.6.0.dist-info/METADATA,sha256=8lUDCYLBPpRIyL1hufg2-vQsfNabBpcciemzo87UvHY,4645
|
|
28
|
+
mysql_shell_client-0.6.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
29
|
+
mysql_shell_client-0.6.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
30
|
+
mysql_shell_client-0.6.0.dist-info/RECORD,,
|