Skip to content

Commit ce29820

Browse files
committed
refactor: Instantiate resolver with API client in Python connector
- Add __init__ to DefaultResolver and DnsResolver to accept client in constructor. - Remove set_client method from DnsResolver. - Lazy-initialize self._resolver in Connector.connect_async passing the initialized self._client. - Update tests to use constructor injection instead of set_client. - Fix lint (ruff) and type checking (mypy) errors.
1 parent cb90198 commit ce29820

33 files changed

Lines changed: 160 additions & 160 deletions

‎google/cloud/sql/connector/__init__.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
from google.cloud.sql.connector.version import __version__
2424

2525
__all__ = [
26-
"__version__",
27-
"create_async_connector",
2826
"Connector",
2927
"DefaultResolver",
3028
"DnsResolver",
3129
"IPTypes",
3230
"RefreshStrategy",
31+
"__version__",
32+
"create_async_connector",
3333
]

‎google/cloud/sql/connector/client.py‎

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import asyncio
1818
import datetime
1919
import logging
20-
from typing import Any, Optional, TYPE_CHECKING
20+
from typing import Any, TYPE_CHECKING
2121

2222
import aiohttp
2323
from cryptography.hazmat.backends import default_backend
@@ -42,7 +42,7 @@
4242
logger = logging.getLogger(name=__name__)
4343

4444

45-
def _format_user_agent(driver: Optional[str], custom: Optional[str]) -> str:
45+
def _format_user_agent(driver: str | None, custom: str | None) -> str:
4646
agent = f"{USER_AGENT}+{driver}" if driver else USER_AGENT
4747
if custom and isinstance(custom, str):
4848
agent = f"{agent} {custom}"
@@ -52,12 +52,12 @@ def _format_user_agent(driver: Optional[str], custom: Optional[str]) -> str:
5252
class CloudSQLClient:
5353
def __init__(
5454
self,
55-
sqladmin_api_endpoint: Optional[str],
56-
quota_project: Optional[str],
55+
sqladmin_api_endpoint: str | None,
56+
quota_project: str | None,
5757
credentials: Credentials,
58-
client: Optional[aiohttp.ClientSession] = None,
59-
driver: Optional[str] = None,
60-
user_agent: Optional[str] = None,
58+
client: aiohttp.ClientSession | None = None,
59+
driver: str | None = None,
60+
user_agent: str | None = None,
6161
) -> None:
6262
"""Establishes the client to be used for Cloud SQL Admin API requests.
6363
@@ -135,7 +135,7 @@ async def _get_metadata(
135135
if message:
136136
resp.reason = message
137137
# skip, raise_for_status will catch all errors in finally block
138-
except Exception:
138+
except Exception: # noqa: BLE001, S110
139139
pass
140140
finally:
141141
resp.raise_for_status()
@@ -195,7 +195,7 @@ async def resolve_connect_settings(
195195
A dictionary containing the resolve response (e.g. connectionName).
196196
"""
197197
# before making Cloud SQL Admin API calls, refresh creds if required
198-
if not self._credentials.token_state == TokenState.FRESH:
198+
if self._credentials.token_state != TokenState.FRESH:
199199
self._credentials.refresh(requests.Request())
200200

