cloud-sql-python-connector 1.23.0__tar.gz → 1.24.0__tar.gz

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.
Files changed (33) hide show
  1. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/PKG-INFO +80 -1
  2. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/README.md +78 -0
  3. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/cloud_sql_python_connector.egg-info/PKG-INFO +80 -1
  4. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/cloud_sql_python_connector.egg-info/SOURCES.txt +1 -0
  5. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/connection_info.py +4 -0
  6. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/connector.py +128 -32
  7. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/instance.py +236 -0
  8. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/lazy.py +45 -1
  9. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/monitored_cache.py +6 -1
  10. cloud_sql_python_connector-1.24.0/google/cloud/sql/connector/postgres_connection.py +235 -0
  11. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/sqldata_client.py +1 -1
  12. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/utils.py +1 -1
  13. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/version.py +1 -1
  14. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/pyproject.toml +1 -0
  15. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/LICENSE +0 -0
  16. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/cloud_sql_python_connector.egg-info/dependency_links.txt +0 -0
  17. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/cloud_sql_python_connector.egg-info/requires.txt +0 -0
  18. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/cloud_sql_python_connector.egg-info/top_level.txt +0 -0
  19. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/__init__.py +0 -0
  20. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/asyncpg.py +0 -0
  21. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/client.py +0 -0
  22. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/connection_name.py +0 -0
  23. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/enums.py +0 -0
  24. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/exceptions.py +0 -0
  25. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/pg8000.py +0 -0
  26. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/psycopg.py +0 -0
  27. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/py.typed +0 -0
  28. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/pymysql.py +0 -0
  29. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/pytds.py +0 -0
  30. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/rate_limiter.py +0 -0
  31. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/refresh_utils.py +0 -0
  32. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/google/cloud/sql/connector/resolver.py +0 -0
  33. {cloud_sql_python_connector-1.23.0 → cloud_sql_python_connector-1.24.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloud-sql-python-connector
3
- Version: 1.23.0
3
+ Version: 1.24.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
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
19
20
  Classifier: Operating System :: OS Independent
20
21
  Requires-Python: >=3.11
21
22
  Description-Content-Type: text/markdown
@@ -789,6 +790,84 @@ async def main():
789
790
  await pool.dispose()
790
791
  ```
791
792
 
793
+ ### Post-Quantum Cryptography (PQC)
794
+
795
+ The Cloud SQL Python Connector establishes mTLS connections over TLS 1.3 using
796
+ Python's standard `ssl` module without restricting key-exchange groups. When
797
+ Python's `ssl` module is linked against **OpenSSL 3.5 or later**, TLS 1.3
798
+ connections automatically offer and negotiate the hybrid post-quantum key
799
+ exchange group **`X25519MLKEM768`** (ML-KEM-768 combined with X25519) whenever
800
+ the Cloud SQL instance supports it. No code changes or connector configuration
801
+ are required.
802
+
803
+ #### Checking your OpenSSL version
804
+
805
+ Post-quantum TLS key exchange depends on the OpenSSL library linked into your
806
+ Python runtime's `ssl` module (not the Python language version or the
807
+ `cryptography` package version alone). Run the following command to check which
808
+ OpenSSL version your Python interpreter uses:
809
+
810
+ ```sh
811
+ python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
812
+ ```
813
+
814
+ - **OpenSSL 3.5.0 or higher** (for example, `python:3.13-trixie` / Debian 13
815
+ "Trixie" container images; check your runtime with the one-liner above):
816
+ `X25519MLKEM768` is enabled by default on TLS 1.3 connections.
817
+ - **OpenSSL earlier than 3.5.0** (for example, Ubuntu 24.04 LTS, which ships
818
+ system OpenSSL 3.0.x): TLS 1.3 connections continue to use classical elliptic
819
+ curve Diffie-Hellman (such as `X25519` or `P-256`).
820
+
821
+ > [!NOTE]
822
+ >
823
+ > Connections configured with `ip_type="sql_data"` (`IPTypes.SQL_DATA`) stream
824
+ > over gRPC (`grpcio`), which uses its bundled BoringSSL (supporting
825
+ > `X25519MLKEM768` independently of Python's `ssl` module and `OPENSSL_CONF`),
826
+ > whereas its direct-TLS fallback path uses `create_ssl_context()` and Python's
827
+ > linked OpenSSL library as described above.
828
+
829
+ #### Backwards compatibility
830
+
831
+ Hybrid post-quantum key exchange is backwards-compatible. On OpenSSL 3.5+,
832
+ Python's `ssl` module advertises both hybrid (`X25519MLKEM768`) and classical
833
+ (`X25519`, `secp256r1`, etc.) groups in the TLS 1.3 `ClientHello`. If either the
834
+ client runtime (OpenSSL `< 3.5`) or the Cloud SQL instance only supports
835
+ classical groups, the TLS 1.3 handshake automatically falls back to classical
836
+ ECDHE without error.
837
+
838
+ #### Opting out
839
+
840
+ If you need to disable post-quantum key exchange on an OpenSSL 3.5+ client (for
841
+ example, to troubleshoot a network middlebox sensitive to larger TLS
842
+ `ClientHello` records), configure OpenSSL's `Groups` list via an OpenSSL
843
+ configuration file (`OPENSSL_CONF`) to advertise only classical curves.
844
+
845
+ > [!WARNING]
846
+ >
847
+ > Setting `OPENSSL_CONF` replaces the system default OpenSSL configuration for
848
+ > the entire Python process. It affects **all** TLS connections made by the
849
+ > process (not just Cloud SQL connections), and starting from a minimal file
850
+ > omits distribution defaults (such as Debian's `MinProtocol` or
851
+ > `CipherString`). Copy your system's `openssl.cnf` (check `openssl version -d`,
852
+ > typically `/etc/ssl/openssl.cnf`) and add `Groups = X25519:P-256:P-384` under
853
+ > its `[system_default_sect]` section rather than using a bare file from
854
+ > scratch.
855
+
856
+ For example, after copying `/etc/ssl/openssl.cnf`, ensure its
857
+ `[system_default_sect]` (referenced via `ssl_conf` -> `system_default`) sets
858
+ `Groups`:
859
+
860
+ ```ini
861
+ [system_default_sect]
862
+ Groups = X25519:P-256:P-384
863
+ ```
864
+
865
+ Then point `OPENSSL_CONF` to your modified copy when starting your application:
866
+
867
+ ```sh
868
+ OPENSSL_CONF=/path/to/openssl.cnf python3 app.py
869
+ ```
870
+
792
871
  ### Debug Logging
793
872
 
794
873
  The Cloud SQL Python Connector uses the standard [Python logging module][python-logging]
@@ -749,6 +749,84 @@ async def main():
749
749
  await pool.dispose()
750
750
  ```
751
751
 
752
+ ### Post-Quantum Cryptography (PQC)
753
+
754
+ The Cloud SQL Python Connector establishes mTLS connections over TLS 1.3 using
755
+ Python's standard `ssl` module without restricting key-exchange groups. When
756
+ Python's `ssl` module is linked against **OpenSSL 3.5 or later**, TLS 1.3
757
+ connections automatically offer and negotiate the hybrid post-quantum key
758
+ exchange group **`X25519MLKEM768`** (ML-KEM-768 combined with X25519) whenever
759
+ the Cloud SQL instance supports it. No code changes or connector configuration
760
+ are required.
761
+
762
+ #### Checking your OpenSSL version
763
+
764
+ Post-quantum TLS key exchange depends on the OpenSSL library linked into your
765
+ Python runtime's `ssl` module (not the Python language version or the
766
+ `cryptography` package version alone). Run the following command to check which
767
+ OpenSSL version your Python interpreter uses:
768
+
769
+ ```sh
770
+ python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
771
+ ```
772
+
773
+ - **OpenSSL 3.5.0 or higher** (for example, `python:3.13-trixie` / Debian 13
774
+ "Trixie" container images; check your runtime with the one-liner above):
775
+ `X25519MLKEM768` is enabled by default on TLS 1.3 connections.
776
+ - **OpenSSL earlier than 3.5.0** (for example, Ubuntu 24.04 LTS, which ships
777
+ system OpenSSL 3.0.x): TLS 1.3 connections continue to use classical elliptic
778
+ curve Diffie-Hellman (such as `X25519` or `P-256`).
779
+
780
+ > [!NOTE]
781
+ >
782
+ > Connections configured with `ip_type="sql_data"` (`IPTypes.SQL_DATA`) stream
783
+ > over gRPC (`grpcio`), which uses its bundled BoringSSL (supporting
784
+ > `X25519MLKEM768` independently of Python's `ssl` module and `OPENSSL_CONF`),
785
+ > whereas its direct-TLS fallback path uses `create_ssl_context()` and Python's
786
+ > linked OpenSSL library as described above.
787
+
788
+ #### Backwards compatibility
789
+
790
+ Hybrid post-quantum key exchange is backwards-compatible. On OpenSSL 3.5+,
791
+ Python's `ssl` module advertises both hybrid (`X25519MLKEM768`) and classical
792
+ (`X25519`, `secp256r1`, etc.) groups in the TLS 1.3 `ClientHello`. If either the
793
+ client runtime (OpenSSL `< 3.5`) or the Cloud SQL instance only supports
794
+ classical groups, the TLS 1.3 handshake automatically falls back to classical
795
+ ECDHE without error.
796
+
797
+ #### Opting out
798
+
799
+ If you need to disable post-quantum key exchange on an OpenSSL 3.5+ client (for
800
+ example, to troubleshoot a network middlebox sensitive to larger TLS
801
+ `ClientHello` records), configure OpenSSL's `Groups` list via an OpenSSL
802
+ configuration file (`OPENSSL_CONF`) to advertise only classical curves.
803
+
804
+ > [!WARNING]
805
+ >
806
+ > Setting `OPENSSL_CONF` replaces the system default OpenSSL configuration for
807
+ > the entire Python process. It affects **all** TLS connections made by the
808
+ > process (not just Cloud SQL connections), and starting from a minimal file
809
+ > omits distribution defaults (such as Debian's `MinProtocol` or
810
+ > `CipherString`). Copy your system's `openssl.cnf` (check `openssl version -d`,
811
+ > typically `/etc/ssl/openssl.cnf`) and add `Groups = X25519:P-256:P-384` under
812
+ > its `[system_default_sect]` section rather than using a bare file from
813
+ > scratch.
814
+
815
+ For example, after copying `/etc/ssl/openssl.cnf`, ensure its
816
+ `[system_default_sect]` (referenced via `ssl_conf` -> `system_default`) sets
817
+ `Groups`:
818
+
819
+ ```ini
820
+ [system_default_sect]
821
+ Groups = X25519:P-256:P-384
822
+ ```
823
+
824
+ Then point `OPENSSL_CONF` to your modified copy when starting your application:
825
+
826
+ ```sh
827
+ OPENSSL_CONF=/path/to/openssl.cnf python3 app.py
828
+ ```
829
+
752
830
  ### Debug Logging
