cloud-sql-python-connector 1.22.0__py3-none-any.whl → 1.23.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.
@@ -0,0 +1,654 @@
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
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ from collections.abc import Callable
19
+ import errno
20
+ import io
21
+ import logging
22
+ import queue
23
+ import socket
24
+ import threading
25
+ from typing import Any
26
+
27
+ from google.api_core.client_options import ClientOptions
28
+ from google.api_core.exceptions import FailedPrecondition
29
+ from google.api_core.exceptions import ResourceExhausted
30
+ from google.auth.credentials import Credentials
31
+ import grpc
32
+
33
+ from google.cloud import sqladmin_v1beta4
34
+ from google.cloud.sql.connector.enums import IPTypes
35
+ from google.cloud.sql.connector.exceptions import CloudSQLIPTypeError
36
+
37
+ SERVER_PROXY_PORT = 3307
38
+ _EOF_SENTINEL = object()
39
+ _STREAM_EOF = object()
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ def _is_grpc_status_error(
45
+ err: Exception,
46
+ exc_cls: type[Exception],
47
+ status_code: grpc.StatusCode,
48
+ ) -> bool:
49
+ """Checks whether an exception or its cause matches the given gRPC status."""
50
+ if isinstance(err, exc_cls):
51
+ return True
52
+ if isinstance(err, grpc.RpcError):
53
+ try:
54
+ return err.code() == status_code
55
+ except Exception: # noqa: BLE001, S110
56
+ pass
57
+ if hasattr(err, "code") and callable(err.code):
58
+ try:
59
+ return err.code() == status_code
60
+ except Exception: # noqa: BLE001, S110
61
+ pass
62
+ cause = getattr(err, "__cause__", None) or getattr(err, "__context__", None)
63
+ if isinstance(cause, Exception) and cause is not err:
64
+ return _is_grpc_status_error(cause, exc_cls, status_code)
65
+ return False
66
+
67
+
68
+ def is_resource_exhausted_error(err: Exception) -> bool:
69
+ """Checks whether an exception represents a RESOURCE_EXHAUSTED error."""
70
+ return _is_grpc_status_error(
71
+ err, ResourceExhausted, grpc.StatusCode.RESOURCE_EXHAUSTED
72
+ )
73
+
74
+
75
+ def is_failed_precondition_error(err: Exception) -> bool:
76
+ """Checks whether an exception represents a FAILED_PRECONDITION error."""
77
+ return _is_grpc_status_error(
78
+ err, FailedPrecondition, grpc.StatusCode.FAILED_PRECONDITION
79
+ )
80
+
81
+
82
+
83
+ class _RequestQueue:
84
+ """Thread-safe queue iterator feeding requests to synchronous gRPC stream."""
85
+
86
+ def __init__(self) -> None:
87
+ self._queue: queue.Queue = queue.Queue(maxsize=1024)
88
+ self._closed = False
89
+ self._lock = threading.Lock()
90
+
91
+ def put(self, item: Any) -> None:
92
+ with self._lock:
93
+ if self._closed:
94
+ raise BrokenPipeError(errno.EPIPE, "Stream request queue is closed")
95
+ self._queue.put(item)
96
+
97
+ def close(self) -> None:
98
+ with self._lock:
99
+ if self._closed:
100
+ return
101
+ self._closed = True
102
+ self._queue.put(_STREAM_EOF)
103
+
104
+ def __iter__(self) -> _RequestQueue:
105
+ return self
106
+
107
+ def __next__(self) -> Any:
108
+ item = self._queue.get()
109
+ if item is _STREAM_EOF:
110
+ raise StopIteration
111
+ return item
112
+
113
+
114
+ class SqlDataRawIO(io.RawIOBase):
115
+ """RawIO wrapper around SqlDataSocket to support makefile()."""
116
+
117
+ def __init__(self, sock: SqlDataSocket) -> None:
118
+ self._sock = sock
119
+
120
+ def readable(self) -> bool:
121
+ return True
122
+
123
+ def writable(self) -> bool:
124
+ return True
125
+
126
+ def seekable(self) -> bool:
127
+ return False
128
+
129
+ def readinto(self, b: Any) -> int:
130
+ return self._sock.recv_into(b)
131
+
132
+ def write(self, b: Any) -> int:
133
+ self._sock.sendall(b)
134
+ return len(b)
135
+
136
+ def close(self) -> None:
137
+ if not self.closed:
138
+ super().close()
139
+ self._sock.close()
140
+
141
+
142
+ class SqlDataSocket(socket.socket):
143
+ """Direct in-process socket adapter connected to a synchronous gRPC SqlData stream.
144
+
145
+ Provides full socket interface compatibility for synchronous database drivers
146
+ (pg8000, pymysql, pytds) while avoiding local TCP loopback and asyncio scheduling.
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ request_queue: _RequestQueue,
152
+ response_stream: Any,
153
+ grpc_client: sqladmin_v1beta4.SqlDataServiceClient | None = None,
154
+ timeout: float | None = None,
155
+ on_close: Callable[[], None] | None = None,
156
+ on_success: Callable[[], None] | None = None,
157
+ on_resource_exhausted: Callable[[Exception], None] | None = None,
158
+ fallback_fn: Callable[[], socket.socket] | None = None,
159
+ ) -> None:
160
+ super().__init__(socket.AF_INET, socket.SOCK_STREAM)
161
+ self._request_queue = request_queue
162
+ self._response_stream = response_stream
163
+ self._grpc_client = grpc_client
164
+ self._timeout = timeout
165
+ self._on_close = on_close
166
+ self._on_success = on_success
167
+ self._on_resource_exhausted = on_resource_exhausted
168
+ self._fallback_fn = fallback_fn
169
+
170
+ self._read_queue: queue.Queue = queue.Queue(maxsize=1024)
171
+ self._read_buf = b""
172
+ self._read_offset = 0
173
+ self._closed = False
174
+ self._first_read_done = False
175
+ self._write_buffer = bytearray()
176
+ self._direct_sock: socket.socket | None = None
177
+ self._error: Exception | None = None
178
+ self._close_lock = threading.Lock()
179
+
180
+ # Start background stream consumer thread
181
+ self._reader_thread = threading.Thread(
182
+ target=self._reader_loop, daemon=True, name="sqldata-reader"
183
+ )
184
+ self._reader_thread.start()
185
+
186
+ def _reader_loop(self) -> None:
187
+ try:
188
+ for resp in self._response_stream:
189
+ if self._closed:
190
+ break
191
+ raw_pb = (
192
+ sqladmin_v1beta4.StreamSqlDataResponse.pb(resp)
193
+ if hasattr(sqladmin_v1beta4.StreamSqlDataResponse, "pb")
194
+ and isinstance(resp, sqladmin_v1beta4.StreamSqlDataResponse)
195
+ else resp
196
+ )
197
+ which = (
198
+ raw_pb.WhichOneof("message")
199
+ if hasattr(raw_pb, "WhichOneof")
200
+ else None
201
+ )
202
+ if which == "terminate_session" or (
203
+ which is None
204
+ and hasattr(resp, "terminate_session")
205
+ and resp.terminate_session
206
+ ):
207
+ logger.debug("Received TerminateSession from server")
208
+ self._closed = True
209
+ break
210
+ if not self._first_read_done:
211
+ self._first_read_done = True
212
+ if self._on_success:
213
+ self._on_success()
214
+ if which == "data" or (which is None and hasattr(resp, "data") and resp.data):
215
+ data = resp.data.data
216
+ if data:
217
+ self._read_queue.put(data)
218
+ elif which == "session_metadata" or (
219
+ which is None and hasattr(resp, "session_metadata") and resp.session_metadata
220
+ ):
221
+ logger.debug("Received SessionMetadata")
222
+ except Exception as e: # noqa: BLE001
223
+ if not self._closed:
224
+ logger.debug(f"gRPC sync stream reader encountered: {e}")
225
+ self._error = e
226
+ if is_resource_exhausted_error(e) and self._on_resource_exhausted:
227
+ self._on_resource_exhausted(e)
228
+ finally:
229
+ self._read_queue.put(_EOF_SENTINEL)
230
+
231
+ def sendall( # type: ignore[override]
232
+ self, data: Any, flags: int = 0
233
+ ) -> None:
234
+ if self._direct_sock is not None:
235
+ self._direct_sock.sendall(data, flags)
236
+ return
237
+ if self._closed:
238
+ raise BrokenPipeError(errno.EPIPE, "Socket is closed")
239
+ if not data:
240
+ return
241
+ data_bytes = bytes(data) if not isinstance(data, bytes) else data
242
+ if not self._first_read_done:
243
+ self._write_buffer.extend(data_bytes)
244
+ packet = sqladmin_v1beta4.DataPacket(data=data_bytes)
245
+ req = sqladmin_v1beta4.StreamSqlDataRequest(data=packet)
246
+ self._request_queue.put(req)
247
+
248
+
249
+ def send( # type: ignore[override]
250
+ self, data: Any, flags: int = 0
251
+ ) -> int:
252
+ if self._direct_sock is not None:
253
+ return self._direct_sock.send(data, flags)
254
+ self.sendall(data, flags)
255
+ return len(data)
256
+
257
+ def recv(self, bufsize: int, flags: int = 0) -> bytes:
258
+ if self._direct_sock is not None:
259
+ return self._direct_sock.recv(bufsize, flags)
260
+
261
+ if bufsize <= 0:
262
+ return b""
263
+
264
+ # Return from buffered chunk if available
265
+ if self._read_offset < len(self._read_buf):
266
+ remaining = len(self._read_buf) - self._read_offset
267
+ to_copy = min(bufsize, remaining)
268
+ chunk = self._read_buf[self._read_offset : self._read_offset + to_copy]
269
+ self._read_offset += to_copy
270
+ if self._read_offset >= len(self._read_buf):
271
+ self._read_buf = b""
272
+ self._read_offset = 0
273
+ return chunk
274
+
275
+ # Pull from incoming queue
276
+ try:
277
+ item = self._read_queue.get(block=True, timeout=self._timeout)
278
+ except queue.Empty:
279
+ if self._closed:
280
+ return b""
281
+ raise TimeoutError("timed out")
282
+
283
+ if item is _EOF_SENTINEL:
284
+ if self._error is not None:
285
+ if (
286
+ not self._first_read_done
287
+ and self._fallback_fn is not None
288
+ and is_failed_precondition_error(self._error)
289
+ ):
290
+ logger.info(
291
+ f"SQL Data Service returned error before first read: {self._error}. "
292
+ "Triggering transparent fallback to direct TLS."
293
+ )
294
+ try:
295
+ self._direct_sock = self._fallback_fn()
296
+ self._direct_sock.settimeout(self._timeout)
297
+ if self._write_buffer:
298
+ self._direct_sock.sendall(bytes(self._write_buffer))
299
+ self._first_read_done = True
300
+ self._write_buffer.clear()
301
+ return self._direct_sock.recv(bufsize, flags)
302
+ except Exception as fallback_err:
303
+ raise fallback_err from self._error
304
+
305
+ raise OSError(
306
+ errno.ECONNRESET, f"Connection error: {self._error}"
307
+ ) from self._error
308
+ return b""
309
+
310
+ self._first_read_done = True
311
+ self._write_buffer.clear()
312
+ if len(item) <= bufsize:
313
+ return item
314
+
315
+ self._read_buf = item
316
+ self._read_offset = bufsize
317
+ return item[:bufsize]
318
+
319
+ def recv_into(self, buffer: Any, nbytes: int = 0, flags: int = 0) -> int:
320
+ if self._direct_sock is not None:
321
+ return self._direct_sock.recv_into(buffer, nbytes, flags)
322
+ target_len = len(buffer) if nbytes == 0 else min(nbytes, len(buffer))
323
+ if target_len <= 0:
324
+ return 0
325
+ data = self.recv(target_len, flags)
326
+ n = len(data)
327
+ buffer[:n] = data
328
+ return n
329
+
330
+ def makefile( # type: ignore[override]
331
+ self,
332
+ mode: str = "r",
333
+ buffering: int = -1,
334
+ encoding: str | None = None,
335
+ errors: str | None = None,
336
+ newline: str | None = None,
337
+ ) -> Any:
338
+ if self._direct_sock is not None:
339
+ return self._direct_sock.makefile( # type: ignore[call-overload]
340
+ mode=mode,
341
+ buffering=buffering,
342
+ encoding=encoding,
343
+ errors=errors,
344
+ newline=newline,
345
+ )
346
+ raw = SqlDataRawIO(self)
347
+ if buffering == 0:
348
+ return raw
349
+
350
+ reading = "r" in mode or "+" in mode
351
+ writing = "w" in mode or "a" in mode or "+" in mode
352
+ binary = "b" in mode
353
+
354
+ buf: Any
355
+ if reading and writing:
356
+ buf = io.BufferedRWPair(raw, raw)
357
+ elif reading:
358
+ buffer_size = io.DEFAULT_BUFFER_SIZE if buffering <= 0 else buffering
359
+ buf = io.BufferedReader(raw, buffer_size=buffer_size)
360
+ elif writing:
361
+ buffer_size = io.DEFAULT_BUFFER_SIZE if buffering <= 0 else buffering
362
+ buf = io.BufferedWriter(raw, buffer_size=buffer_size)
363
+ else:
364
+ buffer_size = io.DEFAULT_BUFFER_SIZE if buffering <= 0 else buffering
365
+ buf = io.BufferedReader(raw, buffer_size=buffer_size)
366
+
367
+ if binary:
368
+ return buf
369
+ return io.TextIOWrapper(
370
+ buf, encoding=encoding, errors=errors, newline=newline
371
+ )
372
+
373
+ def settimeout(self, value: float | None) -> None:
374
+ if value is not None and value < 0:
375
+ raise ValueError("Timeout value must be non-negative")
376
+ self._timeout = value
377
+ if self._direct_sock is not None:
378
+ self._direct_sock.settimeout(value)
379
+
380
+ def gettimeout(self) -> float | None:
381
+ if self._direct_sock is not None:
382
+ return self._direct_sock.gettimeout()
383
+ return self._timeout
384
+
385
+ def setblocking(self, flag: bool) -> None:
386
+ self._timeout = None if flag else 0.0
387
+ if self._direct_sock is not None:
388
+ self._direct_sock.setblocking(flag)
389
+
390
+ def connect(self, *args: Any, **kwargs: Any) -> None:
391
+ # Already connected, no-op for driver compatibility (e.g. pymysql)
392
+ pass
393
+
394
+ def connect_ex(self, *args: Any, **kwargs: Any) -> int:
395
+ return 0
396
+
397
+ def setsockopt(self, *args: Any, **kwargs: Any) -> None:
398
+ if self._direct_sock is not None:
399
+ self._direct_sock.setsockopt(*args, **kwargs)
400
+
401
+ def getsockopt(self, *args: Any, **kwargs: Any) -> Any: # type: ignore[override]
402
+ if self._direct_sock is not None:
403
+ return self._direct_sock.getsockopt(*args, **kwargs)
404
+ return 0
405
+
406
+ def getsockname(self) -> tuple[str, int]:
407
+ if self._direct_sock is not None:
408
+ return self._direct_sock.getsockname()
409
+ return ("127.0.0.1", SERVER_PROXY_PORT)
410
+
411
+ def getpeername(self) -> tuple[str, int]:
412
+ if self._direct_sock is not None:
413
+ return self._direct_sock.getpeername()
414
+ return ("127.0.0.1", SERVER_PROXY_PORT)
415
+
416
+ def shutdown(self, how: int = socket.SHUT_RDWR) -> None:
417
+ self.close()
418
+
419
+ def close(self) -> None:
420
+ with self._close_lock:
421
+ if self._closed:
422
+ return
423
+ self._closed = True
424
+
425
+ if self._direct_sock is not None:
426
+ try:
427
+ self._direct_sock.close()
428
+ except Exception: # noqa: BLE001, S110
429
+ pass
430
+
431
+ self._request_queue.close()
432
+ self._read_queue.put(_EOF_SENTINEL)
433
+ try:
434
+ if hasattr(self._response_stream, "cancel"):
435
+ self._response_stream.cancel()
436
+ except Exception: # noqa: BLE001, S110
437
+ pass
438
+ try:
439
+ if (
440
+ self._grpc_client is not None
441
+ and hasattr(self._grpc_client, "transport")
442
+ and hasattr(self._grpc_client.transport, "close")
443
+ ):
444
+ self._grpc_client.transport.close()
445
+ except Exception: # noqa: BLE001, S110
446
+ pass
447
+ try:
448
+ super().close()
449
+ except Exception: # noqa: BLE001, S110
450
+ pass
451
+
452
+ if self._on_close:
453
+ try:
454
+ self._on_close()
455
+ except Exception: # noqa: BLE001, S110
456
+ pass
457
+
458
+
459
+ class SqlDataClient:
460
+ """Client that establishes direct synchronous gRPC SqlDataService connections."""
461
+
462
+ def __init__(
463
+ self,
464
+ endpoint: str,
465
+ credentials: Credentials,
466
+ quota_project: str | None = None,
467
+ timeout: float | None = None,
468
+ ) -> None:
469
+ self._endpoint = endpoint
470
+ self._credentials = credentials
471
+ self._quota_project = quota_project
472
+ self._timeout = timeout
473
+ self._grpc_client: sqladmin_v1beta4.SqlDataServiceClient | None = None
474
+ self._active_sockets: set[SqlDataSocket] = set()
475
+ self._on_close_callbacks: list[Callable[[], None]] = []
476
+
477
+ async def connect(
478
+ self,
479
+ instance_connection_name: str,
480
+ region: str,
481
+ project: str,
482
+ get_conn_info: Callable[[], Any],
483
+ enable_iam_auth: bool,
484
+ on_fallback: Callable[[str], None],
485
+ is_fallback_cached: Callable[[str], bool],
486
+ on_resource_exhausted: Callable[[Exception], None] | None = None,
487
+ on_success: Callable[[], None] | None = None,
488
+ connect_timeout: float = 30.0,
489
+ ) -> socket.socket:
490
+ """Connects via synchronous gRPC and returns a SqlDataSocket or direct TLS fallback socket."""
491
+ use_fallback = is_fallback_cached(instance_connection_name)
492
+
493
+ async def get_direct_socket_sync_factory() -> Callable[[], socket.socket]:
494
+ conn_info = await get_conn_info()
495
+ targets: list[str] = []
496
+ for t in [IPTypes.PRIVATE, IPTypes.PSC, IPTypes.PUBLIC]:
497
+ try:
498
+ targets.extend(conn_info.get_preferred_ips(t))
499
+ except CloudSQLIPTypeError as e:
500
+ logger.debug(f"IP type {t} not available: {e}")
501
+ continue
502
+ if not targets:
503
+ raise ValueError(
504
+ "Cannot fallback to direct connection: no IP address available."
505
+ )
506
+ ssl_context = await conn_info.create_ssl_context(enable_iam_auth)
507
+
508
+ def create_direct_sock() -> socket.socket:
509
+ last_ex: Exception | None = None
510
+ for target_ip in targets:
511
+ logger.debug(f"Direct TLS connecting to {target_ip}:{SERVER_PROXY_PORT}")
512
+ try:
513
+ raw_sock = socket.create_connection(
514
+ (target_ip, SERVER_PROXY_PORT), timeout=connect_timeout
515
+ )
516
+ ssl_sock = ssl_context.wrap_socket(
517
+ raw_sock, server_hostname=target_ip
518
+ )
519
+ ssl_sock.settimeout(None)
520
+ return ssl_sock
521
+ except Exception as e: # noqa: BLE001
522
+ logger.debug(f"Direct TLS connection to {target_ip} failed: {e}")
523
+ last_ex = e
524
+ if last_ex:
525
+ raise last_ex
526
+ raise ValueError(
527
+ "Cannot fallback to direct connection: no IP address available."
528
+ )
529
+
530
+ return create_direct_sock
531
+
532
+ if use_fallback:
533
+ logger.debug("Using cached fallback direct TLS connection")
534
+ create_direct = await get_direct_socket_sync_factory()
535
+ return create_direct()
536
+
537
+ # Connect synchronous gRPC stream via shared GAPIC client
538
+ if self._grpc_client is None:
539
+ endpoint = self._endpoint.removeprefix("https://").removeprefix("http://")
540
+ client_options = ClientOptions(
541
+ api_endpoint=endpoint,
542
+ quota_project_id=self._quota_project,
543
+ )
544
+ self._grpc_client = sqladmin_v1beta4.SqlDataServiceClient(
545
+ credentials=self._credentials,
546
+ client_options=client_options,
547
+ )
548
+ grpc_client = self._grpc_client
549
+
550
+ instance_id = (
551
+ f"projects/{project}/instances/{instance_connection_name.split(':')[-1]}"
552
+ )
553
+ location_id = f"locations/{region}"
554
+
555
+ metadata = [
556
+ (
557
+ "x-goog-request-params",
558
+ f"instance_id={instance_id}&location_id={location_id}",
559
+ )
560
+ ]
561
+
562
+ loop = asyncio.get_running_loop()
563
+
564
+ def fallback_fn() -> socket.socket:
565
+ on_fallback(instance_connection_name)
566
+ try:
567
+ create_direct_sock_fn = asyncio.run_coroutine_threadsafe(
568
+ get_direct_socket_sync_factory(), loop
569
+ ).result(timeout=connect_timeout)
570
+ except Exception as e:
571
+ logger.debug(f"Could not prepare direct fallback factory: {e}")
572
+ raise ValueError(
573
+ "No direct fallback connection factory available."
574
+ ) from e
575
+ return create_direct_sock_fn()
576
+
577
+ try:
578
+ request_queue = _RequestQueue()
579
+ start_session = sqladmin_v1beta4.StartSession(
580
+ instance_id=instance_id, location_id=location_id
581
+ )
582
+ req = sqladmin_v1beta4.StreamSqlDataRequest(
583
+ start_session=start_session
584
+ )
585
+ request_queue.put(req)
586
+
587
+ response_stream: Any
588
+ if hasattr(grpc_client, "transport") and hasattr(
589
+ grpc_client.transport, "stream_sql_data"
590
+ ):
591
+ response_stream = grpc_client.transport.stream_sql_data( # type: ignore[call-arg]
592
+ request_queue, # type: ignore[arg-type]
593
+ metadata=metadata,
594
+ timeout=self._timeout,
595
+ )
596
+ else:
597
+ response_stream = grpc_client.stream_sql_data( # type: ignore[arg-type]
598
+ requests=request_queue, metadata=metadata, timeout=self._timeout
599
+ )
600
+
601
+ sock = SqlDataSocket(
602
+ request_queue=request_queue,
603
+ response_stream=response_stream,
604
+ grpc_client=None,
605
+ timeout=connect_timeout,
606
+ on_close=lambda: self._active_sockets.discard(sock),
607
+ on_success=on_success,
608
+ on_resource_exhausted=on_resource_exhausted,
609
+ fallback_fn=fallback_fn,
610
+ )
611
+ self._active_sockets.add(sock)
612
+ return sock
613
+
614
+ except Exception as e:
615
+ logger.debug(f"Sync gRPC connection attempt failed: {e}")
616
+ if is_resource_exhausted_error(e):
617
+ if on_resource_exhausted:
618
+ on_resource_exhausted(e)
619
+ raise
620
+
621
+ if not is_failed_precondition_error(e):
622
+ raise
623
+
624
+ # Fallback to direct TLS on FAILED_PRECONDITION
625
+ logger.info(
626
+ f"SQL Data Service connection failed for {instance_connection_name}. "
627
+ "Falling back to direct TLS connection."
628
+ )
629
+ on_fallback(instance_connection_name)
630
+ create_direct = await get_direct_socket_sync_factory()
631
+ return create_direct()
632
+
633
+ async def close(self) -> None:
634
+ """Closes all active sockets created by this client."""
635
+ for sock in list(self._active_sockets):
636
+ try:
637
+ sock.close()
638
+ except Exception: # noqa: BLE001, S110
639
+ pass
640
+ self._active_sockets.clear()
641
+ if self._grpc_client is not None:
642
+ try:
643
+ if hasattr(self._grpc_client, "transport") and hasattr(
644
+ self._grpc_client.transport, "close"
645
+ ):
646
+ self._grpc_client.transport.close()
647
+ except Exception: # noqa: BLE001, S110
648
+ pass
649
+ self._grpc_client = None
650
+ for cb in self._on_close_callbacks:
651
+ try:
652
+ cb()
653
+ except Exception: # noqa: BLE001, S110
654
+ pass
@@ -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.22.0"
15
+ __version__ = "1.23.0"
@@ -1,26 +0,0 @@
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,,