dcicutils 7.4.1__py3-none-any.whl → 7.4.2.1b0__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/common.py +4 -1
- dcicutils/glacier_utils.py +36 -4
- dcicutils/project_utils.py +257 -0
- dcicutils/s3_utils.py +4 -0
- {dcicutils-7.4.1.dist-info → dcicutils-7.4.2.1b0.dist-info}/METADATA +1 -1
- {dcicutils-7.4.1.dist-info → dcicutils-7.4.2.1b0.dist-info}/RECORD +9 -8
- {dcicutils-7.4.1.dist-info → dcicutils-7.4.2.1b0.dist-info}/LICENSE.txt +0 -0
- {dcicutils-7.4.1.dist-info → dcicutils-7.4.2.1b0.dist-info}/WHEEL +0 -0
- {dcicutils-7.4.1.dist-info → dcicutils-7.4.2.1b0.dist-info}/entry_points.txt +0 -0
dcicutils/common.py
CHANGED
@@ -94,7 +94,6 @@ AVAILABLE_S3_STORAGE_CLASSES = [
|
|
94
94
|
#
|
95
95
|
# See boto3 docs for info on possible values, but these 3 are the current ones used for
|
96
96
|
# glacier (that require restore calls) - Will 7 Apr 2023
|
97
|
-
|
98
97
|
S3_GLACIER_CLASSES = [
|
99
98
|
'GLACIER_IR', # Glacier Instant Retrieval
|
100
99
|
'GLACIER', # Glacier Flexible Retrieval
|
@@ -118,6 +117,10 @@ S3StorageClass = Union[
|
|
118
117
|
]
|
119
118
|
|
120
119
|
|
120
|
+
# This constant is used in our Lifecycle management system to automatically transition objects
|
121
|
+
ENCODED_LIFECYCLE_TAG_KEY = 'Lifecycle'
|
122
|
+
|
123
|
+
|
121
124
|
# These numbers come from AWS and is the max size that can be copied with a single request
|
122
125
|
# Any larger than this requires a multipart upload - Will 24 April 2023
|
123
126
|
MAX_STANDARD_COPY_SIZE = 5368709120
|
dcicutils/glacier_utils.py
CHANGED
@@ -2,7 +2,10 @@ import boto3
|
|
2
2
|
from typing import Union, List, Tuple
|
3
3
|
from concurrent.futures import ThreadPoolExecutor
|
4
4
|
from tqdm import tqdm
|
5
|
-
from .common import
|
5
|
+
from .common import (
|
6
|
+
S3_GLACIER_CLASSES, S3StorageClass, MAX_MULTIPART_CHUNKS, MAX_STANDARD_COPY_SIZE,
|
7
|
+
ENCODED_LIFECYCLE_TAG_KEY
|
8
|
+
)
|
6
9
|
from .command_utils import require_confirmation
|
7
10
|
from .misc_utils import PRINT
|
8
11
|
from .ff_utils import get_metadata, search_metadata, get_health_page, patch_metadata
|
@@ -255,8 +258,19 @@ class GlacierUtils:
|
|
255
258
|
PRINT(f'Error deleting Glacier versions of object {bucket}/{key}: {str(e)}')
|
256
259
|
return False
|
257
260
|
|
261
|
+
@staticmethod
|
262
|
+
def _format_tags(tags: List[dict]) -> str:
|
263
|
+
""" Helper method that formats tags so that they match the format expected by the boto3 API
|
264
|
+
|
265
|
+
:param tags: array of dictionaries containing Key, Value mappings to be reformatted
|
266
|
+
:return: String formatted tag list ie:
|
267
|
+
[{Key: key1, Value: value1}, Key: key2, Value: value2}] --> 'key1=value1&key2=value2'
|
268
|
+
"""
|
269
|
+
return '&'.join([f'{tag["Key"]}={tag["Value"]}' for tag in tags])
|
270
|
+
|
258
271
|
def _do_multipart_upload(self, bucket: str, key: str, total_size: int, part_size: int = 200,
|
259
|
-
storage_class: str = 'STANDARD',
|
272
|
+
storage_class: str = 'STANDARD', tags: str = '',
|
273
|
+
version_id: Union[str, None] = None) -> Union[dict, None]:
|
260
274
|
""" Helper function for copy_object_back_to_original_location, not intended to
|
261
275
|
be called directly, will arrange for a multipart copy of large updates
|
262
276
|
to change storage class
|
@@ -266,6 +280,7 @@ class GlacierUtils:
|
|
266
280
|
:param total_size: total size of object
|
267
281
|
:param part_size: what size to divide the object into when uploading the chunks
|
268
282
|
:param storage_class: new storage class to use
|
283
|
+
:param tags: string of tags to apply
|
269
284
|
:param version_id: object version ID, if applicable
|
270
285
|
:return: response, if successful, or else None
|
271
286
|
"""
|
@@ -275,7 +290,12 @@ class GlacierUtils:
|
|
275
290
|
if num_parts > MAX_MULTIPART_CHUNKS:
|
276
291
|
raise GlacierRestoreException(f'Must user a part_size larger than {part_size}'
|
277
292
|
f' that will result in fewer than {MAX_MULTIPART_CHUNKS} chunks')
|
278
|
-
|
293
|
+
cmu = {
|
294
|
+
'Bucket': bucket, 'Key': key, 'StorageClass': storage_class
|
295
|
+
}
|
296
|
+
if tags:
|
297
|
+
cmu['Tagging'] = tags
|
298
|
+
mpu = self.s3.create_multipart_upload(**cmu)
|
279
299
|
mpu_upload_id = mpu['UploadId']
|
280
300
|
except Exception as e:
|
281
301
|
PRINT(f'Error creating multipart upload for {bucket}/{key} : {str(e)}')
|
@@ -327,6 +347,7 @@ class GlacierUtils:
|
|
327
347
|
|
328
348
|
def copy_object_back_to_original_location(self, bucket: str, key: str, storage_class: str = 'STANDARD',
|
329
349
|
part_size: int = 200, # MB
|
350
|
+
preserve_lifecycle_tag: bool = False,
|
330
351
|
version_id: Union[str, None] = None) -> Union[dict, None]:
|
331
352
|
""" Reads the temporary location from the restored object and copies it back to the original location
|
332
353
|
|
@@ -334,6 +355,7 @@ class GlacierUtils:
|
|
334
355
|
:param key: key within bucket where object is stored
|
335
356
|
:param storage_class: new storage class for this object
|
336
357
|
:param part_size: if doing a large copy, size of chunks to upload (in MB)
|
358
|
+
:param preserve_lifecycle_tag: whether to keep existing lifecycle tag on the object
|
337
359
|
:param version_id: version of object, if applicable
|
338
360
|
:return: boolean whether the copy was successful
|
339
361
|
"""
|
@@ -342,12 +364,20 @@ class GlacierUtils:
|
|
342
364
|
response = self.s3.head_object(Bucket=bucket, Key=key)
|
343
365
|
size = response['ContentLength']
|
344
366
|
multipart = (size >= MAX_STANDARD_COPY_SIZE)
|
367
|
+
if not preserve_lifecycle_tag: # default: preserve tags except 'Lifecycle'
|
368
|
+
tags = self.s3.get_object_tagging(Bucket=bucket, Key=key).get('TagSet', [])
|
369
|
+
tags = [tag for tag in tags if tag['Key'] != ENCODED_LIFECYCLE_TAG_KEY]
|
370
|
+
tags = self._format_tags(tags)
|
371
|
+
if not tags:
|
372
|
+
self.s3.delete_object_tagging(Bucket=bucket, Key=key)
|
373
|
+
else:
|
374
|
+
tags = ''
|
345
375
|
except Exception as e:
|
346
376
|
PRINT(f'Could not retrieve metadata on file {bucket}/{key} : {str(e)}')
|
347
377
|
return None
|
348
378
|
try:
|
349
379
|
if multipart:
|
350
|
-
return self._do_multipart_upload(bucket, key, size, part_size, storage_class, version_id)
|
380
|
+
return self._do_multipart_upload(bucket, key, size, part_size, storage_class, tags, version_id)
|
351
381
|
else:
|
352
382
|
# Force copy the object into standard in a single operation
|
353
383
|
copy_source = {'Bucket': bucket, 'Key': key}
|
@@ -358,6 +388,8 @@ class GlacierUtils:
|
|
358
388
|
if version_id:
|
359
389
|
copy_source['VersionId'] = version_id
|
360
390
|
copy_target['CopySourceVersionId'] = version_id
|
391
|
+
if tags:
|
392
|
+
copy_target['Tagging'] = tags
|
361
393
|
response = self.s3.copy_object(CopySource=copy_source, **copy_target)
|
362
394
|
PRINT(f'Response from boto3 copy:\n{response}')
|
363
395
|
PRINT(f'Object {bucket}/{key} copied back to its original location in S3')
|
@@ -0,0 +1,257 @@
|
|
1
|
+
import os
|
2
|
+
import toml
|
3
|
+
|
4
|
+
from pkg_resources import resource_filename
|
5
|
+
from typing import Optional
|
6
|
+
from .env_utils import EnvUtils
|
7
|
+
from .misc_utils import classproperty
|
8
|
+
|
9
|
+
|
10
|
+
def project_filename(filename):
|
11
|
+
# TODO: In fact we should do this based on the working dir so that when this is imported to another repo,
|
12
|
+
# it gets the inserts out of that repo's tests, not our own.
|
13
|
+
return resource_filename(Project.PACKAGE_NAME, filename)
|
14
|
+
|
15
|
+
|
16
|
+
class ProjectRegistry:
|
17
|
+
|
18
|
+
SHOW_HERALD_WHEN_INITIALIZED = True
|
19
|
+
|
20
|
+
REGISTERED_PROJECTS = {}
|
21
|
+
|
22
|
+
# All of these might never be other than None so be careful when accessing them.
|
23
|
+
APPLICATION_PROJECT_HOME = None
|
24
|
+
PYPROJECT_TOML_FILE = None
|
25
|
+
PYPROJECT_TOML = None
|
26
|
+
POETRY_DATA = None
|
27
|
+
# This is expected to ultimately be set properly.
|
28
|
+
_PYPROJECT_NAME = None
|
29
|
+
|
30
|
+
@classproperty
|
31
|
+
def PYPROJECT_NAME(cls) -> str: # noQA - PyCharm thinks this should be 'self'
|
32
|
+
if cls._PYPROJECT_NAME is None:
|
33
|
+
cls.initialize_pyproject_name()
|
34
|
+
result: Optional[str] = cls._PYPROJECT_NAME
|
35
|
+
if result is None:
|
36
|
+
raise ValueError(f"ProjectRegistry.PROJECT_NAME not initialized properly.")
|
37
|
+
return result
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
def initialize_pyproject_name(cls, project_home=None, pyproject_toml_file=None):
|
41
|
+
if cls._PYPROJECT_NAME is None:
|
42
|
+
# This isn't the home of snovault, but the home of the snovault-based application.
|
43
|
+
# So in CGAP, for example, this would want to be the home of the CGAP application.
|
44
|
+
# If not set, it will be assumed that the current working directory is that.
|
45
|
+
if not project_home:
|
46
|
+
project_home = os.environ.get("APPLICATION_PROJECT_HOME", os.path.abspath(os.curdir))
|
47
|
+
cls.APPLICATION_PROJECT_HOME = project_home
|
48
|
+
if not pyproject_toml_file:
|
49
|
+
expected_pyproject_toml_file = os.path.join(project_home, "pyproject.toml")
|
50
|
+
pyproject_toml_file = (expected_pyproject_toml_file
|
51
|
+
if os.path.exists(expected_pyproject_toml_file)
|
52
|
+
else None)
|
53
|
+
cls.PYPROJECT_TOML_FILE = pyproject_toml_file
|
54
|
+
cls.PYPROJECT_TOML = pyproject_toml = (toml.load(cls.PYPROJECT_TOML_FILE)
|
55
|
+
if cls.PYPROJECT_TOML_FILE
|
56
|
+
else None)
|
57
|
+
cls.POETRY_DATA = (pyproject_toml['tool']['poetry']
|
58
|
+
if pyproject_toml
|
59
|
+
else None)
|
60
|
+
|
61
|
+
declared_pyproject_name = os.environ.get("APPLICATION_PYPROJECT_NAME")
|
62
|
+
inferred_pyproject_name = cls.POETRY_DATA['name'] if cls.POETRY_DATA else None
|
63
|
+
if (declared_pyproject_name and inferred_pyproject_name
|
64
|
+
and declared_pyproject_name != inferred_pyproject_name):
|
65
|
+
raise RuntimeError(f"APPLICATION_PYPROJECT_NAME={declared_pyproject_name!r},"
|
66
|
+
f" but {pyproject_toml_file} says it should be {inferred_pyproject_name!r}")
|
67
|
+
|
68
|
+
cls._PYPROJECT_NAME = declared_pyproject_name or inferred_pyproject_name
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def register(cls, name):
|
72
|
+
"""
|
73
|
+
Registers a class to be used based on the name in the top of pyproject.toml.
|
74
|
+
Note that this means that cgap-portal and fourfront will both register as 'encoded',
|
75
|
+
as in:
|
76
|
+
|
77
|
+
@Project.register('encoded')
|
78
|
+
class FourfrontProject(EncodedCoreProject):
|
79
|
+
PRETTY_NAME = "Fourfront"
|
80
|
+
|
81
|
+
Since fourfront and cgap-portal don't occupy the same space, no confusion should result.
|
82
|
+
"""
|
83
|
+
def _wrap_class(the_class):
|
84
|
+
the_class_name = the_class.__name__
|
85
|
+
if not issubclass(the_class, Project):
|
86
|
+
raise ValueError(f"The class {the_class_name} must inherit from Project.")
|
87
|
+
lower_registry_name = name.lower()
|
88
|
+
for x in ['cgap-portal', 'fourfront', 'smaht']:
|
89
|
+
if x in lower_registry_name:
|
90
|
+
# It's an easy error to make, but the name of the project from which we're gaining foothold
|
91
|
+
# in pyproject.toml is 'encoded', not 'cgap-portal', etc., so the name 'encoded' will be
|
92
|
+
# needed for bootstrapping. So it should look like
|
93
|
+
# -kmp 15-May-2023
|
94
|
+
raise ValueError(f"Please use ProjectRegistry.register('encoded'),"
|
95
|
+
f" not ProjectRegistry.register({name!r})."
|
96
|
+
f" This registration is just for bootstrapping."
|
97
|
+
f" The class can still be {the_class_name}.")
|
98
|
+
cls.REGISTERED_PROJECTS[name] = the_class
|
99
|
+
return the_class
|
100
|
+
return _wrap_class
|
101
|
+
|
102
|
+
@classmethod
|
103
|
+
def _lookup(cls, name):
|
104
|
+
"""
|
105
|
+
Returns the project object with the given name.
|
106
|
+
|
107
|
+
:param name: a string name that was used in a ProjectRegistry.register decorator
|
108
|
+
|
109
|
+
NOTE: There is no need for this function to be called outside of this class except for testing.
|
110
|
+
Really only one of these should be instantiated per running application, and that's
|
111
|
+
done automatically by this class.
|
112
|
+
"""
|
113
|
+
project_class = cls.REGISTERED_PROJECTS.get(name)
|
114
|
+
return project_class
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def _make_project(cls):
|
118
|
+
"""
|
119
|
+
Creates and returns an instantiated project object for the current project.
|
120
|
+
|
121
|
+
The project to use can be specified by setting the environment variable APPLICATION_PROJECT_HOME
|
122
|
+
to a particular directory that contains the pyproject.toml file to use.
|
123
|
+
If no such variable is set, the current working directory is used.
|
124
|
+
|
125
|
+
NOTE: There is no need for this function to be called outside of this class except for testing.
|
126
|
+
Really only one of these should be instantiated per running application, and that's
|
127
|
+
done automatically by this class.
|
128
|
+
"""
|
129
|
+
project_class = cls._lookup(cls.PYPROJECT_NAME)
|
130
|
+
assert issubclass(project_class, Project)
|
131
|
+
project: Project = project_class()
|
132
|
+
return project # instantiate and return
|
133
|
+
|
134
|
+
_app_project = None
|
135
|
+
_initialized = False
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def initialize(cls):
|
139
|
+
if cls._initialized:
|
140
|
+
raise RuntimeError(f"{cls.__name__}.initialize() was called more than once.")
|
141
|
+
cls._app_project = cls._make_project()
|
142
|
+
cls._initalized = True
|
143
|
+
if cls.SHOW_HERALD_WHEN_INITIALIZED:
|
144
|
+
cls.show_herald()
|
145
|
+
app_project: Project = cls.app_project
|
146
|
+
return app_project # It's initialized now, so we use the proper interface
|
147
|
+
|
148
|
+
@classmethod
|
149
|
+
def show_herald(cls):
|
150
|
+
app_project = cls.app_project_maker()
|
151
|
+
|
152
|
+
print("=" * 80)
|
153
|
+
print(f"APPLICATION_PROJECT_HOME == {cls.APPLICATION_PROJECT_HOME!r}")
|
154
|
+
print(f"PYPROJECT_TOML_FILE == {cls.PYPROJECT_TOML_FILE!r}")
|
155
|
+
print(f"PYPROJECT_NAME == {cls.PYPROJECT_NAME!r}")
|
156
|
+
the_app_project = Project.app_project
|
157
|
+
the_app_project_class = the_app_project.__class__
|
158
|
+
the_app_project_class_name = the_app_project_class.__name__
|
159
|
+
assert (Project.app_project
|
160
|
+
== app_project()
|
161
|
+
== the_app_project_class.app_project
|
162
|
+
== the_app_project.app_project), (
|
163
|
+
"Project consistency check failed."
|
164
|
+
)
|
165
|
+
print(f"{the_app_project_class_name}.app_project == Project.app_project == app_project() == {app_project()!r}")
|
166
|
+
print(f"app_project().NAME == {app_project().NAME!r}")
|
167
|
+
print(f"app_project().PRETTY_NAME == {app_project().PRETTY_NAME!r}")
|
168
|
+
print(f"app_project().PACKAGE_NAME == {app_project().PACKAGE_NAME!r}")
|
169
|
+
print(f"app_project().APP_NAME == {app_project().APP_NAME!r}")
|
170
|
+
print(f"app_project().APP_PRETTY_NAME == {app_project().APP_PRETTY_NAME!r}")
|
171
|
+
print("=" * 80)
|
172
|
+
|
173
|
+
@classproperty
|
174
|
+
def app_project(cls): # noQA - PyCharm thinks we should use 'self'
|
175
|
+
"""
|
176
|
+
Once the project is initialized, ProjectRegistry.app_project returns the application object
|
177
|
+
that should be used to dispatch project-dependent behavior.
|
178
|
+
"""
|
179
|
+
if cls._app_project is None:
|
180
|
+
# You need to put a call to
|
181
|
+
raise RuntimeError(f"Attempt to access {cls.__name__}.project before .initialize() called.")
|
182
|
+
return cls._app_project
|
183
|
+
|
184
|
+
@classmethod
|
185
|
+
def app_project_maker(cls):
|
186
|
+
|
187
|
+
def app_project(initialize=False, initialization_options: Optional[dict] = None):
|
188
|
+
if initialize:
|
189
|
+
Project.initialize_app_project(**(initialization_options or {}))
|
190
|
+
return ProjectRegistry.app_project
|
191
|
+
|
192
|
+
return app_project
|
193
|
+
|
194
|
+
|
195
|
+
class Project:
|
196
|
+
"""
|
197
|
+
A class that should be a superclass of all classes registered using ProjectRegistry.register
|
198
|
+
|
199
|
+
All such classes have these names:
|
200
|
+
.NAME - The name of the project in pyproject.toml
|
201
|
+
.PACKAGE_NAME - The pypi name of the project, useful for pkg_resources, for example.
|
202
|
+
.PRETTY_NAME - The pretty name of the package name
|
203
|
+
.APP_NAME - The ame of the project application (see dcicutils.common and the orchestrated app in EnvUtils)
|
204
|
+
.APP_PRETTY_NAME - The pretty name of the project application.
|
205
|
+
|
206
|
+
Some sample usess of pre-defined attributes of a Project that may help motivate the choice of attribute names:
|
207
|
+
|
208
|
+
registered |
|
209
|
+
name | NAME | PACKAGE_NAME | PRETTY NAME | APP_NAME | APP_PRETTY_NAME
|
210
|
+
-------------+--------------+----------------+--------------+-----------+----------------
|
211
|
+
snovault | dcicsnovault | snovault | Snovault | snovault | Snovault
|
212
|
+
encoded-core | encoded-core | encoded-core | Encoded Core | core | Core
|
213
|
+
encoded | cgap-portal | cgap-portal | CGAP Portal | cgap | CGAP
|
214
|
+
encoded | fourfront | fourfront | Fourfront | fourfront | Fourfront
|
215
|
+
encoded | smaht-portal | smaht-portal | SMaHT Portal | smaht | SMaHT
|
216
|
+
|
217
|
+
The registered name is the one used with the ProjectRegistry.register() decorator.
|
218
|
+
"""
|
219
|
+
|
220
|
+
NAME = 'project'
|
221
|
+
|
222
|
+
@classmethod
|
223
|
+
def _prettify(cls, name):
|
224
|
+
return name.title().replace("Cgap", "CGAP").replace("Smaht", "SMaHT").replace("-", " ")
|
225
|
+
|
226
|
+
@classproperty
|
227
|
+
def PACKAGE_NAME(cls): # noQA - PyCharm wants the variable name to be self
|
228
|
+
return cls.NAME.replace('dcic', '')
|
229
|
+
|
230
|
+
@classproperty
|
231
|
+
def PRETTY_NAME(cls): # noQA - PyCharm wants the variable name to be self
|
232
|
+
return cls._prettify(cls.PACKAGE_NAME)
|
233
|
+
|
234
|
+
@classproperty
|
235
|
+
def APP_NAME(cls): # noQA - PyCharm wants the variable name to be self
|
236
|
+
return cls.PACKAGE_NAME.replace('-portal', '').replace('encoded-', '')
|
237
|
+
|
238
|
+
@classproperty
|
239
|
+
def APP_PRETTY_NAME(cls): # noQA - PyCharm wants the variable name to be self
|
240
|
+
return cls._prettify(cls.APP_NAME)
|
241
|
+
|
242
|
+
@classproperty
|
243
|
+
def app_project(cls): # noQA - PyCharm wants the variable name to be self
|
244
|
+
"""
|
245
|
+
Project.app_project returns the actual instantiated project for app-specific behavior,
|
246
|
+
which might be of this class or one of its subclasses.
|
247
|
+
|
248
|
+
This access will fail if the project has not been initialized.
|
249
|
+
"""
|
250
|
+
return ProjectRegistry.app_project
|
251
|
+
|
252
|
+
@classmethod
|
253
|
+
def initialize_app_project(cls, initialize_env_utils=True):
|
254
|
+
if initialize_env_utils:
|
255
|
+
EnvUtils.init()
|
256
|
+
project: Project = ProjectRegistry.initialize()
|
257
|
+
return project
|
dcicutils/s3_utils.py
CHANGED
@@ -462,6 +462,8 @@ class s3Utils(s3Base): # NOQA - This class name violates style rules, but a lot
|
|
462
462
|
content_type = mimetypes.guess_type(upload_key)[0]
|
463
463
|
if content_type is None:
|
464
464
|
content_type = 'binary/octet-stream'
|
465
|
+
if isinstance(obj, dict):
|
466
|
+
obj = json.dumps(obj)
|
465
467
|
if acl:
|
466
468
|
# we use this to set some of the object as public
|
467
469
|
return self.s3.put_object(Bucket=self.outfile_bucket,
|
@@ -480,6 +482,8 @@ class s3Utils(s3Base): # NOQA - This class name violates style rules, but a lot
|
|
480
482
|
bucket = self.sys_bucket
|
481
483
|
if not secret:
|
482
484
|
secret = os.environ["S3_ENCRYPT_KEY"]
|
485
|
+
if isinstance(data, dict):
|
486
|
+
data = json.dumps(data)
|
483
487
|
return self.s3.put_object(Bucket=bucket,
|
484
488
|
Key=keyname,
|
485
489
|
Body=data,
|
@@ -4,7 +4,7 @@ dcicutils/beanstalk_utils.py,sha256=nHMWfFnZAXFiJh60oVouwbAPMKsQfHnDtkwz_PDE6S4,
|
|
4
4
|
dcicutils/cloudformation_utils.py,sha256=MtWJrSTXyiImgbPHgRvfH9bWso20ZPLTFJAfhDQSVj4,13786
|
5
5
|
dcicutils/codebuild_utils.py,sha256=CKpmhJ-Z8gYbkt1I2zyMlKtFdsg7T8lqrx3V5ieta-U,1155
|
6
6
|
dcicutils/command_utils.py,sha256=JExll5TMqIcmuiGvuS8q4XDUvoEfi2oSH0E2FVF6suU,15285
|
7
|
-
dcicutils/common.py,sha256=
|
7
|
+
dcicutils/common.py,sha256=nzxqDpEgolw3ogAeeqohN007lHMhdFpcL4nJbL632tE,3723
|
8
8
|
dcicutils/creds_utils.py,sha256=tK2sZXAUHhvu5Mp83_0E0aFj8Fv9Gq9YupShLD92tKE,11032
|
9
9
|
dcicutils/data_utils.py,sha256=k2OxOlsx7AJ6jF-YNlMyGus_JqSUBe4_n1s65Mv1gQQ,3098
|
10
10
|
dcicutils/deployment_utils.py,sha256=M-MlH3wZKYUHB8H_CIu2_YuVFrY2X-gSrJD559pMCmM,68354
|
@@ -23,7 +23,7 @@ dcicutils/exceptions.py,sha256=4giQGtpak-omQv7BP6Ckeu91XK5fnDosC8gfdmN_ccA,9931
|
|
23
23
|
dcicutils/ff_mocks.py,sha256=6RKS4eUiu_Wl8yP_8V0CaV75w4ZdWxdCuL1CVlnMrek,36918
|
24
24
|
dcicutils/ff_utils.py,sha256=3B8jdsX6N5B6jX31rSk_t7eRgg3kPROQAQfdUCwr6OE,66453
|
25
25
|
dcicutils/function_cache_decorator.py,sha256=XMyiEGODVr2WoAQ68vcoX_9_Xb9p8pZXdXl7keU8i2g,10026
|
26
|
-
dcicutils/glacier_utils.py,sha256=
|
26
|
+
dcicutils/glacier_utils.py,sha256=x4zRGeSBS9c3LeurjR2gvEr_ipDTVpULvRFsIMfOVrs,33704
|
27
27
|
dcicutils/jh_utils.py,sha256=Gpsxb9XEzggF_-Eq3ukjKvTnuyb9V1SCSUXkXsES4Kg,11502
|
28
28
|
dcicutils/kibana/dashboards.json,sha256=wHMB_mpJ8OaYhRRgvpZuihaB2lmSF64ADt_8hkBWgQg,16225
|
29
29
|
dcicutils/kibana/readme.md,sha256=3KmHF9FH6A6xwYsNxRFLw27q0XzHYnjZOlYUnn3VkQQ,2164
|
@@ -32,19 +32,20 @@ dcicutils/log_utils.py,sha256=7pWMc6vyrorUZQf-V-M3YC6zrPgNhuV_fzm9xqTPph0,10883
|
|
32
32
|
dcicutils/misc_utils.py,sha256=s_6_6X1It5dklv6UOd4XfZdtbw8xWwpOysWbclUdYNA,90749
|
33
33
|
dcicutils/obfuscation_utils.py,sha256=fo2jOmDRC6xWpYX49u80bVNisqRRoPskFNX3ymFAmjw,5963
|
34
34
|
dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
|
35
|
+
dcicutils/project_utils.py,sha256=Gdae4M6MQ_IZqPOleUCDDAIz4Pj0gLD8uMzSjbZfKE4,11632
|
35
36
|
dcicutils/qa_checkers.py,sha256=Pd40yGTxMsae1IZP86qWExBQ9kFiEMQIrsi5sWA55WE,20234
|
36
37
|
dcicutils/qa_utils.py,sha256=r_vpLlxnsN8FDM_X_XO1KPotuZYAjptICcY4HJurj5Q,154684
|
37
38
|
dcicutils/redis_tools.py,sha256=rqGtnVUjNjTlCdL1EMKuEhEMAgRJMiXZJkrKuX255QA,6509
|
38
39
|
dcicutils/redis_utils.py,sha256=VJ-7g8pOZqR1ZCtdcjKz3-6as2DMUcs1b1zG6wSprH4,6462
|
39
|
-
dcicutils/s3_utils.py,sha256=
|
40
|
+
dcicutils/s3_utils.py,sha256=a9eU3Flh8Asc8xPWLGP16A6UQ_FVwhoFQNqm4ZYgSQ4,28852
|
40
41
|
dcicutils/scripts/publish_to_pypi.py,sha256=s53R-x9rA8ZR5GV_aXVmlG18mh6nkJo8m_JQSyzypoc,11044
|
41
42
|
dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19745
|
42
43
|
dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
|
43
44
|
dcicutils/ssl_certificate_utils.py,sha256=F0ifz_wnRRN9dfrfsz7aCp4UDLgHEY8LaK7PjnNvrAQ,9707
|
44
45
|
dcicutils/task_utils.py,sha256=MF8ujmTD6-O2AC2gRGPHyGdUrVKgtr8epT5XU8WtNjk,8082
|
45
46
|
dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
|
46
|
-
dcicutils-7.4.
|
47
|
-
dcicutils-7.4.
|
48
|
-
dcicutils-7.4.
|
49
|
-
dcicutils-7.4.
|
50
|
-
dcicutils-7.4.
|
47
|
+
dcicutils-7.4.2.1b0.dist-info/LICENSE.txt,sha256=_lknlhA5bbBFtUCrhmOtUogQuvKDYd_AkLCRcjPoMBc,1098
|
48
|
+
dcicutils-7.4.2.1b0.dist-info/METADATA,sha256=UpOK3rRozKvHwVvGGRSKX1jzSepSH2GBETYK_7zceSo,3002
|
49
|
+
dcicutils-7.4.2.1b0.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
50
|
+
dcicutils-7.4.2.1b0.dist-info/entry_points.txt,sha256=xUFTG_l0IJJFMY-WLeijpjCzTv3_3cdmXVqClOeuIGs,74
|
51
|
+
dcicutils-7.4.2.1b0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|