variantgrid-api 1.3.0__tar.gz → 1.3.2__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.
- {variantgrid_api-1.3.0/src/variantgrid_api.egg-info → variantgrid_api-1.3.2}/PKG-INFO +16 -32
- variantgrid_api-1.3.2/README.md +54 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/pyproject.toml +4 -1
- variantgrid_api-1.3.2/src/variantgrid_api/cli.py +153 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2/src/variantgrid_api.egg-info}/PKG-INFO +16 -32
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/SOURCES.txt +3 -0
- variantgrid_api-1.3.2/src/variantgrid_api.egg-info/entry_points.txt +2 -0
- variantgrid_api-1.3.2/tests/test_cli.py +127 -0
- variantgrid_api-1.3.0/README.md +0 -70
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/LICENSE +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/setup.cfg +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api/api_client.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api/data_models.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api/mock_variantgrid_api.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/dependency_links.txt +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/requires.txt +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/top_level.txt +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_api_client.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_api_client_annotation.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_api_client_bulk.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_api_client_validation.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_data_models.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_mock_variantgrid_api.py +0 -0
- {variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/tests/test_sequencer_model_from_name.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: variantgrid_api
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: A Python API client for VariantGrid
|
|
5
5
|
Author-email: Dave Lawrence <davmlaw@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -68,41 +68,25 @@ result = api.create_enrichment_kit(enrichment_kit)
|
|
|
68
68
|
|
|
69
69
|
## Annotate a VCF and download it back
|
|
70
70
|
|
|
71
|
-
Upload a VCF,
|
|
72
|
-
|
|
71
|
+
Upload a VCF, have VariantGrid import + annotate any novel variants, then download the cohort-level annotated
|
|
72
|
+
export (all samples, single-sample VCFs included). Annotation can take a while, so the quickest way in is the
|
|
73
|
+
`vg_api` command line tool: the first call uploads, and running the same command again downloads the result
|
|
74
|
+
once it's ready.
|
|
73
75
|
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# export_type is "vcf" (gzipped *.vcf.gz) or "csv" (zipped *.csv.zip)
|
|
80
|
-
path = api.annotate_vcf("input.vcf", export_type="vcf", dest_path="/data/results/")
|
|
81
|
-
print(f"Annotated VCF written to {path}")
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Or drive each step yourself:
|
|
76
|
+
```console
|
|
77
|
+
$ export VARIANTGRID_API_TOKEN=YOUR_API_TOKEN
|
|
78
|
+
$ vg_api annotate_vcf input.vcf.gz -o results/
|
|
79
|
+
Uploaded input.vcf.gz (id=13256).
|
|
80
|
+
Annotating input.vcf.gz - run the same command again later to download.
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
upload = api.upload_file("input.vcf", path=None)
|
|
89
|
-
uploaded_file_id = upload["uploaded_file_id"] # or upload["sha256_hash"]
|
|
90
|
-
api.wait_for_annotation(uploaded_file_id, timeout=3600, poll_interval=10) # raises on error/timeout
|
|
91
|
-
path = api.download_annotated(uploaded_file_id, export_type="csv", dest_path="/data/results/")
|
|
82
|
+
$ vg_api annotate_vcf input.vcf.gz -o results/ # once it's done
|
|
83
|
+
Annotated vcf written to results/input.vcf_annotated_v254_GRCh38.vcf.gz
|
|
92
84
|
```
|
|
93
85
|
|
|
94
|
-
`
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Notes:
|
|
100
|
-
|
|
101
|
-
- The `annotate_vcf` wrapper already uploads with `path=None`. Only pass a `path` to `upload_file` for SeqAuto
|
|
102
|
-
uploads that link to a registered `JointCalledVCF` / `SingleSampleVCF` (that is what `path` is for - it is
|
|
103
|
-
ignored on non-SeqAuto deployments).
|
|
104
|
-
- Downloads require the target deployment to have the cohort export analysis templates configured
|
|
105
|
-
(`ANALYSIS_TEMPLATES_AUTO_COHORT_EXPORT`) - otherwise a clear error is returned.
|
|
86
|
+
From Python it's `upload_file()` / `poll_upload_status()` / `download_annotated()`, or the blocking
|
|
87
|
+
`annotate_vcf()` one-liner. See
|
|
88
|
+
**[Annotate a VCF](https://github.com/SACGF/variantgrid_api/wiki/Annotate-a-VCF)** on the wiki for batches, the
|
|
89
|
+
submit-now/download-later pattern, and all the options.
|
|
106
90
|
|
|
107
91
|
## Testing
|
|
108
92
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# variantgrid_api
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/variantgrid_api/) [](https://pypi.org/project/variantgrid_api/)
|
|
4
|
+
|
|
5
|
+
Python API client for [VariantGrid](https://github.com/SACGF/variantgrid) Open source Variant database and analysis platform
|
|
6
|
+
|
|
7
|
+
See [changelog](https://github.com/SACGF/variantgrid_api/blob/main/CHANGELOG.md)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
python3 -m pip install variantgrid_api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
from variantgrid_api.api_client import VariantGridAPI
|
|
19
|
+
from variantgrid_api.data_models import EnrichmentKit
|
|
20
|
+
|
|
21
|
+
api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
|
|
22
|
+
enrichment_kit = EnrichmentKit(name="idt_haem", version=1)
|
|
23
|
+
result = api.create_enrichment_kit(enrichment_kit)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Annotate a VCF and download it back
|
|
27
|
+
|
|
28
|
+
Upload a VCF, have VariantGrid import + annotate any novel variants, then download the cohort-level annotated
|
|
29
|
+
export (all samples, single-sample VCFs included). Annotation can take a while, so the quickest way in is the
|
|
30
|
+
`vg_api` command line tool: the first call uploads, and running the same command again downloads the result
|
|
31
|
+
once it's ready.
|
|
32
|
+
|
|
33
|
+
```console
|
|
34
|
+
$ export VARIANTGRID_API_TOKEN=YOUR_API_TOKEN
|
|
35
|
+
$ vg_api annotate_vcf input.vcf.gz -o results/
|
|
36
|
+
Uploaded input.vcf.gz (id=13256).
|
|
37
|
+
Annotating input.vcf.gz - run the same command again later to download.
|
|
38
|
+
|
|
39
|
+
$ vg_api annotate_vcf input.vcf.gz -o results/ # once it's done
|
|
40
|
+
Annotated vcf written to results/input.vcf_annotated_v254_GRCh38.vcf.gz
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
From Python it's `upload_file()` / `poll_upload_status()` / `download_annotated()`, or the blocking
|
|
44
|
+
`annotate_vcf()` one-liner. See
|
|
45
|
+
**[Annotate a VCF](https://github.com/SACGF/variantgrid_api/wiki/Annotate-a-VCF)** on the wiki for batches, the
|
|
46
|
+
submit-now/download-later pattern, and all the options.
|
|
47
|
+
|
|
48
|
+
## Testing
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
# Install required testing packages
|
|
52
|
+
python3 -m pip install -e ".[test]"
|
|
53
|
+
python3 -m pytest --cov=variantgrid_api
|
|
54
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "variantgrid_api"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.2"
|
|
8
8
|
description = "A Python API client for VariantGrid"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Dave Lawrence", email = "davmlaw@gmail.com" }
|
|
@@ -23,6 +23,9 @@ dependencies = [
|
|
|
23
23
|
"dataclasses-json",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
+
[project.scripts]
|
|
27
|
+
vg_api = "variantgrid_api.cli:main"
|
|
28
|
+
|
|
26
29
|
[project.optional-dependencies]
|
|
27
30
|
test = ["pytest>=8", "pytest-cov>=5", "responses"]
|
|
28
31
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Command line interface for the VariantGrid API client.
|
|
2
|
+
|
|
3
|
+
Currently provides `vg_api annotate_vcf <file>`: a stateless upload/download helper. Because the server
|
|
4
|
+
dedups uploads on the file's SHA-256, the VCF file itself is the receipt - the first call uploads it, and
|
|
5
|
+
running the same command again downloads the annotated result (or reports that it isn't ready yet). No local
|
|
6
|
+
state (upload ids, pending.json, ...) is kept, so you can poll from any machine that has the VCF.
|
|
7
|
+
"""
|
|
8
|
+
import argparse
|
|
9
|
+
import hashlib
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
from variantgrid_api.api_client import VariantGridAPI, AnnotationError
|
|
17
|
+
|
|
18
|
+
DEFAULT_SERVER = "https://variantgrid.com"
|
|
19
|
+
|
|
20
|
+
EXIT_OK = 0 # annotated file downloaded
|
|
21
|
+
EXIT_ERROR = 1 # bad input / auth / server or annotation error
|
|
22
|
+
EXIT_PENDING = 3 # uploaded just now, or still annotating - come back later
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _sha256(path):
|
|
26
|
+
"""Content hash the server dedups on - identical to `sha256sum <file>`."""
|
|
27
|
+
h = hashlib.sha256()
|
|
28
|
+
with open(path, "rb") as f:
|
|
29
|
+
for chunk in iter(lambda: f.read(1 << 20), b""):
|
|
30
|
+
h.update(chunk)
|
|
31
|
+
return h.hexdigest()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _build_api(args):
|
|
35
|
+
token = args.token or os.environ.get("VARIANTGRID_API_TOKEN")
|
|
36
|
+
if not token:
|
|
37
|
+
raise SystemExit("No API token - pass --token or set VARIANTGRID_API_TOKEN")
|
|
38
|
+
server = args.server or os.environ.get("VARIANTGRID_API_SERVER") or DEFAULT_SERVER
|
|
39
|
+
# Own logger so we can silence the expected 404 when a file hasn't been uploaded yet.
|
|
40
|
+
logger = logging.getLogger("vg_api.cli")
|
|
41
|
+
return VariantGridAPI(server=server, api_token=token, logger=logger), logger
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _is_not_found(exc):
|
|
45
|
+
resp = getattr(exc, "response", None)
|
|
46
|
+
return resp is not None and resp.status_code == 404
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _probe_status(api, logger, sha256):
|
|
50
|
+
"""Poll status by content hash. Returns the status dict, or None if the server has never
|
|
51
|
+
seen this file (404) - a 404 here just means "not uploaded yet", so we silence its logging."""
|
|
52
|
+
prev_level = logger.level
|
|
53
|
+
logger.setLevel(logging.CRITICAL)
|
|
54
|
+
try:
|
|
55
|
+
return api.poll_upload_status(sha256=sha256)
|
|
56
|
+
except requests.HTTPError as e:
|
|
57
|
+
if _is_not_found(e):
|
|
58
|
+
return None
|
|
59
|
+
raise
|
|
60
|
+
finally:
|
|
61
|
+
logger.setLevel(prev_level)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _download(api, args, sha256):
|
|
65
|
+
path = api.download_annotated(sha256=sha256, export_type=args.export_type, dest_path=args.dest)
|
|
66
|
+
print(f"Annotated {args.export_type} written to {path}")
|
|
67
|
+
return EXIT_OK
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def annotate_vcf_cmd(args):
|
|
71
|
+
if not os.path.isfile(args.vcf):
|
|
72
|
+
print(f"No such file: {args.vcf}", file=sys.stderr)
|
|
73
|
+
return EXIT_ERROR
|
|
74
|
+
|
|
75
|
+
api, logger = _build_api(args)
|
|
76
|
+
name = os.path.basename(args.vcf)
|
|
77
|
+
sha256 = _sha256(args.vcf)
|
|
78
|
+
|
|
79
|
+
status = _probe_status(api, logger, sha256)
|
|
80
|
+
if status is None:
|
|
81
|
+
# We've never uploaded this file - do it now.
|
|
82
|
+
up = api.upload_file(args.vcf, path=None)
|
|
83
|
+
print(f"Uploaded {name} (id={up['uploaded_file_id']}).")
|
|
84
|
+
|
|
85
|
+
if args.wait:
|
|
86
|
+
# Poll the server ourselves until annotation finishes (can take hours), then download.
|
|
87
|
+
if status is None or not status.get("annotation_complete"):
|
|
88
|
+
print("Waiting for annotation to finish - this can take a while...")
|
|
89
|
+
api.wait_for_annotation(sha256=sha256, timeout=args.timeout, poll_interval=args.poll_interval)
|
|
90
|
+
return _download(api, args, sha256)
|
|
91
|
+
|
|
92
|
+
# Single-shot: report where it's at, download only if it's ready.
|
|
93
|
+
if status is None:
|
|
94
|
+
print(f"Annotating {name} - run the same command again later to download.")
|
|
95
|
+
return EXIT_PENDING
|
|
96
|
+
if err := status.get("error"):
|
|
97
|
+
print(f"{name}: annotation error - {err}", file=sys.stderr)
|
|
98
|
+
return EXIT_ERROR
|
|
99
|
+
if status.get("annotation_complete"):
|
|
100
|
+
return _download(api, args, sha256)
|
|
101
|
+
|
|
102
|
+
progress = status.get("progress_percent")
|
|
103
|
+
suffix = f" (progress {progress}%)" if progress is not None else ""
|
|
104
|
+
print(f"{name}: not ready yet{suffix} - run the same command again later.")
|
|
105
|
+
return EXIT_PENDING
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def build_parser():
|
|
109
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
110
|
+
common.add_argument("--server",
|
|
111
|
+
help="VariantGrid server URL (default: $VARIANTGRID_API_SERVER or "
|
|
112
|
+
f"{DEFAULT_SERVER})")
|
|
113
|
+
common.add_argument("--token", help="API token (default: $VARIANTGRID_API_TOKEN)")
|
|
114
|
+
|
|
115
|
+
parser = argparse.ArgumentParser(prog="vg_api", description="VariantGrid API command line tool")
|
|
116
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
117
|
+
|
|
118
|
+
p = sub.add_parser("annotate_vcf", parents=[common],
|
|
119
|
+
help="Upload a VCF for annotation; run again to download it once ready",
|
|
120
|
+
description="Upload a VCF for annotation. Because uploads are keyed on the file's "
|
|
121
|
+
"SHA-256, running the same command again downloads the annotated result "
|
|
122
|
+
"when ready, or reports that it isn't ready yet - no local state is kept.")
|
|
123
|
+
p.add_argument("vcf", help="Path to the VCF (.vcf / .vcf.gz) to annotate")
|
|
124
|
+
p.add_argument("--export-type", choices=("vcf", "csv"), default="vcf",
|
|
125
|
+
help="Download format (default: vcf)")
|
|
126
|
+
p.add_argument("-o", "--dest", default=".",
|
|
127
|
+
help="Destination directory or file for the download (default: current directory)")
|
|
128
|
+
p.add_argument("--wait", action="store_true",
|
|
129
|
+
help="Poll until annotation finishes and download it, instead of returning immediately "
|
|
130
|
+
"(can take hours)")
|
|
131
|
+
p.add_argument("--poll-interval", type=float, default=10,
|
|
132
|
+
help="Seconds between status polls when using --wait (default: 10)")
|
|
133
|
+
p.add_argument("--timeout", type=float, default=86400,
|
|
134
|
+
help="Give up after this many seconds when using --wait (default: 86400 = 24h)")
|
|
135
|
+
p.set_defaults(func=annotate_vcf_cmd)
|
|
136
|
+
return parser
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def main(argv=None):
|
|
140
|
+
parser = build_parser()
|
|
141
|
+
args = parser.parse_args(argv)
|
|
142
|
+
try:
|
|
143
|
+
return args.func(args)
|
|
144
|
+
except AnnotationError as e:
|
|
145
|
+
print(f"Annotation error: {e}", file=sys.stderr)
|
|
146
|
+
return EXIT_ERROR
|
|
147
|
+
except requests.HTTPError as e:
|
|
148
|
+
print(f"HTTP error: {e}", file=sys.stderr)
|
|
149
|
+
return EXIT_ERROR
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: variantgrid_api
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: A Python API client for VariantGrid
|
|
5
5
|
Author-email: Dave Lawrence <davmlaw@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -68,41 +68,25 @@ result = api.create_enrichment_kit(enrichment_kit)
|
|
|
68
68
|
|
|
69
69
|
## Annotate a VCF and download it back
|
|
70
70
|
|
|
71
|
-
Upload a VCF,
|
|
72
|
-
|
|
71
|
+
Upload a VCF, have VariantGrid import + annotate any novel variants, then download the cohort-level annotated
|
|
72
|
+
export (all samples, single-sample VCFs included). Annotation can take a while, so the quickest way in is the
|
|
73
|
+
`vg_api` command line tool: the first call uploads, and running the same command again downloads the result
|
|
74
|
+
once it's ready.
|
|
73
75
|
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# export_type is "vcf" (gzipped *.vcf.gz) or "csv" (zipped *.csv.zip)
|
|
80
|
-
path = api.annotate_vcf("input.vcf", export_type="vcf", dest_path="/data/results/")
|
|
81
|
-
print(f"Annotated VCF written to {path}")
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Or drive each step yourself:
|
|
76
|
+
```console
|
|
77
|
+
$ export VARIANTGRID_API_TOKEN=YOUR_API_TOKEN
|
|
78
|
+
$ vg_api annotate_vcf input.vcf.gz -o results/
|
|
79
|
+
Uploaded input.vcf.gz (id=13256).
|
|
80
|
+
Annotating input.vcf.gz - run the same command again later to download.
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
upload = api.upload_file("input.vcf", path=None)
|
|
89
|
-
uploaded_file_id = upload["uploaded_file_id"] # or upload["sha256_hash"]
|
|
90
|
-
api.wait_for_annotation(uploaded_file_id, timeout=3600, poll_interval=10) # raises on error/timeout
|
|
91
|
-
path = api.download_annotated(uploaded_file_id, export_type="csv", dest_path="/data/results/")
|
|
82
|
+
$ vg_api annotate_vcf input.vcf.gz -o results/ # once it's done
|
|
83
|
+
Annotated vcf written to results/input.vcf_annotated_v254_GRCh38.vcf.gz
|
|
92
84
|
```
|
|
93
85
|
|
|
94
|
-
`
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Notes:
|
|
100
|
-
|
|
101
|
-
- The `annotate_vcf` wrapper already uploads with `path=None`. Only pass a `path` to `upload_file` for SeqAuto
|
|
102
|
-
uploads that link to a registered `JointCalledVCF` / `SingleSampleVCF` (that is what `path` is for - it is
|
|
103
|
-
ignored on non-SeqAuto deployments).
|
|
104
|
-
- Downloads require the target deployment to have the cohort export analysis templates configured
|
|
105
|
-
(`ANALYSIS_TEMPLATES_AUTO_COHORT_EXPORT`) - otherwise a clear error is returned.
|
|
86
|
+
From Python it's `upload_file()` / `poll_upload_status()` / `download_annotated()`, or the blocking
|
|
87
|
+
`annotate_vcf()` one-liner. See
|
|
88
|
+
**[Annotate a VCF](https://github.com/SACGF/variantgrid_api/wiki/Annotate-a-VCF)** on the wiki for batches, the
|
|
89
|
+
submit-now/download-later pattern, and all the options.
|
|
106
90
|
|
|
107
91
|
## Testing
|
|
108
92
|
|
|
@@ -2,17 +2,20 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/variantgrid_api/api_client.py
|
|
5
|
+
src/variantgrid_api/cli.py
|
|
5
6
|
src/variantgrid_api/data_models.py
|
|
6
7
|
src/variantgrid_api/mock_variantgrid_api.py
|
|
7
8
|
src/variantgrid_api.egg-info/PKG-INFO
|
|
8
9
|
src/variantgrid_api.egg-info/SOURCES.txt
|
|
9
10
|
src/variantgrid_api.egg-info/dependency_links.txt
|
|
11
|
+
src/variantgrid_api.egg-info/entry_points.txt
|
|
10
12
|
src/variantgrid_api.egg-info/requires.txt
|
|
11
13
|
src/variantgrid_api.egg-info/top_level.txt
|
|
12
14
|
tests/test_api_client.py
|
|
13
15
|
tests/test_api_client_annotation.py
|
|
14
16
|
tests/test_api_client_bulk.py
|
|
15
17
|
tests/test_api_client_validation.py
|
|
18
|
+
tests/test_cli.py
|
|
16
19
|
tests/test_data_models.py
|
|
17
20
|
tests/test_mock_variantgrid_api.py
|
|
18
21
|
tests/test_sequencer_model_from_name.py
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import responses
|
|
5
|
+
|
|
6
|
+
from variantgrid_api import cli
|
|
7
|
+
|
|
8
|
+
SERVER = "https://vg.example.org"
|
|
9
|
+
|
|
10
|
+
STATUS_RE = re.compile(rf"{SERVER}/upload/api/v1/upload_status/sha256/[0-9a-f]+$")
|
|
11
|
+
UPLOAD_URL = f"{SERVER}/upload/api/v1/file_upload"
|
|
12
|
+
DOWNLOAD_RE = re.compile(rf"{SERVER}/upload/api/v1/download/sha256/[0-9a-f]+/(vcf|csv)$")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def vcf(tmp_path):
|
|
17
|
+
p = tmp_path / "input.vcf"
|
|
18
|
+
p.write_text("##fileformat=VCFv4.2\n")
|
|
19
|
+
return str(p)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _argv(vcf, *extra):
|
|
23
|
+
return ["annotate_vcf", vcf, "--server", SERVER, "--token", "T", *extra]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@responses.activate
|
|
27
|
+
def test_first_call_uploads(vcf):
|
|
28
|
+
"""Unknown file (status 404) -> upload it and report pending."""
|
|
29
|
+
responses.add(responses.GET, STATUS_RE, json={"detail": "not found"}, status=404)
|
|
30
|
+
responses.add(responses.POST, UPLOAD_URL, json={"uploaded_file_id": 42}, status=200)
|
|
31
|
+
|
|
32
|
+
rc = cli.main(_argv(vcf))
|
|
33
|
+
|
|
34
|
+
assert rc == cli.EXIT_PENDING
|
|
35
|
+
assert any(c.request.method == "POST" and c.request.url.startswith(UPLOAD_URL)
|
|
36
|
+
for c in responses.calls)
|
|
37
|
+
# Ad-hoc upload must omit the SeqAuto path hint
|
|
38
|
+
upload_call = next(c for c in responses.calls if c.request.url.startswith(UPLOAD_URL))
|
|
39
|
+
assert "path=" not in upload_call.request.url
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@responses.activate
|
|
43
|
+
def test_second_call_downloads_when_ready(vcf, tmp_path):
|
|
44
|
+
responses.add(responses.GET, STATUS_RE, json={"annotation_complete": True, "error": None}, status=200)
|
|
45
|
+
responses.add(responses.GET, DOWNLOAD_RE, body=b"vcfdata", status=200,
|
|
46
|
+
headers={"Content-Disposition": 'attachment; filename="out.vcf.gz"'})
|
|
47
|
+
|
|
48
|
+
rc = cli.main(_argv(vcf, "-o", str(tmp_path)))
|
|
49
|
+
|
|
50
|
+
assert rc == cli.EXIT_OK
|
|
51
|
+
assert (tmp_path / "out.vcf.gz").read_bytes() == b"vcfdata"
|
|
52
|
+
# It must NOT re-upload when the file is already known
|
|
53
|
+
assert not any(c.request.method == "POST" for c in responses.calls)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@responses.activate
|
|
57
|
+
def test_not_ready_reports_pending(vcf):
|
|
58
|
+
responses.add(responses.GET, STATUS_RE,
|
|
59
|
+
json={"annotation_complete": False, "error": None, "progress_percent": 40}, status=200)
|
|
60
|
+
|
|
61
|
+
rc = cli.main(_argv(vcf))
|
|
62
|
+
|
|
63
|
+
assert rc == cli.EXIT_PENDING
|
|
64
|
+
assert not any(re.match(DOWNLOAD_RE, c.request.url) for c in responses.calls)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@responses.activate
|
|
68
|
+
def test_server_error_status_is_error(vcf):
|
|
69
|
+
responses.add(responses.GET, STATUS_RE,
|
|
70
|
+
json={"annotation_complete": False, "error": "Pipeline failed"}, status=200)
|
|
71
|
+
|
|
72
|
+
rc = cli.main(_argv(vcf))
|
|
73
|
+
assert rc == cli.EXIT_ERROR
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@responses.activate
|
|
77
|
+
def test_csv_export_type(vcf, tmp_path):
|
|
78
|
+
responses.add(responses.GET, STATUS_RE, json={"annotation_complete": True, "error": None}, status=200)
|
|
79
|
+
responses.add(responses.GET, DOWNLOAD_RE, body=b"zip", status=200,
|
|
80
|
+
headers={"Content-Disposition": 'attachment; filename="out.csv.zip"'})
|
|
81
|
+
|
|
82
|
+
rc = cli.main(_argv(vcf, "--export-type", "csv", "-o", str(tmp_path)))
|
|
83
|
+
|
|
84
|
+
assert rc == cli.EXIT_OK
|
|
85
|
+
assert (tmp_path / "out.csv.zip").read_bytes() == b"zip"
|
|
86
|
+
download_call = next(c for c in responses.calls if re.match(DOWNLOAD_RE, c.request.url))
|
|
87
|
+
assert download_call.request.url.endswith("/csv")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@responses.activate
|
|
91
|
+
def test_wait_uploads_polls_then_downloads(vcf, tmp_path):
|
|
92
|
+
"""--wait on a new file: upload, poll until complete, then download - no external loop."""
|
|
93
|
+
responses.add(responses.GET, STATUS_RE, json={"detail": "not found"}, status=404) # probe
|
|
94
|
+
responses.add(responses.POST, UPLOAD_URL, json={"uploaded_file_id": 7}, status=200)
|
|
95
|
+
responses.add(responses.GET, STATUS_RE, json={"annotation_complete": False, "error": None}, status=200)
|
|
96
|
+
responses.add(responses.GET, STATUS_RE, json={"annotation_complete": True, "error": None}, status=200)
|
|
97
|
+
responses.add(responses.GET, DOWNLOAD_RE, body=b"data", status=200,
|
|
98
|
+
headers={"Content-Disposition": 'attachment; filename="out.vcf.gz"'})
|
|
99
|
+
|
|
100
|
+
rc = cli.main(_argv(vcf, "--wait", "-o", str(tmp_path), "--poll-interval", "0"))
|
|
101
|
+
|
|
102
|
+
assert rc == cli.EXIT_OK
|
|
103
|
+
assert (tmp_path / "out.vcf.gz").read_bytes() == b"data"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@responses.activate
|
|
107
|
+
def test_wait_downloads_without_reupload_when_already_known(vcf, tmp_path):
|
|
108
|
+
"""--wait on an already-uploaded, already-complete file must not re-upload."""
|
|
109
|
+
responses.add(responses.GET, STATUS_RE, json={"annotation_complete": True, "error": None}, status=200)
|
|
110
|
+
responses.add(responses.GET, DOWNLOAD_RE, body=b"data", status=200,
|
|
111
|
+
headers={"Content-Disposition": 'attachment; filename="out.vcf.gz"'})
|
|
112
|
+
|
|
113
|
+
rc = cli.main(_argv(vcf, "--wait", "-o", str(tmp_path), "--poll-interval", "0"))
|
|
114
|
+
|
|
115
|
+
assert rc == cli.EXIT_OK
|
|
116
|
+
assert not any(c.request.method == "POST" for c in responses.calls)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_missing_file_is_error(tmp_path):
|
|
120
|
+
rc = cli.main(_argv(str(tmp_path / "nope.vcf")))
|
|
121
|
+
assert rc == cli.EXIT_ERROR
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_missing_token_raises(vcf, monkeypatch):
|
|
125
|
+
monkeypatch.delenv("VARIANTGRID_API_TOKEN", raising=False)
|
|
126
|
+
with pytest.raises(SystemExit):
|
|
127
|
+
cli.main(["annotate_vcf", vcf, "--server", SERVER])
|
variantgrid_api-1.3.0/README.md
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# variantgrid_api
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/variantgrid_api/) [](https://pypi.org/project/variantgrid_api/)
|
|
4
|
-
|
|
5
|
-
Python API client for [VariantGrid](https://github.com/SACGF/variantgrid) Open source Variant database and analysis platform
|
|
6
|
-
|
|
7
|
-
See [changelog](https://github.com/SACGF/variantgrid_api/blob/main/CHANGELOG.md)
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
python3 -m pip install variantgrid_api
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Example
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
from variantgrid_api.api_client import VariantGridAPI
|
|
19
|
-
from variantgrid_api.data_models import EnrichmentKit
|
|
20
|
-
|
|
21
|
-
api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
|
|
22
|
-
enrichment_kit = EnrichmentKit(name="idt_haem", version=1)
|
|
23
|
-
result = api.create_enrichment_kit(enrichment_kit)
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Annotate a VCF and download it back
|
|
27
|
-
|
|
28
|
-
Upload a VCF, wait for VariantGrid to import + annotate any novel variants, then download the
|
|
29
|
-
cohort-level annotated export (all samples, single-sample VCFs included). The whole flow is a one-liner:
|
|
30
|
-
|
|
31
|
-
```python
|
|
32
|
-
from variantgrid_api.api_client import VariantGridAPI
|
|
33
|
-
|
|
34
|
-
api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
|
|
35
|
-
|
|
36
|
-
# export_type is "vcf" (gzipped *.vcf.gz) or "csv" (zipped *.csv.zip)
|
|
37
|
-
path = api.annotate_vcf("input.vcf", export_type="vcf", dest_path="/data/results/")
|
|
38
|
-
print(f"Annotated VCF written to {path}")
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Or drive each step yourself:
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
# For ad-hoc uploads pass path=None so the upload isn't treated as a SeqAuto backend-link hint
|
|
45
|
-
upload = api.upload_file("input.vcf", path=None)
|
|
46
|
-
uploaded_file_id = upload["uploaded_file_id"] # or upload["sha256_hash"]
|
|
47
|
-
api.wait_for_annotation(uploaded_file_id, timeout=3600, poll_interval=10) # raises on error/timeout
|
|
48
|
-
path = api.download_annotated(uploaded_file_id, export_type="csv", dest_path="/data/results/")
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
`poll_upload_status(uploaded_file_id)` returns the raw status dict (including `annotation_complete`,
|
|
52
|
-
`progress_percent`, `error`, `vcf_id`, `samples`, ...) if you want to inspect progress directly. All of
|
|
53
|
-
these accept `sha256=<hash>` instead of `uploaded_file_id` - the server dedups on the content hash, so it is
|
|
54
|
-
stable across machines and useful if you didn't retain the id.
|
|
55
|
-
|
|
56
|
-
Notes:
|
|
57
|
-
|
|
58
|
-
- The `annotate_vcf` wrapper already uploads with `path=None`. Only pass a `path` to `upload_file` for SeqAuto
|
|
59
|
-
uploads that link to a registered `JointCalledVCF` / `SingleSampleVCF` (that is what `path` is for - it is
|
|
60
|
-
ignored on non-SeqAuto deployments).
|
|
61
|
-
- Downloads require the target deployment to have the cohort export analysis templates configured
|
|
62
|
-
(`ANALYSIS_TEMPLATES_AUTO_COHORT_EXPORT`) - otherwise a clear error is returned.
|
|
63
|
-
|
|
64
|
-
## Testing
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
# Install required testing packages
|
|
68
|
-
python3 -m pip install -e ".[test]"
|
|
69
|
-
python3 -m pytest --cov=variantgrid_api
|
|
70
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{variantgrid_api-1.3.0 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/dependency_links.txt
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
|