gen3-dataops-toolkit 4.2.0__tar.gz → 4.3.0__tar.gz
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.
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/PKG-INFO +15 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/README.md +14 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/pyproject.toml +1 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/delete_cmds.py +50 -0
- gen3_dataops_toolkit-4.3.0/src/g3dt/import_order.py +265 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/delete/delete_all_metadata_for_project.py +41 -19
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/delete/delete_metadata.sh +18 -3
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/delete/delete_metadata_by_guid.py +40 -19
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/delete/delete_synth_metadata_by_version.py +40 -19
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/utils/athena_utils.py +0 -9
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/aws_quiet.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/dispatch.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/helptext.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/registry.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/resolve.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/runner.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/safety.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/config_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/dict_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/ec2_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/indexd_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/jobs.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/k8s.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/main.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/metadata.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/pipeline_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/release_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/study_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/synth.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/config.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/contexts.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/indexd/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/indexd/file_access.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/indexd/indexd_registrar.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/ingest/ingest.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/resolver.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/dictionary/deploy_dd.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/dictionary/pull_dict.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/dictionary/upload_dictionary.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/indexd/register_indexd.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/indexd/verify_file_access.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/k8s_ops/argocd_restart_etl.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/k8s_ops/argocd_restart_ms.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/k8s_ops/argocd_restart_schema.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/k8s_ops/login_to_pod.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/k8s_ops/restart_etl_and_ms.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/synthetic_data/delete_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/synthetic_data/full_deploy_dd_and_synth.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/synthetic_data/generate_synth_metadata.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/synthetic_data/upload_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/upload/metadata/upload_all_studies.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/upload/metadata/upload_metadata.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/studies.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/metadata_deleter.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/metadata_submitter.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/upload_synthdata_s3.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/utils/dbt_utils.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/utils/release_writer.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/validate/validate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gen3-dataops-toolkit
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: JoshuaHarris391
|
|
@@ -194,6 +194,20 @@ version until the CDK config catches up —
|
|
|
194
194
|
`g3dt config diff --env <env> --file <wrapper>/config/<project>.<env>.json`
|
|
195
195
|
reports exactly that gap and exits 1, so it can gate CI.
|
|
196
196
|
|
|
197
|
+
### Deleting metadata: where the node order comes from
|
|
198
|
+
|
|
199
|
+
`delete metadata` walks nodes children-before-parents. The order is resolved
|
|
200
|
+
from the first available of: an explicit `--import-order <path|s3://…>`
|
|
201
|
+
(failures are fatal — an explicit source is never silently skipped), the
|
|
202
|
+
registered study's release bucket (`DataImportOrder.txt` next to the release's
|
|
203
|
+
node JSONs), a `DataImportOrder.txt` in the current directory, or a
|
|
204
|
+
topological sort derived from the dictionary itself — the `--dict-version`
|
|
205
|
+
tag's bundle when given (downloaded to `~/.g3dt/schemas` if needed), else the
|
|
206
|
+
env's deployed dictionary. Deriving matters when deleting data submitted
|
|
207
|
+
under an older dictionary whose node layout differs from today's: pass
|
|
208
|
+
`--dict-version <old-tag>`. With `--on ec2`, `--import-order` must be an
|
|
209
|
+
`s3://` URI (a laptop path does not exist on the box).
|
|
210
|
+
|
|
197
211
|
Synthetic data is only schema-valid against the dictionary that generated it, so
|
|
198
212
|
`synth generate` records the dictionary version in each batch and `synth upload`
|
|
199
213
|
refuses a batch that doesn't match the version being uploaded (override with
|
|
@@ -160,6 +160,20 @@ version until the CDK config catches up —
|
|
|
160
160
|
`g3dt config diff --env <env> --file <wrapper>/config/<project>.<env>.json`
|
|
161
161
|
reports exactly that gap and exits 1, so it can gate CI.
|
|
162
162
|
|
|
163
|
+
### Deleting metadata: where the node order comes from
|
|
164
|
+
|
|
165
|
+
`delete metadata` walks nodes children-before-parents. The order is resolved
|
|
166
|
+
from the first available of: an explicit `--import-order <path|s3://…>`
|
|
167
|
+
(failures are fatal — an explicit source is never silently skipped), the
|
|
168
|
+
registered study's release bucket (`DataImportOrder.txt` next to the release's
|
|
169
|
+
node JSONs), a `DataImportOrder.txt` in the current directory, or a
|
|
170
|
+
topological sort derived from the dictionary itself — the `--dict-version`
|
|
171
|
+
tag's bundle when given (downloaded to `~/.g3dt/schemas` if needed), else the
|
|
172
|
+
env's deployed dictionary. Deriving matters when deleting data submitted
|
|
173
|
+
under an older dictionary whose node layout differs from today's: pass
|
|
174
|
+
`--dict-version <old-tag>`. With `--on ec2`, `--import-order` must be an
|
|
175
|
+
`s3://` URI (a laptop path does not exist on the box).
|
|
176
|
+
|
|
163
177
|
Synthetic data is only schema-valid against the dictionary that generated it, so
|
|
164
178
|
`synth generate` records the dictionary version in each batch and `synth upload`
|
|
165
179
|
refuses a batch that doesn't match the version being uploaded (override with
|
|
@@ -14,6 +14,12 @@ synthetic projects are never registered), bare names default to version
|
|
|
14
14
|
``data_version`` property via GraphQL rather than Athena receipts (synthetic
|
|
15
15
|
uploads write none).
|
|
16
16
|
|
|
17
|
+
The node deletion order comes from, in order: an explicit ``--import-order``
|
|
18
|
+
(path or s3:// URI; failures are fatal, never silently skipped), the
|
|
19
|
+
registered study's release bucket, a ``DataImportOrder.txt`` in the current
|
|
20
|
+
directory, or a topological sort derived from the dictionary itself — the
|
|
21
|
+
``--dict-version`` bundle when given, else the env's deployed dictionary.
|
|
22
|
+
|
|
17
23
|
Every command confirms before acting. Production always requires typing the
|
|
18
24
|
target id, even with ``--yes``. Deleting ALL versions always prompts, even with
|
|
19
25
|
``--yes``. Confirmation happens locally before any EC2 dispatch (SSM has no
|
|
@@ -186,6 +192,21 @@ def metadata(
|
|
|
186
192
|
help="Gen3 program for --synthetic (default: program1). "
|
|
187
193
|
"Invalid without --synthetic.",
|
|
188
194
|
),
|
|
195
|
+
import_order: Optional[str] = typer.Option(
|
|
196
|
+
None,
|
|
197
|
+
"--import-order",
|
|
198
|
+
help="Path or s3:// URI of DataImportOrder.txt. Default: auto — the "
|
|
199
|
+
"study's release bucket (registered studies), then "
|
|
200
|
+
"./DataImportOrder.txt, then derived from the dictionary. "
|
|
201
|
+
"With --on ec2 only s3:// URIs are accepted.",
|
|
202
|
+
),
|
|
203
|
+
dict_version: Optional[str] = typer.Option(
|
|
204
|
+
None,
|
|
205
|
+
"--dict-version",
|
|
206
|
+
help="Dictionary git tag to derive the node order from (verbatim, "
|
|
207
|
+
"e.g. v1.3.0). Default: the env's deployed dictionary. Only "
|
|
208
|
+
"used when the order is derived.",
|
|
209
|
+
),
|
|
189
210
|
yes: bool = typer.Option(
|
|
190
211
|
False, "--yes", "-y", help="Skip the non-prod prompt (specific-version only)."
|
|
191
212
|
),
|
|
@@ -212,6 +233,26 @@ def metadata(
|
|
|
212
233
|
err=True,
|
|
213
234
|
)
|
|
214
235
|
raise typer.Exit(2)
|
|
236
|
+
if import_order and dict_version:
|
|
237
|
+
typer.secho(
|
|
238
|
+
"--import-order names the exact file; --dict-version derives one "
|
|
239
|
+
"— pass only one.",
|
|
240
|
+
fg=typer.colors.RED,
|
|
241
|
+
err=True,
|
|
242
|
+
)
|
|
243
|
+
raise typer.Exit(2)
|
|
244
|
+
# A laptop path forwarded to the EC2 box would resolve against the box's
|
|
245
|
+
# filesystem — at best a crash after confirmation, at worst a same-named
|
|
246
|
+
# DIFFERENT file ordering the delete. s3:// URIs (and --dict-version)
|
|
247
|
+
# resolve identically anywhere, so only those may travel.
|
|
248
|
+
if on == Target.ec2 and import_order and not import_order.startswith("s3://"):
|
|
249
|
+
typer.secho(
|
|
250
|
+
"--import-order with --on ec2 must be an s3:// URI (a local path "
|
|
251
|
+
"does not exist on the box). Upload the file, or run locally.",
|
|
252
|
+
fg=typer.colors.RED,
|
|
253
|
+
err=True,
|
|
254
|
+
)
|
|
255
|
+
raise typer.Exit(2)
|
|
215
256
|
if version is None:
|
|
216
257
|
fallback = None
|
|
217
258
|
elif synthetic:
|
|
@@ -266,6 +307,10 @@ def metadata(
|
|
|
266
307
|
# optional on the wire, but an explicit value keeps the contract
|
|
267
308
|
# visible in logs and SSM command history.
|
|
268
309
|
a += ["--synthetic", "--program-id", program_id or "program1"]
|
|
310
|
+
if import_order:
|
|
311
|
+
a += ["--import-order", import_order]
|
|
312
|
+
if dict_version:
|
|
313
|
+
a += ["--dict-version", dict_version]
|
|
269
314
|
return a
|
|
270
315
|
|
|
271
316
|
def remote_cli(env_name):
|
|
@@ -284,6 +329,11 @@ def metadata(
|
|
|
284
329
|
a.append("--synthetic")
|
|
285
330
|
if program_id is not None:
|
|
286
331
|
a += ["--program-id", program_id]
|
|
332
|
+
if import_order:
|
|
333
|
+
# Guaranteed s3:// by the pre-dispatch gate above.
|
|
334
|
+
a += ["--import-order", import_order]
|
|
335
|
+
if dict_version:
|
|
336
|
+
a += ["--dict-version", dict_version]
|
|
287
337
|
return a
|
|
288
338
|
|
|
289
339
|
dispatch.run_or_dispatch(
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Node import-order resolution for metadata deletion.
|
|
2
|
+
|
|
3
|
+
Deleting Gen3 metadata must walk nodes children-before-parents, which the
|
|
4
|
+
toolkit historically took from a ``DataImportOrder.txt`` in the caller's
|
|
5
|
+
working directory — a silent dependency that crashed runs started anywhere
|
|
6
|
+
else. This module resolves the order from an explicit chain of sources:
|
|
7
|
+
|
|
8
|
+
1. an explicit ``--import-order`` (local path or ``s3://`` URI) — fatal if
|
|
9
|
+
unreadable, never silently skipped;
|
|
10
|
+
2. the study's release bucket (registered studies only — releases ship a
|
|
11
|
+
``DataImportOrder.txt`` next to the node JSONs);
|
|
12
|
+
3. a ``DataImportOrder.txt`` in the current directory (legacy behavior);
|
|
13
|
+
4. derivation from the dictionary itself: a topological sort over the raw
|
|
14
|
+
bundle's ``links`` (proven byte-identical to simulator-written order
|
|
15
|
+
files), reading either the deployed dictionary at ``schema_s3_uri`` or a
|
|
16
|
+
``--dict-version`` bundle cached under ``~/.g3dt/schemas``.
|
|
17
|
+
|
|
18
|
+
Deliberately NOT used: ``gen3_validator``'s ``DataDictionary.get_node_order``
|
|
19
|
+
(it force-moves core_metadata_collection last, which a deletion reversal would
|
|
20
|
+
delete FIRST — before the file nodes that link to it) and its
|
|
21
|
+
``get_node_link`` (inspects only ``links[0]`` for subgroups).
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
import urllib.error
|
|
29
|
+
import urllib.request
|
|
30
|
+
from collections import deque
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import List, Optional, Sequence, Tuple
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
#: Bundle keys that are shared definitions, not nodes.
|
|
37
|
+
_META_KEYS = frozenset(
|
|
38
|
+
{"_definitions.yaml", "_terms.yaml", "_settings.yaml", "root.yaml", "metaschema.yaml"}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_ORDER_FILENAME = "DataImportOrder.txt"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ImportOrderError(Exception):
|
|
45
|
+
"""A node order could not be resolved; the message names the fix."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def derive_import_order(schema: dict) -> List[str]:
|
|
49
|
+
"""Topologically sort a raw Gen3 dictionary bundle into submission order.
|
|
50
|
+
|
|
51
|
+
Works on the UNRESOLVED bundle ({"<node>.yaml": {...}, ...}): ``links``
|
|
52
|
+
blocks are self-contained (no $refs), so no schema resolution is needed.
|
|
53
|
+
Skips the shared ``_*.yaml`` definitions, entries without ``properties``,
|
|
54
|
+
and ``submittable: false`` nodes (which removes ``program``). Every
|
|
55
|
+
``links`` entry is flattened — subgroup members and plain links alike —
|
|
56
|
+
into edges ``parent -> child``; edges to nodes outside the set are
|
|
57
|
+
dropped (this handles ``project -> program``).
|
|
58
|
+
|
|
59
|
+
Kahn's algorithm with sorted tie-breaking keeps the output deterministic
|
|
60
|
+
and byte-compatible with simulator-written DataImportOrder.txt files.
|
|
61
|
+
Members of a dependency cycle (impossible in a valid Gen3 dictionary) are
|
|
62
|
+
appended at the end with a warning rather than dropped.
|
|
63
|
+
|
|
64
|
+
Returns SUBMISSION order (parents first); callers reverse for deletion.
|
|
65
|
+
"""
|
|
66
|
+
nodes = {}
|
|
67
|
+
for key, value in schema.items():
|
|
68
|
+
if key in _META_KEYS or not isinstance(value, dict):
|
|
69
|
+
continue
|
|
70
|
+
if "properties" not in value or value.get("submittable") is False:
|
|
71
|
+
continue
|
|
72
|
+
name = key[: -len(".yaml")] if key.endswith(".yaml") else key
|
|
73
|
+
nodes[name] = value
|
|
74
|
+
|
|
75
|
+
graph = {n: [] for n in nodes}
|
|
76
|
+
in_degree = {n: 0 for n in nodes}
|
|
77
|
+
for name, node in nodes.items():
|
|
78
|
+
for entry in node.get("links", []) or []:
|
|
79
|
+
members = entry.get("subgroup", [entry]) if isinstance(entry, dict) else []
|
|
80
|
+
for member in members:
|
|
81
|
+
parent = member.get("target_type")
|
|
82
|
+
if parent in nodes:
|
|
83
|
+
graph[parent].append(name)
|
|
84
|
+
in_degree[name] += 1
|
|
85
|
+
|
|
86
|
+
queue = deque(sorted(n for n, d in in_degree.items() if d == 0))
|
|
87
|
+
ordered: List[str] = []
|
|
88
|
+
while queue:
|
|
89
|
+
node = queue.popleft()
|
|
90
|
+
ordered.append(node)
|
|
91
|
+
for child in sorted(graph[node]):
|
|
92
|
+
in_degree[child] -= 1
|
|
93
|
+
if in_degree[child] == 0:
|
|
94
|
+
queue.append(child)
|
|
95
|
+
queue = deque(sorted(queue))
|
|
96
|
+
|
|
97
|
+
leftovers = sorted(n for n in nodes if n not in ordered)
|
|
98
|
+
if leftovers:
|
|
99
|
+
logger.warning(
|
|
100
|
+
"Dictionary link cycle detected; appending unordered node(s): %s "
|
|
101
|
+
"— deleting them may require a re-run.",
|
|
102
|
+
", ".join(leftovers),
|
|
103
|
+
)
|
|
104
|
+
ordered.extend(leftovers)
|
|
105
|
+
return ordered
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def ensure_local_dictionary(env_cfg, version: str, schema_dir: Optional[Path] = None) -> Path:
|
|
109
|
+
"""Return the local bundle for ``version``, downloading it if needed.
|
|
110
|
+
|
|
111
|
+
The cache is ``$G3DT_SCHEMA_DIR`` (else ``~/.g3dt/schemas``), shared with
|
|
112
|
+
pull_dict.sh. A cache hit requires a non-empty file that parses as JSON —
|
|
113
|
+
``wget -O`` leaves a zero-byte file behind on a failed pull, so mere
|
|
114
|
+
existence is not trusted. Downloads are validated as JSON BEFORE being
|
|
115
|
+
written, to a temp name replaced atomically, so a failed or partial
|
|
116
|
+
download can never poison the cache.
|
|
117
|
+
"""
|
|
118
|
+
from g3dt import config
|
|
119
|
+
|
|
120
|
+
if schema_dir is None:
|
|
121
|
+
schema_dir = Path(
|
|
122
|
+
os.environ.get("G3DT_SCHEMA_DIR", "~/.g3dt/schemas")
|
|
123
|
+
).expanduser()
|
|
124
|
+
target = schema_dir / config.dictionary_filename(env_cfg, version)
|
|
125
|
+
|
|
126
|
+
if target.exists() and target.stat().st_size > 0:
|
|
127
|
+
try:
|
|
128
|
+
json.loads(target.read_text(encoding="utf-8"))
|
|
129
|
+
return target
|
|
130
|
+
except ValueError:
|
|
131
|
+
logger.warning("Cached dictionary %s is corrupt; re-downloading.", target)
|
|
132
|
+
|
|
133
|
+
url = config.dictionary_url(env_cfg, version)
|
|
134
|
+
logger.info("Downloading dictionary %s from %s", version, url)
|
|
135
|
+
try:
|
|
136
|
+
with urllib.request.urlopen(url) as resp:
|
|
137
|
+
body = resp.read()
|
|
138
|
+
except (urllib.error.HTTPError, urllib.error.URLError) as exc:
|
|
139
|
+
raise ImportOrderError(
|
|
140
|
+
f"Could not download dictionary tag '{version}' from {url}: {exc}. "
|
|
141
|
+
f"Check the tag exists in the schema repo, or pass --import-order."
|
|
142
|
+
)
|
|
143
|
+
try:
|
|
144
|
+
json.loads(body.decode("utf-8"))
|
|
145
|
+
except ValueError:
|
|
146
|
+
raise ImportOrderError(
|
|
147
|
+
f"Downloaded dictionary tag '{version}' from {url} is not valid "
|
|
148
|
+
f"JSON — is the tag/path right?"
|
|
149
|
+
)
|
|
150
|
+
schema_dir.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
tmp = target.with_name(target.name + ".part")
|
|
152
|
+
tmp.write_bytes(body)
|
|
153
|
+
os.replace(tmp, target)
|
|
154
|
+
return target
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def to_deletion_order(nodes: Sequence[str], exclude_nodes: Sequence[str]) -> List[str]:
|
|
158
|
+
"""Filter excluded nodes out of a submission order and reverse it."""
|
|
159
|
+
kept = [n for n in nodes if n not in exclude_nodes]
|
|
160
|
+
kept.reverse()
|
|
161
|
+
return kept
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def resolve_import_order(
|
|
165
|
+
*,
|
|
166
|
+
env_cfg,
|
|
167
|
+
session,
|
|
168
|
+
import_order: Optional[str] = None,
|
|
169
|
+
dict_version: Optional[str] = None,
|
|
170
|
+
study_cfg=None,
|
|
171
|
+
cwd: Optional[Path] = None,
|
|
172
|
+
) -> Tuple[List[str], str]:
|
|
173
|
+
"""Resolve the node SUBMISSION order for a delete, returning (nodes, source).
|
|
174
|
+
|
|
175
|
+
The chain (first hit wins): explicit ``import_order`` (fatal on failure —
|
|
176
|
+
an explicit source is never silently skipped), the registered study's
|
|
177
|
+
release bucket (``study_cfg.s3_metadata_path``; a listing error warns and
|
|
178
|
+
falls through), a ``DataImportOrder.txt`` in the current directory
|
|
179
|
+
(legacy behavior), then derivation from the dictionary — the
|
|
180
|
+
``dict_version`` bundle when given, else the deployed dictionary at
|
|
181
|
+
``s3://{env_cfg.schema_s3_uri}``.
|
|
182
|
+
|
|
183
|
+
``source`` is a human-readable description of the winning step; workers
|
|
184
|
+
log it so operators can always see what ordered a destructive run.
|
|
185
|
+
"""
|
|
186
|
+
from g3dt.upload.metadata_submitter import (
|
|
187
|
+
find_data_import_order_file_s3,
|
|
188
|
+
read_data_import_order_txt_s3,
|
|
189
|
+
read_metadata_json_s3,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# 1. Explicit flag: local path or s3:// URI. Failures are fatal.
|
|
193
|
+
if import_order:
|
|
194
|
+
if import_order.startswith("s3://"):
|
|
195
|
+
try:
|
|
196
|
+
nodes = read_data_import_order_txt_s3(import_order, session)
|
|
197
|
+
except Exception as exc:
|
|
198
|
+
raise ImportOrderError(
|
|
199
|
+
f"Could not read --import-order {import_order}: {exc}"
|
|
200
|
+
)
|
|
201
|
+
return nodes, f"explicit --import-order {import_order}"
|
|
202
|
+
path = Path(import_order)
|
|
203
|
+
try:
|
|
204
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
205
|
+
except OSError as exc:
|
|
206
|
+
raise ImportOrderError(
|
|
207
|
+
f"Could not read --import-order {import_order}: {exc}"
|
|
208
|
+
)
|
|
209
|
+
return (
|
|
210
|
+
[line.strip() for line in lines if line.strip()],
|
|
211
|
+
f"explicit --import-order {path}",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# 2. Registered study: the release ships its own DataImportOrder.txt.
|
|
215
|
+
if study_cfg is not None and str(
|
|
216
|
+
getattr(study_cfg, "s3_metadata_path", "")
|
|
217
|
+
).startswith("s3://"):
|
|
218
|
+
try:
|
|
219
|
+
uri = find_data_import_order_file_s3(
|
|
220
|
+
s3_uri=study_cfg.s3_metadata_path, session=session
|
|
221
|
+
)
|
|
222
|
+
return (
|
|
223
|
+
read_data_import_order_txt_s3(uri, session),
|
|
224
|
+
f"release bucket {uri}",
|
|
225
|
+
)
|
|
226
|
+
except Exception as exc:
|
|
227
|
+
logger.warning(
|
|
228
|
+
"No usable DataImportOrder.txt under %s (%s) — falling back.",
|
|
229
|
+
study_cfg.s3_metadata_path,
|
|
230
|
+
exc,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# 3. Legacy: a DataImportOrder.txt in the working directory.
|
|
234
|
+
local = (cwd or Path.cwd()) / _ORDER_FILENAME
|
|
235
|
+
if local.exists():
|
|
236
|
+
lines = local.read_text(encoding="utf-8").splitlines()
|
|
237
|
+
return (
|
|
238
|
+
[line.strip() for line in lines if line.strip()],
|
|
239
|
+
f"{_ORDER_FILENAME} in current directory ({local.resolve()})",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# 4. Derive from the dictionary.
|
|
243
|
+
if dict_version:
|
|
244
|
+
bundle_path = ensure_local_dictionary(env_cfg, dict_version)
|
|
245
|
+
schema = json.loads(bundle_path.read_text(encoding="utf-8"))
|
|
246
|
+
source = f"derived from dictionary {dict_version} ({bundle_path})"
|
|
247
|
+
else:
|
|
248
|
+
uri = f"s3://{env_cfg.schema_s3_uri}"
|
|
249
|
+
try:
|
|
250
|
+
schema = read_metadata_json_s3(uri, session)
|
|
251
|
+
except Exception as exc:
|
|
252
|
+
raise ImportOrderError(
|
|
253
|
+
f"No DataImportOrder.txt found and the deployed dictionary at "
|
|
254
|
+
f"{uri} (SSM app/schema_s3_uri) could not be read: {exc}. "
|
|
255
|
+
f"Pass --import-order <path|s3://...> or --dict-version <tag>."
|
|
256
|
+
)
|
|
257
|
+
source = f"derived from deployed dictionary {uri}"
|
|
258
|
+
|
|
259
|
+
nodes = derive_import_order(schema)
|
|
260
|
+
if not nodes:
|
|
261
|
+
raise ImportOrderError(
|
|
262
|
+
f"Deriving the node order produced no submittable nodes — is the "
|
|
263
|
+
f"object read for '{source}' a Gen3 dictionary bundle?"
|
|
264
|
+
)
|
|
265
|
+
return nodes, source
|
|
@@ -10,6 +10,11 @@ from g3dt.upload.metadata_submitter import (
|
|
|
10
10
|
from g3dt.upload.metadata_deleter import (
|
|
11
11
|
delete_project_metadata,
|
|
12
12
|
)
|
|
13
|
+
from g3dt.import_order import (
|
|
14
|
+
ImportOrderError,
|
|
15
|
+
resolve_import_order,
|
|
16
|
+
to_deletion_order,
|
|
17
|
+
)
|
|
13
18
|
|
|
14
19
|
# ANSI colour codes
|
|
15
20
|
GREEN = "\033[92m"
|
|
@@ -43,20 +48,6 @@ def setup_logger():
|
|
|
43
48
|
from g3dt import config as g3dt_config # noqa: E402
|
|
44
49
|
|
|
45
50
|
|
|
46
|
-
def load_import_order(import_order_path, exclude_nodes=None):
|
|
47
|
-
"""
|
|
48
|
-
Reads the DataImportOrder.txt file and returns the node list
|
|
49
|
-
in deletion order (reversed, with excluded nodes removed).
|
|
50
|
-
"""
|
|
51
|
-
if exclude_nodes is None:
|
|
52
|
-
exclude_nodes = EXCLUDE_NODES
|
|
53
|
-
with open(import_order_path, 'r', encoding='utf-8') as f:
|
|
54
|
-
nodes = [line.strip() for line in f if line.strip()]
|
|
55
|
-
nodes = [n for n in nodes if n not in exclude_nodes]
|
|
56
|
-
nodes.reverse()
|
|
57
|
-
return nodes
|
|
58
|
-
|
|
59
|
-
|
|
60
51
|
def main():
|
|
61
52
|
logger = setup_logger()
|
|
62
53
|
|
|
@@ -98,8 +89,21 @@ def main():
|
|
|
98
89
|
)
|
|
99
90
|
parser.add_argument(
|
|
100
91
|
"--import-order",
|
|
101
|
-
default=
|
|
102
|
-
help=
|
|
92
|
+
default=None,
|
|
93
|
+
help=(
|
|
94
|
+
"Path or s3:// URI of DataImportOrder.txt. Default: auto "
|
|
95
|
+
"(the study's release bucket, then ./DataImportOrder.txt, "
|
|
96
|
+
"then derived from the dictionary)."
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"--dict-version",
|
|
101
|
+
default=None,
|
|
102
|
+
help=(
|
|
103
|
+
"Dictionary git tag to derive the node order from (verbatim, "
|
|
104
|
+
"e.g. v1.3.0). Default: the env's deployed dictionary. Only "
|
|
105
|
+
"used when the order is derived."
|
|
106
|
+
),
|
|
103
107
|
)
|
|
104
108
|
parser.add_argument(
|
|
105
109
|
"--node",
|
|
@@ -119,6 +123,12 @@ def main():
|
|
|
119
123
|
|
|
120
124
|
args = parser.parse_args()
|
|
121
125
|
|
|
126
|
+
if args.import_order and args.dict_version:
|
|
127
|
+
parser.error(
|
|
128
|
+
"--import-order names the exact file; --dict-version derives one "
|
|
129
|
+
"— pass only one."
|
|
130
|
+
)
|
|
131
|
+
|
|
122
132
|
# Env facts from SSM; the study registry from the marker or
|
|
123
133
|
# SSM /{project}/{env}/studies/* (legacy studies.yaml fallback until 5.0).
|
|
124
134
|
# Synthetic projects are not registered studies: --synthetic skips the
|
|
@@ -126,6 +136,7 @@ def main():
|
|
|
126
136
|
try:
|
|
127
137
|
env_cfg = g3dt_config.resolve_env(args.env)
|
|
128
138
|
if args.synthetic:
|
|
139
|
+
study_cfg = None
|
|
129
140
|
project_id = args.study
|
|
130
141
|
program_id = args.program_id
|
|
131
142
|
else:
|
|
@@ -162,12 +173,23 @@ def main():
|
|
|
162
173
|
BLUE, RESET, args.node,
|
|
163
174
|
)
|
|
164
175
|
else:
|
|
165
|
-
|
|
176
|
+
try:
|
|
177
|
+
nodes, source = resolve_import_order(
|
|
178
|
+
env_cfg=env_cfg,
|
|
179
|
+
session=session,
|
|
180
|
+
import_order=args.import_order,
|
|
181
|
+
dict_version=args.dict_version,
|
|
182
|
+
study_cfg=study_cfg,
|
|
183
|
+
)
|
|
184
|
+
except ImportOrderError as exc:
|
|
185
|
+
logger.error(str(exc))
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
nodes_to_delete = to_deletion_order(nodes, EXCLUDE_NODES)
|
|
166
188
|
logger.info(
|
|
167
|
-
"
|
|
189
|
+
"Import order source: %s (%s nodes, deletion order, "
|
|
168
190
|
"excluding %s)",
|
|
191
|
+
source,
|
|
169
192
|
len(nodes_to_delete),
|
|
170
|
-
args.import_order,
|
|
171
193
|
EXCLUDE_NODES,
|
|
172
194
|
)
|
|
173
195
|
|
|
@@ -24,6 +24,11 @@ Arguments:
|
|
|
24
24
|
--synthetic (optional) Registry-free synthetic-data mode: each study name is
|
|
25
25
|
the Gen3 project code itself (no SSM study registry lookup)
|
|
26
26
|
--program-id (optional) Gen3 program for --synthetic mode (default program1)
|
|
27
|
+
--import-order (optional) Path or s3:// URI of DataImportOrder.txt. Default:
|
|
28
|
+
auto — the study's release bucket (registered studies), then
|
|
29
|
+
./DataImportOrder.txt, then derived from the dictionary
|
|
30
|
+
--dict-version (optional) Dictionary git tag to derive the node order from
|
|
31
|
+
(default: the env's deployed dictionary)
|
|
27
32
|
|
|
28
33
|
Behaviour:
|
|
29
34
|
* version 'all' -> delete_all_metadata_for_project.py (deletes whole nodes)
|
|
@@ -52,6 +57,8 @@ VERSION=""
|
|
|
52
57
|
NODE=""
|
|
53
58
|
SYNTHETIC=0
|
|
54
59
|
PROGRAM_ID="program1"
|
|
60
|
+
IMPORT_ORDER=""
|
|
61
|
+
DICT_VERSION=""
|
|
55
62
|
|
|
56
63
|
while [[ $# -gt 0 ]]; do
|
|
57
64
|
case "$1" in
|
|
@@ -79,6 +86,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
79
86
|
PROGRAM_ID="$2"
|
|
80
87
|
shift 2
|
|
81
88
|
;;
|
|
89
|
+
--import-order)
|
|
90
|
+
IMPORT_ORDER="$2"
|
|
91
|
+
shift 2
|
|
92
|
+
;;
|
|
93
|
+
--dict-version)
|
|
94
|
+
DICT_VERSION="$2"
|
|
95
|
+
shift 2
|
|
96
|
+
;;
|
|
82
97
|
*)
|
|
83
98
|
echo "ERROR: Unknown argument: $1"
|
|
84
99
|
usage
|
|
@@ -157,17 +172,17 @@ for i in "${!STUDY_NAMES[@]}"; do
|
|
|
157
172
|
CMD=("${G3DT_PYTHON:-python3}" "${SCRIPT_DIR}/delete_all_metadata_for_project.py"
|
|
158
173
|
--study "$study" --env "$ENV")
|
|
159
174
|
[[ $SYNTHETIC -eq 1 ]] && CMD+=(--synthetic --program-id "$PROGRAM_ID")
|
|
160
|
-
[[ -n "$NODE" ]] && CMD+=(--node "$NODE")
|
|
161
175
|
elif [[ $SYNTHETIC -eq 1 ]]; then
|
|
162
176
|
CMD=("${G3DT_PYTHON:-python3}" "${SCRIPT_DIR}/delete_synth_metadata_by_version.py"
|
|
163
177
|
--study "$study" --env "$ENV" --version "$study_version"
|
|
164
178
|
--program-id "$PROGRAM_ID" --skip-if-empty)
|
|
165
|
-
[[ -n "$NODE" ]] && CMD+=(--node "$NODE")
|
|
166
179
|
else
|
|
167
180
|
CMD=("${G3DT_PYTHON:-python3}" "${SCRIPT_DIR}/delete_metadata_by_guid.py"
|
|
168
181
|
--study "$study" --env "$ENV" --version "$study_version" --skip-if-empty)
|
|
169
|
-
[[ -n "$NODE" ]] && CMD+=(--node "$NODE")
|
|
170
182
|
fi
|
|
183
|
+
[[ -n "$NODE" ]] && CMD+=(--node "$NODE")
|
|
184
|
+
[[ -n "$IMPORT_ORDER" ]] && CMD+=(--import-order "$IMPORT_ORDER")
|
|
185
|
+
[[ -n "$DICT_VERSION" ]] && CMD+=(--dict-version "$DICT_VERSION")
|
|
171
186
|
|
|
172
187
|
# Run the worker without aborting the loop on a non-zero exit.
|
|
173
188
|
set +e
|
|
@@ -13,6 +13,11 @@ from g3dt.upload.metadata_deleter import (
|
|
|
13
13
|
query_metadata_upload_guids,
|
|
14
14
|
delete_records_by_guid,
|
|
15
15
|
)
|
|
16
|
+
from g3dt.import_order import (
|
|
17
|
+
ImportOrderError,
|
|
18
|
+
resolve_import_order,
|
|
19
|
+
to_deletion_order,
|
|
20
|
+
)
|
|
16
21
|
|
|
17
22
|
# ANSI colour codes (matching metadata_submitter.py style)
|
|
18
23
|
GREEN = "\033[92m"
|
|
@@ -51,20 +56,6 @@ def setup_logger():
|
|
|
51
56
|
from g3dt import config as g3dt_config, resolver # noqa: E402
|
|
52
57
|
|
|
53
58
|
|
|
54
|
-
def load_import_order(import_order_path, exclude_nodes=None):
|
|
55
|
-
"""
|
|
56
|
-
Reads the DataImportOrder.txt file and returns the node list
|
|
57
|
-
in deletion order (reversed, with excluded nodes removed).
|
|
58
|
-
"""
|
|
59
|
-
if exclude_nodes is None:
|
|
60
|
-
exclude_nodes = EXCLUDE_NODES
|
|
61
|
-
with open(import_order_path, 'r', encoding='utf-8') as f:
|
|
62
|
-
nodes = [line.strip() for line in f if line.strip()]
|
|
63
|
-
nodes = [n for n in nodes if n not in exclude_nodes]
|
|
64
|
-
nodes.reverse()
|
|
65
|
-
return nodes
|
|
66
|
-
|
|
67
|
-
|
|
68
59
|
def main():
|
|
69
60
|
logger = setup_logger()
|
|
70
61
|
|
|
@@ -96,8 +87,21 @@ def main():
|
|
|
96
87
|
)
|
|
97
88
|
parser.add_argument(
|
|
98
89
|
"--import-order",
|
|
99
|
-
default=
|
|
100
|
-
help=
|
|
90
|
+
default=None,
|
|
91
|
+
help=(
|
|
92
|
+
"Path or s3:// URI of DataImportOrder.txt. Default: auto "
|
|
93
|
+
"(the study's release bucket, then ./DataImportOrder.txt, "
|
|
94
|
+
"then derived from the dictionary)."
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--dict-version",
|
|
99
|
+
default=None,
|
|
100
|
+
help=(
|
|
101
|
+
"Dictionary git tag to derive the node order from (verbatim, "
|
|
102
|
+
"e.g. v1.3.0). Default: the env's deployed dictionary. Only "
|
|
103
|
+
"used when the order is derived."
|
|
104
|
+
),
|
|
101
105
|
)
|
|
102
106
|
parser.add_argument(
|
|
103
107
|
"--node",
|
|
@@ -151,6 +155,12 @@ def main():
|
|
|
151
155
|
|
|
152
156
|
args = parser.parse_args()
|
|
153
157
|
|
|
158
|
+
if args.import_order and args.dict_version:
|
|
159
|
+
parser.error(
|
|
160
|
+
"--import-order names the exact file; --dict-version derives one "
|
|
161
|
+
"— pass only one."
|
|
162
|
+
)
|
|
163
|
+
|
|
154
164
|
if args.verbose:
|
|
155
165
|
logger.setLevel(logging.DEBUG)
|
|
156
166
|
|
|
@@ -225,12 +235,23 @@ def main():
|
|
|
225
235
|
BLUE, RESET, args.node,
|
|
226
236
|
)
|
|
227
237
|
else:
|
|
228
|
-
|
|
238
|
+
try:
|
|
239
|
+
nodes, source = resolve_import_order(
|
|
240
|
+
env_cfg=env_cfg,
|
|
241
|
+
session=session,
|
|
242
|
+
import_order=args.import_order,
|
|
243
|
+
dict_version=args.dict_version,
|
|
244
|
+
study_cfg=study_cfg,
|
|
245
|
+
)
|
|
246
|
+
except ImportOrderError as exc:
|
|
247
|
+
logger.error(str(exc))
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
nodes_to_delete = to_deletion_order(nodes, EXCLUDE_NODES)
|
|
229
250
|
logger.info(
|
|
230
|
-
"
|
|
251
|
+
"Import order source: %s (%s nodes, deletion order, "
|
|
231
252
|
"excluding %s)",
|
|
253
|
+
source,
|
|
232
254
|
len(nodes_to_delete),
|
|
233
|
-
args.import_order,
|
|
234
255
|
EXCLUDE_NODES,
|
|
235
256
|
)
|
|
236
257
|
|
|
@@ -26,6 +26,11 @@ from g3dt.upload.metadata_submitter import (
|
|
|
26
26
|
create_gen3_submission_class,
|
|
27
27
|
)
|
|
28
28
|
from g3dt.upload.metadata_deleter import delete_node_records_by_property
|
|
29
|
+
from g3dt.import_order import (
|
|
30
|
+
ImportOrderError,
|
|
31
|
+
resolve_import_order,
|
|
32
|
+
to_deletion_order,
|
|
33
|
+
)
|
|
29
34
|
|
|
30
35
|
# ANSI colour codes (matching metadata_submitter.py style)
|
|
31
36
|
GREEN = "\033[92m"
|
|
@@ -64,20 +69,6 @@ def setup_logger():
|
|
|
64
69
|
from g3dt import config as g3dt_config # noqa: E402
|
|
65
70
|
|
|
66
71
|
|
|
67
|
-
def load_import_order(import_order_path, exclude_nodes=None):
|
|
68
|
-
"""
|
|
69
|
-
Reads the DataImportOrder.txt file and returns the node list
|
|
70
|
-
in deletion order (reversed, with excluded nodes removed).
|
|
71
|
-
"""
|
|
72
|
-
if exclude_nodes is None:
|
|
73
|
-
exclude_nodes = EXCLUDE_NODES
|
|
74
|
-
with open(import_order_path, 'r', encoding='utf-8') as f:
|
|
75
|
-
nodes = [line.strip() for line in f if line.strip()]
|
|
76
|
-
nodes = [n for n in nodes if n not in exclude_nodes]
|
|
77
|
-
nodes.reverse()
|
|
78
|
-
return nodes
|
|
79
|
-
|
|
80
|
-
|
|
81
72
|
def main():
|
|
82
73
|
logger = setup_logger()
|
|
83
74
|
|
|
@@ -116,8 +107,21 @@ def main():
|
|
|
116
107
|
)
|
|
117
108
|
parser.add_argument(
|
|
118
109
|
"--import-order",
|
|
119
|
-
default=
|
|
120
|
-
help=
|
|
110
|
+
default=None,
|
|
111
|
+
help=(
|
|
112
|
+
"Path or s3:// URI of DataImportOrder.txt. Default: auto "
|
|
113
|
+
"(./DataImportOrder.txt, then derived from the dictionary — "
|
|
114
|
+
"synthetic projects have no release bucket)."
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"--dict-version",
|
|
119
|
+
default=None,
|
|
120
|
+
help=(
|
|
121
|
+
"Dictionary git tag to derive the node order from (verbatim, "
|
|
122
|
+
"e.g. v1.3.0). Default: the env's deployed dictionary. Only "
|
|
123
|
+
"used when the order is derived."
|
|
124
|
+
),
|
|
121
125
|
)
|
|
122
126
|
parser.add_argument(
|
|
123
127
|
"--node",
|
|
@@ -171,6 +175,12 @@ def main():
|
|
|
171
175
|
|
|
172
176
|
args = parser.parse_args()
|
|
173
177
|
|
|
178
|
+
if args.import_order and args.dict_version:
|
|
179
|
+
parser.error(
|
|
180
|
+
"--import-order names the exact file; --dict-version derives one "
|
|
181
|
+
"— pass only one."
|
|
182
|
+
)
|
|
183
|
+
|
|
174
184
|
if args.verbose:
|
|
175
185
|
logger.setLevel(logging.DEBUG)
|
|
176
186
|
|
|
@@ -215,12 +225,23 @@ def main():
|
|
|
215
225
|
BLUE, RESET, args.node,
|
|
216
226
|
)
|
|
217
227
|
else:
|
|
218
|
-
|
|
228
|
+
try:
|
|
229
|
+
nodes, source = resolve_import_order(
|
|
230
|
+
env_cfg=env_cfg,
|
|
231
|
+
session=session,
|
|
232
|
+
import_order=args.import_order,
|
|
233
|
+
dict_version=args.dict_version,
|
|
234
|
+
study_cfg=None, # synthetic projects are never registered
|
|
235
|
+
)
|
|
236
|
+
except ImportOrderError as exc:
|
|
237
|
+
logger.error(str(exc))
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
nodes_to_delete = to_deletion_order(nodes, EXCLUDE_NODES)
|
|
219
240
|
logger.info(
|
|
220
|
-
"
|
|
241
|
+
"Import order source: %s (%s nodes, deletion order, "
|
|
221
242
|
"excluding %s)",
|
|
243
|
+
source,
|
|
222
244
|
len(nodes_to_delete),
|
|
223
|
-
args.import_order,
|
|
224
245
|
EXCLUDE_NODES,
|
|
225
246
|
)
|
|
226
247
|
|
|
@@ -13,7 +13,6 @@ import pytz
|
|
|
13
13
|
import base64
|
|
14
14
|
import numpy as np
|
|
15
15
|
from decimal import Decimal
|
|
16
|
-
from gen3_validator.dict import DataDictionary
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger(__name__)
|
|
19
18
|
|
|
@@ -916,14 +915,6 @@ def write_gold_json_to_s3(
|
|
|
916
915
|
logger.info(f"Object created at s3://{s3_bucket}/{s3_object_key}")
|
|
917
916
|
|
|
918
917
|
|
|
919
|
-
def construct_data_import_order(s3_uri) -> list:
|
|
920
|
-
from g3dt.validate.validate import load_schema_from_s3_uri
|
|
921
|
-
schema_dict = load_schema_from_s3_uri(s3_uri)
|
|
922
|
-
dd = DataDictionary(schema_dict)
|
|
923
|
-
dd.schema = schema_dict
|
|
924
|
-
dd.calculate_node_order()
|
|
925
|
-
return dd.node_order
|
|
926
|
-
|
|
927
918
|
def write_release_jsons_to_s3(s3_bucket, release_id, study_id, table_name, json_data,
|
|
928
919
|
s3_client=None, key_prefix="release_jsons"):
|
|
929
920
|
"""
|
|
File without changes
|
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/__init__.py
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/aws_quiet.py
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/dispatch.py
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/helptext.py
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/cli/_internal/registry.py
RENAMED
|
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
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/indexd/indexd_registrar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/dictionary/deploy_dd.sh
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/dictionary/pull_dict.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/services/k8s_ops/login_to_pod.sh
RENAMED
|
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
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/metadata_deleter.py
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/metadata_submitter.py
RENAMED
|
File without changes
|
{gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-4.3.0}/src/g3dt/upload/upload_synthdata_s3.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|