deriva 1.7.3__py3-none-any.whl → 1.7.5__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.
- deriva/config/rollback_annotation.py +1 -1
- deriva/core/__init__.py +1 -1
- deriva/core/datapath.py +55 -55
- deriva/core/ermrest_model.py +702 -186
- deriva/core/hatrac_cli.py +3 -5
- deriva/core/hatrac_store.py +9 -20
- deriva/core/mmo.py +379 -0
- deriva/transfer/download/processors/postprocess/transfer_post_processor.py +2 -2
- deriva/transfer/download/processors/query/base_query_processor.py +2 -1
- deriva/transfer/upload/deriva_upload.py +4 -0
- {deriva-1.7.3.dist-info → deriva-1.7.5.dist-info}/METADATA +1 -1
- {deriva-1.7.3.dist-info → deriva-1.7.5.dist-info}/RECORD +25 -16
- tests/deriva/core/mmo/__init__.py +0 -0
- tests/deriva/core/mmo/base.py +300 -0
- tests/deriva/core/mmo/test_mmo_drop.py +252 -0
- tests/deriva/core/mmo/test_mmo_find.py +90 -0
- tests/deriva/core/mmo/test_mmo_prune.py +196 -0
- tests/deriva/core/mmo/test_mmo_rename.py +222 -0
- tests/deriva/core/mmo/test_mmo_replace.py +180 -0
- tests/deriva/core/test_datapath.py +52 -26
- tests/deriva/core/test_ermrest_model.py +782 -0
- {deriva-1.7.3.dist-info → deriva-1.7.5.dist-info}/LICENSE +0 -0
- {deriva-1.7.3.dist-info → deriva-1.7.5.dist-info}/WHEEL +0 -0
- {deriva-1.7.3.dist-info → deriva-1.7.5.dist-info}/entry_points.txt +0 -0
- {deriva-1.7.3.dist-info → deriva-1.7.5.dist-info}/top_level.txt +0 -0
deriva/core/hatrac_cli.py
CHANGED
|
@@ -185,14 +185,14 @@ class DerivaHatracCLI (BaseCLI):
|
|
|
185
185
|
|
|
186
186
|
try:
|
|
187
187
|
acls = self.store.get_acl(self.resource, args.access, args.role)
|
|
188
|
-
if acls is None:
|
|
189
|
-
raise DerivaHatracCLIException('No such object or namespace or ACL entry')
|
|
190
188
|
for access in acls:
|
|
191
189
|
print("%s:" % access)
|
|
192
190
|
for role in acls.get(access, []):
|
|
193
191
|
print(" %s" % role)
|
|
194
192
|
except HTTPError as e:
|
|
195
|
-
if e.response.status_code == requests.codes.
|
|
193
|
+
if e.response.status_code == requests.codes.not_found:
|
|
194
|
+
raise ResourceException('No such object or namespace or ACL entry', e)
|
|
195
|
+
elif e.response.status_code == requests.codes.bad_request:
|
|
196
196
|
raise ResourceException('Invalid ACL name %s' % args.access, e)
|
|
197
197
|
else:
|
|
198
198
|
raise e
|
|
@@ -316,8 +316,6 @@ class DerivaHatracCLI (BaseCLI):
|
|
|
316
316
|
except HatracHashMismatch as e:
|
|
317
317
|
logging.debug(format_exception(e))
|
|
318
318
|
eprint(_resource_error_message('Checksum verification failed'))
|
|
319
|
-
except DerivaHatracCLIException as e:
|
|
320
|
-
eprint(e)
|
|
321
319
|
except RuntimeError as e:
|
|
322
320
|
logging.debug(format_exception(e))
|
|
323
321
|
eprint('Unexpected runtime error occurred')
|
deriva/core/hatrac_store.py
CHANGED
|
@@ -448,14 +448,8 @@ class HatracStore(DerivaBinding):
|
|
|
448
448
|
"""Retrieve a namespace.
|
|
449
449
|
"""
|
|
450
450
|
headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
namespaces = resp.json()
|
|
454
|
-
return namespaces
|
|
455
|
-
except requests.HTTPError as e:
|
|
456
|
-
if e.response.status_code == requests.codes.not_found:
|
|
457
|
-
return None
|
|
458
|
-
raise
|
|
451
|
+
resp = self.get(namespace_path, headers)
|
|
452
|
+
return resp.json()
|
|
459
453
|
|
|
460
454
|
def create_namespace(self, namespace_path, parents=True):
|
|
461
455
|
"""Create a namespace.
|
|
@@ -485,18 +479,13 @@ class HatracStore(DerivaBinding):
|
|
|
485
479
|
url += '/' + urlquote(role)
|
|
486
480
|
elif role:
|
|
487
481
|
raise ValueError('Do not specify "role" if "access" mode is not specified.')
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return resp.json()
|
|
496
|
-
except requests.HTTPError as e:
|
|
497
|
-
if e.response.status_code == requests.codes.not_found:
|
|
498
|
-
return None
|
|
499
|
-
raise
|
|
482
|
+
resp = self.get(url, headers)
|
|
483
|
+
if role:
|
|
484
|
+
return {access: [role]}
|
|
485
|
+
elif access:
|
|
486
|
+
return {access: resp.json()}
|
|
487
|
+
else:
|
|
488
|
+
return resp.json()
|
|
500
489
|
|
|
501
490
|
def set_acl(self, resource_name, access, roles, add_role=False):
|
|
502
491
|
"""Set the object or namespace ACL resource.
|
deriva/core/mmo.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Model management operations.
|
|
2
|
+
"""
|
|
3
|
+
import logging
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
|
|
6
|
+
from deriva.core import tag as tags
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
Match = namedtuple('Match', 'anchor tag context container mapping')
|
|
11
|
+
|
|
12
|
+
__search_box__ = 'search-box'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def replace(model, symbol, replacement):
|
|
16
|
+
"""Replaces `symbol` with `replacement` where symbol is found in mappings.
|
|
17
|
+
|
|
18
|
+
See the 'find' function for notes on what are 'symbols' and how symbols are found in the model.
|
|
19
|
+
|
|
20
|
+
The `symbol` and its `replacement` must be of the same type (i.e., columns or constraints). For columns, only the
|
|
21
|
+
column name may differ. For constraints, the whole name or just the schema part may be replaced.
|
|
22
|
+
"""
|
|
23
|
+
logger.debug(f'Replacing symbol "{symbol}" with "{replacement}".')
|
|
24
|
+
assert len(symbol) == len(replacement), "symbol and replacement must have same length"
|
|
25
|
+
assert len(symbol) != 3 or symbol[0:2] == replacement[0:2], "column symbols may only differ in the column name"
|
|
26
|
+
|
|
27
|
+
# step 1: find all matches
|
|
28
|
+
for anchor, tag, context, container, mapping in find(model, symbol):
|
|
29
|
+
# step 2: replace symbol in each matching mapping found in model
|
|
30
|
+
|
|
31
|
+
# case: mapping is a `str` column name in a vizcol context _or_ a source-defs columns `list`
|
|
32
|
+
if isinstance(mapping, str) and isinstance(container, list):
|
|
33
|
+
assert mapping == symbol[-1], "expected mapping to match the column name"
|
|
34
|
+
for idx, val in enumerate(container):
|
|
35
|
+
if val == mapping:
|
|
36
|
+
container[idx] = replacement[-1]
|
|
37
|
+
|
|
38
|
+
# case: mapping is a `dict` pseudo-column and `source` is a `str` column name
|
|
39
|
+
elif isinstance(mapping, dict) and isinstance(mapping.get('source'), str):
|
|
40
|
+
assert mapping['source'] == symbol[-1], "expected pseudo-column `source` to match the column name"
|
|
41
|
+
mapping['source'] = replacement[-1]
|
|
42
|
+
|
|
43
|
+
# case: mapping is a `dict` pseudo-column and `source` is a source path `list`
|
|
44
|
+
elif isinstance(mapping, dict) and isinstance(mapping.get('source'), list):
|
|
45
|
+
_replace_symbol_in_source_path(anchor, mapping['source'], symbol, replacement)
|
|
46
|
+
|
|
47
|
+
# case: mapping is a `str` sourcekey in a source-defs source `dict`
|
|
48
|
+
elif isinstance(mapping, str) and isinstance(container, dict):
|
|
49
|
+
assert mapping in container, "expected to find sourcekey `mapping` in sources `container`"
|
|
50
|
+
assert isinstance(container[mapping], dict), "expected `container[mapping]` to be a source definition dict"
|
|
51
|
+
_replace_symbol_in_source_path(anchor, container[mapping]['source'], symbol, replacement)
|
|
52
|
+
|
|
53
|
+
# case: mapping is a `list` constraint name in a vizcol or vizfkey context _or_ a source-defs fkeys `list`
|
|
54
|
+
elif isinstance(mapping, list) and isinstance(container, list):
|
|
55
|
+
assert _is_constraint_match(mapping, symbol), "expected mapping to match the constraint name"
|
|
56
|
+
for idx, val in enumerate(container):
|
|
57
|
+
if val == mapping:
|
|
58
|
+
container[idx] = _rewrite_constraint_name(val, replacement)
|
|
59
|
+
else:
|
|
60
|
+
logger.warning(f'Unhandled case for replace operation')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def prune(model, symbol):
|
|
64
|
+
"""Prunes mappings from a model where symbol found in mapping.
|
|
65
|
+
|
|
66
|
+
See the 'find' function for notes on what are 'symbols' and how symbols are found in the model.
|
|
67
|
+
|
|
68
|
+
**Note on pruning source definitions**
|
|
69
|
+
When this methods prunes a `source-definitions` entry from the model, it will also prune
|
|
70
|
+
`visible-{columns|foreign-keys}` that reference its `sourcekey` directly or as a "path prefix" in a pseudo-column
|
|
71
|
+
definition. It will prune any references found in `wait_for` display attributes. Also, it will recurse over the
|
|
72
|
+
source definitions repeating the above pruning for each sourcekey dependent on the originally affected sourcekey.
|
|
73
|
+
"""
|
|
74
|
+
logger.debug(f'Pruning symbol "{symbol}".')
|
|
75
|
+
# step 1: find all matches
|
|
76
|
+
for anchor, tag, context, container, mapping in find(model, symbol):
|
|
77
|
+
# step 2: remove mapping from model, for each mapping found
|
|
78
|
+
if tag in [tags.visible_columns, tags.visible_foreign_keys]:
|
|
79
|
+
container.remove(mapping)
|
|
80
|
+
elif tag == tags.source_definitions:
|
|
81
|
+
logger.debug(f'Removing "{tag}" mapping "{mapping}" from container "{container}".')
|
|
82
|
+
if isinstance(container, list):
|
|
83
|
+
# step 2.a. match found in 'columns' or 'fkeys' lists, remove item and continue
|
|
84
|
+
container.remove(mapping)
|
|
85
|
+
else:
|
|
86
|
+
# step 2.b. match found in 'sources' dictionary, remove then search for dependencies
|
|
87
|
+
assert isinstance(container, dict), "Expected dictionary typed container"
|
|
88
|
+
del container[mapping]
|
|
89
|
+
|
|
90
|
+
# step 2.c. find all dependencies on sourcekey in anchor's annotations and remove them
|
|
91
|
+
for match in _find_sourcekey(anchor, mapping):
|
|
92
|
+
logger.debug(f'Removing "{mapping}" sourcekey dependency; mapping "{match.mapping}" from "{match.tag}".')
|
|
93
|
+
if match.tag in [tags.visible_columns, tags.visible_foreign_keys]:
|
|
94
|
+
match.container.remove(match.mapping)
|
|
95
|
+
elif match.tag == tags.citation:
|
|
96
|
+
del match.anchor.annotations[match.tag]
|
|
97
|
+
elif match.tag == tags.source_definitions:
|
|
98
|
+
del match.container[match.mapping]
|
|
99
|
+
else:
|
|
100
|
+
logger.warning(f'Unexpected tag "{match.tag}".')
|
|
101
|
+
else:
|
|
102
|
+
logger.warning(f'Unhandled tag "{tag}".')
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def find(model, symbol):
|
|
106
|
+
"""Finds mappings within a model where the mapping contains a given symbol.
|
|
107
|
+
|
|
108
|
+
Searches the following annotation tags:
|
|
109
|
+
- source-definitions
|
|
110
|
+
- visible-columns
|
|
111
|
+
- visible-foreign-keys
|
|
112
|
+
|
|
113
|
+
Presently, there are two forms of symbols:
|
|
114
|
+
- constraint: `[schema_name, constraint_name]` may refer to a key or fkey. If `constraint_name` is None then the
|
|
115
|
+
match is based only on `schema_name`
|
|
116
|
+
- column: `[schema_name, table_name, column_name]`
|
|
117
|
+
|
|
118
|
+
returns: list containing Match(anchor, tag, context, container, mapping) tuples
|
|
119
|
+
- anchor: the model object that anchors the mapping
|
|
120
|
+
- tag: the annotation tag where the mapping was found
|
|
121
|
+
- context: the annotation context where the mapping was found
|
|
122
|
+
- container: the parent container of the mapping
|
|
123
|
+
- mapping: the mapping in which the symbol was found
|
|
124
|
+
"""
|
|
125
|
+
# [NOTE] If/when table must be supported, the ambiguity could be addressed as:
|
|
126
|
+
# - table [schema_name, table_name, None] where the final None in the column category implies that we are removing
|
|
127
|
+
# not a single column but the whole table (and hence all of its columns)
|
|
128
|
+
matches = []
|
|
129
|
+
|
|
130
|
+
# At present, mappings only reside in model elements: table and column.
|
|
131
|
+
for schema in model.schemas.values():
|
|
132
|
+
for table in schema.tables.values():
|
|
133
|
+
for tag in table.annotations:
|
|
134
|
+
|
|
135
|
+
# case: visible-columns or visible-foreign-keys
|
|
136
|
+
if tag == tags.visible_columns or tag == tags.visible_foreign_keys:
|
|
137
|
+
for context in table.annotations[tag]:
|
|
138
|
+
if context == 'filter':
|
|
139
|
+
vizsrcs = table.annotations[tag][context].get('and', [])
|
|
140
|
+
else:
|
|
141
|
+
vizsrcs = table.annotations[tag][context]
|
|
142
|
+
|
|
143
|
+
for vizsrc in vizsrcs: # vizsrc is a vizcol or vizfkey entry
|
|
144
|
+
# case: constraint form of vizsrc
|
|
145
|
+
if isinstance(vizsrc, list) \
|
|
146
|
+
and _is_constraint_match(vizsrc, symbol):
|
|
147
|
+
matches.append(Match(table, tag, context, vizsrcs, vizsrc))
|
|
148
|
+
# case: pseudo-column form of vizsrc
|
|
149
|
+
elif isinstance(vizsrc, dict) and 'source' in vizsrc \
|
|
150
|
+
and _is_symbol_in_source(table, vizsrc['source'], symbol):
|
|
151
|
+
matches.append(Match(table, tag, context, vizsrcs, vizsrc))
|
|
152
|
+
# case: column form of vizsrc
|
|
153
|
+
elif isinstance(vizsrc, str) \
|
|
154
|
+
and [table.schema.name, table.name, vizsrc] == symbol:
|
|
155
|
+
matches.append(Match(table, tag, context, vizsrcs, vizsrc))
|
|
156
|
+
|
|
157
|
+
# case: source-definitions
|
|
158
|
+
elif tag == tags.source_definitions:
|
|
159
|
+
# search 'columns'
|
|
160
|
+
cols = table.annotations[tag].get('columns')
|
|
161
|
+
if isinstance(cols, list) \
|
|
162
|
+
and len(symbol) == 3 \
|
|
163
|
+
and [table.schema.name, table.name] == symbol[0:2] \
|
|
164
|
+
and symbol[-1] in cols:
|
|
165
|
+
matches.append(Match(table, tag, 'columns', cols, symbol[-1]))
|
|
166
|
+
|
|
167
|
+
# search 'fkeys'
|
|
168
|
+
fkeys = table.annotations[tag].get('fkeys')
|
|
169
|
+
if isinstance(fkeys, list):
|
|
170
|
+
for fkey in fkeys:
|
|
171
|
+
if _is_constraint_match(fkey, symbol):
|
|
172
|
+
matches.append(Match(table, tag, 'fkeys', fkeys, fkey))
|
|
173
|
+
|
|
174
|
+
# search 'sources'
|
|
175
|
+
sources = table.annotations[tag].get('sources')
|
|
176
|
+
for sourcekey in sources:
|
|
177
|
+
if _is_symbol_in_source(table, sources[sourcekey].get('source', []), symbol):
|
|
178
|
+
matches.append(Match(table, tag, 'sources', sources, sourcekey))
|
|
179
|
+
|
|
180
|
+
# search 'search-box'
|
|
181
|
+
search_box = table.annotations[tag].get(__search_box__)
|
|
182
|
+
if isinstance(search_box, dict) and isinstance(search_box.get('or'), list):
|
|
183
|
+
for search_col in search_box['or']:
|
|
184
|
+
if _is_symbol_in_source(table, search_col.get('source'), symbol):
|
|
185
|
+
matches.append(Match(table, tag, __search_box__, search_box['or'], search_col))
|
|
186
|
+
|
|
187
|
+
return matches
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _is_constraint_match(constraint_name, symbol):
|
|
191
|
+
"""Tests if the constraint name matches the given symbol.
|
|
192
|
+
|
|
193
|
+
:param constraint_name: a constraint name pair
|
|
194
|
+
:param symbol: either a constraint name part or a schema name
|
|
195
|
+
"""
|
|
196
|
+
if not (isinstance(constraint_name, list) and len(constraint_name) == 2):
|
|
197
|
+
# case: malformed constraint name
|
|
198
|
+
return False
|
|
199
|
+
if not isinstance(symbol, list) or len(symbol) != 2:
|
|
200
|
+
# case: symbol not a constraint name
|
|
201
|
+
return False
|
|
202
|
+
elif symbol[1]:
|
|
203
|
+
# case: symbol is a complete constraint name
|
|
204
|
+
return constraint_name == symbol
|
|
205
|
+
else:
|
|
206
|
+
# case: symbol is a schema name only
|
|
207
|
+
return constraint_name[0] == symbol[0]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _is_symbol_in_source(table, source, symbol):
|
|
211
|
+
"""Finds symbol in a source mapping.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
# case: source is a column name
|
|
215
|
+
if isinstance(source, str):
|
|
216
|
+
return [table.schema.name, table.name, source] == symbol
|
|
217
|
+
|
|
218
|
+
# case: source is a path, symbol is a constraint
|
|
219
|
+
if isinstance(source, list) and isinstance(symbol, list):
|
|
220
|
+
|
|
221
|
+
# case: symbol is a constraint
|
|
222
|
+
if len(symbol) == 2:
|
|
223
|
+
for pathelem in source:
|
|
224
|
+
if isinstance(pathelem, dict):
|
|
225
|
+
constraint_name = pathelem.get('inbound') or pathelem.get('outbound')
|
|
226
|
+
if _is_constraint_match(constraint_name, symbol):
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
# case: symbol is a column name
|
|
230
|
+
# -- start with column in path and test if matches last value of source path
|
|
231
|
+
# -- then determine if the table reference matches (last fkey in/out points to correct table)
|
|
232
|
+
# -- isinstance(source[-1], str) and source[-1] == symbol[-1]
|
|
233
|
+
# -- isinstance(source[-2], dict) -- ie, a constraint
|
|
234
|
+
# -- lookup fkey = model.fkeys(*source[-2].get('inbound' or 'outbound')
|
|
235
|
+
# -- if 'inbound' and fkey.table == symbol's table
|
|
236
|
+
# -- if 'outbound' and fkey.pk_table == symbol's table
|
|
237
|
+
if len(symbol) == 3 \
|
|
238
|
+
and len(source) >= 2 \
|
|
239
|
+
and source[-1] == symbol[-1] \
|
|
240
|
+
and isinstance(source[-2], dict):
|
|
241
|
+
|
|
242
|
+
# case: inbound fkey
|
|
243
|
+
if 'inbound' in source[-2]:
|
|
244
|
+
fkey = table.schema.model.fkey(source[-2]['inbound'])
|
|
245
|
+
return [fkey.table.schema.name, fkey.table.name] == symbol[0:2]
|
|
246
|
+
|
|
247
|
+
# case: outbound fkey
|
|
248
|
+
elif 'outbound' in source[-2]:
|
|
249
|
+
fkey = table.schema.model.fkey(source[-2]['outbound'])
|
|
250
|
+
return [fkey.pk_table.schema.name, fkey.pk_table.name] == symbol[0:2]
|
|
251
|
+
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _find_sourcekey(table, sourcekey):
|
|
256
|
+
"""Find usages of `sourcekey` in `table`'s annotations.
|
|
257
|
+
"""
|
|
258
|
+
matches = []
|
|
259
|
+
|
|
260
|
+
# case: source-definitions
|
|
261
|
+
# sources, <sourcekey>, display, wait_for (markdown_pattern)
|
|
262
|
+
# source, [0], sourcekey
|
|
263
|
+
#
|
|
264
|
+
# case: citation
|
|
265
|
+
# wait_for (*_pattern)
|
|
266
|
+
#
|
|
267
|
+
# case: visible-columns or visible-foreign-keys
|
|
268
|
+
# <context>, <pseudocolumn>, sourcekey
|
|
269
|
+
# source, [0], sourcekey
|
|
270
|
+
# display, wait_for (markdown_pattern)
|
|
271
|
+
|
|
272
|
+
# step 1: find all references to sourcekey in source-definitions (recursively)
|
|
273
|
+
sources = table.annotations[tags.source_definitions].get('sources', {})
|
|
274
|
+
sourcekeys = _find_dependent_sourcekeys(sourcekey, sources)
|
|
275
|
+
matches.extend([
|
|
276
|
+
Match(table, tags.source_definitions, None, sources, sourcekey)
|
|
277
|
+
for sourcekey in sourcekeys
|
|
278
|
+
])
|
|
279
|
+
|
|
280
|
+
# step 2: find all other references to the set of {sourcekeys} | {sourcekey} found in first step
|
|
281
|
+
sourcekeys.add(sourcekey)
|
|
282
|
+
|
|
283
|
+
# 2.a.: find sourcekey in citations
|
|
284
|
+
citation = table.annotations.get(tags.citation)
|
|
285
|
+
if citation:
|
|
286
|
+
for sourcekey in sourcekeys:
|
|
287
|
+
if sourcekey in citation.get('wait_for', []):
|
|
288
|
+
matches.append(Match(table, tags.citation, None, None, None))
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
# 2.b.: find sourcekey in visible-columns or visible-foreign-keys
|
|
292
|
+
for tag in (tags.visible_columns, tags.visible_foreign_keys):
|
|
293
|
+
for context in table.annotations.get(tag, {}):
|
|
294
|
+
vizcols = table.annotations[tag][context]
|
|
295
|
+
for vizcol in vizcols:
|
|
296
|
+
for sourcekey in sourcekeys:
|
|
297
|
+
if _is_dependent_on_sourcekey(sourcekey, vizcol):
|
|
298
|
+
matches.append(Match(table, tag, context, vizcols, vizcol))
|
|
299
|
+
|
|
300
|
+
return matches
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _find_dependent_sourcekeys(sourcekey, sources, deps=None):
|
|
304
|
+
"""Find 'sourcekey' dependencies in 'sources' source definitions.
|
|
305
|
+
"""
|
|
306
|
+
# initialize deps to empty set
|
|
307
|
+
deps = deps or set()
|
|
308
|
+
|
|
309
|
+
# remove self from sources
|
|
310
|
+
if sourcekey in sources:
|
|
311
|
+
sources = sources.copy()
|
|
312
|
+
sources.pop(sourcekey)
|
|
313
|
+
|
|
314
|
+
# test if each 'candidate' source is dependent on this 'sourcekey'
|
|
315
|
+
for candidate in sources:
|
|
316
|
+
# case: source 'search-box', need to check sources in its `or` block, but do not recurse
|
|
317
|
+
if candidate == __search_box__:
|
|
318
|
+
for source_def in sources[__search_box__].get('or', []):
|
|
319
|
+
if _is_dependent_on_sourcekey(sourcekey, source_def):
|
|
320
|
+
# add 'search-box' to deps, continue
|
|
321
|
+
deps.add(__search_box__)
|
|
322
|
+
break
|
|
323
|
+
# case: all other sources
|
|
324
|
+
if _is_dependent_on_sourcekey(sourcekey, sources[candidate]):
|
|
325
|
+
# add to deps
|
|
326
|
+
deps.add(candidate)
|
|
327
|
+
# union with deps of the candidate
|
|
328
|
+
deps.union(_find_dependent_sourcekeys(candidate, sources, deps=deps))
|
|
329
|
+
|
|
330
|
+
return deps
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _is_dependent_on_sourcekey(sourcekey, source_def):
|
|
334
|
+
"""Tests if 'source_def' is dependent on 'sourcekey'.
|
|
335
|
+
"""
|
|
336
|
+
# case: source_def is not a pseudo-column
|
|
337
|
+
if not isinstance(source_def, dict):
|
|
338
|
+
return False
|
|
339
|
+
# case: sourcekey referenced directly
|
|
340
|
+
if sourcekey == source_def.get('sourcekey'):
|
|
341
|
+
return True
|
|
342
|
+
# case: sourcekey in path prefix
|
|
343
|
+
if not isinstance(source_def.get('source'), str) and sourcekey == source_def.get('source', [{}])[0].get('sourcekey'):
|
|
344
|
+
return True
|
|
345
|
+
# case: sourcekey in wait_for
|
|
346
|
+
if sourcekey in source_def.get('display', {}).get('wait_for', []):
|
|
347
|
+
return True
|
|
348
|
+
# not dependent
|
|
349
|
+
return False
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _rewrite_constraint_name(constraint_name, replacement):
|
|
353
|
+
"""Rewrites constraint name according to replacement symbol.
|
|
354
|
+
"""
|
|
355
|
+
return [
|
|
356
|
+
replacement[0],
|
|
357
|
+
replacement[1] if replacement[1] else constraint_name[1]
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _replace_symbol_in_source_path(anchor, source, symbol, replacement):
|
|
362
|
+
"""Replaces `symbol` with `replacement` in `source`.
|
|
363
|
+
"""
|
|
364
|
+
assert isinstance(source, list), 'expected source to be a list'
|
|
365
|
+
assert source, 'expected source to be non-empty'
|
|
366
|
+
|
|
367
|
+
if len(symbol) == 3: # case: column symbol replacement
|
|
368
|
+
assert source[-1] == symbol[-1], "expected source path to terminate in symbol column name"
|
|
369
|
+
source[-1] = replacement[-1]
|
|
370
|
+
|
|
371
|
+
if len(symbol) == 2: # case: constraint name replacement
|
|
372
|
+
assert _is_symbol_in_source(anchor, source, symbol), "expected to find symbol in mapping source"
|
|
373
|
+
for i, pathelem in enumerate(source):
|
|
374
|
+
if not isinstance(pathelem, dict):
|
|
375
|
+
continue
|
|
376
|
+
for direction in ('inbound', 'outbound'):
|
|
377
|
+
if _is_constraint_match(pathelem.get(direction), symbol):
|
|
378
|
+
pathelem[direction] = _rewrite_constraint_name(pathelem[direction], replacement)
|
|
379
|
+
break
|
|
@@ -128,12 +128,12 @@ class Boto3UploadPostProcessor(UploadPostProcessor):
|
|
|
128
128
|
object_qualifier = compute_hashes(
|
|
129
129
|
identity.encode() if identity else
|
|
130
130
|
"anon-" + self.envars.get("request_ip", "unknown").encode(), hashes=['md5'])['md5'][0]
|
|
131
|
-
if not stob(self.parameters.get("overwrite",
|
|
131
|
+
if not stob(self.parameters.get("overwrite", False)):
|
|
132
132
|
now = datetime.datetime.now()
|
|
133
133
|
object_qualifier = "/".join([object_qualifier, now.strftime("%Y-%m-%d_%H.%M.%S")])
|
|
134
134
|
|
|
135
135
|
for k, v in self.outputs.items():
|
|
136
|
-
object_name = "/".join([self.path, object_qualifier, k])
|
|
136
|
+
object_name = "/".join([self.path, object_qualifier, k]).lstrip("/")
|
|
137
137
|
file_path = v[LOCAL_PATH_KEY]
|
|
138
138
|
acl = self.parameters.get("acl", "private")
|
|
139
139
|
signed_url = stob(self.parameters.get("signed_url", acl == "public-read"))
|
|
@@ -138,7 +138,8 @@ class BaseQueryProcessor(BaseProcessor):
|
|
|
138
138
|
if not (urlparts.scheme and urlparts.netloc):
|
|
139
139
|
urlparts = urlsplit(self.catalog.get_server_uri())
|
|
140
140
|
server_uri = urlparts.scheme + "://" + urlparts.netloc
|
|
141
|
-
|
|
141
|
+
sep = "/" if not url.startswith("/") else ""
|
|
142
|
+
url = ''.join([server_uri, sep, url])
|
|
142
143
|
|
|
143
144
|
return url
|
|
144
145
|
|
|
@@ -329,6 +329,10 @@ class DerivaUpload(object):
|
|
|
329
329
|
@staticmethod
|
|
330
330
|
def getCatalogTable(asset_mapping, metadata_dict=None):
|
|
331
331
|
schema_name, table_name = asset_mapping.get('target_table', [None, None])
|
|
332
|
+
# allow for template substitution in the schema/table name tuple
|
|
333
|
+
if (schema_name and table_name) and metadata_dict is not None:
|
|
334
|
+
schema_name = schema_name.format(**metadata_dict)
|
|
335
|
+
table_name = table_name.format(**metadata_dict)
|
|
332
336
|
if not (schema_name and table_name):
|
|
333
337
|
metadata_dict_lower = {k.lower(): v for k, v in metadata_dict.items()}
|
|
334
338
|
schema_name = metadata_dict_lower.get("schema")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deriva
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.5
|
|
4
4
|
Summary: Python APIs and CLIs (Command-Line Interfaces) for the DERIVA platform.
|
|
5
5
|
Home-page: https://github.com/informatics-isi-edu/deriva-py
|
|
6
6
|
Author: USC Information Sciences Institute, Informatics Systems Research Division
|
|
@@ -5,20 +5,21 @@ deriva/config/annotation_config.py,sha256=sWcJnRfS5vcb85IdUOd420cmFHRyb4br5OxLCO
|
|
|
5
5
|
deriva/config/annotation_validate.py,sha256=ZN0jq6As49hfsAmWONUxAStqFG9TeYzvCmRU9TQ-zD8,5228
|
|
6
6
|
deriva/config/base_config.py,sha256=Y5sFotuAWWyo-LZvzbzVY2ZDaC-zhwbMXusgBF1OJYM,20724
|
|
7
7
|
deriva/config/dump_catalog_annotations.py,sha256=QzaWDLfWIAQ0eWVV11zeceWgwDBOYIePJnDQgRydwTE,9362
|
|
8
|
-
deriva/config/rollback_annotation.py,sha256=
|
|
8
|
+
deriva/config/rollback_annotation.py,sha256=vqrIcen-KZX8LDpu2OVNivzIHpQoQgWkZAChZJctvtk,3015
|
|
9
9
|
deriva/config/examples/group_owner_policy.json,sha256=8v3GWM1F_BWnYD9x_f6Eo4kBDvyy8g7mRqujfoEKLNc,2408
|
|
10
10
|
deriva/config/examples/self_serve_policy.json,sha256=pW-cqWz4rJNNXwY4eVZFkQ8gKCHclC9yDa22ylfcDqY,1676
|
|
11
|
-
deriva/core/__init__.py,sha256=
|
|
11
|
+
deriva/core/__init__.py,sha256=IT6J4hMqwaL8v4DRAsPa8FVUOfVCOWY_FTw0b8UthzQ,4945
|
|
12
12
|
deriva/core/annotation.py,sha256=PkAkPkxX1brQsb8_drR1Qj5QjQA5mjkpXhkq9NuZ1g8,13432
|
|
13
13
|
deriva/core/base_cli.py,sha256=EkLXOTeaFWUbPaYV-eLuLGga1PbkFVWi3Jjo-e_Vb-U,2681
|
|
14
14
|
deriva/core/catalog_cli.py,sha256=-6Bo6GLWFWap7y3VxkzPs73HAe_XzRXIJMW-Ri84m3M,23273
|
|
15
|
-
deriva/core/datapath.py,sha256=
|
|
15
|
+
deriva/core/datapath.py,sha256=w8LvPAd_DuknKxHc_YS6NUHhTY6XpCSkfa0m_xQUdZE,89068
|
|
16
16
|
deriva/core/deriva_binding.py,sha256=_sA9HGrcVRqT-OhrneMDMOquyVOFOxLq3WzBQhasLIM,12970
|
|
17
17
|
deriva/core/deriva_server.py,sha256=nsW3gwg1sIaHl3BTf-nL41AkSj3dEpcEBlatvjvN8CQ,200
|
|
18
18
|
deriva/core/ermrest_catalog.py,sha256=_eqQg16i1aA95R99B7tLZxHWQlYk-rLpN_0zghfNWRc,54991
|
|
19
|
-
deriva/core/ermrest_model.py,sha256=
|
|
20
|
-
deriva/core/hatrac_cli.py,sha256=
|
|
21
|
-
deriva/core/hatrac_store.py,sha256=
|
|
19
|
+
deriva/core/ermrest_model.py,sha256=NdvrMLkmNTY4ncodzojpyV1XjgSlod7x-SXyUu65Z0Q,124921
|
|
20
|
+
deriva/core/hatrac_cli.py,sha256=l9QmneLRHSMiG_z9S83ea0QGVhTS3Wq1KGPEKEDpecM,14522
|
|
21
|
+
deriva/core/hatrac_store.py,sha256=pRupabYZzXvf1iQTYmv0AME8n7KCkTCMfrojhl_JOTs,22178
|
|
22
|
+
deriva/core/mmo.py,sha256=dcB8akgsqbYMi22ClbVpOKVL6so8FjSDjSb6gP4_jFo,17852
|
|
22
23
|
deriva/core/polling_ermrest_catalog.py,sha256=KsjiFqPQaHWnJZCVF5i77sdzfubqZHgMBbQ1p8V8D3s,10351
|
|
23
24
|
deriva/core/schemas/app_links.schema.json,sha256=AxrkC2scxomM6N7jyjtdYA73BbZzPrmuqU8PYWe7okI,954
|
|
24
25
|
deriva/core/schemas/asset.schema.json,sha256=rVQobTnTxPtC-HOQhzSjc9CHo43cK97rImYqnxSp5ZA,1287
|
|
@@ -65,11 +66,11 @@ deriva/transfer/download/processors/__init__.py,sha256=evLp36tZn-Z_AMshdfV3JJO8w
|
|
|
65
66
|
deriva/transfer/download/processors/base_processor.py,sha256=R6IIHSa_euv4X2Dyhd8fvQAiVYDGJTWMQtPoukHQn-Q,3837
|
|
66
67
|
deriva/transfer/download/processors/postprocess/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
68
|
deriva/transfer/download/processors/postprocess/identifier_post_processor.py,sha256=rQVnh7A9n9l5zE0MqDMYTOdpA6Ku9enFLbzWwT6hKTg,5370
|
|
68
|
-
deriva/transfer/download/processors/postprocess/transfer_post_processor.py,sha256=
|
|
69
|
+
deriva/transfer/download/processors/postprocess/transfer_post_processor.py,sha256=8pSYicB-Yn_fONJcfZNGdaV8w6Zb7CiqnUMKZ9PAj94,7838
|
|
69
70
|
deriva/transfer/download/processors/postprocess/url_post_processor.py,sha256=s68iIYqQSZHtbv4y-fCG8pjhApAeMEG6hYcKx2Pvf5Y,2745
|
|
70
71
|
deriva/transfer/download/processors/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
72
|
deriva/transfer/download/processors/query/bag_fetch_query_processor.py,sha256=tiQtfuy01YgOFFD5b_sP7TGjMnt0Jqcg2gp1KNWqeLE,5645
|
|
72
|
-
deriva/transfer/download/processors/query/base_query_processor.py,sha256=
|
|
73
|
+
deriva/transfer/download/processors/query/base_query_processor.py,sha256=DGvYM4Ykwo5OYq2D-HKB2bYPuqhJMKdMQ8GGw7OCMz0,10393
|
|
73
74
|
deriva/transfer/download/processors/query/file_download_query_processor.py,sha256=Hg1NbKsaGJh9cB86yIyL7Fm7ywSNVop837Dv8aFXUes,7257
|
|
74
75
|
deriva/transfer/download/processors/transform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
76
|
deriva/transfer/download/processors/transform/base_transform_processor.py,sha256=Ddw5gsNpDANeuLvUaF4utp8psaxOtAzlgXtOg8gb-Pc,4109
|
|
@@ -85,7 +86,7 @@ deriva/transfer/restore/deriva_restore.py,sha256=s0h7cXit2USSdjrIfrj0dr7BJ0rrHHM
|
|
|
85
86
|
deriva/transfer/restore/deriva_restore_cli.py,sha256=2ViZ1Lyl5ndXPKeJFCHHGnwzkg3DfHhTuRa_bN7eJm8,5603
|
|
86
87
|
deriva/transfer/upload/__init__.py,sha256=4mlc_iUX-v7SpXzlCZmhxQtSiW5JeDGb2FX7bb1E6tY,304
|
|
87
88
|
deriva/transfer/upload/__main__.py,sha256=hqnXtGpRqPthwpO6uvrnf_TQm7McheeyOt960hStSMY,340
|
|
88
|
-
deriva/transfer/upload/deriva_upload.py,sha256=
|
|
89
|
+
deriva/transfer/upload/deriva_upload.py,sha256=_kndWxoAHb9vKJERCC9tjExp2EWISrOzhBXnSEbFIKI,60766
|
|
89
90
|
deriva/transfer/upload/deriva_upload_cli.py,sha256=-Q6xgiYabQziTQcMQdGNDAv-eLxCCHO-BCSo4umbDE4,5082
|
|
90
91
|
deriva/transfer/upload/processors/__init__.py,sha256=sMM5xdJ82UIRdB1lGMKk7ft0BgtjS2oJ0sI4SQSqiIU,2481
|
|
91
92
|
deriva/transfer/upload/processors/archive_processor.py,sha256=ID0lDwDn4vPe5nbxy6m28Ssj_TsZpK4df2xRrM6nJRQ,2015
|
|
@@ -97,10 +98,18 @@ deriva/transfer/upload/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
97
98
|
deriva/utils/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
|
|
98
99
|
tests/deriva/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
100
|
tests/deriva/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
|
-
tests/deriva/core/test_datapath.py,sha256=
|
|
101
|
-
deriva
|
|
102
|
-
deriva
|
|
103
|
-
deriva
|
|
104
|
-
deriva
|
|
105
|
-
deriva
|
|
106
|
-
deriva
|
|
101
|
+
tests/deriva/core/test_datapath.py,sha256=0vUlQ48KYkDW5L7hzwyGAETa_40y0JmZDalk0W0fCq4,38722
|
|
102
|
+
tests/deriva/core/test_ermrest_model.py,sha256=tmWmAgXTcLBueY4iUS14uq-b1SjgKMaFGt2lw9mYvbg,34330
|
|
103
|
+
tests/deriva/core/mmo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
|
+
tests/deriva/core/mmo/base.py,sha256=3iLqUDNdiJaERB-8-G-5NEvkJr9fz0YUMvPxl5hMINE,9356
|
|
105
|
+
tests/deriva/core/mmo/test_mmo_drop.py,sha256=R_yl-PIHGx-3kchT5TH8O9TJktepWjgs6hHISOc6d2k,8930
|
|
106
|
+
tests/deriva/core/mmo/test_mmo_find.py,sha256=PcUN76sik68B3XKg0G3wHVpKcPEld_6RtbxeGzkrMQ8,4172
|
|
107
|
+
tests/deriva/core/mmo/test_mmo_prune.py,sha256=4pYtYL8g1BgadlewNPVpVA5lT_gV6SPTDYf04ZKzBTA,6851
|
|
108
|
+
tests/deriva/core/mmo/test_mmo_rename.py,sha256=4oSR1G3Od701Ss3AnolI1Z7CbMxKuQF2uSr2_IcoR6s,8512
|
|
109
|
+
tests/deriva/core/mmo/test_mmo_replace.py,sha256=w-66LWyiQ_ajC7Ipmhc4kAKwIloPdQELeUPsvelTdX8,8439
|
|
110
|
+
deriva-1.7.5.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
|
111
|
+
deriva-1.7.5.dist-info/METADATA,sha256=10B2Mj3_umQBki2v847VPJZx-PfF6f1STkv7RkcFMD4,1623
|
|
112
|
+
deriva-1.7.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
113
|
+
deriva-1.7.5.dist-info/entry_points.txt,sha256=72BEmEE4Bes5QhVxUHrl7EvUARrgISWxI2KGa8BbNZ8,786
|
|
114
|
+
deriva-1.7.5.dist-info/top_level.txt,sha256=_LHDie5-O53wFlexfrxjewpVkf04oydf3CqX5h75DXE,13
|
|
115
|
+
deriva-1.7.5.dist-info/RECORD,,
|
|
File without changes
|