|
1 | | -# Copyright (c) 2023, 2024, Oracle and/or its affiliates. |
| 1 | +# Copyright (c) 2023, 2025, Oracle and/or its affiliates. |
2 | 2 | # |
3 | 3 | # This program is free software; you can redistribute it and/or modify |
4 | 4 | # it under the terms of the GNU General Public License, version 2.0, as |
|
28 | 28 |
|
29 | 29 | """MySQL Connector/Python - MySQL driver written in Python.""" |
30 | 30 |
|
31 | | -__all__ = ["CMySQLConnection", "MySQLConnection", "connect"] |
| 31 | +from .connection import MySQLConnection, MySQLConnectionAbstract |
| 32 | +from .pooling import MySQLConnectionPool, PooledMySQLConnection, connect |
32 | 33 |
|
33 | | -import random |
34 | | - |
35 | | -from typing import Any |
36 | | - |
37 | | -from ..constants import DEFAULT_CONFIGURATION |
38 | | -from ..errors import Error, InterfaceError, ProgrammingError |
39 | | -from ..pooling import ERROR_NO_CEXT |
40 | | -from .abstracts import MySQLConnectionAbstract |
41 | | -from .connection import MySQLConnection |
42 | | - |
43 | | -try: |
44 | | - import dns.exception |
45 | | - import dns.resolver |
46 | | -except ImportError: |
47 | | - HAVE_DNSPYTHON = False |
48 | | -else: |
49 | | - HAVE_DNSPYTHON = True |
50 | | - |
51 | | - |
52 | | -try: |
53 | | - from .connection_cext import CMySQLConnection |
54 | | -except ImportError: |
55 | | - CMySQLConnection = None |
56 | | - |
57 | | - |
58 | | -async def connect(*args: Any, **kwargs: Any) -> MySQLConnectionAbstract: |
59 | | - """Creates or gets a MySQL connection object. |
60 | | -
|
61 | | - In its simpliest form, `connect()` will open a connection to a |
62 | | - MySQL server and return a `MySQLConnectionAbstract` subclass |
63 | | - object such as `MySQLConnection` or `CMySQLConnection`. |
64 | | -
|
65 | | - When any connection pooling arguments are given, for example `pool_name` |
66 | | - or `pool_size`, a pool is created or a previously one is used to return |
67 | | - a `PooledMySQLConnection`. |
68 | | -
|
69 | | - Args: |
70 | | - *args: N/A. |
71 | | - **kwargs: For a complete list of possible arguments, see [1]. If no arguments |
72 | | - are given, it uses the already configured or default values. |
73 | | -
|
74 | | - Returns: |
75 | | - A `MySQLConnectionAbstract` subclass instance (such as `MySQLConnection` or |
76 | | - a `CMySQLConnection`) instance. |
77 | | -
|
78 | | - Examples: |
79 | | - A connection with the MySQL server can be established using either the |
80 | | - `mysql.connector.connect()` method or a `MySQLConnectionAbstract` subclass: |
81 | | - ``` |
82 | | - >>> from mysql.connector.aio import MySQLConnection, HAVE_CEXT |
83 | | - >>> |
84 | | - >>> cnx1 = await mysql.connector.aio.connect(user='joe', database='test') |
85 | | - >>> cnx2 = MySQLConnection(user='joe', database='test') |
86 | | - >>> await cnx2.connect() |
87 | | - >>> |
88 | | - >>> cnx3 = None |
89 | | - >>> if HAVE_CEXT: |
90 | | - >>> from mysql.connector.aio import CMySQLConnection |
91 | | - >>> cnx3 = CMySQLConnection(user='joe', database='test') |
92 | | - ``` |
93 | | -
|
94 | | - References: |
95 | | - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html |
96 | | - """ |
97 | | - # DNS SRV |
98 | | - dns_srv = kwargs.pop("dns_srv") if "dns_srv" in kwargs else False |
99 | | - |
100 | | - if not isinstance(dns_srv, bool): |
101 | | - raise InterfaceError("The value of 'dns-srv' must be a boolean") |
102 | | - |
103 | | - if dns_srv: |
104 | | - if not HAVE_DNSPYTHON: |
105 | | - raise InterfaceError( |
106 | | - "MySQL host configuration requested DNS " |
107 | | - "SRV. This requires the Python dnspython " |
108 | | - "module. Please refer to documentation" |
109 | | - ) |
110 | | - if "unix_socket" in kwargs: |
111 | | - raise InterfaceError( |
112 | | - "Using Unix domain sockets with DNS SRV lookup is not allowed" |
113 | | - ) |
114 | | - if "port" in kwargs: |
115 | | - raise InterfaceError( |
116 | | - "Specifying a port number with DNS SRV lookup is not allowed" |
117 | | - ) |
118 | | - if "failover" in kwargs: |
119 | | - raise InterfaceError( |
120 | | - "Specifying multiple hostnames with DNS SRV look up is not allowed" |
121 | | - ) |
122 | | - if "host" not in kwargs: |
123 | | - kwargs["host"] = DEFAULT_CONFIGURATION["host"] |
124 | | - |
125 | | - try: |
126 | | - srv_records = dns.resolver.query(kwargs["host"], "SRV") |
127 | | - except dns.exception.DNSException: |
128 | | - raise InterfaceError( |
129 | | - f"Unable to locate any hosts for '{kwargs['host']}'" |
130 | | - ) from None |
131 | | - |
132 | | - failover = [] |
133 | | - for srv in srv_records: |
134 | | - failover.append( |
135 | | - { |
136 | | - "host": srv.target.to_text(omit_final_dot=True), |
137 | | - "port": srv.port, |
138 | | - "priority": srv.priority, |
139 | | - "weight": srv.weight, |
140 | | - } |
141 | | - ) |
142 | | - |
143 | | - failover.sort(key=lambda x: (x["priority"], -x["weight"])) |
144 | | - kwargs["failover"] = [ |
145 | | - {"host": srv["host"], "port": srv["port"]} for srv in failover |
146 | | - ] |
147 | | - |
148 | | - # Failover |
149 | | - if "failover" in kwargs: |
150 | | - return await _get_failover_connection(**kwargs) |
151 | | - |
152 | | - # Use C Extension by default |
153 | | - use_pure = kwargs.get("use_pure", False) |
154 | | - if "use_pure" in kwargs: |
155 | | - del kwargs["use_pure"] # Remove 'use_pure' from kwargs |
156 | | - if not use_pure and CMySQLConnection is None: |
157 | | - raise ImportError(ERROR_NO_CEXT) |
158 | | - |
159 | | - if CMySQLConnection and not use_pure: |
160 | | - cnx = CMySQLConnection(*args, **kwargs) |
161 | | - else: |
162 | | - cnx = MySQLConnection(*args, **kwargs) |
163 | | - await cnx.connect() |
164 | | - return cnx |
165 | | - |
166 | | - |
167 | | -async def _get_failover_connection(**kwargs: Any) -> MySQLConnectionAbstract: |
168 | | - """Return a MySQL connection and try to failover if needed. |
169 | | -
|
170 | | - An InterfaceError is raise when no MySQL is available. ValueError is |
171 | | - raised when the failover server configuration contains an illegal |
172 | | - connection argument. Supported arguments are user, password, host, port, |
173 | | - unix_socket and database. ValueError is also raised when the failover |
174 | | - argument was not provided. |
175 | | -
|
176 | | - Returns MySQLConnection instance. |
177 | | - """ |
178 | | - config = kwargs.copy() |
179 | | - try: |
180 | | - failover = config["failover"] |
181 | | - except KeyError: |
182 | | - raise ValueError("failover argument not provided") from None |
183 | | - del config["failover"] |
184 | | - |
185 | | - support_cnx_args = set( |
186 | | - [ |
187 | | - "user", |
188 | | - "password", |
189 | | - "host", |
190 | | - "port", |
191 | | - "unix_socket", |
192 | | - "database", |
193 | | - "pool_name", |
194 | | - "pool_size", |
195 | | - "priority", |
196 | | - ] |
197 | | - ) |
198 | | - |
199 | | - # First check if we can add all use the configuration |
200 | | - priority_count = 0 |
201 | | - for server in failover: |
202 | | - diff = set(server.keys()) - support_cnx_args |
203 | | - if diff: |
204 | | - arg = "s" if len(diff) > 1 else "" |
205 | | - lst = ", ".join(diff) |
206 | | - raise ValueError( |
207 | | - f"Unsupported connection argument {arg} in failover: {lst}" |
208 | | - ) |
209 | | - if hasattr(server, "priority"): |
210 | | - priority_count += 1 |
211 | | - |
212 | | - server["priority"] = server.get("priority", 100) |
213 | | - if server["priority"] < 0 or server["priority"] > 100: |
214 | | - raise InterfaceError( |
215 | | - "Priority value should be in the range of 0 to 100, " |
216 | | - f"got : {server['priority']}" |
217 | | - ) |
218 | | - if not isinstance(server["priority"], int): |
219 | | - raise InterfaceError( |
220 | | - "Priority value should be an integer in the range of 0 to " |
221 | | - f"100, got : {server['priority']}" |
222 | | - ) |
223 | | - |
224 | | - if 0 < priority_count < len(failover): |
225 | | - raise ProgrammingError( |
226 | | - "You must either assign no priority to any " |
227 | | - "of the routers or give a priority for " |
228 | | - "every router" |
229 | | - ) |
230 | | - |
231 | | - server_directory = {} |
232 | | - server_priority_list = [] |
233 | | - for server in sorted(failover, key=lambda x: x["priority"], reverse=True): |
234 | | - if server["priority"] not in server_directory: |
235 | | - server_directory[server["priority"]] = [server] |
236 | | - server_priority_list.append(server["priority"]) |
237 | | - else: |
238 | | - server_directory[server["priority"]].append(server) |
239 | | - |
240 | | - for priority in server_priority_list: |
241 | | - failover_list = server_directory[priority] |
242 | | - for _ in range(len(failover_list)): |
243 | | - last = len(failover_list) - 1 |
244 | | - index = random.randint(0, last) |
245 | | - server = failover_list.pop(index) |
246 | | - new_config = config.copy() |
247 | | - new_config.update(server) |
248 | | - new_config.pop("priority", None) |
249 | | - try: |
250 | | - return await connect(**new_config) |
251 | | - except Error: |
252 | | - # If we failed to connect, we try the next server |
253 | | - pass |
254 | | - |
255 | | - raise InterfaceError("Unable to connect to any of the target hosts") |
| 34 | +__all__ = [ |
| 35 | + "MySQLConnection", |
| 36 | + "connect", |
| 37 | + "MySQLConnectionAbstract", |
| 38 | + "MySQLConnectionPool", |
| 39 | + "PooledMySQLConnection", |
| 40 | +] |
0 commit comments