cloud-sql-python-connector 1.21.0__py3-none-any.whl → 1.22.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloud-sql-python-connector
3
- Version: 1.21.0
3
+ Version: 1.22.0
4
4
  Summary: Google Cloud SQL Python Connector library
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License-Expression: Apache-2.0
@@ -37,6 +37,8 @@ Provides-Extra: pytds
37
37
  Requires-Dist: python-tds>=1.15.0; extra == "pytds"
38
38
  Provides-Extra: asyncpg
39
39
  Requires-Dist: asyncpg>=0.30.0; extra == "asyncpg"
40
+ Provides-Extra: psycopg
41
+ Requires-Dist: psycopg>=3.1.0; extra == "psycopg"
40
42
  Dynamic: license-file
41
43
 
42
44
  <p align="center">
@@ -82,7 +84,8 @@ providing the following benefits:
82
84
  The Cloud SQL Python Connector is a package to be used alongside a database driver.
83
85
  Currently supported drivers are:
84
86
  - [`pymysql`](https://github.com/PyMySQL/PyMySQL) (MySQL)
85
- - [`pg8000`](https://github.com/tlocke/pg8000) (PostgreSQL)
87
+ - [`pg8000`](https://codeberg.org/tlocke/pg8000) (PostgreSQL)
88
+ - [`psycopg`](https://github.com/psycopg/psycopg) (PostgreSQL)
86
89
  - [`asyncpg`](https://github.com/MagicStack/asyncpg) (PostgreSQL)
87
90
  - [`pytds`](https://github.com/denisenkom/pytds) (SQL Server)
88
91
 
@@ -97,12 +100,16 @@ based on your database dialect.
97
100
  pip install "cloud-sql-python-connector[pymysql]"
98
101
  ```
99
102
  ### Postgres
100
- There are two different database drivers that are supported for the Postgres dialect:
103
+ There are three different database drivers that are supported for the Postgres dialect:
101
104
 
102
105
  #### pg8000
103
106
  ```
104
107
  pip install "cloud-sql-python-connector[pg8000]"
105
108
  ```
109
+ #### psycopg
110
+ ```
111
+ pip install "cloud-sql-python-connector[psycopg]"
112
+ ```
106
113
  #### asyncpg
107
114
  ```
108
115
  pip install "cloud-sql-python-connector[asyncpg]"
@@ -178,6 +185,18 @@ pool = sqlalchemy.create_engine(
178
185
  db="my-db-name"
179
186
  ),
180
187
  )
188
+
189
+ # Or with Postgres (psycopg):
190
+ pool = sqlalchemy.create_engine(
191
+ "postgresql+psycopg://",
192
+ creator=lambda: connector.connect(
193
+ "project:region:instance",
194
+ "psycopg",
195
+ user="my-user",
196
+ password="my-password",
197
+ db="my-db-name"
198
+ ),
199
+ )
181
200
  ```
182
201
 
183
202
  The returned connection pool engine can then be used to query and modify the database.
@@ -0,0 +1,26 @@
1
+ cloud_sql_python_connector-1.22.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
+ google/cloud/sql/connector/__init__.py,sha256=pfa-FD-VRpYI49OUul9p6UFDp7G_yT2f6WS9rZMn8CM,1148
3
+ google/cloud/sql/connector/asyncpg.py,sha256=MpZhwFke0WXvYD3OcEF1Ok0qHl-CU7ZIPy85PCog5QY,2002
4
+ google/cloud/sql/connector/client.py,sha256=he2qm0cUQD4FGzMfT6eBye-KLEQtFdQsedsawNUhm7c,14598
5
+ google/cloud/sql/connector/connection_info.py,sha256=INQMBXGxXSth2MsAnqe9e9pCOiqMj55V3fzwxCmoAZc,5399
6
+ google/cloud/sql/connector/connection_name.py,sha256=SCCVeWb8YN0f9My09_l9FGTr6iRbi1b_52TJQaS6xzM,2574
7
+ google/cloud/sql/connector/connector.py,sha256=dEn9rN5fzRx4BE891f-lBeEpRNi7MhnkTvkuq5TLoMY,28040
8
+ google/cloud/sql/connector/enums.py,sha256=wESKVNRFNwKk-Bs093zM6YSCGEgaDHu3yLBscWuKpn8,2994
9
+ google/cloud/sql/connector/exceptions.py,sha256=hFdDZRR48qLYSFI_CRxbftrKkt77Jl5ci6SEHcAGMok,2209
10
+ google/cloud/sql/connector/instance.py,sha256=xz-TLSaYOyUQdbKsv2m-MDvA2kjJCkcaj3GXfsYIw2g,8691
11
+ google/cloud/sql/connector/lazy.py,sha256=N3Ud8KobhASnsH0pACG8oEHjmS7Z7MVzkHJ9skDLxMo,4932
12
+ google/cloud/sql/connector/monitored_cache.py,sha256=n-nG19VZ7hfzz0Dbg245mrkhRga1f-bP56aRku212mk,6656
13
+ google/cloud/sql/connector/pg8000.py,sha256=tbqFSIoSguMX1hTZ4kWeBOKLhLHKX1Vq5SespfA0Y0w,1801
14
+ google/cloud/sql/connector/psycopg.py,sha256=LcGencGQlQPDKWTeQrk1SiLMtVB09uZw6RdPQAosC8k,8038
15
+ google/cloud/sql/connector/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ google/cloud/sql/connector/pymysql.py,sha256=MOau37nTOTesUR5Hor7lLtEckvvGThzjECEWQYHcM8U,2166
17
+ google/cloud/sql/connector/pytds.py,sha256=e-KBCqv0m3a7cRi9YmFHNuYqClhlJbf-KoF611w-1Ck,2304
18
+ google/cloud/sql/connector/rate_limiter.py,sha256=60k-r9h-nUBqovVIHnIGbW0NMgUGD4exH_rZ02ukNa0,2924
19
+ google/cloud/sql/connector/refresh_utils.py,sha256=kouF2OJc-NEu7MQS94GVJ7jN04-7a_wLKL-46kAVQuM,5219
20
+ google/cloud/sql/connector/resolver.py,sha256=4U8G0Pxwmy64qoRmP5shLnmIykxcGcGCoFSH_7tr120,6353
21
+ google/cloud/sql/connector/utils.py,sha256=ApLG4z4H0S4lWXQ9PyclXbrEoY5VrshqN9QB6BG8lyg,4132
22
+ google/cloud/sql/connector/version.py,sha256=0JV131RUM4deaBtRNiyn9A-CSQMDjxlhfH0wdHi3fGQ,598
23
+ cloud_sql_python_connector-1.22.0.dist-info/METADATA,sha256=Sn1H3KwvLEFOSGWPJY5APuFykTx73RLrk5DjCCP_t50,31731
24
+ cloud_sql_python_connector-1.22.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
25
+ cloud_sql_python_connector-1.22.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
26
+ cloud_sql_python_connector-1.22.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (83.0.0)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -51,7 +51,9 @@ async def connect(
51
51
  'Unable to import module "asyncpg." Please install and try again.'
52
52
  )
53
53
  user = kwargs.pop("user")
54
- db = kwargs.pop("db")
54
+ db = kwargs.pop("database", kwargs.pop("db", None))
55
+ if db is None:
56
+ raise KeyError("database")
55
57
  passwd = kwargs.pop("password", None)
56
58
 
57
59
  return await asyncpg.connect(
@@ -31,6 +31,7 @@ from google.auth.credentials import with_scopes_if_required
31
31
 
32
32
  from google.cloud.sql.connector import asyncpg
33
33
  from google.cloud.sql.connector import pg8000
34
+ from google.cloud.sql.connector import psycopg
34
35
  from google.cloud.sql.connector import pymysql
35
36
  from google.cloud.sql.connector import pytds
36
37
  from google.cloud.sql.connector.client import CloudSQLClient
@@ -362,6 +363,7 @@ class Connector:
362
363
  "pg8000": pg8000.connect,
363
364
  "asyncpg": asyncpg.connect,
364
365
  "pytds": pytds.connect,
366
+ "psycopg": psycopg.connect,
365
367
  }
366
368
 
367
369
  # only accept supported database drivers
@@ -62,6 +62,7 @@ class DriverMapping(Enum):
62
62
 
63
63
  ASYNCPG = "POSTGRES"
64
64
  PG8000 = "POSTGRES" # noqa: PIE796
65
+ PSYCOPG = "POSTGRES" # noqa: PIE796
65
66
  PYMYSQL = "MYSQL"
66
67
  PYTDS = "SQLSERVER"
67
68
 
@@ -73,6 +73,12 @@ class DnsResolutionError(Exception):
73
73
  """
74
74
 
75
75
 
76
+ class DnsLoopError(DnsResolutionError):
77
+ """
78
+ Exception to be raised when a CNAME loop is detected during DNS resolution.
79
+ """
80
+
81
+
76
82
  class CacheClosedError(Exception):
77
83
  """
78
84
  Exception to be raised when a ConnectionInfoCache can not be accessed after
@@ -19,13 +19,17 @@ import logging
19
19
  import ssl
20
20
  from typing import Any, Callable
21
21
 
22
+ import aiohttp
23
+
22
24
  from google.cloud.sql.connector.connection_info import ConnectionInfo
23
25
  from google.cloud.sql.connector.connection_info import ConnectionInfoCache
24
26
  from google.cloud.sql.connector.exceptions import CacheClosedError
27
+ from google.cloud.sql.connector.exceptions import DnsLoopError
25
28
  from google.cloud.sql.connector.instance import RefreshAheadCache
26
29
  from google.cloud.sql.connector.lazy import LazyRefreshCache
27
30
  from google.cloud.sql.connector.resolver import DefaultResolver
28
31
  from google.cloud.sql.connector.resolver import DnsResolver
32
+ from google.cloud.sql.connector.resolver import is_instance_dns_name
29
33
 
30
34
  logger = logging.getLogger(name=__name__)
31
35
 
@@ -44,7 +48,16 @@ class MonitoredCache(ConnectionInfoCache):
44
48
 
45
49
  # If domain name is configured for instance and failover period is set,
46
50
  # poll for DNS record changes.
47
- if self.cache.conn_name.domain_name and failover_period > 0:
51
+ is_instance_dns = False
52
+ if self.cache.conn_name.domain_name:
53
+ is_instance_dns = is_instance_dns_name(
54
+ self.cache.conn_name.domain_name or ""
55
+ )
56
+ if (
57
+ self.cache.conn_name.domain_name
58
+ and failover_period > 0
59
+ and not is_instance_dns
60
+ ):
48
61
  self.domain_name_ticker = asyncio.create_task(
49
62
  ticker(failover_period, self._check_domain_name)
50
63
  )
@@ -90,12 +103,28 @@ class MonitoredCache(ConnectionInfoCache):
90
103
  await self.close()
91
104
 
92
105
  except Exception as e: # noqa: BLE001
93
- # Domain name checks should not be fatal, log error and continue.
94
- logger.debug(
95
- f"['{self.cache.conn_name}']: Unable to check domain name, "
96
- f"domain name {self.cache.conn_name.domain_name} did not "
97
- f"resolve: {e}"
106
+ if self._is_non_transient(e):
107
+ logger.debug(
108
+ f"['{self.cache.conn_name}']: Domain name check failed with "
109
+ f"non-transient error, closing all connections: {e}"
110
+ )
111
+ await self.close()
112
+ else:
113
+ # Domain name checks should not be fatal for transient errors, log error and continue.
114
+ logger.debug(
115
+ f"['{self.cache.conn_name}']: Unable to check domain name, "
116
+ f"domain name {self.cache.conn_name.domain_name} did not "
117
+ f"resolve (transient error): {e}"
118
+ )
119
+
120
+ def _is_non_transient(self, e: Exception) -> bool:
121
+ return (
122
+ isinstance(e, (ValueError, DnsLoopError))
123
+ or (
124
+ isinstance(e, aiohttp.ClientResponseError)
125
+ and e.status in (403, 404)
98
126
  )
127
+ )
99
128
 
100
129
  async def connect_info(self) -> ConnectionInfo:
101
130
  if self.closed:
@@ -48,7 +48,9 @@ def connect(
48
48
  )
49
49
 
50
50
  user = kwargs.pop("user")
51
- db = kwargs.pop("db")
51
+ db = kwargs.pop("database", kwargs.pop("db", None))
52
+ if db is None:
53
+ raise KeyError("database")
52
54
  passwd = kwargs.pop("password", None)
53
55
  return pg8000.dbapi.connect(
54
56
  user,
@@ -0,0 +1,235 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import logging
16
+ import os
17
+ import selectors
18
+ import socket
19
+ import ssl
20
+ import tempfile
21
+ import threading
22
+ from typing import Any, TYPE_CHECKING
23
+
24
+ if TYPE_CHECKING:
25
+ import psycopg
26
+
27
+ logger = logging.getLogger(name=__name__)
28
+
29
+
30
+ def _proxy(local: socket.socket, remote: "ssl.SSLSocket") -> None:
31
+ """Single-threaded selectors-based proxy to avoid SSLSocket thread-safety issues."""
32
+ sel = selectors.DefaultSelector()
33
+ sel.register(local, selectors.EVENT_READ, data="local")
34
+ sel.register(remote, selectors.EVENT_READ, data="remote")
35
+
36
+ def forward_pending() -> bool:
37
+ """Read any pending decrypted data from SSL buffer and forward it.
38
+ Returns True if EOF was reached or error occurred (should exit).
39
+ """
40
+ if not hasattr(remote, "pending"):
41
+ return False
42
+ pending_bytes = remote.pending()
43
+ if not isinstance(pending_bytes, int):
44
+ return False
45
+
46
+ while pending_bytes > 0:
47
+ try:
48
+ data = remote.recv(8192)
49
+ except OSError as e:
50
+ logger.debug("psycopg proxy: remote recv pending error: %s", e)
51
+ return True
52
+ if not data:
53
+ logger.debug("psycopg proxy: remote pending EOF")
54
+ return True
55
+ try:
56
+ local.sendall(data)
57
+ except OSError as e:
58
+ logger.debug("psycopg proxy: local send pending error: %s", e)
59
+ return True
60
+ try:
61
+ pending_bytes = remote.pending()
62
+ except OSError:
63
+ break
64
+ if not isinstance(pending_bytes, int):
65
+ break
66
+ return False
67
+
68
+ try:
69
+ while True:
70
+ # First check if there is any pending data in SSL buffer
71
+ if forward_pending():
72
+ break
73
+
74
+ events = sel.select()
75
+
76
+ for key, mask in events:
77
+ if key.data == "local":
78
+ try:
79
+ data = local.recv(8192)
80
+ except OSError as e:
81
+ logger.debug("psycopg proxy: local recv error: %s", e)
82
+ return
83
+ if not data:
84
+ logger.debug("psycopg proxy: local EOF")
85
+ return
86
+ try:
87
+ remote.sendall(data)
88
+ except OSError as e:
89
+ logger.debug("psycopg proxy: remote send error: %s", e)
90
+ return
91
+ elif key.data == "remote":
92
+ try:
93
+ data = remote.recv(8192)
94
+ except OSError as e:
95
+ logger.debug("psycopg proxy: remote recv error: %s", e)
96
+ return
97
+ if not data:
98
+ logger.debug("psycopg proxy: remote EOF")
99
+ return
100
+ try:
101
+ local.sendall(data)
102
+ except OSError as e:
103
+ logger.debug("psycopg proxy: local send error: %s", e)
104
+ return
105
+ except OSError as e:
106
+ logger.debug("psycopg proxy: OSError in loop: %s", e)
107
+ finally:
108
+ sel.close()
109
+ for s in (local, remote):
110
+ try:
111
+ s.shutdown(socket.SHUT_RDWR)
112
+ except OSError:
113
+ pass
114
+ try:
115
+ s.close()
116
+ except OSError:
117
+ pass
118
+
119
+
120
+ def connect(
121
+ ip_address: str, remote_sock: "ssl.SSLSocket", **kwargs: Any
122
+ ) -> "psycopg.Connection":
123
+ """Create a psycopg DBAPI connection object.
124
+
125
+ Because psycopg does not accept a pre-connected socket, this function
126
+ creates a temporary Unix domain socket, tells psycopg to connect there,
127
+ and runs a background proxy that forwards bytes between that socket and
128
+ the already-established Cloud SQL TLS connection.
129
+
130
+ Args:
131
+ ip_address (str): IP address of the Cloud SQL instance.
132
+ remote_sock (ssl.SSLSocket): SSL/TLS secure socket stream connected to the
133
+ Cloud SQL proxy server.
134
+
135
+ Returns:
136
+ psycopg.Connection: A psycopg Connection object for the Cloud SQL instance.
137
+ """
138
+ try:
139
+ import psycopg
140
+ except ImportError:
141
+ raise ImportError(
142
+ 'Unable to import module "psycopg." Please install and try again.'
143
+ )
144
+
145
+ if not hasattr(socket, "AF_UNIX"):
146
+ raise NotImplementedError(
147
+ "Unix domain sockets (AF_UNIX) are not supported on this platform"
148
+ )
149
+
150
+ tmpdir = tempfile.mkdtemp()
151
+ socket_path = os.path.join(tmpdir, ".s.PGSQL.5432")
152
+ logger.debug("psycopg: created Unix socket at %s", socket_path)
153
+
154
+ local_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
155
+ local_sock.bind(socket_path)
156
+ local_sock.listen(1)
157
+
158
+ def _accept_and_proxy() -> None:
159
+ """Accept one connection then proxy bytes until the connection closes."""
160
+ unix_conn = None
161
+ try:
162
+ unix_conn, _ = local_sock.accept()
163
+ local_sock.close()
164
+ logger.debug("psycopg proxy: accepted connection, starting proxy")
165
+ _proxy(unix_conn, remote_sock)
166
+ except Exception as e: # noqa: BLE001
167
+ logger.debug("psycopg proxy: error in accept/proxy thread: %s", e)
168
+ # Ensure cleanup on any exception
169
+ if unix_conn:
170
+ try:
171
+ unix_conn.shutdown(socket.SHUT_RDWR)
172
+ except OSError:
173
+ pass
174
+ try:
175
+ unix_conn.close()
176
+ except OSError:
177
+ pass
178
+ try:
179
+ remote_sock.shutdown(socket.SHUT_RDWR)
180
+ except OSError:
181
+ pass
182
+ try:
183
+ remote_sock.close()
184
+ except OSError:
185
+ pass
186
+
187
+ threading.Thread(target=_accept_and_proxy, daemon=True).start()
188
+
189
+ user = kwargs.pop("user")
190
+ db = kwargs.pop("db")
191
+ passwd = kwargs.pop("password", None)
192
+ # SSL is already handled by the underlying SSLSocket; disable it on the
193
+ # Unix socket so psycopg does not attempt a second TLS handshake.
194
+ kwargs.pop("sslmode", None)
195
+ timeout = kwargs.pop("timeout", None)
196
+ if timeout is not None:
197
+ kwargs["connect_timeout"] = int(timeout)
198
+
199
+ logger.debug("psycopg: connecting as user=%s dbname=%s", user, db)
200
+ try:
201
+ conn = psycopg.connect(
202
+ user=user,
203
+ dbname=db,
204
+ password=passwd,
205
+ host=tmpdir,
206
+ port=5432,
207
+ sslmode="disable",
208
+ **kwargs,
209
+ )
210
+ logger.debug("psycopg: connection established")
211
+ return conn
212
+ except Exception as e:
213
+ logger.debug("psycopg: connection failed: %s", e)
214
+ # psycopg never connected (or failed mid-handshake); close the server
215
+ # socket so the proxy thread unblocks and exits cleanly.
216
+ try:
217
+ local_sock.close()
218
+ except OSError:
219
+ pass
220
+ try:
221
+ remote_sock.close()
222
+ except OSError:
223
+ pass
224
+ raise
225
+ finally:
226
+ # The socket file and its parent directory are only needed during the
227
+ # initial connect() call; remove them now regardless of outcome.
228
+ try:
229
+ os.remove(socket_path)
230
+ except OSError:
231
+ pass
232
+ try:
233
+ os.rmdir(tmpdir)
234
+ except OSError:
235
+ pass
@@ -52,6 +52,13 @@ def connect(
52
52
  # pop timeout as timeout arg is called 'connect_timeout' for pymysql
53
53
  timeout = kwargs.pop("timeout")
54
54
  kwargs["connect_timeout"] = kwargs.get("connect_timeout", timeout)
55
+
56
+ # map 'db' to 'database' to avoid deprecation warning in pymysql,
57
+ # giving precedence to 'database' if both are provided
58
+ db = kwargs.pop("database", kwargs.pop("db", None))
59
+ if db is not None:
60
+ kwargs["database"] = db
61
+
55
62
  # Create pymysql connection object and hand in pre-made connection
56
63
  conn = pymysql.Connection(host=ip_address, defer_connect=True, **kwargs)
57
64
  conn.connect(sock)
@@ -47,7 +47,7 @@ def connect(ip_address: str, sock: ssl.SSLSocket, **kwargs: Any) -> "pytds.Conne
47
47
  'Unable to import module "pytds." Please install and try again.'
48
48
  )
49
49
 
50
- db = kwargs.pop("db", None)
50
+ db = kwargs.pop("database", kwargs.pop("db", None))
51
51
 
52
52
  if kwargs.pop("active_directory_auth", False):
53
53
  if platform.system() == "Windows":
@@ -25,12 +25,25 @@ from google.cloud.sql.connector.connection_name import (
25
25
  _parse_connection_name_with_domain_name,
26
26
  )
27
27
  from google.cloud.sql.connector.connection_name import ConnectionName
28
+ from google.cloud.sql.connector.exceptions import DnsLoopError
28
29
  from google.cloud.sql.connector.exceptions import DnsResolutionError
29
30
 
30
- PSC_DNS_PATTERN = re.compile(
31
- r"^([a-f0-9]{12})\.([^.]+)\.([a-z0-9]+-[a-z0-9]+)\.(sql|sql-psa|sql-psc)\.goog\.?$"
31
+ # ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])
32
+
33
+ INSTANCE_DNS_NAME_PATTERN = re.compile(
34
+ r"^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.(sql|sql-psa|sql-psc)\.goog\.?$"
32
35
  )
33
36
 
37
+ GLOBAL_INSTANCE_DNS_PATTERN = re.compile(r"\.global\.sql(-\w+)?\.goog\.?$")
38
+
39
+
40
+ def is_instance_dns_name(name: str) -> bool:
41
+ lower = name.lower()
42
+ return bool(
43
+ INSTANCE_DNS_NAME_PATTERN.search(lower)
44
+ and not GLOBAL_INSTANCE_DNS_PATTERN.search(lower)
45
+ )
46
+
34
47
 
35
48
  class DefaultResolver:
36
49
  """DefaultResolver simply validates and parses instance connection name."""
@@ -68,7 +81,7 @@ class DnsResolver(dns.asyncresolver.Resolver):
68
81
  pass
69
82
 
70
83
  dns_normalized = current.rstrip(".")
71
- match = PSC_DNS_PATTERN.match(dns_normalized.lower())
84
+ match = INSTANCE_DNS_NAME_PATTERN.match(dns_normalized.lower())
72
85
  if match:
73
86
  region = match.group(3)
74
87
  if region != "global":
@@ -102,7 +115,7 @@ class DnsResolver(dns.asyncresolver.Resolver):
102
115
 
103
116
  if cname_found:
104
117
  if cname in visited:
105
- raise DnsResolutionError(f"CNAME loop detected for `{dns}`")
118
+ raise DnsLoopError(f"CNAME loop detected for `{dns}`")
106
119
  visited.add(cname)
107
120
  current = cname
108
121
  continue
@@ -130,7 +143,7 @@ class DnsResolver(dns.asyncresolver.Resolver):
130
143
  else f"Unable to resolve TXT record for `{current}`"
131
144
  )
132
145
 
133
- raise DnsResolutionError(
146
+ raise DnsLoopError(
134
147
  f"CNAME loop detected or max resolution depth reached for `{dns}`"
135
148
  )
136
149
 
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "1.21.0"
15
+ __version__ = "1.22.0"
@@ -1,25 +0,0 @@
1
- cloud_sql_python_connector-1.21.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
- google/cloud/sql/connector/__init__.py,sha256=pfa-FD-VRpYI49OUul9p6UFDp7G_yT2f6WS9rZMn8CM,1148
3
- google/cloud/sql/connector/asyncpg.py,sha256=v4ohOnSYjHTOvJPhTE761xP1KKo4FUJQW_WAnNW_2g4,1918
4
- google/cloud/sql/connector/client.py,sha256=he2qm0cUQD4FGzMfT6eBye-KLEQtFdQsedsawNUhm7c,14598
5
- google/cloud/sql/connector/connection_info.py,sha256=INQMBXGxXSth2MsAnqe9e9pCOiqMj55V3fzwxCmoAZc,5399
6
- google/cloud/sql/connector/connection_name.py,sha256=SCCVeWb8YN0f9My09_l9FGTr6iRbi1b_52TJQaS6xzM,2574
7
- google/cloud/sql/connector/connector.py,sha256=9IDpZ8u1cQPoBvyrUoVuuoAcRAJ_RbCPsEUbsbIcXeY,27953
8
- google/cloud/sql/connector/enums.py,sha256=o8QzPw3xLlr9GruxdMbydJ1N1Y2N-dfzbSKFDgMnxRU,2953
9
- google/cloud/sql/connector/exceptions.py,sha256=WvgtVvbXWLAfEfTL8rAQsZOTjaJFVImMo1bookFBl3Y,2071
10
- google/cloud/sql/connector/instance.py,sha256=xz-TLSaYOyUQdbKsv2m-MDvA2kjJCkcaj3GXfsYIw2g,8691
11
- google/cloud/sql/connector/lazy.py,sha256=N3Ud8KobhASnsH0pACG8oEHjmS7Z7MVzkHJ9skDLxMo,4932
12
- google/cloud/sql/connector/monitored_cache.py,sha256=MHsn8KVCrsnLr9qI6lEe9Nctvj1Cp2NpErglrg-63Y0,5607
13
- google/cloud/sql/connector/pg8000.py,sha256=TnS_SNbgY19oNEawOx-qJyW-f_k42-SnR3VTBOSr5FI,1717
14
- google/cloud/sql/connector/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- google/cloud/sql/connector/pymysql.py,sha256=6EQLUaLgQ9V5IGal4m43sFmRlNEEMFDDixkceRuu5HA,1924
16
- google/cloud/sql/connector/pytds.py,sha256=KZxmJQQG9XfmsmUoEGX7RpMirWdoreJ2-_FuHO6Td-s,2280
17
- google/cloud/sql/connector/rate_limiter.py,sha256=60k-r9h-nUBqovVIHnIGbW0NMgUGD4exH_rZ02ukNa0,2924
18
- google/cloud/sql/connector/refresh_utils.py,sha256=kouF2OJc-NEu7MQS94GVJ7jN04-7a_wLKL-46kAVQuM,5219
19
- google/cloud/sql/connector/resolver.py,sha256=ecgE0MQUZqXoVwwV4RPNp416JQ9XqCwG4oMaHTfIzX0,5821
20
- google/cloud/sql/connector/utils.py,sha256=ApLG4z4H0S4lWXQ9PyclXbrEoY5VrshqN9QB6BG8lyg,4132
21
- google/cloud/sql/connector/version.py,sha256=T8B-4zjneVFweA_cav_qt3c8Lxvxp12wayrS4RtS_zg,598
22
- cloud_sql_python_connector-1.21.0.dist-info/METADATA,sha256=IbdOWUxj3-12_3LdcsuPru1UHjkGCMQCk2yxN-Rnaic,31243
23
- cloud_sql_python_connector-1.21.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
24
- cloud_sql_python_connector-1.21.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
25
- cloud_sql_python_connector-1.21.0.dist-info/RECORD,,