dcicutils 6.9.9.0b2__py3-none-any.whl → 6.9.9.0b6__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.
- dcicutils/ff_mocks.py +17 -0
- dcicutils/ff_utils.py +16 -11
- dcicutils/misc_utils.py +12 -0
- dcicutils/redis_tools.py +25 -32
- dcicutils/redis_utils.py +22 -2
- dcicutils/s3_utils.py +5 -1
- dcicutils/secrets_utils.py +2 -1
- {dcicutils-6.9.9.0b2.dist-info → dcicutils-6.9.9.0b6.dist-info}/METADATA +1 -1
- {dcicutils-6.9.9.0b2.dist-info → dcicutils-6.9.9.0b6.dist-info}/RECORD +10 -10
- {dcicutils-6.9.9.0b2.dist-info → dcicutils-6.9.9.0b6.dist-info}/WHEEL +0 -0
    
        dcicutils/ff_mocks.py
    CHANGED
    
    | @@ -190,6 +190,23 @@ def mocked_s3utils(environments=None, require_sse=False, other_access_key_names= | |
| 190 190 | 
             
                                                    yield mock_boto3
         | 
| 191 191 |  | 
| 192 192 |  | 
| 193 | 
            +
            DEFAULT_SSE_ENVIRONMENTS_TO_MOCK = ['fourfront-foo', 'fourfront-bar']
         | 
| 194 | 
            +
             | 
| 195 | 
            +
             | 
| 196 | 
            +
            @contextlib.contextmanager
         | 
| 197 | 
            +
            def mocked_s3utils_with_sse(beanstalks=None, environments=None, require_sse=True, files=None):
         | 
| 198 | 
            +
                # beanstalks= is deprecated. Use environments= instead.
         | 
| 199 | 
            +
                environments = (DEFAULT_SSE_ENVIRONMENTS_TO_MOCK
         | 
| 200 | 
            +
                                if beanstalks is None and environments is None
         | 
| 201 | 
            +
                                else (beanstalks or []) + (environments or []))
         | 
| 202 | 
            +
                with mocked_s3utils(environments=environments, require_sse=require_sse) as mock_boto3:
         | 
| 203 | 
            +
                    s3 = mock_boto3.client('s3')
         | 
| 204 | 
            +
                    assert isinstance(s3, MockBotoS3Client)
         | 
| 205 | 
            +
                    for filename, string in (files or {}).items():
         | 
| 206 | 
            +
                        s3.s3_files.files[filename] = string.encode('utf-8')
         | 
| 207 | 
            +
                    yield mock_boto3
         | 
| 208 | 
            +
             | 
| 209 | 
            +
             | 
| 193 210 | 
             
            # Here we set up some variables, auxiliary functions, and mocks containing common values needed for testing
         | 
| 194 211 | 
             
            # of the next several functions so that the functions don't have to set them up over
         | 
| 195 212 | 
             
            # and over again in each test.
         | 
    
        dcicutils/ff_utils.py
    CHANGED
    
    | @@ -6,13 +6,11 @@ import requests | |
| 6 6 | 
             
            import time
         | 
| 7 7 |  | 
| 8 8 | 
             
            from collections import namedtuple
         | 
| 9 | 
            +
            from dcicutils.lang_utils import disjoined_list
         | 
| 9 10 | 
             
            from elasticsearch.exceptions import AuthorizationException
         | 
| 10 11 | 
             
            from typing import Dict, List
         | 
| 11 12 | 
             
            from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
         | 
| 12 | 
            -
            from . import  | 
| 13 | 
            -
                s3_utils,
         | 
| 14 | 
            -
                es_utils,
         | 
| 15 | 
            -
            )
         | 
| 13 | 
            +
            from . import s3_utils, es_utils
         | 
| 16 14 | 
             
            from .misc_utils import PRINT
         | 
| 17 15 |  | 
| 18 16 |  | 
| @@ -107,8 +105,6 @@ def search_request_with_retries(request_fxn, url, auth, verb, **kwargs): | |
| 107 105 | 
             
                    try:
         | 
| 108 106 | 
             
                        res_json = res.json()
         | 
| 109 107 | 
             
                    except ValueError:
         | 
| 110 | 
            -
                        # PyCharm notes this is unused. -kmp 17-Jul-2020
         | 
| 111 | 
            -
                        # res_json = {}
         | 
| 112 108 | 
             
                        try:
         | 
