Python 3.x 在模块级和单元测试上缓存值

Python 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

下面是一个查询和缓存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() + 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