cloud-sql-python-connector 1.17.0.dev0__py3-none-any.whl → 1.18.1__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.
@@ -20,9 +20,10 @@ import asyncio
20
20
  from functools import partial
21
21
  import logging
22
22
  import os
23
+ import socket
23
24
  from threading import Thread
24
25
  from types import TracebackType
25
- from typing import Any, Optional, Union
26
+ from typing import Any, Callable, Optional, Union
26
27
 
27
28
  import google.auth
28
29
  from google.auth.credentials import Credentials
@@ -35,6 +36,7 @@ from google.cloud.sql.connector.enums import IPTypes
35
36
  from google.cloud.sql.connector.enums import RefreshStrategy
36
37
  from google.cloud.sql.connector.instance import RefreshAheadCache
37
38
  from google.cloud.sql.connector.lazy import LazyRefreshCache
39
+ from google.cloud.sql.connector.monitored_cache import MonitoredCache
38
40
  import google.cloud.sql.connector.pg8000 as pg8000
39
41
  import google.cloud.sql.connector.pymysql as pymysql
40
42
  import google.cloud.sql.connector.pytds as pytds
@@ -46,6 +48,7 @@ from google.cloud.sql.connector.utils import generate_keys
46
48
  logger = logging.getLogger(name=__name__)
47
49
 
48
50
  ASYNC_DRIVERS = ["asyncpg"]
51
+ SERVER_PROXY_PORT = 3307
49
52
  _DEFAULT_SCHEME = "https://"
50
53
  _DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
51
54
  _SQLADMIN_HOST_TEMPLATE = "sqladmin.{universe_domain}"
@@ -67,6 +70,7 @@ class Connector:
67
70
  universe_domain: Optional[str] = None,
68
71
  refresh_strategy: str | RefreshStrategy = RefreshStrategy.BACKGROUND,
69
72
  resolver: type[DefaultResolver] | type[DnsResolver] = DefaultResolver,
73
+ failover_period: int = 30,
70
74
  ) -> None:
71
75
  """Initializes a Connector instance.
72
76
 
@@ -114,6 +118,11 @@ class Connector:
114
118
  name. To resolve a DNS record to an instance connection name, use
115
119
  DnsResolver.
116
120
  Default: DefaultResolver
121
+
122
+ failover_period (int): The time interval in seconds between each
123
+ attempt to check if a failover has occured for a given instance.
124
+ Must be used with `resolver=DnsResolver` to have any effect.
125
+ Default: 30
117
126
  """
118
127
  # if refresh_strategy is str, convert to RefreshStrategy enum
119
128
  if isinstance(refresh_strategy, str):
@@ -143,9 +152,7 @@ class Connector:
143
152
  )
144
153
  # initialize dict to store caches, key is a tuple consisting of instance
145
154
  # connection name string and enable_iam_auth boolean flag
146
- self._cache: dict[
147
- tuple[str, bool], Union[RefreshAheadCache, LazyRefreshCache]
148
- ] = {}
155
+ self._cache: dict[tuple[str, bool], MonitoredCache] = {}
149
156
  self._client: Optional[CloudSQLClient] = None
150
157
 
151
158
  # initialize credentials
@@ -167,6 +174,7 @@ class Connector:
167
174
  self._enable_iam_auth = enable_iam_auth
168
175
  self._user_agent = user_agent
169
176
  self._resolver = resolver()
177
+ self._failover_period = failover_period
170
178
  # if ip_type is str, convert to IPTypes enum
171
179
  if isinstance(ip_type, str):
172
180
  ip_type = IPTypes._from_str(ip_type)
@@ -285,15 +293,19 @@ class Connector:
285
293
  driver=driver,
286
294
  )
287
295
  enable_iam_auth = kwargs.pop("enable_iam_auth", self._enable_iam_auth)