| 113 109 | 
             
                            res.raise_for_status()
         | 
| 114 110 | 
             
                        except Exception as e:
         | 
| @@ -238,7 +234,7 @@ def internal_compute_authorized_request(url, *, auth, ff_env, verb, retry_fxn, * | |
| 238 234 | 
             
                authorized_request('https://data.4dnucleome.org/<some path>', ff_env='fourfront-webprod')
         | 
| 239 235 | 
             
                """
         | 
| 240 236 | 
             
                # Save to uncomment if debugging unit tests...
         | 
| 241 | 
            -
                # print("authorized_request\n URL | 
| 237 | 
            +
                # print(f"authorized_request\n URL={url}\n auth={auth}\n ff_env={ff_env}\n verb={verb}\n")
         | 
| 242 238 | 
             
                use_auth = unified_authentication(auth, ff_env)
         | 
| 243 239 | 
             
                headers = kwargs.get('headers')
         | 
| 244 240 | 
             
                if not headers:
         | 
| @@ -246,11 +242,12 @@ def internal_compute_authorized_request(url, *, auth, ff_env, verb, retry_fxn, * | |
| 246 242 | 
             
                if 'timeout' not in kwargs:
         | 
| 247 243 | 
             
                    kwargs['timeout'] = 60  # default timeout
         | 
| 248 244 |  | 
| 245 | 
            +
                verb_upper = verb
         | 
| 249 246 | 
             
                try:
         | 
| 250 | 
            -
                     | 
| 247 | 
            +
                    verb_upper = verb.upper()
         | 
| 248 | 
            +
                    the_verb = REQUESTS_VERBS[verb_upper]
         | 
| 251 249 | 
             
                except KeyError:
         | 
| 252 | 
            -
                    raise ValueError("Provided verb  | 
| 253 | 
            -
                                     % (verb.upper(), ', '.join(REQUESTS_VERBS.keys())))
         | 
| 250 | 
            +
                    raise ValueError(f"Provided verb {verb} is not valid. Must be one of {disjoined_list(REQUESTS_VERBS)}.")
         | 
| 254 251 | 
             
                # automatically detect a search and overwrite the retry if it is standard
         | 
| 255 252 | 
             
                if '/search/' in url and retry_fxn == standard_request_with_retries:
         | 
| 256 253 | 
             
                    retry_fxn = search_request_with_retries
         | 
| @@ -262,7 +259,15 @@ def internal_compute_authorized_request(url, *, auth, ff_env, verb, retry_fxn, * | |
| 262 259 |  | 
| 263 260 |  | 
| 264 261 | 
             
            def _sls(val):
         | 
| 265 | 
            -
                """ | 
| 262 | 
            +
                """
         | 
| 263 | 
            +
                Helper to strip any leading slashes on ids in API functions.
         | 
| 264 | 
            +
                Examples:
         | 
| 265 | 
            +
                    >>> _sls('foo')
         | 
| 266 | 
            +
                    foo
         | 
| 267 | 
            +
                    >>> _sls('/foo')
         | 
| 268 | 
            +
                    foo
         | 
| 269 | 
            +
                    >>> _sls('/foo/bar/baz/')
         | 
| 270 | 
            +
                    foo/bar/baz/
         | 
| 266 271 | 
             
                """
         | 
| 267 272 | 
             
                return val.lstrip('/')
         | 
| 268 273 |  | 
    
        dcicutils/misc_utils.py
    CHANGED
    
    | @@ -12,6 +12,7 @@ import io | |
| 12 12 | 
             
            import os
         | 
| 13 13 | 
             
            import logging
         | 
| 14 14 | 
             
            import pytz
         | 
| 15 | 
            +
            import re
         | 
| 15 16 | 
             
            import rfc3986.validators
         | 
| 16 17 | 
             
            import rfc3986.exceptions
         | 
| 17 18 | 
             
            import time
         | 
| @@ -1316,6 +1317,17 @@ def string_list(s): | |
| 1316 1317 | 
             
                return [p for p in [part.strip() for part in s.split(",")] if p]
         | 
| 1317 1318 |  | 
| 1318 1319 |  | 
| 1320 | 
            +
            def is_c4_arn(arn: str) -> bool:
         | 
| 1321 | 
            +
                """
         | 
| 1322 | 
            +
                Returns True iff the given (presumed) AWS ARN string value looks like it
         | 
| 1323 | 
            +
                refers to a CGAP or Fourfront Cloudformation entity, otherwise returns False.
         | 
| 1324 | 
            +
                :param arn: String representing an AWS ARN.
         | 
| 1325 | 
            +
                :return: True if the given ARN refers to a CGAP or Fourfront Cloudformation entity, else False.
         | 
| 1326 | 
            +
                """
         | 
| 1327 | 
            +
                pattern = r"(fourfront|cgap|[:/]c4-)"
         | 
| 1328 | 
            +
                return True if re.search(pattern, arn) else False
         | 
| 1329 | 
            +
             | 
| 1330 | 
            +
             | 
| 1319 1331 | 
             
            def string_md5(unicode_string):
         | 
| 1320 1332 | 
             
                """
         | 
| 1321 1333 | 
             
                Returns the md5 signature for the given u unicode string.
         | 
    
        dcicutils/redis_tools.py
    CHANGED
    
    | @@ -8,6 +8,9 @@ from dcicutils.redis_utils import RedisBase, RedisException | |
| 8 8 | 
             
            log = structlog.getLogger(__name__)
         | 
| 9 9 |  | 
| 10 10 |  | 
| 11 | 
            +
            SESSION_TOKEN_COOKIE = 'c4_st'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
             | 
| 11 14 | 
             
            def make_session_token(n_bytes: int = 32) -> str:
         | 
| 12 15 | 
             
                """ Uses the secrets module to create a cryptographically secure and URL safe string
         | 
| 13 16 | 
             
                :param n_bytes: number of bytes to use, default 32
         | 
| @@ -21,29 +24,16 @@ class RedisSessionToken: | |
| 21 24 | 
             
                Model used by Redis to store session tokens
         | 
| 22 25 | 
             
                Keystore structure:
         | 
| 23 26 | 
             
                    <env_namespace>:session:<token>
         | 
| 24 | 
            -
                        -> Redis  | 
| 27 | 
            +
                        -> Redis str of JWT with automated expiration
         | 
| 25 28 | 
             
                """
         | 
| 26 | 
            -
                JWT = 'jwt'
         | 
| 27 | 
            -
                EXPIRATION = 'expiration'
         | 
| 28 29 |  | 
| 29 30 | 
             
                @staticmethod
         | 
| 30 | 
            -
                def _build_session_expiration(n_hours: int = 3) ->  | 
| 31 | 
            +
                def _build_session_expiration(n_hours: int = 3) -> datetime.timedelta:
         | 
| 31 32 | 
             
                    """ Builds a session expiration date 3 hours after generation
         | 
| 32 33 | 
             
                    :param n_hours: timestamp after which the session token is invalid
         | 
| 33 34 | 
             
                    :return: datetime string 3 hours from creation time
         | 
| 34 35 | 
             
                    """
         | 
| 35 | 
            -
                    return  | 
| 36 | 
            -
             | 
| 37 | 
            -
                def _build_session_hset(self, jwt: str, expiration=None) -> dict:
         | 
| 38 | 
            -
                    """ Builds Redis hset record for the session token
         | 
| 39 | 
            -
                    :param jwt: encoded jwt value
         | 
| 40 | 
            -
                    :param expiration: expiration if using an existing one
         | 
| 41 | 
            -
                    :return: dictionary to be stored as a hash in Redis
         | 
| 42 | 
            -
                    """
         | 
| 43 | 
            -
                    return {
         | 
| 44 | 
            -
                        self.JWT: jwt,
         | 
| 45 | 
            -
                        self.EXPIRATION: self._build_session_expiration() if not expiration else expiration
         | 
| 46 | 
            -
                    }
         | 
| 36 | 
            +
                    return datetime.timedelta(hours=n_hours)
         | 
| 47 37 |  | 
| 48 38 | 
             
                @staticmethod
         | 
| 49 39 | 
             
                def _build_redis_key(namespace: str, token: str) -> str:
         | 
| @@ -69,20 +59,24 @@ class RedisSessionToken: | |
| 69 59 | 
             
                    self.namespace = namespace
         | 
| 70 60 | 
             
                    self.redis_key = self._build_redis_key(self.namespace, self.session_token)
         | 
| 71 61 | 
             
                    self.jwt = jwt
         | 
| 72 | 
            -
                    self. | 
| 62 | 
            +
                    self.expiration = expiration or self._build_session_expiration()
         | 
| 73 63 |  | 
| 74 64 | 
             
                def __eq__(self, other):
         | 
| 75 65 | 
             
                    """ Evaluates equality of two session objects based on the value of the session hset """
         | 
| 76 | 
            -
                    return self. | 
| 66 | 
            +
                    return (self.redis_key == other.redis_key) and (self.jwt == other.jwt)
         | 
| 77 67 |  | 
| 78 68 | 
             
                def get_session_token(self) -> str:
         | 
| 79 69 | 
             
                    """ Extracts the session token stored on this object """
         | 
| 80 70 | 
             
                    return self.session_token
         | 
| 81 71 |  | 
| 82 72 | 
             
                def get_redis_key(self) -> str:
         | 
| 83 | 
            -
                    """ Returns the key under which the Redis  | 
| 73 | 
            +
                    """ Returns the key under which the Redis set is stored - note that this contains the token! """
         | 
| 84 74 | 
             
                    return self.redis_key
         | 
| 85 75 |  | 
| 76 | 
            +
                def get_expiration(self) -> datetime:
         | 
| 77 | 
            +
                    """ Returns the expiration timedelta """
         | 
| 78 | 
            +
                    return self.expiration
         | 
| 79 | 
            +
             | 
| 86 80 | 
             
                @classmethod
         | 
| 87 81 | 
             
                def from_redis(cls, *, redis_handler: RedisBase, namespace: str, token: str):
         | 
| 88 82 | 
             
                    """ Builds a RedisSessionToken from an existing record - allows extracting JWT
         | 
| @@ -93,9 +87,11 @@ class RedisSessionToken: | |
| 93 87 | 
             
                    :return: A RedisSessionToken object built from an existing record in Redis
         | 
| 94 88 | 
             
                    """
         | 
| 95 89 | 
             
                    redis_key = f'{namespace}:session:{token}'
         | 
| 96 | 
            -
                    redis_entry = redis_handler. | 
| 97 | 
            -
                     | 
| 98 | 
            -
             | 
| 90 | 
            +
                    redis_entry = redis_handler.get(redis_key)
         | 
| 91 | 
            +
                    if redis_entry:
         | 
| 92 | 
            +
                        expiration = redis_handler.ttl(redis_key)
         | 
| 93 | 
            +
                        return cls(namespace=namespace, jwt=redis_entry,
         | 
| 94 | 
            +
                                   token=token, expiration=expiration)
         | 
| 99 95 |  | 
| 100 96 | 
             
                def decode_jwt(self, audience: str, secret: str, leeway: int = 30) -> dict:
         | 
| 101 97 | 
             
                    """ Decodes JWT to grab info such as the email
         | 
| @@ -113,7 +109,7 @@ class RedisSessionToken: | |
| 113 109 | 
             
                    :return: True if successful, raise Exception otherwise
         | 
| 114 110 | 
             
                    """
         | 
| 115 111 | 
             
                    try:
         | 
| 116 | 
            -
                        redis_handler. | 
| 112 | 
            +
                        redis_handler.set(self.redis_key, self.jwt, exp=self.expiration)
         | 
| 117 113 | 
             
                    except Exception as e:
         | 
| 118 114 | 
             
                        log.error(str(e))
         | 
| 119 115 | 
             
                        raise RedisException()
         | 
| @@ -124,15 +120,12 @@ class RedisSessionToken: | |
| 124 120 | 
             
                    :param redis_handler: handle to Redis API
         | 
| 125 121 | 
             
                    :return: True if token matches that in Redis and is not expired
         | 
| 126 122 | 
             
                    """
         | 
| 127 | 
            -
                    redis_token = redis_handler. | 
| 123 | 
            +
                    redis_token = redis_handler.get(self.redis_key)
         | 
| 128 124 | 
             
                    if not redis_token:
         | 
| 129 125 | 
             
                        return False  # if it doesn't exist it's not valid
         | 
| 130 | 
            -
                     | 
| 131 | 
            -
                        redis_token[self.EXPIRATION]) > datetime.datetime.utcnow()
         | 
| 132 | 
            -
                    )
         | 
| 133 | 
            -
                    return timestamp_is_valid
         | 
| 126 | 
            +
                    return True  # if it does exist it must be valid since we always send with TTL
         | 
| 134 127 |  | 
| 135 | 
            -
                def update_session_token(self, *, redis_handler: RedisBase, jwt) -> bool:
         | 
| 128 | 
            +
                def update_session_token(self, *, redis_handler: RedisBase, jwt: str) -> bool:
         | 
| 136 129 | 
             
                    """ Refreshes the session token, jwt (if different) and expiration stored in Redis
         | 
| 137 130 | 
             
                    :param redis_handler: handle to Redis API
         | 
| 138 131 | 
             
                    :param jwt: jwt of user
         | 
| @@ -143,13 +136,13 @@ class RedisSessionToken: | |
| 143 136 | 
             
                    # build new one
         | 
| 144 137 | 
             
                    self.session_token = make_session_token()
         | 
| 145 138 | 
             
                    self.redis_key = self._build_redis_key(self.namespace, self.session_token)
         | 
| 139 | 
            +
                    self.expiration = self._build_session_expiration()
         | 
| 146 140 | 
             
                    self.jwt = jwt
         | 
| 147 | 
            -
                    self.session_hset = self._build_session_hset(jwt)
         | 
| 148 141 | 
             
                    return self.store_session_token(redis_handler=redis_handler)
         | 
| 149 142 |  | 
| 150 | 
            -
                def delete_session_token(self, *, redis_handler) -> bool:
         | 
| 143 | 
            +
                def delete_session_token(self, *, redis_handler: RedisBase) -> bool:
         | 
| 151 144 | 
             
                    """ Deletes the session token from redis, effectively logging out
         | 
| 152 145 | 
             
                    :param redis_handler: handle to Redis API
         | 
| 153 146 | 
             
                    :return: True if successful, False otherwise
         | 
| 154 147 | 
             
                    """
         | 
| 155 | 
            -
                    return redis_handler.delete(self.redis_key)
         | 
| 148 | 
            +
                    return 1 == redis_handler.delete(self.redis_key)
         | 
    
        dcicutils/redis_utils.py
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            import redis
         | 
| 2 | 
            +
            import datetime
         | 
| 2 3 | 
             
            from typing import Union
         | 
| 3 4 | 
             
            # Low level utilities for working with Redis
         | 
| 4 5 |  | 
| @@ -59,13 +60,17 @@ class RedisBase(object): | |
| 59 60 | 
             
                    """
         | 
| 60 61 | 
             
                    return self.redis.info()
         | 
| 61 62 |  | 
| 62 | 
            -
                def set(self, key: str, value: Union[str, int, float]) -> str:
         | 
| 63 | 
            +
                def set(self, key: str, value: Union[str, int, float], exp: Union[int, datetime.timedelta] = None) -> str:
         | 
| 63 64 | 
             
                    """ Sets the given key to the given value https://redis.io/commands/set/
         | 
| 64 65 | 
             
                    :param key: string to store value under
         | 
| 65 66 | 
             
                    :param value: value mapped to from key
         | 
| 67 | 
            +
                    :param exp: expiration time in seconds as int or datetime.timedelta (optional)
         | 
| 66 68 | 
             
                    :return: a "true" string <OK>
         | 
| 67 69 | 
             
                    """
         | 
| 68 | 
            -
                     | 
| 70 | 
            +
                    kwargs = {}
         | 
| 71 | 
            +
                    if exp:
         | 
| 72 | 
            +
                        kwargs['ex'] = exp
         | 
| 73 | 
            +
                    return self.redis.set(self._encode_value(key), self._encode_value(value), **kwargs)
         | 
| 69 74 |  | 
| 70 75 | 
             
                def get(self, key: str) -> str:
         | 
| 71 76 | 
             
                    """ Gets the given key from Redis https://redis.io/commands/get/
         | 
| @@ -77,6 +82,21 @@ class RedisBase(object): | |
| 77 82 | 
             
                        val = self._decode_value(val)
         | 
| 78 83 | 
             
                    return val
         | 
| 79 84 |  | 
| 85 | 
            +
                def set_expiration(self, key: str, t: Union[int, datetime.time]) -> bool:
         | 
| 86 | 
            +
                    """ Sets the TTL of the given key manually
         | 
| 87 | 
            +
                    :param key: key to set TTL
         | 
| 88 | 
            +
                    :param t: time in seconds until expiration (datetime.timedelta or int)
         | 
| 89 | 
            +
                    :returns: True if successful
         | 
| 90 | 
            +
                    """
         | 
| 91 | 
            +
                    return self.redis.expire(key, t, gt=True)
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def ttl(self, key: str) -> datetime.time:
         | 
| 94 | 
            +
                    """ Gets the TTL of the given key
         | 
| 95 | 
            +
                    :param key: key to get TTL for
         | 
| 96 | 
            +
                    :return: datetime value in seconds
         | 
| 97 | 
            +
                    """
         | 
| 98 | 
            +
                    return self.redis.ttl(key)
         | 
| 99 | 
            +
             | 
| 80 100 | 
             
                def delete(self, key: str) -> int:
         | 
| 81 101 | 
             
                    """ Deletes the given key from Redis https://redis.io/commands/del/
         | 
| 82 102 | 
             
                    :param key: key to delete
         | 
    
        dcicutils/s3_utils.py
    CHANGED
    
    | @@ -52,6 +52,7 @@ class HealthPageKey:  # This is moving here from cgap-portal. | |
| 52 52 | 
             
                PROCESSED_FILE_BUCKET = 'processed_file_bucket'          # = s3Utils.OUTFILE_BUCKET_HEALTH_PAGE_KEY
         | 
| 53 53 | 
             
                PROJECT_VERSION = 'project_version'
         | 
| 54 54 | 
             
                PYTHON_VERSION = 'python_version'
         | 
| 55 | 
            +
                REDIS = 'redis'
         | 
| 55 56 | 
             
                S3_ENCRYPT_KEY_ID = 's3_encrypt_key_id'
         | 
| 56 57 | 
             
                SNOVAULT_VERSION = 'snovault_version'
         | 
| 57 58 | 
             
                SYSTEM_BUCKET = 'system_bucket'                          # = s3Utils.SYS_BUCKET_HEALTH_PAGE_KEY
         | 
| @@ -235,10 +236,13 @@ class s3Utils(s3Base):  # NOQA - This class name violates style rules, but a lot | |
| 235 236 | 
             
                    if not es_url.startswith('http'):  # skips on both http:// and https://
         | 
| 236 237 | 
             
                        es_url = ('https://' if es_url.endswith(":443") else 'http://') + es_url
         | 
| 237 238 |  | 
| 239 | 
            +
                    redis_url = s3u.health_page_get(HealthPageKey.REDIS)
         | 
| 240 | 
            +
             | 
| 238 241 | 
             
                    env_info = {
         | 
| 239 242 | 
             
                        'ff_env': env_full_name,
         | 
| 240 243 | 
             
                        'fourfront': portal_url,
         | 
| 241 | 
            -
                        'es': es_url
         | 
| 244 | 
            +
                        'es': es_url,
         | 
| 245 | 
            +
                        'redis': redis_url  # default to None
         | 
| 242 246 | 
             
                    }
         | 
| 243 247 |  | 
| 244 248 | 
             
                    return env_info
         | 
    
        dcicutils/secrets_utils.py
    CHANGED
    
    | @@ -117,7 +117,8 @@ def apply_overrides(*, secrets: dict, rename_keys: Optional[dict] = None, | |
| 117 117 | 
             
                            raise ValueError(f"Cannot rename {key} to {new_name}"
         | 
| 118 118 | 
             
                                             f" because {a_or_an(new_name)} attribute already exists.")
         | 
| 119 119 | 
             
                        if key not in secrets:
         | 
| 120 | 
            -
                             | 
| 120 | 
            +
                            logger.warning(f"Cannot rename {key} to {new_name} because the secrets has no {key} attribute.")
         | 
| 121 | 
            +
                            continue
         | 
| 121 122 | 
             
                        secrets[new_name] = secrets.pop(key)
         | 
| 122 123 | 
             
                if override_values:
         | 
| 123 124 | 
             
                    secrets = dict(secrets, **override_values)
         | 
| @@ -20,23 +20,23 @@ dcicutils/env_utils.py,sha256=MQ3zcXcbxJV9dO5y1nN2HNswtiUw2VDK4KrROrZOUtA,46285 | |
| 20 20 | 
             
            dcicutils/env_utils_legacy.py,sha256=J81OAtJHN69o1beHO6q1j7_J6TeblSjnAHlS8VA5KSM,29032
         | 
| 21 21 | 
             
            dcicutils/es_utils.py,sha256=ZksLh5ei7kRUfiFltk8sd2ZSfh15twbstrMzBr8HNw4,7541
         | 
| 22 22 | 
             
            dcicutils/exceptions.py,sha256=fO-zIwcT0kdy-BL1qczva3QLY9ZEkhAFVGfBS9w-Row,9257
         | 
| 23 | 
            -
            dcicutils/ff_mocks.py,sha256= | 
| 24 | 
            -
            dcicutils/ff_utils.py,sha256= | 
| 23 | 
            +
            dcicutils/ff_mocks.py,sha256=wmf7-_C0cq9bpZZGUyKQO6eC-1p2ltKPVCSi-ln5y68,37025
         | 
| 24 | 
            +
            dcicutils/ff_utils.py,sha256=nHrVyf252HgP1mnerFmqJzUo6_Cm2hRk-Rnj5KN1RUQ,64324
         | 
| 25 25 | 
             
            dcicutils/jh_utils.py,sha256=Gpsxb9XEzggF_-Eq3ukjKvTnuyb9V1SCSUXkXsES4Kg,11502
         | 
| 26 26 | 
             
            dcicutils/kibana/dashboards.json,sha256=wHMB_mpJ8OaYhRRgvpZuihaB2lmSF64ADt_8hkBWgQg,16225
         | 
| 27 27 | 
             
            dcicutils/kibana/readme.md,sha256=3KmHF9FH6A6xwYsNxRFLw27q0XzHYnjZOlYUnn3VkQQ,2164
         | 
| 28 28 | 
             
            dcicutils/lang_utils.py,sha256=MaCJG-4KB2oOd3wcdd0e048eRBLkoxCKFkqR1KlrjkI,27302
         | 
| 29 29 | 
             
            dcicutils/log_utils.py,sha256=7pWMc6vyrorUZQf-V-M3YC6zrPgNhuV_fzm9xqTPph0,10883
         | 
| 30 | 
            -
            dcicutils/misc_utils.py,sha256= | 
| 30 | 
            +
            dcicutils/misc_utils.py,sha256=jRazVlj2gJ_DZsI2JjPIEfTmFLkcxDwPQStqGtaGMRc,86651
         | 
| 31 31 | 
             
            dcicutils/obfuscation_utils.py,sha256=xUCQYAe-nQY3vCKMLjT_3tAFhIeYkJuhMaZnxunF0Hc,5510
         | 
| 32 32 | 
             
            dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
         | 
| 33 33 | 
             
            dcicutils/qa_checkers.py,sha256=hdduo-6ZITEurrmZYjll5N-nKQeyKaFLbERE9d4gRdU,20232
         | 
| 34 34 | 
             
            dcicutils/qa_utils.py,sha256=g0nuAigZUWCXd4ZBqOKHxLq1H3AGI2X2wD57rvBdAOY,120561
         | 
| 35 | 
            -
            dcicutils/redis_tools.py,sha256= | 
| 36 | 
            -
            dcicutils/redis_utils.py,sha256= | 
| 37 | 
            -
            dcicutils/s3_utils.py,sha256= | 
| 38 | 
            -
            dcicutils/secrets_utils.py,sha256= | 
| 35 | 
            +
            dcicutils/redis_tools.py,sha256=VQ5cTnDnKQ8AX0n9iuikCqtEUtIlE0ZOEqVZR3iNGTI,6389
         | 
| 36 | 
            +
            dcicutils/redis_utils.py,sha256=VJ-7g8pOZqR1ZCtdcjKz3-6as2DMUcs1b1zG6wSprH4,6462
         | 
| 37 | 
            +
            dcicutils/s3_utils.py,sha256=PAIUXpv7l7NNK0IzVLS6URJhLxg-SxLbZc0fJTpjI8E,25659
         | 
| 38 | 
            +
            dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19745
         | 
| 39 39 | 
             
            dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
         | 
| 40 | 
            -
            dcicutils-6.9.9. | 
| 41 | 
            -
            dcicutils-6.9.9. | 
| 42 | 
            -
            dcicutils-6.9.9. | 
| 40 | 
            +
            dcicutils-6.9.9.0b6.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
         | 
| 41 | 
            +
            dcicutils-6.9.9.0b6.dist-info/METADATA,sha256=oWx6nepGIzhI4H0gi13-CV8KXVaYXtaSRmi8R4F1GUM,2722
         | 
| 42 | 
            +
            dcicutils-6.9.9.0b6.dist-info/RECORD,,
         | 
| 
            File without changes
         |