variantgrid-api 1.3.1__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.
Files changed (23) hide show
  1. {variantgrid_api-1.3.1/src/variantgrid_api.egg-info → variantgrid_api-1.3.2}/PKG-INFO +16 -14
  2. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/README.md +17 -15
  3. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/pyproject.toml +1 -1
  4. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api/cli.py +42 -26
  5. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2/src/variantgrid_api.egg-info}/PKG-INFO +16 -14
  6. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_cli.py +29 -0
  7. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/LICENSE +0 -0
  8. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/setup.cfg +0 -0
  9. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api/api_client.py +0 -0
  10. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api/data_models.py +0 -0
  11. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api/mock_variantgrid_api.py +0 -0
  12. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/SOURCES.txt +0 -0
  13. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/dependency_links.txt +0 -0
  14. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/entry_points.txt +0 -0
  15. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/requires.txt +0 -0
  16. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/src/variantgrid_api.egg-info/top_level.txt +0 -0
  17. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_api_client.py +0 -0
  18. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_api_client_annotation.py +0 -0
  19. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_api_client_bulk.py +0 -0
  20. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_api_client_validation.py +0 -0
  21. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_data_models.py +0 -0
  22. {variantgrid_api-1.3.1 → variantgrid_api-1.3.2}/tests/test_mock_variantgrid_api.py +0 -0
  23. {variantgrid_api-1.3.1 → 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.1
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,23 +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, wait for VariantGrid to import + annotate any novel variants, then download the
72
- cohort-level annotated export (all samples, single-sample VCFs included). The whole flow is a one-liner:
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
- ```python
75
- from variantgrid_api.api_client import VariantGridAPI
76
-
77
- api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
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.
78
81
 
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
+ $ 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
82
84
  ```
83
85
 
84
- There's also a `vg_api annotate_vcf` command line tool (first call uploads, run it again to download once
85
- ready), a step-by-step form (`upload_file` → `wait_for_annotation` → `download_annotated`), and a "submit now,
86
- download later" pattern for long-running jobs. See
87
- **[Annotate a VCF](https://github.com/SACGF/variantgrid_api/wiki/Annotate-a-VCF)** on the wiki.
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.
88
90
 
89
91
  ## Testing
90
92
 
@@ -25,23 +25,25 @@ result = api.create_enrichment_kit(enrichment_kit)
25
25
 
26
26
  ## Annotate a VCF and download it back
27
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}")
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
39
41
  ```
40
42
 
41
- There's also a `vg_api annotate_vcf` command line tool (first call uploads, run it again to download once
42
- ready), a step-by-step form (`upload_file` → `wait_for_annotation` → `download_annotated`), and a "submit now,
43
- download later" pattern for long-running jobs. See
44
- **[Annotate a VCF](https://github.com/SACGF/variantgrid_api/wiki/Annotate-a-VCF)** on the wiki.
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.
45
47
 
46
48
  ## Testing
47
49
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "variantgrid_api"
7
- version = "1.3.1"
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" }
@@ -46,6 +46,27 @@ def _is_not_found(exc):
46
46
  return resp is not None and resp.status_code == 404
47
47
 
48
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
+
49
70
  def annotate_vcf_cmd(args):
50
71
  if not os.path.isfile(args.vcf):
51
72
  print(f"No such file: {args.vcf}", file=sys.stderr)
@@ -53,38 +74,30 @@ def annotate_vcf_cmd(args):
53
74
 
54
75
  api, logger = _build_api(args)
55
76
  name = os.path.basename(args.vcf)
56
-
57
- if args.wait:
58
- # Blocking one-shot: upload, wait (can take hours), download.
59
- path = api.annotate_vcf(args.vcf, export_type=args.export_type, dest_path=args.dest,
60
- poll_interval=args.poll_interval)
61
- print(f"Annotated {args.export_type} written to {path}")
62
- return EXIT_OK
63
-
64
77
  sha256 = _sha256(args.vcf)
65
78
 
66
- # Probe by content hash. A 404 means we've never uploaded this file - so upload it now.
67
- prev_level = logger.level
68
- logger.setLevel(logging.CRITICAL) # the probe 404 is expected; don't scare the user
69
- try:
70
- status = api.poll_upload_status(sha256=sha256)
71
- except requests.HTTPError as e:
72
- if _is_not_found(e):
73
- up = api.upload_file(args.vcf, path=None)
74
- print(f"Uploaded {name} (id={up['uploaded_file_id']}). "
75
- f"Annotating - run the same command again later to download.")
76
- return EXIT_PENDING
77
- raise
78
- finally:
79
- logger.setLevel(prev_level)
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']}).")
80
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
81
96
  if err := status.get("error"):
82
97
  print(f"{name}: annotation error - {err}", file=sys.stderr)
83
98
  return EXIT_ERROR
84
99
  if status.get("annotation_complete"):
85
- path = api.download_annotated(sha256=sha256, export_type=args.export_type, dest_path=args.dest)
86
- print(f"Annotated {args.export_type} written to {path}")
87
- return EXIT_OK
100
+ return _download(api, args, sha256)
88
101
 
89
102
  progress = status.get("progress_percent")
90
103
  suffix = f" (progress {progress}%)" if progress is not None else ""
@@ -113,9 +126,12 @@ def build_parser():
113
126
  p.add_argument("-o", "--dest", default=".",
114
127
  help="Destination directory or file for the download (default: current directory)")
115
128
  p.add_argument("--wait", action="store_true",
116
- help="Block until annotation finishes, then download (can take hours)")
129
+ help="Poll until annotation finishes and download it, instead of returning immediately "
130
+ "(can take hours)")
117
131
  p.add_argument("--poll-interval", type=float, default=10,
118
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)")
119
135
  p.set_defaults(func=annotate_vcf_cmd)
120
136
  return parser
121
137
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: variantgrid_api
3
- Version: 1.3.1
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,23 +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, wait for VariantGrid to import + annotate any novel variants, then download the
72
- cohort-level annotated export (all samples, single-sample VCFs included). The whole flow is a one-liner:
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
- ```python
75
- from variantgrid_api.api_client import VariantGridAPI
76
-
77
- api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
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.
78
81
 
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
+ $ 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
82
84
  ```
83
85
 
84
- There's also a `vg_api annotate_vcf` command line tool (first call uploads, run it again to download once
85
- ready), a step-by-step form (`upload_file` → `wait_for_annotation` → `download_annotated`), and a "submit now,
86
- download later" pattern for long-running jobs. See
87
- **[Annotate a VCF](https://github.com/SACGF/variantgrid_api/wiki/Annotate-a-VCF)** on the wiki.
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.
88
90
 
89
91
  ## Testing
90
92
 
@@ -87,6 +87,35 @@ def test_csv_export_type(vcf, tmp_path):
87
87
  assert download_call.request.url.endswith("/csv")
88
88
 
89
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
+
90
119
  def test_missing_file_is_error(tmp_path):
91
120
  rc = cli.main(_argv(str(tmp_path / "nope.vcf")))
92
121
  assert rc == cli.EXIT_ERROR
File without changes