cloud-sql-python-connector 1.9.2__py2.py3-none-any.whl → 1.11.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
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
+ import asyncio
16
+ from datetime import datetime
17
+ from datetime import timedelta
18
+ from datetime import timezone
19
+ import logging
20
+ from typing import Optional
21
+
22
+ from google.cloud.sql.connector.client import CloudSQLClient
23
+ from google.cloud.sql.connector.connection_info import ConnectionInfo
24
+ from google.cloud.sql.connector.instance import _parse_instance_connection_name
25
+ from google.cloud.sql.connector.refresh_utils import _refresh_buffer
26
+
27
+ logger = logging.getLogger(name=__name__)
28
+
29
+
30
+ class LazyRefreshCache:
31
+ """Cache that refreshes connection info when a caller requests a connection.
32
+
33
+ Only refreshes the cache when a new connection is requested and the current
34
+ certificate is close to or already expired.
35
+
36
+ This is the recommended option for serverless environments.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ instance_connection_string: str,
42
+ client: CloudSQLClient,
43
+ keys: asyncio.Future,
44
+ enable_iam_auth: bool = False,
45
+ ) -> None:
46
+ """Initializes a LazyRefreshCache instance.
47
+
48
+ Args:
49
+ instance_connection_string (str): The Cloud SQL Instance's
50
+ connection string (also known as an instance connection name).
51
+ client (CloudSQLClient): The Cloud SQL Client instance.
52
+ keys (asyncio.Future): A future to the client's public-private key
53
+ pair.
54
+ enable_iam_auth (bool): Enables automatic IAM database authentication
55
+ (Postgres and MySQL) as the default authentication method for all
56
+ connections.
57
+ """
58
+ # validate and parse instance connection name
59
+ self._project, self._region, self._instance = _parse_instance_connection_name(
60
+ instance_connection_string
61
+ )
62
+ self._instance_connection_string = instance_connection_string
63
+
64
+ self._enable_iam_auth = enable_iam_auth
65
+ self._keys = keys
66
+ self._client = client
67
+ self._lock = asyncio.Lock()
68
+ self._cached: Optional[ConnectionInfo] = None
69
+ self._needs_refresh = False
70
+
71
+ async def force_refresh(self) -> None:
72
+ """
73
+ Invalidates the cache and configures the next call to
74
+ connect_info() to retrieve a fresh ConnectionInfo instance.
75
+ """
76
+ async with self._lock:
77
+ self._needs_refresh = True
78
+
79
+ async def connect_info(self) -> ConnectionInfo:
80
+ """Retrieves ConnectionInfo instance for establishing a secure
81
+ connection to the Cloud SQL instance.
82
+ """
83
+ async with self._lock:
84
+ # If connection info is cached, check expiration.
85
+ # Pad expiration with a buffer to give the client plenty of time to
86
+ # establish a connection to the server with the certificate.
87
+ if (
88
+ self._cached
89
+ and not self._needs_refresh
90
+ and datetime.now(timezone.utc)
91
+ < (self._cached.expiration - timedelta(seconds=_refresh_buffer))
92
+ ):
93
+ logger.debug(
94
+ f"['{self._instance_connection_string}']: Connection info "
95
+ "is still valid, using cached info"
96
+ )
97
+ return self._cached
98
+ logger.debug(
99
+ f"['{self._instance_connection_string}']: Connection info "
100
+ "refresh operation started"
101
+ )
102
+ try:
103
+ conn_info = await self._client.get_connection_info(
104
+ self._project,
105
+ self._region,
106
+ self._instance,
107
+ self._keys,
108
+ self._enable_iam_auth,
109
+ )
110
+ except Exception as e:
111
+ logger.debug(
112
+ f"['{self._instance_connection_string}']: Connection info "
113
+ f"refresh operation failed: {str(e)}"
114
+ )
115
+ raise
116
+ logger.debug(
117
+ f"['{self._instance_connection_string}']: Connection info "
118
+ "refresh operation completed successfully"
119
+ )
120
+ logger.debug(
121
+ f"['{self._instance_connection_string}']: Current certificate "
122
+ f"expiration = {str(conn_info.expiration)}"
123
+ )
124
+ self._cached = conn_info
125
+ self._needs_refresh = False
126
+ return conn_info
127
+
128
+ async def close(self) -> None:
129
+ """Close is a no-op and provided purely for a consistent interface with
130
+ other cache types.
131
+ """
132
+ pass
@@ -20,8 +20,10 @@ import asyncio
20
20
  import copy
21
21
  import datetime
22
22
  import logging
23
- from typing import List
23
+ import random
24
+ from typing import Any, Callable, List
24
25
 
26
+ import aiohttp
25
27
  from google.auth.credentials import Credentials
26
28
  from google.auth.credentials import Scoped
27
29
  import google.auth.transport.requests
@@ -105,3 +107,48 @@ def _downscope_credentials(
105
107
  request = google.auth.transport.requests.Request()
106
108
  scoped_creds.refresh(request)
107
109
  return scoped_creds
110
+
111
+
112
+ def _exponential_backoff(attempt: int) -> float:
113
+ """Calculates a duration to backoff in milliseconds based on the attempt i.
114
+
115
+ The formula is:
116
+
117
+ base * multi^(attempt + 1 + random)
118
+
119
+ With base = 200ms and multi = 1.618, and random = [0.0, 1.0),
120
+ the backoff values would fall between the following low and high ends:
121
+
122
+ Attempt Low (ms) High (ms)
123
+
124
+ 0 324 524
125
+ 1 524 847
126
+ 2 847 1371
127
+ 3 1371 2218
128
+ 4 2218 3588
129
+
130
+ The theoretical worst case scenario would have a client wait 8.5s in total
131
+ for an API request to complete (with the first four attempts failing, and
132
+ the fifth succeeding).
133
+ """
134
+ base = 200
135
+ multi = 1.618
136
+ exp = attempt + 1 + random.random()
137
+ return base * pow(multi, exp)
138
+
139
+
140
+ async def retry_50x(
141
+ request_coro: Callable, *args: Any, **kwargs: Any
142
+ ) -> aiohttp.ClientResponse:
143
+ """Retry any 50x HTTP response up to X number of times."""
144
+ max_retries = 5
145
+ for i in range(max_retries):
146
+ resp = await request_coro(*args, **kwargs)
147
+ # backoff for any 50X errors
148
+ if resp.status >= 500 and i < max_retries:
149
+ # calculate backoff time
150
+ backoff = _exponential_backoff(i)
151
+ await asyncio.sleep(backoff / 1000)
152
+ else:
153
+ break
154
+ return resp
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "1.9.2"
15
+ __version__ = "1.11.0"
@@ -1,19 +0,0 @@
1
- google/cloud/sql/connector/__init__.py,sha256=Z3AkPOQwjNF1ogo2DR18GN-CnYvI4MTmhsCe8hURKUo,883
2
- google/cloud/sql/connector/asyncpg.py,sha256=c4lAlvEgGf_eBq0G8MVNPRAJQQvo6DcwybiXDW36qkU,1838
3
- google/cloud/sql/connector/client.py,sha256=NZZnPqE_f13S04tpRCs0UGF-RxR4EL-_vH7ebQBXTaA,8494
4
- google/cloud/sql/connector/connector.py,sha256=q1P0lqDjU5O5U8FQjNa9punrdExwcjvVz7SodbaVmSw,19147
5
- google/cloud/sql/connector/exceptions.py,sha256=rTBtbCoiZ3U5IFVsyXPw5baHbCODixx2hPDQeXUzgzs,1524
6
- google/cloud/sql/connector/instance.py,sha256=Oed1kFTaVolce74DzsSRbooj6mUthwZtt75OamTfOQs,14966
7
- google/cloud/sql/connector/pg8000.py,sha256=EZuO7XgCkG_Y79NQlh3Vv8Ff2w85BFJyFVO237hx1GM,1814
8
- google/cloud/sql/connector/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- google/cloud/sql/connector/pymysql.py,sha256=wldUgwDPlPa8Nj5F3JaipuP8hJyvzlgOAJbRnOoaB1U,2105
10
- google/cloud/sql/connector/pytds.py,sha256=vKJoE7E2P0GrhZojg8bGZkqCMLc8dgKAcXE4ee7vEVc,2452
11
- google/cloud/sql/connector/rate_limiter.py,sha256=bPTupfM4f5QRSCmCAWMqjQylEB7bUM-50plVBU3W2CA,2993
12
- google/cloud/sql/connector/refresh_utils.py,sha256=P2ye3qDymdK1iOlGCXby4VIaqwvHdc9GEmuUsMcufxY,3680
13
- google/cloud/sql/connector/utils.py,sha256=FQs7RgoViJLIFgJIynVT-_DwAVWnpyXIKu5WZ4m10Ts,3448
14
- google/cloud/sql/connector/version.py,sha256=x6crtIS26FuBXZ_fZ636i8UD16NlwWdlYJf5SEIqLIc,597
15
- cloud_sql_python_connector-1.9.2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
16
- cloud_sql_python_connector-1.9.2.dist-info/METADATA,sha256=5ufJWE8PP34QHa8_1oh1vPJmzaOY-Byqfso7o_BbCnw,24113
17
- cloud_sql_python_connector-1.9.2.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
18
- cloud_sql_python_connector-1.9.2.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
19
- cloud_sql_python_connector-1.9.2.dist-info/RECORD,,