288
- if (instance_connection_string, enable_iam_auth) in self._cache:
289
- cache = self._cache[(instance_connection_string, enable_iam_auth)]
296
+
297
+ conn_name = await self._resolver.resolve(instance_connection_string)
298
+ # Cache entry must exist and not be closed
299
+ if (str(conn_name), enable_iam_auth) in self._cache and not self._cache[
300
+ (str(conn_name), enable_iam_auth)
301
+ ].closed:
302
+ monitored_cache = self._cache[(str(conn_name), enable_iam_auth)]
290
303
  else:
291
- conn_name = await self._resolver.resolve(instance_connection_string)
292
304
  if self._refresh_strategy == RefreshStrategy.LAZY:
293
305
  logger.debug(
294
306
  f"['{conn_name}']: Refresh strategy is set to lazy refresh"
295
307
  )
296
- cache = LazyRefreshCache(
308
+ cache: Union[LazyRefreshCache, RefreshAheadCache] = LazyRefreshCache(
297
309
  conn_name,
298
310
  self._client,
299
311
  self._keys,
@@ -309,8 +321,14 @@ class Connector:
309
321
  self._keys,
310
322
  enable_iam_auth,
311
323
  )
324
+ # wrap cache as a MonitoredCache
325
+ monitored_cache = MonitoredCache(
326
+ cache,
327
+ self._failover_period,
328
+ self._resolver,
329
+ )
312
330
  logger.debug(f"['{conn_name}']: Connection info added to cache")
313
- self._cache[(instance_connection_string, enable_iam_auth)] = cache
331
+ self._cache[(str(conn_name), enable_iam_auth)] = monitored_cache
314
332
 
315
333
  connect_func = {
316
334
  "pymysql": pymysql.connect,
@@ -321,7 +339,7 @@ class Connector:
321
339
 
322
340
  # only accept supported database drivers
323
341
  try:
324
- connector = connect_func[driver]
342
+ connector: Callable = connect_func[driver] # type: ignore
325
343
  except KeyError:
326
344
  raise KeyError(f"Driver '{driver}' is not supported.")
327
345
 
@@ -339,14 +357,14 @@ class Connector:
339
357
 
340
358
  # attempt to get connection info for Cloud SQL instance
341
359
  try:
342
- conn_info = await cache.connect_info()
360
+ conn_info = await monitored_cache.connect_info()
343
361
  # validate driver matches intended database engine
344
362
  DriverMapping.validate_engine(driver, conn_info.database_version)
345
363
  ip_address = conn_info.get_preferred_ip(ip_type)
346
364
  except Exception:
347
365
  # with an error from Cloud SQL Admin API call or IP type, invalidate
348
366
  # the cache and re-raise the error
349
- await self._remove_cached(instance_connection_string, enable_iam_auth)
367
+ await self._remove_cached(str(conn_name), enable_iam_auth)
350
368
  raise
351
369
  logger.debug(f"['{conn_info.conn_name}']: Connecting to {ip_address}:3307")
352
370
  # format `user` param for automatic IAM database authn
@@ -367,18 +385,28 @@ class Connector:
367
385
  await conn_info.create_ssl_context(enable_iam_auth),
368
386
  **kwargs,
369
387
  )
370
- # synchronous drivers are blocking and run using executor
388
+ # Create socket with SSLContext for sync drivers
389
+ ctx = await conn_info.create_ssl_context(enable_iam_auth)
390
+ sock = ctx.wrap_socket(
391
+ socket.create_connection((ip_address, SERVER_PROXY_PORT)),
392
+ server_hostname=ip_address,
393
+ )
394
+ # If this connection was opened using a domain name, then store it
395
+ # for later in case we need to forcibly close it on failover.
396
+ if conn_info.conn_name.domain_name:
397
+ monitored_cache.sockets.append(sock)
398
+ # Synchronous drivers are blocking and run using executor
371
399
  connect_partial = partial(
372
400
  connector,
373
401
  ip_address,
374
- await conn_info.create_ssl_context(enable_iam_auth),
402
+ sock,
375
403
  **kwargs,
376
404
  )
377
405
  return await self._loop.run_in_executor(None, connect_partial)
378
406
 
379
407
  except Exception:
380
408
  # with any exception, we attempt a force refresh, then throw the error
381
- await cache.force_refresh()
409
+ await monitored_cache.force_refresh()
382
410
  raise
383
411
 
384
412
  async def _remove_cached(
@@ -456,6 +484,7 @@ async def create_async_connector(
456
484
  universe_domain: Optional[str] = None,
457
485
  refresh_strategy: str | RefreshStrategy = RefreshStrategy.BACKGROUND,
458
486
  resolver: type[DefaultResolver] | type[DnsResolver] = DefaultResolver,
487
+ failover_period: int = 30,
459
488
  ) -> Connector:
460
489
  """Helper function to create Connector object for asyncio connections.
461
490
 
@@ -507,6 +536,11 @@ async def create_async_connector(
507
536
  DnsResolver.
508
537
  Default: DefaultResolver
509
538
 
539
+ failover_period (int): The time interval in seconds between each
540
+ attempt to check if a failover has occured for a given instance.
541
+ Must be used with `resolver=DnsResolver` to have any effect.
542
+ Default: 30
543
+
510
544
  Returns:
511
545
  A Connector instance configured with running event loop.
512
546
  """
@@ -525,4 +559,5 @@ async def create_async_connector(
525
559
  universe_domain=universe_domain,
526
560
  refresh_strategy=refresh_strategy,
527
561
  resolver=resolver,
562
+ failover_period=failover_period,
528
563
  )
@@ -77,3 +77,10 @@ class DnsResolutionError(Exception):
77
77
  Exception to be raised when an instance connection name can not be resolved
78
78
  from a DNS record.
79
79
  """
80
+
81
+
82
+ class CacheClosedError(Exception):
83
+ """
84
+ Exception to be raised when a ConnectionInfoCache can not be accessed after
85
+ it is closed.
86
+ """
@@ -24,6 +24,7 @@ import logging
24
24
 
25
25
  from google.cloud.sql.connector.client import CloudSQLClient
26
26
  from google.cloud.sql.connector.connection_info import ConnectionInfo
27
+ from google.cloud.sql.connector.connection_info import ConnectionInfoCache
27
28
  from google.cloud.sql.connector.connection_name import ConnectionName
28
29
  from google.cloud.sql.connector.exceptions import RefreshNotValidError
29
30
  from google.cloud.sql.connector.rate_limiter import AsyncRateLimiter
@@ -35,7 +36,7 @@ logger = logging.getLogger(name=__name__)
35
36
  APPLICATION_NAME = "cloud-sql-python-connector"
36
37
 
37
38
 
38
- class RefreshAheadCache:
39
+ class RefreshAheadCache(ConnectionInfoCache):
39
40
  """Cache that refreshes connection info in the background prior to expiration.
40
41
 
41
42
  Background tasks are used to schedule refresh attempts to get a new
@@ -74,6 +75,15 @@ class RefreshAheadCache:
74
75
  self._refresh_in_progress = asyncio.locks.Event()
75
76
  self._current: asyncio.Task = self._schedule_refresh(0)
76
77
  self._next: asyncio.Task = self._current
78
+ self._closed = False
79
+
80
+ @property
81
+ def conn_name(self) -> ConnectionName:
82
+ return self._conn_name
83
+
84
+ @property
85
+ def closed(self) -> bool:
86
+ return self._closed
77
87
 
78
88
  async def force_refresh(self) -> None:
79
89
  """
@@ -212,3 +222,4 @@ class RefreshAheadCache:
212
222
  # gracefully wait for tasks to cancel
213
223
  tasks = asyncio.gather(self._current, self._next, return_exceptions=True)
214
224
  await asyncio.wait_for(tasks, timeout=2.0)
225
+ self._closed = True
@@ -21,13 +21,14 @@ from typing import Optional
21
21
 
22
22
  from google.cloud.sql.connector.client import CloudSQLClient
23
23
  from google.cloud.sql.connector.connection_info import ConnectionInfo
24
+ from google.cloud.sql.connector.connection_info import ConnectionInfoCache
24
25
  from google.cloud.sql.connector.connection_name import ConnectionName
25
26
  from google.cloud.sql.connector.refresh_utils import _refresh_buffer
26
27
 
27
28
  logger = logging.getLogger(name=__name__)
28
29
 
29
30
 
30
- class LazyRefreshCache:
31
+ class LazyRefreshCache(ConnectionInfoCache):
31
32
  """Cache that refreshes connection info when a caller requests a connection.
32
33
 
33
34
  Only refreshes the cache when a new connection is requested and the current
@@ -62,6 +63,15 @@ class LazyRefreshCache:
62
63
  self._lock = asyncio.Lock()
63
64
  self._cached: Optional[ConnectionInfo] = None
64
65
  self._needs_refresh = False
66
+ self._closed = False
67
+
68
+ @property
69
+ def conn_name(self) -> ConnectionName:
70
+ return self._conn_name
71
+
72
+ @property
73
+ def closed(self) -> bool:
74
+ return self._closed
65
75
 
66
76
  async def force_refresh(self) -> None:
67
77
  """
@@ -121,4 +131,5 @@ class LazyRefreshCache:
121
131
  """Close is a no-op and provided purely for a consistent interface with
122
132
  other cache types.
123
133
  """
124
- pass
134
+ self._closed = True
135
+ return
@@ -0,0 +1,146 @@
1
+ # Copyright 2025 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 asyncio
16
+ import logging
17
+ import ssl
18
+ from typing import Any, Callable, Optional, Union
19
+
20
+ from google.cloud.sql.connector.connection_info import ConnectionInfo
21
+ from google.cloud.sql.connector.connection_info import ConnectionInfoCache
22
+ from google.cloud.sql.connector.exceptions import CacheClosedError
23
+ from google.cloud.sql.connector.instance import RefreshAheadCache
24
+ from google.cloud.sql.connector.lazy import LazyRefreshCache
25
+ from google.cloud.sql.connector.resolver import DefaultResolver
26
+ from google.cloud.sql.connector.resolver import DnsResolver
27
+
28
+ logger = logging.getLogger(name=__name__)
29
+
30
+
31
+ class MonitoredCache(ConnectionInfoCache):
32
+ def __init__(
33
+ self,
34
+ cache: Union[RefreshAheadCache, LazyRefreshCache],
35
+ failover_period: int,
36
+ resolver: Union[DefaultResolver, DnsResolver],
37
+ ) -> None:
38
+ self.resolver = resolver
39
+ self.cache = cache
40
+ self.domain_name_ticker: Optional[asyncio.Task] = None
41
+ self.sockets: list[ssl.SSLSocket] = []
42
+
43
+ # If domain name is configured for instance and failover period is set,
44
+ # poll for DNS record changes.
45
+ if self.cache.conn_name.domain_name and failover_period > 0:
46
+ self.domain_name_ticker = asyncio.create_task(
47
+ ticker(failover_period, self._check_domain_name)
48
+ )
49
+ logger.debug(
50
+ f"['{self.cache.conn_name}']: Configured polling of domain "
51
+ f"name with failover period of {failover_period} seconds."
52
+ )
53
+
54
+ @property
55
+ def closed(self) -> bool:
56
+ return self.cache.closed
57
+
58
+ def _purge_closed_sockets(self) -> None:
59
+ """Remove closed sockets from monitored cache.
60
+
61
+ If a socket is closed by the database driver we should remove it from
62
+ list of sockets.
63
+ """
64
+ open_sockets = []
65
+ for socket in self.sockets:
66
+ # Check fileno for if socket is closed. Will return
67
+ # -1 on failure, which will be used to signal socket closed.
68
+ if socket.fileno() != -1:
69
+ open_sockets.append(socket)
70
+ self.sockets = open_sockets
71
+
72
+ async def _check_domain_name(self) -> None:
73
+ # remove any closed connections from cache
74
+ self._purge_closed_sockets()
75
+ try:
76
+ # Resolve domain name and see if Cloud SQL instance connection name
77
+ # has changed. If it has, close all connections.
78
+ new_conn_name = await self.resolver.resolve(
79
+ self.cache.conn_name.domain_name
80
+ )
81
+ if new_conn_name != self.cache.conn_name:
82
+ logger.debug(
83
+ f"['{self.cache.conn_name}']: Cloud SQL instance changed "
84
+ f"from {self.cache.conn_name.get_connection_string()} to "
85
+ f"{new_conn_name.get_connection_string()}, closing all "
86
+ "connections!"
87
+ )
88
+ await self.close()
89
+
90
+ except Exception as e:
91
+ # Domain name checks should not be fatal, log error and continue.
92
+ logger.debug(
93
+ f"['{self.cache.conn_name}']: Unable to check domain name, "
94
+ f"domain name {self.cache.conn_name.domain_name} did not "
95
+ f"resolve: {e}"
96
+ )
97
+
98
+ async def connect_info(self) -> ConnectionInfo:
99
+ if self.closed:
100
+ raise CacheClosedError(
101
+ "Can not get connection info, cache has already been closed."
102
+ )
103
+ return await self.cache.connect_info()
104
+
105
+ async def force_refresh(self) -> None:
106
+ # if cache is closed do not refresh
107
+ if self.closed:
108
+ return
109
+ return await self.cache.force_refresh()
110
+
111
+ async def close(self) -> None:
112
+ # Cancel domain name ticker task.
113
+ if self.domain_name_ticker:
114
+ self.domain_name_ticker.cancel()
115
+ try:
116
+ await self.domain_name_ticker
117
+ except asyncio.CancelledError:
118
+ logger.debug(
119
+ f"['{self.cache.conn_name}']: Cancelled domain name polling task."
120
+ )
121
+ finally:
122
+ self.domain_name_ticker = None
123
+ # If cache is already closed, no further work.
124
+ if self.closed:
125
+ return
126
+
127
+ # Close underyling ConnectionInfoCache
128
+ await self.cache.close()
129
+
130
+ # Close any still open sockets
131
+ for socket in self.sockets:
132
+ # Check fileno for if socket is closed. Will return
133
+ # -1 on failure, which will be used to signal socket closed.
134
+ if socket.fileno() != -1:
135
+ socket.close()
136
+
137
+
138
+ async def ticker(interval: int, function: Callable, *args: Any, **kwargs: Any) -> None:
139
+ """
140
+ Ticker function to sleep for specified interval and then schedule call
141
+ to given function.
142
+ """
143
+ while True:
144
+ # Sleep for interval and then schedule task
145
+ await asyncio.sleep(interval)
146
+ asyncio.create_task(function(*args, **kwargs))
@@ -14,32 +14,31 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
- import socket
18
17
  import ssl
19
18
  from typing import Any, TYPE_CHECKING
20
19
 
21
- SERVER_PROXY_PORT = 3307
22
-
23
20
  if TYPE_CHECKING:
24
21
  import pg8000
25
22
 
26
23
 
27
24
  def connect(
28
- ip_address: str, ctx: ssl.SSLContext, **kwargs: Any
25
+ ip_address: str, sock: ssl.SSLSocket, **kwargs: Any
29
26
  ) -> "pg8000.dbapi.Connection":
30
27
  """Helper function to create a pg8000 DB-API connection object.
31
28
 
32
- :type ip_address: str
33
- :param ip_address: A string containing an IP address for the Cloud SQL
34
- instance.
35
-
36
- :type ctx: ssl.SSLContext
37
- :param ctx: An SSLContext object created from the Cloud SQL server CA
38
- cert and ephemeral cert.
29
+ Args:
30
+ ip_address (str): A string containing an IP address for the Cloud SQL
31
+ instance.
32
+ sock (ssl.SSLSocket): An SSLSocket object created from the Cloud SQL
33
+ server CA cert and ephemeral cert.
34
+ kwargs: Additional arguments to pass to the pg8000 connect method.
39
35
 
36
+ Returns:
37
+ pg8000.dbapi.Connection: A pg8000 connection to the Cloud SQL
38
+ instance.
40
39
 
41
- :rtype: pg8000.dbapi.Connection
42
- :returns: A pg8000 Connection object for the Cloud SQL instance.
40
+ Raises:
41
+ ImportError: The pg8000 module cannot be imported.
43
42
  """
44
43
  try:
45
44
  import pg8000
@@ -48,12 +47,6 @@ def connect(
48
47
  'Unable to import module "pg8000." Please install and try again.'
49
48
  )
50
49
 
51
- # Create socket and wrap with context.
52
- sock = ctx.wrap_socket(
53
- socket.create_connection((ip_address, SERVER_PROXY_PORT)),
54
- server_hostname=ip_address,
55
- )
56
-
57
50
  user = kwargs.pop("user")
58
51
  db = kwargs.pop("db")
59
52
  passwd = kwargs.pop("password", None)
@@ -14,31 +14,30 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
- import socket
18
17
  import ssl
19
18
  from typing import Any, TYPE_CHECKING
20
19
 
21
- SERVER_PROXY_PORT = 3307
22
-
23
20
  if TYPE_CHECKING:
24
21
  import pymysql
25
22
 
26
23
 
27
24
  def connect(
28
- ip_address: str, ctx: ssl.SSLContext, **kwargs: Any
25
+ ip_address: str, sock: ssl.SSLSocket, **kwargs: Any
29
26
  ) -> "pymysql.connections.Connection":
30
27
  """Helper function to create a pymysql DB-API connection object.
31
28
 
32
- :type ip_address: str
33
- :param ip_address: A string containing an IP address for the Cloud SQL
34
- instance.
29
+ Args:
30
+ ip_address (str): A string containing an IP address for the Cloud SQL
31
+ instance.
32
+ sock (ssl.SSLSocket): An SSLSocket object created from the Cloud SQL
33
+ server CA cert and ephemeral cert.
35
34
 
36
- :type ctx: ssl.SSLContext
37
- :param ctx: An SSLContext object created from the Cloud SQL server CA
38
- cert and ephemeral cert.
35
+ Returns:
36
+ pymysql.connections.Connection: A pymysql connection to the Cloud SQL
37
+ instance.
39
38
 
40
- :rtype: pymysql.Connection
41
- :returns: A PyMySQL Connection object for the Cloud SQL instance.
39
+ Raises:
40
+ ImportError: The pymysql module cannot be imported.
42
41
  """
43
42
  try:
44
43
  import pymysql
@@ -50,11 +49,6 @@ def connect(
50
49
  # allow automatic IAM database authentication to not require password
51
50
  kwargs["password"] = kwargs["password"] if "password" in kwargs else None
52
51
 
53
- # Create socket and wrap with context.
54
- sock = ctx.wrap_socket(
55
- socket.create_connection((ip_address, SERVER_PROXY_PORT)),
56
- server_hostname=ip_address,
57
- )
58
52
  # pop timeout as timeout arg is called 'connect_timeout' for pymysql
59
53
  timeout = kwargs.pop("timeout")
60
54
  kwargs["connect_timeout"] = kwargs.get("connect_timeout", timeout)
@@ -15,32 +15,30 @@ limitations under the License.
15
15
  """
16
16
 
17
17
  import platform
18
- import socket
19
18
  import ssl
20
19
  from typing import Any, TYPE_CHECKING
21
20
 
22
21
  from google.cloud.sql.connector.exceptions import PlatformNotSupportedError
23
22
 
24
- SERVER_PROXY_PORT = 3307
25
-
26
23
  if TYPE_CHECKING:
27
24
  import pytds
28
25
 
29
26
 
30
- def connect(ip_address: str, ctx: ssl.SSLContext, **kwargs: Any) -> "pytds.Connection":
27
+ def connect(ip_address: str, sock: ssl.SSLSocket, **kwargs: Any) -> "pytds.Connection":
31
28
  """Helper function to create a pytds DB-API connection object.
32
29
 
33
- :type ip_address: str
34
- :param ip_address: A string containing an IP address for the Cloud SQL
35
- instance.
36
-
37
- :type ctx: ssl.SSLContext
38
- :param ctx: An SSLContext object created from the Cloud SQL server CA
39
- cert and ephemeral cert.
30
+ Args:
31
+ ip_address (str): A string containing an IP address for the Cloud SQL
32
+ instance.
33
+ sock (ssl.SSLSocket): An SSLSocket object created from the Cloud SQL
34
+ server CA cert and ephemeral cert.
40
35
 
36
+ Returns:
37
+ pytds.Connection: A pytds connection to the Cloud SQL
38
+ instance.
41
39
 
42
- :rtype: pytds.Connection
43
- :returns: A pytds Connection object for the Cloud SQL instance.
40
+ Raises:
41
+ ImportError: The pytds module cannot be imported.
44
42
  """
45
43
  try:
46
44
  import pytds
@@ -51,11 +49,6 @@ def connect(ip_address: str, ctx: ssl.SSLContext, **kwargs: Any) -> "pytds.Conne
51
49
 
52
50
  db = kwargs.pop("db", None)
53
51
 
54
- # Create socket and wrap with context.
55
- sock = ctx.wrap_socket(
56
- socket.create_connection((ip_address, SERVER_PROXY_PORT)),
57
- server_hostname=ip_address,
58
- )
59
52
  if kwargs.pop("active_directory_auth", False):
60
53
  if platform.system() == "Windows":
61
54
  # Ignore username and password if using active directory auth
@@ -19,24 +19,16 @@ from typing import Optional
19
19
 
20
20
 
21
21
  class AsyncRateLimiter(object):
22
- """
23
- An asyncio-compatible rate limiter which uses the Token Bucket algorithm
24
- (https://en.wikipedia.org/wiki/Token_bucket) to limit the number of function calls over a time interval using an event queue.
25
-
26
- :type max_capacity: int
27
- :param: max_capacity:
28
- The maximum capacity of tokens the bucket will store at any one time.
29
- Default: 1
30
-
31
- :type rate: float
32
- :param: rate:
33
- The number of tokens that should be added per second.
34
-
35
- :type loop: asyncio.AbstractEventLoop
36
- :param: loop:
37
- The event loop to use. If not provided, the default event loop will be used.
38
-
22
+ """An asyncio-compatible rate limiter which uses the Token Bucket algorithm
23
+ (https://en.wikipedia.org/wiki/Token_bucket) to limit the number
24
+ of function calls over a time interval using an event queue.
39
25
 
26
+ Args:
27
+ max_capacity (int): The maximum capacity of tokens the bucket
28
+ will store at any one time. Default: 1
29
+ rate (float): The number of tokens that should be added per second.
30
+ loop (asyncio.AbstractEventLoop): The event loop to use.
31
+ If not provided, the default event loop will be used.
40
32
  """
41
33
 
42
34
  def __init__(
@@ -44,8 +44,11 @@ def _seconds_until_refresh(
44
44
  Usually the duration will be half of the time until certificate
45
45
  expiration.
46
46
 
47
- :rtype: int
48
- :returns: Time in seconds to wait before performing next refresh.
47
+ Args:
48
+ expiration (datetime.datetime): The expiration time of the certificate.
49
+
50
+ Returns:
51
+ int: Time in seconds to wait before performing next refresh.
49
52
  """
50
53
 
51
54
  duration = int(
@@ -81,16 +84,14 @@ def _downscope_credentials(
81
84
  ) -> Credentials:
82
85
  """Generate a down-scoped credential.
83
86
 
84
- :type credentials: google.auth.credentials.Credentials
85
- :param credentials
86
- Credentials object used to generate down-scoped credentials.
87
-
88
- :type scopes: list[str]
89
- :param scopes
90
- List of Google scopes to include in down-scoped credentials object.
87
+ Args:
88
+ credentials (google.auth.credentials.Credentials):
89
+ Credentials object used to generate down-scoped credentials.
90
+ scopes (list[str]): List of Google scopes to
91
+ include in down-scoped credentials object.
91
92
 
92
- :rtype: google.auth.credentials.Credentials
93
- :returns: Down-scoped credentials object.
93
+ Returns:
94
+ google.auth.credentials.Credentials: Down-scoped credentials object.
94
95
  """
95
96
  # credentials sourced from a service account or metadata are children of
96
97
  # Scoped class and are capable of being re-scoped