dcicutils 6.9.9.0b2__py3-none-any.whl → 6.9.9.0b6__py3-none-any.whl

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/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=%s\n auth=%s\n ff_env=%s\n verb=%s\n" % (url, auth, ff_env, verb))
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
- the_verb = REQUESTS_VERBS[verb.upper()]
247
+ verb_upper = verb.upper()
248
+ the_verb = REQUESTS_VERBS[verb_upper]
251
249
  except KeyError:
252
- raise ValueError("Provided verb %s is not valid. Must be one of: %s"
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
- """general helper to check for and strip leading slashes on ids in API fxns
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 hset containing the associated JWT and expiration time (3 hours)
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) -> str:
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 str(datetime.datetime.utcnow() + datetime.timedelta(hours=n_hours))
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.session_hset = self._build_session_hset(self.jwt, expiration=expiration)
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.session_hset == other.session_hset
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 hset is stored - note that this contains the token! """
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.hgetall(redis_key)
97
- return cls(namespace=namespace, jwt=redis_entry[cls.JWT],
98
- token=token, expiration=redis_entry[cls.EXPIRATION])
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.hset_multiple(self.redis_key, self.session_hset)
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.hgetall(self.redis_key)
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
- timestamp_is_valid = (datetime.datetime.fromisoformat(
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
- return self.redis.set(self._encode_value(key), self._encode_value(value))
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
@@ -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
- raise ValueError(f"Cannot rename {key} to {new_name} because the secrets has no {key} attribute.")
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
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 6.9.9.0b2
3
+ Version: 6.9.9.0b6
4
4
  Summary: Utility package for interacting with the 4DN Data Portal and other 4DN resources
5
5
  Home-page: https://github.com/4dn-dcic/utils
6
6
  License: MIT
@@ -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=eUdIJQNaNf5UCT7_-ruH3HXP5rlKQNHW3OMKpHymlGQ,36260
24
- dcicutils/ff_utils.py,sha256=I25EmbKzPm-V7zEaMbx6fZPSD3OKh5sLmKrnsopHtus,64240
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=iz-MAy4OlmQJUY2yD5dMRqfcEtNhfBHVkLdCi8QXdsg,86187
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=mo3GV6yqmRFN508x7q1aUACB3lpu1uNRuxojkqw3Ilc,6784
36
- dcicutils/redis_utils.py,sha256=vV-7NwRXVrQZhJMFnU-0qAZTfZX9tWEvJjDojogsmd0,5677
37
- dcicutils/s3_utils.py,sha256=sl2golIuUCCuO9CsXVGI3ZgRqHuSigkoe0Wy0e_R-3E,25526
38
- dcicutils/secrets_utils.py,sha256=d-oVHmyJL6Bcslxth73Ab31GeNoYtygWoVtOuzZ0xVQ,19722
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.0b2.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
41
- dcicutils-6.9.9.0b2.dist-info/METADATA,sha256=w0nIhFQw9aesViWOCVBy4I2M3xvqMVGXsEJLY7GAf54,2722
42
- dcicutils-6.9.9.0b2.dist-info/RECORD,,
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,,