Python 3.x 在模块级和单元测试上缓存值
下面是一个查询和缓存AWS STS令牌的模块,目的是避免在存在有效令牌时查询STSPython 3.x 在模块级和单元测试上缓存值,python-3.x,pytest,Python 3.x,Pytest,下面是一个查询和缓存AWS STS令牌的模块,目的是避免在存在有效令牌时查询STS class Credentials: def __init__(self): self.sts_credentials = None self.token_expiry_time = None def is_token_expired(self): current_time_with_buffer = datetime.now() + timede
class Credentials:
def __init__(self):
self.sts_credentials = None
self.token_expiry_time = None
def is_token_expired(self):
current_time_with_buffer = datetime.now() + timedelta(minutes=2)
return not self.token_expiry_time or self.token_expiry_time < current_time_with_buffer
CREDENTIALS_ = Credentials()
def get_credentials():
if CREDENTIALS_.is_token_expired():
sts_client = boto3.client('sts')
LOGGER.info("The credentials are either empty or expiring, refreshing")
try:
sts_token = sts_client.assume_role(
RoleArn=os.environ["KINESIS_ASSUME_ROLE"],
RoleSessionName=str(uuid.uuid4()))
except Exception as e:
LOGGER.error(f"Error occurred while trying to assume role with {os.environ['KINESIS_ASSUME_ROLE']}", e)
raise e
CREDENTIALS_.sts_credentials = {
"aws_access_key_id": sts_token['Credentials']['AccessKeyId'],
"aws_secret_access_key": sts_token['Credentials']['SecretAccessKey'],
"aws_session_token": sts_token['Credentials']['SessionToken']
}
CREDENTIALS_.token_expiry_time = sts_token["Credentials"]["Expiration"]
return CREDENTIALS_.sts_credentials
更简洁的方法是确保您的
单元
测试正在执行单元测试。这意味着,对于每个单元,不应与其他单元进行交互。由于您使用的是全局变量凭证
,这几乎是不可能的
1) 容易解决
一个简单的解决方法是将凭证作为输入参数传递。然后,您可以在每个测试期间创建一个伪凭证
对象,该对象根据您的测试条件进行定制
2) 更好的解决方案
更好的解决方案是,除了使用credential
输入参数外,还可以分解get\u凭证
中的逻辑。通过将其拆分为较小的功能,您可以将服务器逻辑和凭证更新分开。使模拟和测试更容易。整个职能的可能划分为:
- 获取\u sts\u令牌
- 更新\u凭据
- 获取您的凭据
现在,get\u sts\u令牌
已连接到服务器,但是update\u凭证
和get\u凭证
不必直接与服务器交互
代码
例1)
现在,您可以在测试中插入自己的凭证
对象
例2)
示例2看起来更干净,但有两个问题1。由于管理令牌的实现细节现在是公共函数2,因此抽象现在是泄漏的。状态(credentials对象)现在向公众公开,以便能够有效地使用这些函数。您可以强制凭据类内部的凭据更新作为一个非常私有的函数(\uuuuuu gets\u sts\u token
),或者使用\uuuuu init\uuuuuu.py
将这些函数排除到API中。对于公开的数据。我不确定是否得到它,但在我看来,CREDENTIALS\uu
也是公共可用的,并且CREDENTIALS类是检索到的(真实)凭据的存储。如果要完全限制用户对此的访问,可以使用注册类,其中只允许get\u credentials
方法公开。
def test_get_credentials_refreshes_token_if_about_to_expire(sts_response, credentials):
with mock.patch("boto3.client") as mock_boto_client:
mock_assume_role = mock_boto_client.return_value.assume_role
mock_assume_role.return_value = sts_response
get_credentials()
actual_credentials = get_credentials()
calls = [call('sts'),
call().assume_role(RoleArn='arn:aws:iam::000000000000:role/dummyarn', RoleSessionName=ANY),
call('sts'),
call().assume_role(RoleArn='arn:aws:iam::000000000000:role/dummyarn', RoleSessionName=ANY)]
assert credentials == actual_credentials
mock_boto_client.assert_has_calls(calls)
def update_credentials(credentials):
if credentials.is_token_expired():
sts_client = boto3.client('sts')
LOGGER.info("The credentials are either empty or expiring, refreshing")
try:
sts_token = sts_client.assume_role(
RoleArn=os.environ["KINESIS_ASSUME_ROLE"],
RoleSessionName=str(uuid.uuid4()))
except Exception as e:
LOGGER.error(f"Error occurred while trying to assume role with {os.environ['KINESIS_ASSUME_ROLE']}", e)
raise e
credentials.sts_credentials = {
"aws_access_key_id": sts_token['Credentials']['AccessKeyId'],
"aws_secret_access_key": sts_token['Credentials']['SecretAccessKey'],
"aws_session_token": sts_token['Credentials']['SessionToken']
}
credentials.token_expiry_time = sts_token["Credentials"]["Expiration"]
return credentials
# Where you need the credentials
CREDENTIALS_ = update_credentials(CREDENTIALS_)
CREDENTIALS_.sts_credentials
def get_sts_token():
sts_client = boto3.client('sts')
LOGGER.info("The credentials are either empty or expiring, refreshing")
try:
sts_token = sts_client.assume_role(
RoleArn=os.environ["KINESIS_ASSUME_ROLE"],
RoleSessionName=str(uuid.uuid4()))
except Exception as e:
LOGGER.error(f"Error occurred while trying to assume role with {os.environ['KINESIS_ASSUME_ROLE']}", e)
raise e
return sts_token
def update_credentials(credentials, sts_token):
credentials.sts_credentials = {
"aws_access_key_id": sts_token['Credentials']['AccessKeyId'],
"aws_secret_access_key": sts_token['Credentials']['SecretAccessKey'],
"aws_session_token": sts_token['Credentials']['SessionToken']
}
return credentials
def get_credentials(credentials: Credentials):
if credentials.is_token_expired():
sts_token = get_sts_token()
credentials = update_credentials(credentials, sts_token)
return credentials.sts_credentials