201201
headers = {
@@ -213,7 +213,7 @@ async def resolve_connect_settings(
213213
message = ret_dict.get("error", {}).get("message")
214214
if message:
215215
resp.reason = message
216-
except Exception:
216+
except Exception: # noqa: BLE001, S110
217217
pass
218218
finally:
219219
resp.raise_for_status()
@@ -266,7 +266,7 @@ async def _get_ephemeral(
266266
if message:
267267
resp.reason = message
268268
# skip, raise_for_status will catch all errors in finally block
269-
except Exception:
269+
except Exception: # noqa: BLE001, S110
270270
pass
271271
finally:
272272
resp.raise_for_status()
@@ -287,8 +287,7 @@ async def _get_ephemeral(
287287
# Ref: https://github.com/googleapis/google-auth-library-python/blob/49a5ff7411a2ae4d32a7d11700f9f961c55406a9/google/auth/_helpers.py#L93-L99
288288
token_expiration = token_expiration.replace(tzinfo=datetime.timezone.utc)
289289

290-
if expiration > token_expiration:
291-
expiration = token_expiration
290+
expiration = min(expiration, token_expiration)
292291
return ephemeral_cert, expiration
293292

294293
async def get_connection_info(
@@ -317,7 +316,7 @@ async def get_connection_info(
317316
"""
318317
priv_key, pub_key = await keys
319318
# before making Cloud SQL Admin API calls, refresh creds if required
320-
if not self._credentials.token_state == TokenState.FRESH:
319+
if self._credentials.token_state != TokenState.FRESH:
321320
self._credentials.refresh(requests.Request())
322321

323322
metadata_task = asyncio.create_task(

‎google/cloud/sql/connector/connection_info.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from dataclasses import dataclass
1919
import logging
2020
import ssl
21-
from typing import Any, Optional, TYPE_CHECKING
21+
from typing import Any, TYPE_CHECKING
2222

2323
from google.cloud.sql.connector.connection_name import ConnectionName
2424
from google.cloud.sql.connector.exceptions import CloudSQLIPTypeError
@@ -67,7 +67,7 @@ class ConnectionInfo:
6767
ip_addrs: dict[str, Any]
6868
database_version: str
6969
expiration: datetime.datetime
70-
context: Optional[ssl.SSLContext] = None
70+
context: ssl.SSLContext | None = None
7171

7272
async def create_ssl_context(self, enable_iam_auth: bool = False) -> ssl.SSLContext:
7373
"""Constructs a SSL/TLS context for the given connection info.

‎google/cloud/sql/connector/connection_name.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# Instance connection name is the format <PROJECT>:<REGION>:<INSTANCE_NAME>
1919
# Additionally, we have to support legacy "domain-scoped" projects
2020
# (e.g. "google.com:PROJECT")
21-
CONN_NAME_REGEX = re.compile(("([^:]+(:[^:]+)?):([^:]+):([^:]+)"))
21+
CONN_NAME_REGEX = re.compile("([^:]+(:[^:]+)?):([^:]+):([^:]+)")
2222
# The domain name pattern in accordance with RFC 1035, RFC 1123 and RFC 2181.
2323
DOMAIN_NAME_REGEX = re.compile(
2424
r"^(?:[_a-z0-9](?:[_a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?)?$"
@@ -48,9 +48,7 @@ def get_connection_string(self) -> str:
4848

4949

5050
def _is_valid_domain(domain_name: str) -> bool:
51-
if DOMAIN_NAME_REGEX.fullmatch(domain_name) is None:
52-
return False
53-
return True
51+
return DOMAIN_NAME_REGEX.fullmatch(domain_name) is not None
5452

5553

5654
def _parse_connection_name(connection_name: str) -> ConnectionName:

‎google/cloud/sql/connector/connector.py‎

100755100644
Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
import socket
2424
from threading import Thread
2525
from types import TracebackType
26-
from typing import Any, Callable, Optional, Union
26+
from typing import Any, Callable
2727

2828
import google.auth
2929
from google.auth.credentials import Credentials
3030
from google.auth.credentials import with_scopes_if_required
3131

32-
import google.cloud.sql.connector.asyncpg as asyncpg
32+
from google.cloud.sql.connector import asyncpg
33+
from google.cloud.sql.connector import pg8000
34+
from google.cloud.sql.connector import pymysql
35+
from google.cloud.sql.connector import pytds
3336
from google.cloud.sql.connector.client import CloudSQLClient
3437
from google.cloud.sql.connector.enums import DriverMapping
3538
from google.cloud.sql.connector.enums import IPTypes
@@ -39,9 +42,6 @@
3942
from google.cloud.sql.connector.instance import RefreshAheadCache
4043
from google.cloud.sql.connector.lazy import LazyRefreshCache
4144
from google.cloud.sql.connector.monitored_cache import MonitoredCache
42-
import google.cloud.sql.connector.pg8000 as pg8000
43-
import google.cloud.sql.connector.pymysql as pymysql
44-
import google.cloud.sql.connector.pytds as pytds
4545
from google.cloud.sql.connector.resolver import DefaultResolver
4646
from google.cloud.sql.connector.resolver import DnsResolver
4747
from google.cloud.sql.connector.utils import format_database_user
@@ -64,14 +64,14 @@ def __init__(
6464
ip_type: str | IPTypes = IPTypes.PUBLIC,
6565
enable_iam_auth: bool = False,
6666
timeout: int = 30,
67-
credentials: Optional[Credentials] = None,
68-
loop: Optional[asyncio.AbstractEventLoop] = None,
69-
quota_project: Optional[str] = None,
70-
sqladmin_api_endpoint: Optional[str] = None,
71-
user_agent: Optional[str] = None,
72-
universe_domain: Optional[str] = None,
67+
credentials: Credentials | None = None,
68+
loop: asyncio.AbstractEventLoop | None = None,
69+
quota_project: str | None = None,
70+
sqladmin_api_endpoint: str | None = None,
71+
user_agent: str | None = None,
72+
universe_domain: str | None = None,
7373
refresh_strategy: str | RefreshStrategy = RefreshStrategy.BACKGROUND,
74-
resolver: type[DefaultResolver] | type[DnsResolver] = DefaultResolver,
74+
resolver: type[DefaultResolver | DnsResolver] = DefaultResolver,
7575
failover_period: int = 30,
7676
) -> None:
7777
"""Initializes a Connector instance.
@@ -133,10 +133,10 @@ def __init__(
133133
# if event loop is given, use for background tasks
134134
if loop:
135135
self._loop: asyncio.AbstractEventLoop = loop
136-
self._thread: Optional[Thread] = None
136+
self._thread: Thread | None = None
137137
# if lazy refresh is specified we should lazy init keys
138138
if self._refresh_strategy == RefreshStrategy.LAZY:
139-
self._keys: Optional[asyncio.Future] = None
139+
self._keys: asyncio.Future | None = None
140140
else:
141141
self._keys = loop.create_task(generate_keys())
142142
# if no event loop is given, spin up new loop in background thread
@@ -155,7 +155,7 @@ def __init__(
155155
# initialize dict to store caches, key is a tuple consisting of instance
156156
# connection name string and enable_iam_auth boolean flag
157157
self._cache: dict[tuple[str, bool], MonitoredCache] = {}
158-
self._client: Optional[CloudSQLClient] = None
158+
self._client: CloudSQLClient | None = None
159159
self._closed: bool = False
160160

161161
# initialize credentials
@@ -176,7 +176,8 @@ def __init__(
176176
self._timeout = timeout
177177
self._enable_iam_auth = enable_iam_auth
178178
self._user_agent = user_agent
179-
self._resolver = resolver()
179+
self._resolver_cls = resolver
180+
self._resolver: DefaultResolver | DnsResolver | None = None
180181
self._failover_period = failover_period
181182
# if ip_type is str, convert to IPTypes enum
182183
if isinstance(ip_type, str):
@@ -316,8 +317,8 @@ async def connect_async(
316317
user_agent=self._user_agent,
317318
driver=driver,
318319
)
319-
if hasattr(self._resolver, "set_client"):
320-
self._resolver.set_client(self._client)
320+
if self._resolver is None:
321+
self._resolver = self._resolver_cls(client=self._client)
321322
enable_iam_auth = kwargs.pop("enable_iam_auth", self._enable_iam_auth)
322323

323324
conn_name = await self._resolver.resolve(instance_connection_string)
@@ -331,7 +332,7 @@ async def connect_async(
331332
logger.debug(
332333
f"['{conn_name}']: Refresh strategy is set to lazy refresh"
333334
)
334-
cache: Union[LazyRefreshCache, RefreshAheadCache] = LazyRefreshCache(
335+
cache: LazyRefreshCache | RefreshAheadCache = LazyRefreshCache(
335336
conn_name,
336337
self._client,
337338
self._keys,
@@ -414,7 +415,7 @@ async def connect_async(
414415
f"entries, using '{preferred_ips}' from instance metadata"
415416
)
416417
targets.extend(preferred_ips)
417-
except Exception as e:
418+
except Exception as e: # noqa: BLE001
418419
logger.debug(
419420
f"['{instance_connection_string}']: Custom DNS name "
420421
f"'{conn_info.conn_name.domain_name}' did not resolve to an IP "
@@ -476,7 +477,7 @@ async def connect_async(
476477
conn = await self._loop.run_in_executor(None, connect_partial)
477478
last_ex = None
478479
return conn
479-
except Exception as e:
480+
except Exception as e: # noqa: BLE001
480481
logger.debug(
481482
f"['{conn_info.conn_name}']: Connection to {target_ip} failed: {e}"
482483
)
@@ -509,9 +510,9 @@ def __enter__(self) -> Any:
509510

510511
def __exit__(
511512
self,
512-
exc_type: Optional[type[BaseException]],
513-
exc_val: Optional[BaseException],
514-
exc_tb: Optional[TracebackType],
513+
exc_type: type[BaseException] | None,
514+
exc_val: BaseException | None,
515+
exc_tb: TracebackType | None,
515516
) -> None:
516517
"""Exit context manager by closing Connector"""
517518
self.close()
@@ -522,9 +523,9 @@ async def __aenter__(self) -> Any:
522523

523524
async def __aexit__(
524525
self,
525-
exc_type: Optional[type[BaseException]],
526-
exc_val: Optional[BaseException],
527-
exc_tb: Optional[TracebackType],
526+
exc_type: type[BaseException] | None,
527+
exc_val: BaseException | None,
528+
exc_tb: TracebackType | None,
528529
) -> None:
529530
"""Exit async context manager by closing Connector"""
530531
await self.close_async()
@@ -558,14 +559,14 @@ async def create_async_connector(
558559
ip_type: str | IPTypes = IPTypes.PUBLIC,
559560
enable_iam_auth: bool = False,
560561
timeout: int = 30,
561-
credentials: Optional[Credentials] = None,
562-
loop: Optional[asyncio.AbstractEventLoop] = None,
563-
quota_project: Optional[str] = None,
564-
sqladmin_api_endpoint: Optional[str] = None,
565-
user_agent: Optional[str] = None,
566-
universe_domain: Optional[str] = None,
562+
credentials: Credentials | None = None,
563+
loop: asyncio.AbstractEventLoop | None = None,
564+
quota_project: str | None = None,
565+
sqladmin_api_endpoint: str | None = None,
566+
user_agent: str | None = None,
567+
universe_domain: str | None = None,
567568
refresh_strategy: str | RefreshStrategy = RefreshStrategy.BACKGROUND,
568-
resolver: type[DefaultResolver] | type[DnsResolver] = DefaultResolver,
569+
resolver: type[DefaultResolver | DnsResolver] = DefaultResolver,
569570
failover_period: int = 30,
570571
) -> Connector:
571572
"""Helper function to create Connector object for asyncio connections.

‎google/cloud/sql/connector/enums.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class DriverMapping(Enum):
6161
"""Maps a given database driver to it's corresponding database engine."""
6262

6363
ASYNCPG = "POSTGRES"
64-
PG8000 = "POSTGRES"
64+
PG8000 = "POSTGRES" # noqa: PIE796
6565
PYMYSQL = "MYSQL"
6666
PYTDS = "SQLSERVER"
6767

‎google/cloud/sql/connector/exceptions.py‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,27 @@ class ConnectorLoopError(Exception):
2121
in an invalid state (event loop in current thread).
2222
"""
2323

24-
pass
2524

2625

2726
class TLSVersionError(Exception):
2827
"""
2928
Raised when the required TLS protocol version is not supported.
3029
"""
3130

32-
pass
3331

3432

3533
class CloudSQLIPTypeError(Exception):
3634
"""
3735
Raised when IP address for the preferred IP type is not found.
3836
"""
3937

40-
pass
4138

4239

4340
class PlatformNotSupportedError(Exception):
4441
"""
4542
Raised when a feature is not supported on the current platform.
4643
"""
4744

48-
pass
4945

5046

5147
class AutoIAMAuthNotSupported(Exception):
@@ -54,15 +50,13 @@ class AutoIAMAuthNotSupported(Exception):
5450
supported with database engine version.
5551
"""
5652

57-
pass
5853

5954

6055
class RefreshNotValidError(Exception):
6156
"""
6257
Exception to be raised when the task returned from refresh is not valid.
6358
"""
6459

65-
pass
6660

6761

6862
class IncompatibleDriverError(Exception):

‎google/cloud/sql/connector/instance.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ async def _perform_refresh(self) -> ConnectionInfo:
130130
except Exception as e:
131131
logger.debug(
132132
f"['{self._conn_name}']: Connection info "
133-
f"refresh operation failed: {str(e)}"
133+
f"refresh operation failed: {e!s}"
134134
)
135135
raise
136136

0 commit comments

Comments
 (0)