dcicutils 6.9.9.0b4__tar.gz → 6.9.9.0b6__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dcicutils might be problematic. Click here for more details.
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/PKG-INFO +1 -1
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/ff_mocks.py +17 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/ff_utils.py +16 -11
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/misc_utils.py +12 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/redis_tools.py +20 -35
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/redis_utils.py +22 -2
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/secrets_utils.py +2 -1
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/pyproject.toml +1 -1
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/setup.py +1 -1
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/README.rst +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/__init__.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/base.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/beanstalk_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/cloudformation_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/codebuild_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/command_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/common.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/creds_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/data_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/deployment_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/diff_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/docker_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/ecr_scripts.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/ecr_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/ecs_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/env_base.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/env_manager.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/env_scripts.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/env_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/env_utils_legacy.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/es_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/exceptions.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/jh_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/kibana/dashboards.json +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/kibana/readme.md +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/lang_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/log_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/obfuscation_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/opensearch_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/qa_checkers.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/qa_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/s3_utils.py +0 -0
- {dcicutils-6.9.9.0b4 → dcicutils-6.9.9.0b6}/dcicutils/snapshot_utils.py +0 -0
@@ -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.
|
@@ -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
|
|
@@ -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.
|
@@ -24,29 +24,16 @@ class RedisSessionToken:
|
|
24
24
|
Model used by Redis to store session tokens
|
25
25
|
Keystore structure:
|
26
26
|
<env_namespace>:session:<token>
|
27
|
-
-> Redis
|
27
|
+
-> Redis str of JWT with automated expiration
|
28
28
|
"""
|
29
|
-
JWT = 'jwt'
|
30
|
-
EXPIRATION = 'expiration'
|
31
29
|
|
32
30
|
@staticmethod
|
33
|
-
def _build_session_expiration(n_hours: int = 3) ->
|
31
|
+
def _build_session_expiration(n_hours: int = 3) -> datetime.timedelta:
|
34
32
|
""" Builds a session expiration date 3 hours after generation
|
35
33
|
:param n_hours: timestamp after which the session token is invalid
|
36
34
|
:return: datetime string 3 hours from creation time
|
37
35
|
"""
|
38
|
-
return
|
39
|
-
|
40
|
-
def _build_session_hset(self, jwt: str, expiration=None) -> dict:
|
41
|
-
""" Builds Redis hset record for the session token
|
42
|
-
:param jwt: encoded jwt value
|
43
|
-
:param expiration: expiration if using an existing one
|
44
|
-
:return: dictionary to be stored as a hash in Redis
|
45
|
-
"""
|
46
|
-
return {
|
47
|
-
self.JWT: jwt,
|
48
|
-
self.EXPIRATION: self._build_session_expiration() if not expiration else expiration
|
49
|
-
}
|
36
|
+
return datetime.timedelta(hours=n_hours)
|
50
37
|
|
51
38
|
@staticmethod
|
52
39
|
def _build_redis_key(namespace: str, token: str) -> str:
|
@@ -72,23 +59,23 @@ class RedisSessionToken:
|
|
72
59
|
self.namespace = namespace
|
73
60
|
self.redis_key = self._build_redis_key(self.namespace, self.session_token)
|
74
61
|
self.jwt = jwt
|
75
|
-
self.
|
62
|
+
self.expiration = expiration or self._build_session_expiration()
|
76
63
|
|
77
64
|
def __eq__(self, other):
|
78
65
|
""" Evaluates equality of two session objects based on the value of the session hset """
|
79
|
-
return self.
|
66
|
+
return (self.redis_key == other.redis_key) and (self.jwt == other.jwt)
|
80
67
|
|
81
68
|
def get_session_token(self) -> str:
|
82
69
|
""" Extracts the session token stored on this object """
|
83
70
|
return self.session_token
|
84
71
|
|
85
72
|
def get_redis_key(self) -> str:
|
86
|
-
""" Returns the key under which the Redis
|
73
|
+
""" Returns the key under which the Redis set is stored - note that this contains the token! """
|
87
74
|
return self.redis_key
|
88
75
|
|
89
|
-
def get_expiration(self) ->
|
90
|
-
""" Returns the expiration
|
91
|
-
return self.
|
76
|
+
def get_expiration(self) -> datetime:
|
77
|
+
""" Returns the expiration timedelta """
|
78
|
+
return self.expiration
|
92
79
|
|
93
80
|
@classmethod
|
94
81
|
def from_redis(cls, *, redis_handler: RedisBase, namespace: str, token: str):
|
@@ -100,10 +87,11 @@ class RedisSessionToken:
|
|
100
87
|
:return: A RedisSessionToken object built from an existing record in Redis
|
101
88
|
"""
|
102
89
|
redis_key = f'{namespace}:session:{token}'
|
103
|
-
redis_entry = redis_handler.
|
90
|
+
redis_entry = redis_handler.get(redis_key)
|
104
91
|
if redis_entry:
|
105
|
-
|
106
|
-
|
92
|
+
expiration = redis_handler.ttl(redis_key)
|
93
|
+
return cls(namespace=namespace, jwt=redis_entry,
|
94
|
+
token=token, expiration=expiration)
|
107
95
|
|
108
96
|
def decode_jwt(self, audience: str, secret: str, leeway: int = 30) -> dict:
|
109
97
|
""" Decodes JWT to grab info such as the email
|
@@ -121,7 +109,7 @@ class RedisSessionToken:
|
|
121
109
|
:return: True if successful, raise Exception otherwise
|
122
110
|
"""
|
123
111
|
try:
|
124
|
-
redis_handler.
|
112
|
+
redis_handler.set(self.redis_key, self.jwt, exp=self.expiration)
|
125
113
|
except Exception as e:
|
126
114
|
log.error(str(e))
|
127
115
|
raise RedisException()
|
@@ -132,15 +120,12 @@ class RedisSessionToken:
|
|
132
120
|
:param redis_handler: handle to Redis API
|
133
121
|
:return: True if token matches that in Redis and is not expired
|
134
122
|
"""
|
135
|
-
redis_token = redis_handler.
|
123
|
+
redis_token = redis_handler.get(self.redis_key)
|
136
124
|
if not redis_token:
|
137
125
|
return False # if it doesn't exist it's not valid
|
138
|
-
|
139
|
-
redis_token[self.EXPIRATION]) > datetime.datetime.utcnow()
|
140
|
-
)
|
141
|
-
return timestamp_is_valid
|
126
|
+
return True # if it does exist it must be valid since we always send with TTL
|
142
127
|
|
143
|
-
def update_session_token(self, *, redis_handler: RedisBase, jwt) -> bool:
|
128
|
+
def update_session_token(self, *, redis_handler: RedisBase, jwt: str) -> bool:
|
144
129
|
""" Refreshes the session token, jwt (if different) and expiration stored in Redis
|
145
130
|
:param redis_handler: handle to Redis API
|
146
131
|
:param jwt: jwt of user
|
@@ -151,13 +136,13 @@ class RedisSessionToken:
|
|
151
136
|
# build new one
|
152
137
|
self.session_token = make_session_token()
|
153
138
|
self.redis_key = self._build_redis_key(self.namespace, self.session_token)
|
139
|
+
self.expiration = self._build_session_expiration()
|
154
140
|
self.jwt = jwt
|
155
|
-
self.session_hset = self._build_session_hset(jwt)
|
156
141
|
return self.store_session_token(redis_handler=redis_handler)
|
157
142
|
|
158
|
-
def delete_session_token(self, *, redis_handler) -> bool:
|
143
|
+
def delete_session_token(self, *, redis_handler: RedisBase) -> bool:
|
159
144
|
""" Deletes the session token from redis, effectively logging out
|
160
145
|
:param redis_handler: handle to Redis API
|
161
146
|
:return: True if successful, False otherwise
|
162
147
|
"""
|
163
|
-
return redis_handler.delete(self.redis_key)
|
148
|
+
return 1 == redis_handler.delete(self.redis_key)
|
@@ -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
|
@@ -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)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "dcicutils"
|
3
|
-
version = "6.9.9.
|
3
|
+
version = "6.9.9.0b6" # to become 7.0.0
|
4
4
|
description = "Utility package for interacting with the 4DN Data Portal and other 4DN resources"
|
5
5
|
authors = ["4DN-DCIC Team <support@4dnucleome.org>"]
|
6
6
|
license = "MIT"
|
@@ -32,7 +32,7 @@ install_requires = \
|
|
32
32
|
|
33
33
|
setup_kwargs = {
|
34
34
|
'name': 'dcicutils',
|
35
|
-
'version': '6.9.9.
|
35
|
+
'version': '6.9.9.0b6',
|
36
36
|
'description': 'Utility package for interacting with the 4DN Data Portal and other 4DN resources',
|
37
37
|
'long_description': '=====\nutils\n=====\n\nCheck out our full documentation `here <https://dcic-utils.readthedocs.io/en/latest/>`_\n\nThis repository contains various utility modules shared amongst several projects in the 4DN-DCIC. It is meant to be used internally by the DCIC team and externally as a Python API to `Fourfront <https://data.4dnucleome.org>`_\\ , the 4DN data portal.\n\npip installable as the ``dcicutils`` package with: ``pip install dcicutils``\n\nSee `this document <https://dcic-utils.readthedocs.io/en/latest/getting_started.html>`_ for tips on getting started. `Go here <https://dcic-utils.readthedocs.io/en/latest/examples.html>`_ for examples of some of the most useful functions.\n\n\n.. image:: https://travis-ci.org/4dn-dcic/utils.svg?branch=master\n :target: https://travis-ci.org/4dn-dcic/utils\n :alt: Build Status\n\n\n.. image:: https://coveralls.io/repos/github/4dn-dcic/utils/badge.svg?branch=master\n :target: https://coveralls.io/github/4dn-dcic/utils?branch=master\n :alt: Coverage\n\n.. image:: https://readthedocs.org/projects/dcic-utils/badge/?version=latest\n :target: https://dcic-utils.readthedocs.io/en/latest/?badge=latest\n :alt: Documentation Status\n',
|
38
38
|
'author': '4DN-DCIC Team',
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|