753
831
 
754
832
  The Cloud SQL Python Connector uses the standard [Python logging module][python-logging]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloud-sql-python-connector
3
- Version: 1.23.0
3
+ Version: 1.24.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
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
19
20
  Classifier: Operating System :: OS Independent
20
21
  Requires-Python: >=3.11
21
22
  Description-Content-Type: text/markdown
@@ -789,6 +790,84 @@ async def main():
789
790
  await pool.dispose()
790
791
  ```
791
792
 
793
+ ### Post-Quantum Cryptography (PQC)
794
+
795
+ The Cloud SQL Python Connector establishes mTLS connections over TLS 1.3 using
796
+ Python's standard `ssl` module without restricting key-exchange groups. When
797
+ Python's `ssl` module is linked against **OpenSSL 3.5 or later**, TLS 1.3
798
+ connections automatically offer and negotiate the hybrid post-quantum key
799
+ exchange group **`X25519MLKEM768`** (ML-KEM-768 combined with X25519) whenever
800
+ the Cloud SQL instance supports it. No code changes or connector configuration
801
+ are required.
802
+
803
+ #### Checking your OpenSSL version
804
+
805
+ Post-quantum TLS key exchange depends on the OpenSSL library linked into your
806
+ Python runtime's `ssl` module (not the Python language version or the
807
+ `cryptography` package version alone). Run the following command to check which
808
+ OpenSSL version your Python interpreter uses:
809
+
810
+ ```sh
811
+ python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
812
+ ```
813
+
814
+ - **OpenSSL 3.5.0 or higher** (for example, `python:3.13-trixie` / Debian 13
815
+ "Trixie" container images; check your runtime with the one-liner above):
816
+ `X25519MLKEM768` is enabled by default on TLS 1.3 connections.
817
+ - **OpenSSL earlier than 3.5.0** (for example, Ubuntu 24.04 LTS, which ships
818
+ system OpenSSL 3.0.x): TLS 1.3 connections continue to use classical elliptic
819
+ curve Diffie-Hellman (such as `X25519` or `P-256`).
820
+
821
+ > [!NOTE]
822
+ >
823
+ > Connections configured with `ip_type="sql_data"` (`IPTypes.SQL_DATA`) stream
824
+ > over gRPC (`grpcio`), which uses its bundled BoringSSL (supporting
825
+ > `X25519MLKEM768` independently of Python's `ssl` module and `OPENSSL_CONF`),
826
+ > whereas its direct-TLS fallback path uses `create_ssl_context()` and Python's
827
+ > linked OpenSSL library as described above.
828
+
829
+ #### Backwards compatibility
830
+
831
+ Hybrid post-quantum key exchange is backwards-compatible. On OpenSSL 3.5+,
832
+ Python's `ssl` module advertises both hybrid (`X25519MLKEM768`) and classical
833
+ (`X25519`, `secp256r1`, etc.) groups in the TLS 1.3 `ClientHello`. If either the
834
+ client runtime (OpenSSL `< 3.5`) or the Cloud SQL instance only supports
835
+ classical groups, the TLS 1.3 handshake automatically falls back to classical
836
+ ECDHE without error.
837
+
838
+ #### Opting out
839
+
840
+ If you need to disable post-quantum key exchange on an OpenSSL 3.5+ client (for
841
+ example, to troubleshoot a network middlebox sensitive to larger TLS
842
+ `ClientHello` records), configure OpenSSL's `Groups` list via an OpenSSL
843
+ configuration file (`OPENSSL_CONF`) to advertise only classical curves.
844
+
845
+ > [!WARNING]
846
+ >
847
+ > Setting `OPENSSL_CONF` replaces the system default OpenSSL configuration for
848
+ > the entire Python process. It affects **all** TLS connections made by the
849
+ > process (not just Cloud SQL connections), and starting from a minimal file
850
+ > omits distribution defaults (such as Debian's `MinProtocol` or
851
+ > `CipherString`). Copy your system's `openssl.cnf` (check `openssl version -d`,
852
+ > typically `/etc/ssl/openssl.cnf`) and add `Groups = X25519:P-256:P-384` under
853
+ > its `[system_default_sect]` section rather than using a bare file from
854
+ > scratch.
855
+
856
+ For example, after copying `/etc/ssl/openssl.cnf`, ensure its
857
+ `[system_default_sect]` (referenced via `ssl_conf` -> `system_default`) sets
858
+ `Groups`:
859
+
860
+ ```ini
861
+ [system_default_sect]
862
+ Groups = X25519:P-256:P-384
863
+ ```
864
+
865
+ Then point `OPENSSL_CONF` to your modified copy when starting your application:
866
+
867
+ ```sh
868
+ OPENSSL_CONF=/path/to/openssl.cnf python3 app.py
869
+ ```
870
+
792
871
  ### Debug Logging
793
872
 
794
873
  The Cloud SQL Python Connector uses the standard [Python logging module][python-logging]
@@ -18,6 +18,7 @@ google/cloud/sql/connector/instance.py
18
18
  google/cloud/sql/connector/lazy.py
19
19
  google/cloud/sql/connector/monitored_cache.py
20
20
  google/cloud/sql/connector/pg8000.py
21
+ google/cloud/sql/connector/postgres_connection.py
21
22
  google/cloud/sql/connector/psycopg.py
22
23
  google/cloud/sql/connector/py.typed
23
24
  google/cloud/sql/connector/pymysql.py
@@ -46,6 +46,10 @@ class ConnectionInfoCache(abc.ABC):
46
46
  async def force_refresh(self) -> None:
47
47
  pass
48
48
 
49
+ @abc.abstractmethod
50
+ def record_principal(self, user: str, database: str) -> None:
51
+ pass
52
+
49
53
  @abc.abstractmethod
50
54
  async def close(self) -> None:
51
55
  pass
@@ -23,6 +23,7 @@ import logging
23
23
  import os
24
24
  import random
25
25
  import socket
26
+ import ssl
26
27
  from threading import Thread
27
28
  import time
28
29
  from types import TracebackType
@@ -38,6 +39,7 @@ from google.cloud.sql.connector import psycopg
38
39
  from google.cloud.sql.connector import pymysql
39
40
  from google.cloud.sql.connector import pytds
40
41
  from google.cloud.sql.connector.client import CloudSQLClient
42
+ from google.cloud.sql.connector.connection_info import ConnectionInfo
41
43
  from google.cloud.sql.connector.connection_name import ConnectionName
42
44
  from google.cloud.sql.connector.enums import DriverMapping
43
45
  from google.cloud.sql.connector.enums import IPTypes
@@ -49,6 +51,7 @@ from google.cloud.sql.connector.exceptions import ResourceExhaustedError
49
51
  from google.cloud.sql.connector.instance import RefreshAheadCache
50
52
  from google.cloud.sql.connector.lazy import LazyRefreshCache
51
53
  from google.cloud.sql.connector.monitored_cache import MonitoredCache
54
+ from google.cloud.sql.connector.postgres_connection import PostgresConnection
52
55
  from google.cloud.sql.connector.resolver import DefaultResolver
53
56
  from google.cloud.sql.connector.resolver import DnsResolver
54
57
  from google.cloud.sql.connector.sqldata_client import SqlDataClient
@@ -346,6 +349,7 @@ class Connector:
346
349
  self,
347
350
  conn_name: ConnectionName,
348
351
  enable_iam_auth: bool,
352
+ ip_type: IPTypes | None = None,
349
353
  ) -> MonitoredCache:
350
354
  assert self._client is not None, "client must be initialized before creating cache"
351
355
  assert self._keys is not None, "keys must be initialized before creating cache"
@@ -355,6 +359,13 @@ class Connector:
355
359
  ].closed:
356
360
  return self._cache[(str(conn_name), enable_iam_auth)]
357
361
 
362
+ effective_ip_type = ip_type if ip_type is not None else self._ip_type
363
+ dial_func = partial(
364
+ self._dial_probe_connection,
365
+ enable_iam_auth=enable_iam_auth,
366
+ ip_type=effective_ip_type,
367
+ )
368
+
358
369
  if self._refresh_strategy == RefreshStrategy.LAZY:
359
370
  logger.debug(
360
371
  f"['{conn_name}']: Refresh strategy is set to lazy refresh"
@@ -364,6 +375,9 @@ class Connector:
364
375
  self._client,
365
376
  self._keys,
366
377
  enable_iam_auth,
378
+ ip_type=effective_ip_type,
379
+ timeout=self._timeout,
380
+ dial_func=dial_func,
367
381
  )
368
382
  else:
369
383
  logger.debug(
@@ -374,6 +388,9 @@ class Connector:
374
388
  self._client,
375
389
  self._keys,
376
390
  enable_iam_auth,
391
+ ip_type=effective_ip_type,
392
+ timeout=self._timeout,
393
+ dial_func=dial_func,
377
394
  )
378
395
  # wrap cache as a MonitoredCache
379
396
  monitored_cache = MonitoredCache(
@@ -452,7 +469,9 @@ class Connector:
452
469
 
453
470
  conn_name = await self._resolver.resolve(instance_connection_string)
454
471
  if ip_type != IPTypes.SQL_DATA:
455
- monitored_cache = self._get_or_create_cache(conn_name, enable_iam_auth)
472
+ monitored_cache = self._get_or_create_cache(
473
+ conn_name, enable_iam_auth, ip_type
474
+ )
456
475
 
457
476
  connect_func = {
458
477
  "pymysql": pymysql.connect,
@@ -587,33 +606,7 @@ class Connector:
587
606
  await self._remove_cached(str(conn_name), enable_iam_auth)
588
607
  raise
589
608
 
590
- targets = []
591
- if conn_info.conn_name.domain_name and isinstance(self._resolver, DnsResolver):
592
- try:
593
- ips = await self._resolver.resolve_a_record(conn_info.conn_name.domain_name)
594
- if ips:
595
- targets.extend(ips)
596
- logger.debug(
597
- f"['{instance_connection_string}']: Custom DNS name "
598
- f"'{conn_info.conn_name.domain_name}' resolved to '{ips}', "
599
- "using it to connect"
600
- )
601
- else:
602
- logger.debug(
603
- f"['{instance_connection_string}']: Custom DNS name "
604
- f"'{conn_info.conn_name.domain_name}' resolved but returned no "
605
- f"entries, using '{preferred_ips}' from instance metadata"
606
- )
607
- targets.extend(preferred_ips)
608
- except Exception as e: # noqa: BLE001
609
- logger.debug(
610
- f"['{instance_connection_string}']: Custom DNS name "
611
- f"'{conn_info.conn_name.domain_name}' did not resolve to an IP "
612
- f"address: {e}, using '{preferred_ips}' from instance metadata"
613
- )
614
- targets.extend(preferred_ips)
615
- else:
616
- targets.extend(preferred_ips)
609
+ targets = await self._resolve_targets(conn_info, preferred_ips)
617
610
  # format `user` param for automatic IAM database authn
618
611
  if enable_iam_auth:
619
612
  formatted_user = format_database_user(
@@ -636,14 +629,28 @@ class Connector:
636
629
  await conn_info.create_ssl_context(enable_iam_auth),
637
630
  **kwargs,
638
631
  )
632
+ if (
633
+ enable_iam_auth
634
+ and conn_info.database_version.startswith("POSTGRES")
635
+ ):
636
+ db_name = kwargs.get("database")
637
+ if db_name is None:
638
+ db_name = kwargs.get("db")
639
+ user = kwargs.get("user")
640
+ if user:
641
+ monitored_cache.record_principal(
642
+ str(user),
643
+ str(db_name) if db_name else str(user),
644
+ )
639
645
  last_ex = None
640
646
  return conn
641
647
 
642
648
  # Create socket with SSLContext for sync drivers
643
649
  ctx = await conn_info.create_ssl_context(enable_iam_auth)
644
650
  raw_sock = socket.create_connection((target_ip, SERVER_PROXY_PORT))
651
+ sync_sock: socket.socket | ssl.SSLSocket | PostgresConnection
645
652
  try:
646
- sock = ctx.wrap_socket(
653
+ sync_sock = ctx.wrap_socket(
647
654
  raw_sock,
648
655
  server_hostname=target_ip,
649
656
  )
@@ -651,15 +658,24 @@ class Connector:
651
658
  raw_sock.close()
652
659
  raise
653
660
 
661
+ if (
662
+ enable_iam_auth
663
+ and conn_info.database_version.startswith("POSTGRES")
664
+ ):
665
+ sync_sock = PostgresConnection(
666
+ sync_sock,
667
+ on_startup=monitored_cache.record_principal,
668
+ )
669
+
654
670
  # If this connection was opened using a domain name, then store it
655
671
  # for later in case we need to forcibly close it on failover.
656
672
  if conn_info.conn_name.domain_name:
657
- monitored_cache.sockets.append(sock)
673
+ monitored_cache.sockets.append(sync_sock)
658
674
  # Synchronous drivers are blocking and run using executor
659
675
  connect_partial = partial(
660
676
  connector,
661
677
  target_ip,
662
- sock,
678
+ sync_sock,
663
679
  **kwargs,
664
680
  )
665
681
  conn = await self._loop.run_in_executor(None, connect_partial)
@@ -670,7 +686,6 @@ class Connector:
670
686
  f"['{conn_info.conn_name}']: Connection to {target_ip} failed: {e}"
671
687
  )
672
688
  last_ex = e
673
-
674
689
  if last_ex:
675
690
  raise last_ex
676
691
 
@@ -694,6 +709,87 @@ class Connector:
694
709
  cache = self._cache.pop((instance_connection_string, enable_iam_auth))
695
710
  await cache.close()
696
711
 
712
+ async def _resolve_targets(
713
+ self,
714
+ conn_info: ConnectionInfo,
715
+ preferred_ips: list[str],
716
+ ) -> list[str]:
717
+ """Resolves target IP addresses for a Cloud SQL instance, with DNS fallback."""
718
+ targets: list[str] = []
719
+ if conn_info.conn_name.domain_name and isinstance(self._resolver, DnsResolver):
720
+ try:
721
+ ips = await self._resolver.resolve_a_record(
722
+ conn_info.conn_name.domain_name
723
+ )
724
+ if ips:
725
+ targets.extend(ips)
726
+ logger.debug(
727
+ f"['{conn_info.conn_name}']: Custom DNS name "
728
+ f"'{conn_info.conn_name.domain_name}' resolved to '{ips}', "
729
+ "using it to connect"
730
+ )
731
+ else:
732
+ logger.debug(
733
+ f"['{conn_info.conn_name}']: Custom DNS name "
734
+ f"'{conn_info.conn_name.domain_name}' resolved but returned no "
735
+ f"entries, using '{preferred_ips}' from instance metadata"
736
+ )
737
+ targets.extend(preferred_ips)
738
+ except Exception as e: # noqa: BLE001
739
+ logger.debug(
740
+ f"['{conn_info.conn_name}']: Custom DNS name "
741
+ f"'{conn_info.conn_name.domain_name}' did not resolve to an IP "
742
+ f"address: {e}, using '{preferred_ips}' from instance metadata"
743
+ )
744
+ targets.extend(preferred_ips)
745
+ else:
746
+ targets.extend(preferred_ips)
747
+ return targets
748
+
749
+ async def _dial_probe_connection(
750
+ self,
751
+ conn_info: ConnectionInfo,
752
+ enable_iam_auth: bool,
753
+ ip_type: IPTypes | None = None,
754
+ ) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]:
755
+ """Dials a TLS probe connection using the connector's IP resolution and SSLContext."""
756
+ effective_ip_type = ip_type if ip_type is not None else self._ip_type
757
+ preferred_ips = conn_info.get_preferred_ips(effective_ip_type)
758
+ targets = await self._resolve_targets(conn_info, preferred_ips)
759
+
760
+ if not targets:
761
+ raise ConnectionError(
762
+ f"No target IP addresses found for {conn_info.conn_name}"
763
+ )
764
+
765
+ ssl_context = await conn_info.create_ssl_context(enable_iam_auth)
766
+ server_hostname = (
767
+ conn_info.conn_name.domain_name
768
+ if conn_info.conn_name.domain_name
769
+ else None
770
+ )
771
+
772
+ last_err: Exception | None = None
773
+ for target in targets:
774
+ try:
775
+ reader, writer = await asyncio.wait_for(
776
+ asyncio.open_connection(
777
+ host=target,
778
+ port=SERVER_PROXY_PORT,
779
+ ssl=ssl_context,
780
+ server_hostname=server_hostname,
781
+ ),
782
+ timeout=float(max(1, self._timeout)),
783
+ )
784
+ return reader, writer
785
+ except Exception as e: # noqa: BLE001
786
+ last_err = e
787
+ if last_err:
788
+ raise last_err
789
+ raise ConnectionError(
790
+ f"Failed to connect to any target for {conn_info.conn_name}"
791
+ )
792
+
697
793
  def __enter__(self) -> Any:
698
794
  """Enter context manager by returning Connector object"""
699
795
  return self