Skip to content

Commit a5869ef

Browse files
Added support for caching configurations for a configurable period of
time.
1 parent a5778dc commit a5869ef

File tree

10 files changed

+394
-136
lines changed

10 files changed

+394
-136
lines changed

doc/src/user_guide/connection_handling.rst

Lines changed: 114 additions & 106 deletions
Large diffs are not rendered by default.

src/oracledb/base_impl.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ cdef class ConnectParamsImpl:
572572
public str driver_name
573573
public dict extra_auth_params
574574
public bint thick_mode_dsn_passthrough
575+
public str _config_cache_key
575576

576577
cdef int _check_credentials(self) except -1
577578
cdef int _copy(self, ConnectParamsImpl other_params) except -1

src/oracledb/base_impl.pyx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ from .interchange.nanoarrow_bridge cimport (
5959
import array
6060

6161
import base64
62+
import copy
6263
import datetime
6364
import decimal
6465
import getpass
@@ -71,6 +72,7 @@ import socket
7172
import ssl
7273
import string
7374
import sys
75+
import time
7476
import warnings
7577

7678
cydatetime.import_datetime()

src/oracledb/impl/base/connect_params.pyx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ cdef class ConnectParamsImpl:
120120
if address is not self._default_address:
121121
address.set_from_args(args)
122122

123-
def set_from_config(self, dict config):
123+
def set_from_config(self, dict config, bint update_cache=True):
124124
"""
125125
Internal method for setting the property from the supplied
126126
configuration.
@@ -140,6 +140,8 @@ cdef class ConnectParamsImpl:
140140
args = config.get("pyo")
141141
if args is not None:
142142
self.set(args)
143+
if update_cache and self._config_cache_key is not None:
144+
update_config_cache(self._config_cache_key, config)
143145

144146
cdef int _check_credentials(self) except -1:
145147
"""

src/oracledb/impl/base/parsers.pyx

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ EXTENDED_PARAM_NAMES = set([
159159

160160
])
161161

162+
# this cache stores the configurations acquired from one of the configuration
163+
# stores
164+
cdef dict cached_configs = {}
165+
162166
# add all of the common parameters to the extended parameters using the
163167
# python-oracledb specific name
164168
for name in COMMON_PARAM_NAMES:
@@ -227,6 +231,43 @@ cdef class BaseParser:
227231
self.temp_pos += 1
228232

229233

234+
cdef int update_config_cache(str config_cache_key, dict config) except -1:
235+
"""
236+
Updates the cache with the specified configuration.
237+
"""
238+
cdef:
239+
double current_time, soft_expiry_time, hard_expiry_time
240+
uint32_t time_to_live, time_to_live_grace_period
241+
object setting
242+
243+
# the config can include config_time_to_live; the default value is 86400
244+
# (24 hours); an explicit value of 0 disables caching
245+
setting = config.get("config_time_to_live")
246+
if setting is None:
247+
time_to_live = 86400
248+
else:
249+
time_to_live = int(setting)
250+
if time_to_live == 0:
251+
return 0
252+
253+
# the config settings can also include config_time_to_live_grace_period;
254+
# the default value is 1800 (30 minutes)
255+
setting = config.get("config_time_to_live_grace_period")
256+
if setting is None:
257+
time_to_live_grace_period = 1800
258+
else:
259+
time_to_live_grace_period = int(setting)
260+
261+
# calculate soft and hard expiry times and keep them with the config
262+
current_time = time.monotonic()
263+
soft_expiry_time = current_time + time_to_live
264+
hard_expiry_time = soft_expiry_time + time_to_live_grace_period
265+
config = copy.deepcopy(config)
266+
config["config_cache_soft_expiry_time"] = soft_expiry_time
267+
config["config_cache_hard_expiry_time"] = hard_expiry_time
268+
cached_configs[config_cache_key] = config
269+
270+
230271
cdef class ConnectStringParser(BaseParser):
231272

232273
cdef:
@@ -237,6 +278,48 @@ cdef class ConnectStringParser(BaseParser):
237278
Description description
238279
dict parameters
239280

281+
cdef int _call_protocol_hook(self, str protocol, str arg,
282+
object fn) except -1:
283+
"""
284+
Check if the config cache has an entry; if an extry exists and it has
285+
not expired, use it; otherwise, call the protocol hook function.
286+
"""
287+
cdef:
288+
double current_time = 0, expiry_time = 0
289+
dict config
290+
291+
# check to see if the cache has a value and that it has not reached the
292+
# soft expiry time
293+
config = cached_configs.get(self.data_as_str)
294+
if config is not None:
295+
current_time = time.monotonic()
296+
expiry_time = config["config_cache_soft_expiry_time"]
297+
if current_time <= expiry_time:
298+
self.params_impl.set_from_config(config, update_cache=False)
299+
return 0
300+
301+
# call the protocol hook function; the cache key is set on the
302+
# parameters instance so that calls by the hook function to
303+
# set_from_config() will update the cache
304+
params = self.params_impl._get_public_instance()
305+
self.params_impl._config_cache_key = self.data_as_str
306+
try:
307+
fn(protocol, arg, params)
308+
except Exception as e:
309+
310+
# if the hook fails but a config exists in the cache and the hard
311+
# expiry time has not been reached the existing config is used
312+
if config is not None:
313+
expiry_time = config["config_cache_hard_expiry_time"]
314+
if current_time <= expiry_time:
315+
self.params_impl.set_from_config(config, update_cache=False)
316+
return 0
317+
del cached_configs[self.params_impl._config_cache_key]
318+
errors._raise_err(errors.ERR_PROTOCOL_HANDLER_FAILED,
319+
protocol=protocol, arg=arg, cause=e)
320+
finally:
321+
self.params_impl._config_cache_key = None
322+
240323
cdef bint _is_host_or_service_name_char(self, Py_UCS4 ch):
241324
"""
242325
Returns whether or not the given character is allowed to be used inside
@@ -377,19 +460,14 @@ cdef class ConnectStringParser(BaseParser):
377460
Parses an easy connect string.
378461
"""
379462
cdef:
380-
object params, fn
463+
object fn
381464
str protocol, arg
382465
protocol = self._parse_easy_connect_protocol()
383466
if protocol is not None:
384467
fn = REGISTERED_PROTOCOLS.get(protocol)
385468
if fn is not None:
386469
arg = self.data_as_str[self.temp_pos:]
387-
params = self.params_impl._get_public_instance()
388-
try:
389-
fn(protocol, arg, params)
390-
except Exception as e:
391-
errors._raise_err(errors.ERR_PROTOCOL_HANDLER_FAILED,
392-
protocol=protocol, arg=arg, cause=e)
470+
self._call_protocol_hook(protocol, arg, fn)
393471
self.description_list = self.params_impl.description_list
394472
self.pos = self.num_chars
395473
return 0

src/oracledb/plugins/azure_config_provider.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ def _process_config(parameters, connect_params):
181181
)
182182
config["user"] = _get_setting(client, key, "user", label, required=False)
183183
pwd = _get_setting(client, key, "password", label, required=False)
184-
185184
if pwd is not None:
186185
try:
187186
pwd = json.loads(pwd)
@@ -193,8 +192,14 @@ def _process_config(parameters, connect_params):
193192
" containing Azure Vault details."
194193
)
195194
raise Exception(message)
196-
197195
config["password"] = pwd
196+
config["config_time_to_live"] = _get_setting(
197+
client, key, "config_time_to_live", label, required=False
198+
)
199+
config["config_time_to_live_grace_period"] = _get_setting(
200+
client, key, "config_time_to_live_grace_period", label, required=False
201+
)
202+
198203
# get the python-oracledb specific parameters
199204
settings = _get_setting(client, key, "pyo", label, required=False)
200205
if settings is not None:

