cloud-sql-python-connector 1.6.0__tar.gz → 1.8.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 (26) hide show
  1. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/PKG-INFO +13 -14
  2. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/README.md +11 -13
  3. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/PKG-INFO +13 -14
  4. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/SOURCES.txt +1 -0
  5. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/requires.txt +3 -3
  6. cloud-sql-python-connector-1.8.0/google/cloud/sql/connector/client.py +221 -0
  7. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/connector.py +110 -52
  8. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/exceptions.py +0 -8
  9. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/instance.py +30 -91
  10. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/rate_limiter.py +3 -1
  11. cloud-sql-python-connector-1.8.0/google/cloud/sql/connector/refresh_utils.py +107 -0
  12. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/utils.py +1 -24
  13. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/version.py +1 -1
  14. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/setup.py +4 -3
  15. cloud-sql-python-connector-1.6.0/google/cloud/sql/connector/refresh_utils.py +0 -293
  16. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/LICENSE +0 -0
  17. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/dependency_links.txt +0 -0
  18. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/not-zip-safe +0 -0
  19. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/cloud_sql_python_connector.egg-info/top_level.txt +0 -0
  20. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/__init__.py +0 -0
  21. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/asyncpg.py +0 -0
  22. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/pg8000.py +0 -0
  23. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/py.typed +0 -0
  24. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/pymysql.py +0 -0
  25. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/google/cloud/sql/connector/pytds.py +0 -0
  26. {cloud-sql-python-connector-1.6.0 → cloud-sql-python-connector-1.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloud-sql-python-connector
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
5
5
  Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
6
6
  Author: Google LLC
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Requires-Python: >=3.8
19
20
  Description-Content-Type: text/markdown
20
21
  Provides-Extra: pymysql
@@ -136,7 +137,7 @@ format "project:region:instance".
136
137
  ### How to use this Connector
137
138
 
138
139
  To connect to Cloud SQL using the connector, inititalize a `Connector`
139
- object and call it's `connect` method with the proper input parameters.
140
+ object and call its `connect` method with the proper input parameters.
140
141
 
141
142
  The `Connector` itself creates connection objects by calling its `connect` method but does not manage database connection pooling. For this reason, it is recommended to use the connector alongside a library that can create connection pools, such as [SQLAlchemy](https://www.sqlalchemy.org/). This will allow for connections to remain open and be reused, reducing connection overhead and the number of connections needed.
142
143
 
@@ -192,7 +193,7 @@ with pool.connect() as db_conn:
192
193
  print(row)
193
194
  ```
194
195
 
195
- To close the `Connector` object's background resources, call it's `close()` method as follows:
196
+ To close the `Connector` object's background resources, call its `close()` method as follows:
196
197
 
197
198
  ```python
198
199
  connector.close()
@@ -213,7 +214,7 @@ from google.cloud.sql.connector import Connector, IPTypes
213
214
 
214
215
  # Note: all parameters below are optional
215
216
  connector = Connector(
216
- ip_type=IPTypes.PUBLIC,
217
+ ip_type="public", # can also be "private" or "psc"
217
218
  enable_iam_auth=False,
218
219
  timeout=30,
219
220
  credentials=custom_creds # google.auth.credentials.Credentials
@@ -286,18 +287,16 @@ using both public and private IP addresses, as well as
286
287
  with, set the `ip_type` keyword argument when initializing a `Connector()` or when
287
288
  calling `connector.connect()`.
288
289
 
289
- Possible values for `ip_type` are `IPTypes.PUBLIC` (default value),
290
- `IPTypes.PRIVATE`, and `IPTypes.PSC`.
290
+ Possible values for `ip_type` are `"public"` (default value),
291
+ `"private"`, and `"psc"`.
291
292
 
292
293
  Example:
293
294
 
294
295
  ```python
295
- from google.cloud.sql.connector import IPTypes
296
-
297
296
  conn = connector.connect(
298
297
  "project:region:instance",
299
298
  "pymysql",
300
- ip_type=IPTypes.PRIVATE # use private IP
299
+ ip_type="private" # use private IP
301
300
  ... insert other kwargs ...
302
301
  )
303
302
  ```
@@ -358,7 +357,7 @@ conn = connector.connect(
358
357
  db="my-db-name",
359
358
  active_directory_auth=True,
360
359
  server_name="private.[instance].[location].[project].cloudsql.[domain]",
361
- ip_type=IPTypes.PRIVATE
360
+ ip_type="private"
362
361
  )
363
362
  ```
364
363
 
@@ -383,7 +382,7 @@ your web application through the following:
383
382
  ```python
384
383
  from flask import Flask
385
384
  from flask_sqlalchemy import SQLAlchemy
386
- from google.cloud.sql.connector import Connector, IPTypes
385
+ from google.cloud.sql.connector import Connector
387
386
 
388
387
 
389
388
  # initialize Python Connector object
@@ -397,7 +396,7 @@ def getconn():
397
396
  user="my-user",
398
397
  password="my-password",
399
398
  db="my-database",
400
- ip_type= IPTypes.PUBLIC # IPTypes.PRIVATE for private IP
399
+ ip_type="public" # "private" for private IP
401
400
  )
402
401
  return conn
403
402
 
@@ -432,7 +431,7 @@ from sqlalchemy import create_engine
432
431
  from sqlalchemy.engine import Engine
433
432
  from sqlalchemy.ext.declarative import declarative_base
434
433
  from sqlalchemy.orm import sessionmaker
435
- from google.cloud.sql.connector import Connector, IPTypes
434
+ from google.cloud.sql.connector import Connector
436
435
 
437
436
  # helper function to return SQLAlchemy connection pool
438
437
  def init_connection_pool(connector: Connector) -> Engine:
@@ -444,7 +443,7 @@ def init_connection_pool(connector: Connector) -> Engine:
444
443
  user="my-user",
445
444
  password="my-password",
446
445
  db="my-database",
447
- ip_type= IPTypes.PUBLIC # IPTypes.PRIVATE for private IP
446
+ ip_type="public" # "private" for private IP
448
447
  )
449
448
  return conn
450
449
 
@@ -111,7 +111,7 @@ format "project:region:instance".
111
111
  ### How to use this Connector
112
112
 
113
113
  To connect to Cloud SQL using the connector, inititalize a `Connector`
114
- object and call it's `connect` method with the proper input parameters.
114
+ object and call its `connect` method with the proper input parameters.
115
115
 
116
116
  The `Connector` itself creates connection objects by calling its `connect` method but does not manage database connection pooling. For this reason, it is recommended to use the connector alongside a library that can create connection pools, such as [SQLAlchemy](https://www.sqlalchemy.org/). This will allow for connections to remain open and be reused, reducing connection overhead and the number of connections needed.
117
117
 
@@ -167,7 +167,7 @@ with pool.connect() as db_conn:
167
167
  print(row)
168
168
  ```
169
169
 
170
- To close the `Connector` object's background resources, call it's `close()` method as follows:
170
+ To close the `Connector` object's background resources, call its `close()` method as follows:
171
171
 
172
172
  ```python
173
173
  connector.close()
@@ -188,7 +188,7 @@ from google.cloud.sql.connector import Connector, IPTypes
188
188
 
189
189
  # Note: all parameters below are optional
190
190
  connector = Connector(
191
- ip_type=IPTypes.PUBLIC,
191
+ ip_type="public", # can also be "private" or "psc"
192
192
  enable_iam_auth=False,
193
193
  timeout=30,
194
194
  credentials=custom_creds # google.auth.credentials.Credentials
@@ -261,18 +261,16 @@ using both public and private IP addresses, as well as
261
261
  with, set the `ip_type` keyword argument when initializing a `Connector()` or when
262
262
  calling `connector.connect()`.
263
263
 
264
- Possible values for `ip_type` are `IPTypes.PUBLIC` (default value),
265
- `IPTypes.PRIVATE`, and `IPTypes.PSC`.
264
+ Possible values for `ip_type` are `"public"` (default value),
265
+ `"private"`, and `"psc"`.
266
266
 
267
267
  Example:
268
268
 
269
269
  ```python
270
- from google.cloud.sql.connector import IPTypes
271
-
272
270
  conn = connector.connect(
273
271
  "project:region:instance",
274
272
  "pymysql",
275
- ip_type=IPTypes.PRIVATE # use private IP
273
+ ip_type="private" # use private IP
276
274
  ... insert other kwargs ...
277
275
  )
278
276
  ```
@@ -333,7 +331,7 @@ conn = connector.connect(
333
331
  db="my-db-name",
334
332
  active_directory_auth=True,
335
333
  server_name="private.[instance].[location].[project].cloudsql.[domain]",
336
- ip_type=IPTypes.PRIVATE
334
+ ip_type="private"
337
335
  )
338
336
  ```
339
337
 
@@ -358,7 +356,7 @@ your web application through the following:
358
356
  ```python
359
357
  from flask import Flask
360
358
  from flask_sqlalchemy import SQLAlchemy
361
- from google.cloud.sql.connector import Connector, IPTypes
359
+ from google.cloud.sql.connector import Connector
362
360
 
363
361
 
364
362
  # initialize Python Connector object
@@ -372,7 +370,7 @@ def getconn():
372
370
  user="my-user",
373
371
  password="my-password",
374
372
  db="my-database",
375
- ip_type= IPTypes.PUBLIC # IPTypes.PRIVATE for private IP
373
+ ip_type="public" # "private" for private IP
376
374
  )
377
375
  return conn
378
376
 
@@ -407,7 +405,7 @@ from sqlalchemy import create_engine
407
405
  from sqlalchemy.engine import Engine
408
406
  from sqlalchemy.ext.declarative import declarative_base
409
407
  from sqlalchemy.orm import sessionmaker
410
- from google.cloud.sql.connector import Connector, IPTypes
408
+ from google.cloud.sql.connector import Connector
411
409
 
412
410
  # helper function to return SQLAlchemy connection pool
413
411
  def init_connection_pool(connector: Connector) -> Engine:
@@ -419,7 +417,7 @@ def init_connection_pool(connector: Connector) -> Engine:
419
417
  user="my-user",
420
418
  password="my-password",
421
419
  db="my-database",
422
- ip_type= IPTypes.PUBLIC # IPTypes.PRIVATE for private IP
420
+ ip_type="public" # "private" for private IP
423
421
  )
424
422
  return conn
425
423
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloud-sql-python-connector
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
5
5
  Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
6
6
  Author: Google LLC
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Requires-Python: >=3.8
19
20
  Description-Content-Type: text/markdown
20
21
  Provides-Extra: pymysql
@@ -136,7 +137,7 @@ format "project:region:instance".
136
137
  ### How to use this Connector
137
138
 
138
139
  To connect to Cloud SQL using the connector, inititalize a `Connector`
139
- object and call it's `connect` method with the proper input parameters.
140
+ object and call its `connect` method with the proper input parameters.
140
141
 
141
142
  The `Connector` itself creates connection objects by calling its `connect` method but does not manage database connection pooling. For this reason, it is recommended to use the connector alongside a library that can create connection pools, such as [SQLAlchemy](https://www.sqlalchemy.org/). This will allow for connections to remain open and be reused, reducing connection overhead and the number of connections needed.
142
143
 
@@ -192,7 +193,7 @@ with pool.connect() as db_conn:
192
193
  print(row)
193
194
  ```
194
195
 
195
- To close the `Connector` object's background resources, call it's `close()` method as follows:
196
+ To close the `Connector` object's background resources, call its `close()` method as follows:
196
197
 
197
198
  ```python
198
199
  connector.close()
@@ -213,7 +214,7 @@ from google.cloud.sql.connector import Connector, IPTypes
213
214
 
214
215
  # Note: all parameters below are optional
215
216
  connector = Connector(
216
- ip_type=IPTypes.PUBLIC,
217
+ ip_type="public", # can also be "private" or "psc"
217
218
  enable_iam_auth=False,
218
219
  timeout=30,
219
220
  credentials=custom_creds # google.auth.credentials.Credentials
@@ -286,18 +287,16 @@ using both public and private IP addresses, as well as
286
287
  with, set the `ip_type` keyword argument when initializing a `Connector()` or when
287
288
  calling `connector.connect()`.
288
289
 
289
- Possible values for `ip_type` are `IPTypes.PUBLIC` (default value),
290
- `IPTypes.PRIVATE`, and `IPTypes.PSC`.
290
+ Possible values for `ip_type` are `"public"` (default value),
291
+ `"private"`, and `"psc"`.
291
292
 
292
293
  Example:
293
294
 
294
295
  ```python
295
- from google.cloud.sql.connector import IPTypes
296
-
297
296
  conn = connector.connect(
298
297
  "project:region:instance",
299
298
  "pymysql",
300
- ip_type=IPTypes.PRIVATE # use private IP
299
+ ip_type="private" # use private IP
301
300
  ... insert other kwargs ...
302
301
  )
303
302
  ```
@@ -358,7 +357,7 @@ conn = connector.connect(
358
357
  db="my-db-name",
359
358
  active_directory_auth=True,
360
359
  server_name="private.[instance].[location].[project].cloudsql.[domain]",
361
- ip_type=IPTypes.PRIVATE
360
+ ip_type="private"
362
361
  )
363
362
  ```
364
363
 
@@ -383,7 +382,7 @@ your web application through the following:
383
382
  ```python
384
383
  from flask import Flask
385
384
  from flask_sqlalchemy import SQLAlchemy
386
- from google.cloud.sql.connector import Connector, IPTypes
385
+ from google.cloud.sql.connector import Connector
387
386
 
388
387
 
389
388
  # initialize Python Connector object
@@ -397,7 +396,7 @@ def getconn():
397
396
  user="my-user",
398
397
  password="my-password",
399
398
  db="my-database",
400
- ip_type= IPTypes.PUBLIC # IPTypes.PRIVATE for private IP
399
+ ip_type="public" # "private" for private IP
401
400
  )
402
401
  return conn
403
402
 
@@ -432,7 +431,7 @@ from sqlalchemy import create_engine
432
431
  from sqlalchemy.engine import Engine
433
432
  from sqlalchemy.ext.declarative import declarative_base
434
433
  from sqlalchemy.orm import sessionmaker
435
- from google.cloud.sql.connector import Connector, IPTypes
434
+ from google.cloud.sql.connector import Connector
436
435
 
437
436
  # helper function to return SQLAlchemy connection pool
438
437
  def init_connection_pool(connector: Connector) -> Engine:
@@ -444,7 +443,7 @@ def init_connection_pool(connector: Connector) -> Engine:
444
443
  user="my-user",
445
444
  password="my-password",
446
445
  db="my-database",
447
- ip_type= IPTypes.PUBLIC # IPTypes.PRIVATE for private IP
446
+ ip_type="public" # "private" for private IP
448
447
  )
449
448
  return conn
450
449
 
@@ -10,6 +10,7 @@ cloud_sql_python_connector.egg-info/requires.txt
10
10
  cloud_sql_python_connector.egg-info/top_level.txt
11
11
  google/cloud/sql/connector/__init__.py
12
12
  google/cloud/sql/connector/asyncpg.py
13
+ google/cloud/sql/connector/client.py
13
14
  google/cloud/sql/connector/connector.py
14
15
  google/cloud/sql/connector/exceptions.py
15
16
  google/cloud/sql/connector/instance.py
@@ -1,5 +1,5 @@
1
1
  aiohttp
2
- cryptography>=38.0.3
2
+ cryptography>=42.0.0
3
3
  Requests
4
4
  google-auth
5
5
 
@@ -7,10 +7,10 @@ google-auth
7
7
  asyncpg>=0.29.0
8
8
 
9
9
  [pg8000]
10
- pg8000>=1.30.4
10
+ pg8000>=1.30.5
11
11
 
12
12
  [pymysql]
13
13
  PyMySQL>=1.1.0
14
14
 
15
15
  [pytds]
16
- python-tds>=1.13.0
16
+ python-tds>=1.15.0
@@ -0,0 +1,221 @@
1
+ # Copyright 2024 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 datetime
18
+ import logging
19
+ from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING
20
+
21
+ import aiohttp
22
+ from cryptography.hazmat.backends import default_backend
23
+ from cryptography.x509 import load_pem_x509_certificate
24
+ import google.auth.transport.requests
25
+
26
+ from google.cloud.sql.connector.refresh_utils import _downscope_credentials
27
+ from google.cloud.sql.connector.version import __version__ as version
28
+
29
+ if TYPE_CHECKING:
30
+ from google.auth.credentials import Credentials
31
+
32
+ USER_AGENT: str = f"cloud-sql-python-connector/{version}"
33
+ API_VERSION: str = "v1beta4"
34
+
35
+ logger = logging.getLogger(name=__name__)
36
+
37
+
38
+ def _format_user_agent(driver: Optional[str], custom: Optional[str]) -> str:
39
+ agent = f"{USER_AGENT}+{driver}" if driver else USER_AGENT
40
+ if custom:
41
+ agent = f"{agent} {custom}"
42
+ return agent
43
+
44
+
45
+ class CloudSQLClient:
46
+ def __init__(
47
+ self,
48
+ sqladmin_api_endpoint: str,
49
+ quota_project: Optional[str],
50
+ credentials: Credentials,
51
+ client: Optional[aiohttp.ClientSession] = None,
52
+ driver: Optional[str] = None,
53
+ user_agent: Optional[str] = None,
54
+ ) -> None:
55
+ """
56
+ Establish the client to be used for Cloud SQL Admin API requests.
57
+
58
+ Args:
59
+ sqladmin_api_endpoint (str): Base URL to use when calling
60
+ the Cloud SQL Admin API endpoints.
61
+ quota_project (str): The Project ID for an existing Google Cloud
62
+ project. The project specified is used for quota and
63
+ billing purposes.
64
+ credentials (google.auth.credentials.Credentials):
65
+ A credentials object created from the google-auth Python library.
66
+ Must have the Cloud SQL Admin scopes. For more info check out
67
+ https://google-auth.readthedocs.io/en/latest/.
68
+ client (aiohttp.ClientSession): Async client used to make requests to
69
+ Cloud SQL Admin APIs.
70
+ Optional, defaults to None and creates new client.
71
+ driver (str): Database driver to be used by the client.
72
+ """
73
+ user_agent = _format_user_agent(driver, user_agent)
74
+ headers = {
75
+ "x-goog-api-client": user_agent,
76
+ "User-Agent": user_agent,
77
+ "Content-Type": "application/json",
78
+ }
79
+ if quota_project:
80
+ headers["x-goog-user-project"] = quota_project
81
+
82
+ self._client = client if client else aiohttp.ClientSession(headers=headers)
83
+ self._credentials = credentials
84
+ self._sqladmin_api_endpoint = sqladmin_api_endpoint
85
+ self._user_agent = user_agent
86
+
87
+ async def _get_metadata(
88
+ self,
89
+ project: str,
90
+ region: str,
91
+ instance: str,
92
+ ) -> Dict[str, Any]:
93
+ """Requests metadata from the Cloud SQL Instance
94
+ and returns a dictionary containing the IP addresses and certificate
95
+ authority of the Cloud SQL Instance.
96
+
97
+ :type project: str
98
+ :param project:
99
+ A string representing the name of the project.
100
+
101
+ :type region: str
102
+ :param region : A string representing the name of the region.
103
+
104
+ :type instance: str
105
+ :param instance: A string representing the name of the instance.
106
+
107
+ :rtype: Dict[str: Union[Dict, str]]
108
+ :returns: Returns a dictionary containing a dictionary of all IP
109
+ addresses and their type and a string representing the
110
+ certificate authority.
111
+ """
112
+ if not self._credentials.valid:
113
+ request = google.auth.transport.requests.Request()
114
+ self._credentials.refresh(request)
115
+
116
+ headers = {
117
+ "Authorization": f"Bearer {self._credentials.token}",
118
+ }
119
+
120
+ url = f"{self._sqladmin_api_endpoint}/sql/{API_VERSION}/projects/{project}/instances/{instance}/connectSettings"
121
+
122
+ logger.debug(f"['{instance}']: Requesting metadata")
123
+
124
+ resp = await self._client.get(url, headers=headers, raise_for_status=True)
125
+ ret_dict = await resp.json()
126
+
127
+ if ret_dict["region"] != region:
128
+ raise ValueError(
129
+ f'[{project}:{region}:{instance}]: Provided region was mismatched - got region {region}, expected {ret_dict["region"]}.'
130
+ )
131
+
132
+ ip_addresses = (
133
+ {ip["type"]: ip["ipAddress"] for ip in ret_dict["ipAddresses"]}
134
+ if "ipAddresses" in ret_dict
135
+ else {}
136
+ )
137
+ if "dnsName" in ret_dict:
138
+ ip_addresses["PSC"] = ret_dict["dnsName"]
139
+
140
+ return {
141
+ "ip_addresses": ip_addresses,
142
+ "server_ca_cert": ret_dict["serverCaCert"]["cert"],
143
+ "database_version": ret_dict["databaseVersion"],
144
+ }
145
+
146
+ async def _get_ephemeral(
147
+ self,
148
+ project: str,
149
+ instance: str,
150
+ pub_key: str,
151
+ enable_iam_auth: bool = False,
152
+ ) -> Tuple[str, datetime.datetime]:
153
+ """Asynchronously requests an ephemeral certificate from the Cloud SQL Instance.
154
+
155
+ :type project: str
156
+ :param project : A string representing the name of the project.
157
+
158
+ :type instance: str
159
+ :param instance: A string representing the name of the instance.
160
+
161
+ :type pub_key:
162
+ :param str: A string representing PEM-encoded RSA public key.
163
+
164
+ :type enable_iam_auth: bool
165
+ :param enable_iam_auth
166
+ Enables automatic IAM database authentication for Postgres or MySQL
167
+ instances.
168
+
169
+ :rtype: str
170
+ :returns: An ephemeral certificate from the Cloud SQL instance that allows
171
+ authorized connections to the instance.
172
+ """
173
+
174
+ logger.debug(f"['{instance}']: Requesting ephemeral certificate")
175
+
176
+ if not self._credentials.valid:
177
+ request = google.auth.transport.requests.Request()
178
+ self._credentials.refresh(request)
179
+
180
+ headers = {
181
+ "Authorization": f"Bearer {self._credentials.token}",
182
+ }
183
+
184
+ url = f"{self._sqladmin_api_endpoint}/sql/{API_VERSION}/projects/{project}/instances/{instance}:generateEphemeralCert"
185
+
186
+ data = {"public_key": pub_key}
187
+
188
+ if enable_iam_auth:
189
+ # down-scope credentials with only IAM login scope (refreshes them too)
190
+ login_creds = _downscope_credentials(self._credentials)
191
+ data["access_token"] = login_creds.token
192
+
193
+ resp = await self._client.post(
194
+ url, headers=headers, json=data, raise_for_status=True
195
+ )
196
+
197
+ ret_dict = await resp.json()
198
+
199
+ ephemeral_cert: str = ret_dict["ephemeralCert"]["cert"]
200
+
201
+ # decode cert to read expiration
202
+ x509 = load_pem_x509_certificate(
203
+ ephemeral_cert.encode("UTF-8"), default_backend()
204
+ )
205
+ expiration = x509.not_valid_after_utc
206
+ # for IAM authentication OAuth2 token is embedded in cert so it
207
+ # must still be valid for successful connection
208
+ if enable_iam_auth:
209
+ token_expiration: datetime.datetime = login_creds.expiry
210
+ # google.auth library strips timezone info for backwards compatibality
211
+ # reasons with Python 2. Add it back to allow timezone aware datetimes.
212
+ # Ref: https://github.com/googleapis/google-auth-library-python/blob/49a5ff7411a2ae4d32a7d11700f9f961c55406a9/google/auth/_helpers.py#L93-L99
213
+ token_expiration = token_expiration.replace(tzinfo=datetime.timezone.utc)
214
+
215
+ if expiration > token_expiration:
216
+ expiration = token_expiration
217
+ return ephemeral_cert, expiration
218
+
219
+ async def close(self) -> None:
220
+ """Close CloudSQLClient gracefully."""
221
+ await self._client.close()