src/oracledb/plugins/azure_tokens.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
# -----------------------------------------------------------------------------
2626
# azure_tokens.py
2727
#
28-
# Python file defining the methods that generates an OAuth access token
29-
# using the MSAL SDK
28+
# Methods that generates an OAuth2 access token using the MSAL SDK
3029
# -----------------------------------------------------------------------------
3130

3231
import msal
@@ -37,11 +36,14 @@ def generate_token(token_auth_config, refresh=False):
3736
"""
3837
Generates an Azure access token based on provided credentials.
3938
"""
40-
auth_type = token_auth_config.get("authType", "").lower()
39+
user_auth_type = token_auth_config.get("auth_type") or ""
40+
auth_type = user_auth_type.lower()
4141
if auth_type == "azureserviceprincipal":
4242
return _service_principal_credentials(token_auth_config)
4343
else:
44-
raise ValueError(f"Unrecognized authentication method: {auth_type}")
44+
raise ValueError(
45+
f"Unrecognized auth_type authentication method: {user_auth_type}"
46+
)
4547

4648

4749
def _service_principal_credentials(token_auth_config):
@@ -50,8 +52,8 @@ def _service_principal_credentials(token_auth_config):
5052
"""
5153
msal_config = {
5254
"authority": token_auth_config["authority"],
53-
"client_id": token_auth_config["clientId"],
54-
"client_credential": token_auth_config["clientSecret"],
55+
"client_id": token_auth_config["client_id"],
56+
"client_credential": token_auth_config["client_credential"],
5557
}
5658
# Initialize the Confidential Client Application
5759
cca = msal.ConfidentialClientApplication(**msal_config)

src/oracledb/plugins/oci_config_provider.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def _get_config(parameters, connect_params):
8686
settings, "connect_descriptor"
8787
)
8888

89+
# user and password
8990
if connect_params.user is None:
9091
config["user"] = settings.get("user")
9192
if "password" in settings:
@@ -97,10 +98,16 @@ def _get_config(parameters, connect_params):
9798
# password should be stored in JSON and not plain text.
9899
config["password"] = pwd
99100

101+
# config cache settings
102+
config["config_time_to_live"] = settings.get("config_time_to_live")
103+
config["config_time_to_live_grace_period"] = settings.get(
104+
"config_time_to_live_grace_period"
105+
)
106+
100107
# pyo parameters settings
101108
config["pyo"] = settings.get("pyo", None)
102109

103-
# parse connect string and set requested parameters
110+
# set the configuration
104111
connect_params.set_from_config(config)
105112

106113

src/oracledb/plugins/oci_tokens.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
# -----------------------------------------------------------------------------
2626
# oci_tokens.py
2727
#
28-
# Python file defining the methods that genearates an OCI access
29-
# token using the OCI SDK
28+
# Methods that generates an OCI access token using the OCI SDK
3029
# -----------------------------------------------------------------------------
3130

3231
import oci
@@ -39,13 +38,16 @@ def generate_token(token_auth_config, refresh=False):
3938
"""
4039
Generates an OCI access token based on provided credentials.
4140
"""
42-
auth_type = token_auth_config.get("authType", "").lower()
43-
if auth_type == "configfilebasedauthentication":
41+
user_auth_type = token_auth_config.get("auth_type") or ""
42+
auth_type = user_auth_type.lower()
43+
if auth_type == "configfileauthentication":
4444
return _config_file_based_authentication(token_auth_config)
4545
elif auth_type == "simpleauthentication":
4646
return _simple_authentication(token_auth_config)
4747
else:
48-
raise ValueError(f"Unrecognized authentication method: {auth_type}")
48+
raise ValueError(
49+
f"Unrecognized auth_type authentication method {user_auth_type}"
50+
)
4951

5052

5153
def _get_key_pair():
@@ -81,16 +83,16 @@ def _get_key_pair():
8183
)
8284
private_key_pem = p_key
8385

84-
return {"privateKey": private_key_pem, "publicKey": public_key_pem}
86+
return {"private_key": private_key_pem, "public_key": public_key_pem}
8587

8688

8789
def _config_file_based_authentication(token_auth_config):
8890
"""
89-
Config file base authentication implementation, config parameters
91+
Config file base authentication implementation: config parameters
9092
are provided in a file.
9193
"""
9294
file_location = token_auth_config.get(
93-
"configFileLocation", oci.config.DEFAULT_LOCATION
95+
"file_location", oci.config.DEFAULT_LOCATION
9496
)
9597
profile = token_auth_config.get("profile", oci.config.DEFAULT_PROFILE)
9698

@@ -105,22 +107,22 @@ def _config_file_based_authentication(token_auth_config):
105107

106108
response = client.generate_scoped_access_token(
107109
generate_scoped_access_token_details=oci.identity_data_plane.models.GenerateScopedAccessTokenDetails(
108-
scope="urn:oracle:db::id::*", public_key=key_pair["publicKey"]
110+
scope="urn:oracle:db::id::*", public_key=key_pair["public_key"]
109111
)
110112
)
111113

112114
# access_token is a tuple holding token and private key
113115
access_token = (
114116
response.data.token,
115-
key_pair["privateKey"],
117+
key_pair["private_key"],
116118
)
117119

118120
return access_token
119121

120122

121123
def _simple_authentication(token_auth_config):
122124
"""
123-
Simple authentication, config parameters are passed as parameters
125+
Simple authentication: config parameters are passed as parameters
124126
"""
125127
config = {
126128
"user": token_auth_config["user"],
@@ -139,14 +141,14 @@ def _simple_authentication(token_auth_config):
139141

140142
response = client.generate_scoped_access_token(
141143
generate_scoped_access_token_details=oci.identity_data_plane.models.GenerateScopedAccessTokenDetails(
142-
scope="urn:oracle:db::id::*", public_key=key_pair["publicKey"]
144+
scope="urn:oracle:db::id::*", public_key=key_pair["public_key"]
143145
)
144146
)
145147

146148
# access_token is a tuple holding token and private key
147149
access_token = (
148150
response.data.token,
149-
key_pair["privateKey"],
151+
key_pair["private_key"],
150152
)
151153

152154
return access_token

0 commit comments

Comments
 (0)