variantgrid-api 1.9.0__tar.gz → 1.10.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.
- {variantgrid_api-1.9.0/src/variantgrid_api.egg-info → variantgrid_api-1.10.0}/PKG-INFO +27 -10
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/README.md +26 -9
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/pyproject.toml +1 -1
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api/api_client.py +39 -1
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api/data_models.py +1 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api/mock_variantgrid_api.py +20 -1
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0/src/variantgrid_api.egg-info}/PKG-INFO +27 -10
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_api_client_capabilities.py +3 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_api_client_patients.py +125 -1
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_mock_variantgrid_api.py +31 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/LICENSE +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/setup.cfg +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api/cli.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/SOURCES.txt +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/dependency_links.txt +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/entry_points.txt +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/requires.txt +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/top_level.txt +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_alignment_files.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_api_client.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_api_client_annotation.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_api_client_bulk.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_api_client_validation.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_cli.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/tests/test_data_models.py +0 -0
- {variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/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
|
+
Version: 1.10.0
|
|
4
4
|
Summary: A Python API client for VariantGrid
|
|
5
5
|
Author-email: Dave Lawrence <davmlaw@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -90,6 +90,21 @@ api.upload_file("sample.vcf.gz", path=None,
|
|
|
90
90
|
A bare string names a record by its local reference. Use `ExternalReference(code=..., external_type=...)` to
|
|
91
91
|
name it by a LIMS identifier instead. See `examples/example_tso500.py` for a full run.
|
|
92
92
|
|
|
93
|
+
Once the sample sheet is registered, link its sequencing samples to their extractions in one call, keyed on
|
|
94
|
+
sample name (what `SequencingSampleLookup.sample_name` holds):
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
api.link_sequencing_sample_extractions(sample_sheet_lookup, {"sample_1": "2600000001C",
|
|
98
|
+
"sample_2": "2600000002C"})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It returns a list with one `{"sequencing_sample", "match_status", "match_error", "extraction"}` per sample. An
|
|
102
|
+
extraction the server doesn't have yet is `Pending` for that sample only (HTTP 202) and attaches itself once the
|
|
103
|
+
extraction is created; a sample name that isn't on the sheet is a 400 for the whole batch. On a server from
|
|
104
|
+
before SACGF/variantgrid#2176 (no `link_extractions_bulk` feature) the client makes one
|
|
105
|
+
`link_sequencing_sample_extraction` call per sample instead, so pipelines can use the bulk call unconditionally.
|
|
106
|
+
`link_sequencing_sample_extraction` is still there for a single late link. See `examples/example_haem_20_999.py`.
|
|
107
|
+
|
|
93
108
|
## BAMs and CRAMs
|
|
94
109
|
|
|
95
110
|
A sequencing sample can have several alignment files, eg a BAM and its recalibrated BAM, or a BAM plus a CRAM.
|
|
@@ -114,13 +129,15 @@ file as `bam_file`, and a QC's first alignment file as its `bam_file`. A CRAM th
|
|
|
114
129
|
Servers of different ages accept different calls. The client asks the server which features it has
|
|
115
130
|
(`GET api/v1/capabilities`, fetched once on first use), and each call that needs a newer server
|
|
116
131
|
checks that list first: the patient / specimen / extraction calls, specimen measures,
|
|
117
|
-
`link_sequencing_sample_extraction`, the annotate flow
|
|
118
|
-
`download_annotated`, `annotate_vcf`), and `upload_file` with
|
|
119
|
-
capabilities endpoint counts as legacy and reports no features.
|
|
132
|
+
`link_sequencing_sample_extraction` and `link_sequencing_sample_extractions`, the annotate flow
|
|
133
|
+
(`poll_upload_status`, `wait_for_annotation`, `download_annotated`, `annotate_vcf`), and `upload_file` with
|
|
134
|
+
`metadata` or `file_type`. A server without the capabilities endpoint counts as legacy and reports no features.
|
|
120
135
|
|
|
121
136
|
By default an unsupported call raises `UnsupportedFeatureError`. To run the same code against old and new
|
|
122
|
-
servers, skip those calls instead: they log a warning and return `None`.
|
|
123
|
-
server
|
|
137
|
+
servers, skip those calls instead: they log a warning and return `None`. Two calls downgrade rather than skip
|
|
138
|
+
when the server has the older form of the feature: `upload_file` on a server without upload metadata drops the
|
|
139
|
+
`metadata` and still uploads the file, and `link_sequencing_sample_extractions` on a server with only
|
|
140
|
+
`link_extraction` makes one call per sample.
|
|
124
141
|
|
|
125
142
|
```
|
|
126
143
|
from variantgrid_api.api_client import VariantGridAPI, UnsupportedFeaturePolicy
|
|
@@ -163,10 +180,10 @@ specimen measures. See `examples/example_tso500.py` for a full run.
|
|
|
163
180
|
## Permission to write sequencing data
|
|
164
181
|
|
|
165
182
|
Posts to the server's `seqauto/api/` endpoints - the sequencing `create_*` calls from `create_experiment` through
|
|
166
|
-
`create_sequencing_data` and the QC calls, and `link_sequencing_sample_extraction` - need a
|
|
167
|
-
or one belonging to a member of the server's SeqAuto write group (`seqauto_api_write`
|
|
168
|
-
`SEQAUTO_API_WRITE_GROUP` setting says otherwise). Anyone else gets a 403
|
|
169
|
-
`sequencing_run_has_vcf()`, need only a valid token.
|
|
183
|
+
`create_sequencing_data` and the QC calls, and the `link_sequencing_sample_extraction(s)` calls - need a
|
|
184
|
+
superuser's API token, or one belonging to a member of the server's SeqAuto write group (`seqauto_api_write`
|
|
185
|
+
unless the server's `SEQAUTO_API_WRITE_GROUP` setting says otherwise). Anyone else gets a 403
|
|
186
|
+
(`requests.HTTPError`). Reads, such as `sequencing_run_has_vcf()`, need only a valid token.
|
|
170
187
|
|
|
171
188
|
## Testing
|
|
172
189
|
|
|
@@ -69,6 +69,21 @@ api.upload_file("sample.vcf.gz", path=None,
|
|
|
69
69
|
A bare string names a record by its local reference. Use `ExternalReference(code=..., external_type=...)` to
|
|
70
70
|
name it by a LIMS identifier instead. See `examples/example_tso500.py` for a full run.
|
|
71
71
|
|
|
72
|
+
Once the sample sheet is registered, link its sequencing samples to their extractions in one call, keyed on
|
|
73
|
+
sample name (what `SequencingSampleLookup.sample_name` holds):
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
api.link_sequencing_sample_extractions(sample_sheet_lookup, {"sample_1": "2600000001C",
|
|
77
|
+
"sample_2": "2600000002C"})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
It returns a list with one `{"sequencing_sample", "match_status", "match_error", "extraction"}` per sample. An
|
|
81
|
+
extraction the server doesn't have yet is `Pending` for that sample only (HTTP 202) and attaches itself once the
|
|
82
|
+
extraction is created; a sample name that isn't on the sheet is a 400 for the whole batch. On a server from
|
|
83
|
+
before SACGF/variantgrid#2176 (no `link_extractions_bulk` feature) the client makes one
|
|
84
|
+
`link_sequencing_sample_extraction` call per sample instead, so pipelines can use the bulk call unconditionally.
|
|
85
|
+
`link_sequencing_sample_extraction` is still there for a single late link. See `examples/example_haem_20_999.py`.
|
|
86
|
+
|
|
72
87
|
## BAMs and CRAMs
|
|
73
88
|
|
|
74
89
|
A sequencing sample can have several alignment files, eg a BAM and its recalibrated BAM, or a BAM plus a CRAM.
|
|
@@ -93,13 +108,15 @@ file as `bam_file`, and a QC's first alignment file as its `bam_file`. A CRAM th
|
|
|
93
108
|
Servers of different ages accept different calls. The client asks the server which features it has
|
|
94
109
|
(`GET api/v1/capabilities`, fetched once on first use), and each call that needs a newer server
|
|
95
110
|
checks that list first: the patient / specimen / extraction calls, specimen measures,
|
|
96
|
-
`link_sequencing_sample_extraction`, the annotate flow
|
|
97
|
-
`download_annotated`, `annotate_vcf`), and `upload_file` with
|
|
98
|
-
capabilities endpoint counts as legacy and reports no features.
|
|
111
|
+
`link_sequencing_sample_extraction` and `link_sequencing_sample_extractions`, the annotate flow
|
|
112
|
+
(`poll_upload_status`, `wait_for_annotation`, `download_annotated`, `annotate_vcf`), and `upload_file` with
|
|
113
|
+
`metadata` or `file_type`. A server without the capabilities endpoint counts as legacy and reports no features.
|
|
99
114
|
|
|
100
115
|
By default an unsupported call raises `UnsupportedFeatureError`. To run the same code against old and new
|
|
101
|
-
servers, skip those calls instead: they log a warning and return `None`.
|
|
102
|
-
server
|
|
116
|
+
servers, skip those calls instead: they log a warning and return `None`. Two calls downgrade rather than skip
|
|
117
|
+
when the server has the older form of the feature: `upload_file` on a server without upload metadata drops the
|
|
118
|
+
`metadata` and still uploads the file, and `link_sequencing_sample_extractions` on a server with only
|
|
119
|
+
`link_extraction` makes one call per sample.
|
|
103
120
|
|
|
104
121
|
```
|
|
105
122
|
from variantgrid_api.api_client import VariantGridAPI, UnsupportedFeaturePolicy
|
|
@@ -142,10 +159,10 @@ specimen measures. See `examples/example_tso500.py` for a full run.
|
|
|
142
159
|
## Permission to write sequencing data
|
|
143
160
|
|
|
144
161
|
Posts to the server's `seqauto/api/` endpoints - the sequencing `create_*` calls from `create_experiment` through
|
|
145
|
-
`create_sequencing_data` and the QC calls, and `link_sequencing_sample_extraction` - need a
|
|
146
|
-
or one belonging to a member of the server's SeqAuto write group (`seqauto_api_write`
|
|
147
|
-
`SEQAUTO_API_WRITE_GROUP` setting says otherwise). Anyone else gets a 403
|
|
148
|
-
`sequencing_run_has_vcf()`, need only a valid token.
|
|
162
|
+
`create_sequencing_data` and the QC calls, and the `link_sequencing_sample_extraction(s)` calls - need a
|
|
163
|
+
superuser's API token, or one belonging to a member of the server's SeqAuto write group (`seqauto_api_write`
|
|
164
|
+
unless the server's `SEQAUTO_API_WRITE_GROUP` setting says otherwise). Anyone else gets a 403
|
|
165
|
+
(`requests.HTTPError`). Reads, such as `sequencing_run_has_vcf()`, need only a valid token.
|
|
149
166
|
|
|
150
167
|
## Testing
|
|
151
168
|
|
|
@@ -7,7 +7,7 @@ import urllib
|
|
|
7
7
|
import warnings
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import List, Optional, Callable, Union
|
|
10
|
+
from typing import Dict, List, Optional, Callable, Union
|
|
11
11
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
@@ -438,6 +438,44 @@ class VariantGridAPI:
|
|
|
438
438
|
}
|
|
439
439
|
return self._post("seqauto/api/v1/sequencing_sample/link_extraction", json_data)
|
|
440
440
|
|
|
441
|
+
def link_sequencing_sample_extractions(self, sample_sheet_lookup: SampleSheetLookup,
|
|
442
|
+
extractions: Dict[str, ReferenceLike]) -> Optional[List[dict]]:
|
|
443
|
+
""" link_sequencing_sample_extraction() for a whole sample sheet in one call. extractions maps each
|
|
444
|
+
sequencing sample's name (SequencingSampleLookup.sample_name) to its extraction reference.
|
|
445
|
+
|
|
446
|
+
Returns one {"sequencing_sample", "match_status", "match_error", "extraction"} per sample, in the
|
|
447
|
+
order given. match_status is per sample: an extraction the server doesn't have yet is 'Pending'
|
|
448
|
+
(the response is a 202) and attaches itself once created, without affecting the others. A sample
|
|
449
|
+
name that isn't on the sample sheet is a 400 for the whole batch - nothing is linked.
|
|
450
|
+
|
|
451
|
+
Needs ServerFeature.LINK_EXTRACTIONS_BULK (SACGF/variantgrid#2176). A server with only
|
|
452
|
+
ServerFeature.LINK_EXTRACTION gets one link_sequencing_sample_extraction() call per sample and
|
|
453
|
+
the results are collected, so pipelines can call this unconditionally. unsupported_feature_policy
|
|
454
|
+
applies only when the server has neither feature. """
|
|
455
|
+
if not self.supports(ServerFeature.LINK_EXTRACTIONS_BULK) and not self._require(ServerFeature.LINK_EXTRACTION):
|
|
456
|
+
return None
|
|
457
|
+
self._validate_object("sample_sheet_lookup", sample_sheet_lookup)
|
|
458
|
+
if not extractions:
|
|
459
|
+
self.validation_handler("extractions: empty")
|
|
460
|
+
for sample_name, extraction_reference in extractions.items():
|
|
461
|
+
self._validate_string("extractions: sample name", sample_name)
|
|
462
|
+
self._validate_reference(f"extractions['{sample_name}']", extraction_reference)
|
|
463
|
+
|
|
464
|
+
if not self.supports(ServerFeature.LINK_EXTRACTIONS_BULK):
|
|
465
|
+
self.logger.info("Server doesn't support feature '%s' - linking %d sequencing sample(s) one at a time",
|
|
466
|
+
ServerFeature.LINK_EXTRACTIONS_BULK, len(extractions))
|
|
467
|
+
return [self.link_sequencing_sample_extraction(
|
|
468
|
+
SequencingSampleLookup(sample_sheet_lookup=sample_sheet_lookup, sample_name=sample_name),
|
|
469
|
+
extraction_reference)
|
|
470
|
+
for sample_name, extraction_reference in extractions.items()]
|
|
471
|
+
|
|
472
|
+
json_data = {
|
|
473
|
+
"sample_sheet": sample_sheet_lookup.to_dict(),
|
|
474
|
+
"links": [{"sample_name": sample_name, "extraction": reference_json(extraction_reference)}
|
|
475
|
+
for sample_name, extraction_reference in extractions.items()],
|
|
476
|
+
}
|
|
477
|
+
return self._post("seqauto/api/v1/sequencing_sample/link_extractions", json_data)
|
|
478
|
+
|
|
441
479
|
def upload_file(self, filename: str, path=_UNSET, metadata: Optional[dict] = None,
|
|
442
480
|
file_type: Optional[Union[UploadFileType, str]] = None):
|
|
443
481
|
""" Upload a file via multipart POST to upload/api/v1/file_upload.
|
|
@@ -520,6 +520,7 @@ class ServerFeature(_ServerName):
|
|
|
520
520
|
variantgrid/views_rest.py). An older server just doesn't list a newer one """
|
|
521
521
|
PATIENTS = "patients"
|
|
522
522
|
LINK_EXTRACTION = "link_extraction"
|
|
523
|
+
LINK_EXTRACTIONS_BULK = "link_extractions_bulk" # sequencing_sample/link_extractions (SACGF/variantgrid#2176)
|
|
523
524
|
UPLOAD_STATUS = "upload_status"
|
|
524
525
|
JOINT_CALLED_VCF_CROSS_RUN = "joint_called_vcf_cross_run"
|
|
525
526
|
UPLOAD_METADATA = "upload_metadata"
|
{variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api/mock_variantgrid_api.py
RENAMED
|
@@ -19,7 +19,8 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, List, Optional, Tuple, Union
|
|
20
20
|
|
|
21
21
|
from variantgrid_api.api_client import UnsupportedFeaturePolicy, UnsupportedFeatureError
|
|
22
|
-
from variantgrid_api.data_models import reference_json,
|
|
22
|
+
from variantgrid_api.data_models import reference_json, SequencingSampleLookup, ServerCapabilities, \
|
|
23
|
+
ServerFeature, UploadFileType
|
|
23
24
|
|
|
24
25
|
_UNSET = object()
|
|
25
26
|
|
|
@@ -219,6 +220,24 @@ class MockVariantGridAPI:
|
|
|
219
220
|
"extraction": str(reference_json(extraction_reference)),
|
|
220
221
|
})
|
|
221
222
|
|
|
223
|
+
def link_sequencing_sample_extractions(self, sample_sheet_lookup, extractions):
|
|
224
|
+
""" As the real client, a server without 'link_extractions_bulk' gets one
|
|
225
|
+
link_sequencing_sample_extraction call per sample - those are what's recorded, not this call """
|
|
226
|
+
if not self.supports(ServerFeature.LINK_EXTRACTIONS_BULK):
|
|
227
|
+
if not self._require(ServerFeature.LINK_EXTRACTION):
|
|
228
|
+
return None
|
|
229
|
+
return [self.link_sequencing_sample_extraction(
|
|
230
|
+
SequencingSampleLookup(sample_sheet_lookup=sample_sheet_lookup, sample_name=sample_name),
|
|
231
|
+
extraction_reference)
|
|
232
|
+
for sample_name, extraction_reference in extractions.items()]
|
|
233
|
+
self._record("link_sequencing_sample_extractions", sample_sheet_lookup, extractions)
|
|
234
|
+
return self._ret("link_sequencing_sample_extractions", [{
|
|
235
|
+
"sequencing_sample": sample_name,
|
|
236
|
+
"match_status": "Matched",
|
|
237
|
+
"match_error": None,
|
|
238
|
+
"extraction": str(reference_json(extraction_reference)),
|
|
239
|
+
} for sample_name, extraction_reference in extractions.items()])
|
|
240
|
+
|
|
222
241
|
def upload_file(self, filename, path=_UNSET, metadata=None, file_type=None):
|
|
223
242
|
if metadata and not self.supports(ServerFeature.UPLOAD_METADATA):
|
|
224
243
|
self._unsupported(f"upload metadata for '{filename}' (server doesn't support feature 'upload_metadata')")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: variantgrid_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: A Python API client for VariantGrid
|
|
5
5
|
Author-email: Dave Lawrence <davmlaw@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -90,6 +90,21 @@ api.upload_file("sample.vcf.gz", path=None,
|
|
|
90
90
|
A bare string names a record by its local reference. Use `ExternalReference(code=..., external_type=...)` to
|
|
91
91
|
name it by a LIMS identifier instead. See `examples/example_tso500.py` for a full run.
|
|
92
92
|
|
|
93
|
+
Once the sample sheet is registered, link its sequencing samples to their extractions in one call, keyed on
|
|
94
|
+
sample name (what `SequencingSampleLookup.sample_name` holds):
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
api.link_sequencing_sample_extractions(sample_sheet_lookup, {"sample_1": "2600000001C",
|
|
98
|
+
"sample_2": "2600000002C"})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It returns a list with one `{"sequencing_sample", "match_status", "match_error", "extraction"}` per sample. An
|
|
102
|
+
extraction the server doesn't have yet is `Pending` for that sample only (HTTP 202) and attaches itself once the
|
|
103
|
+
extraction is created; a sample name that isn't on the sheet is a 400 for the whole batch. On a server from
|
|
104
|
+
before SACGF/variantgrid#2176 (no `link_extractions_bulk` feature) the client makes one
|
|
105
|
+
`link_sequencing_sample_extraction` call per sample instead, so pipelines can use the bulk call unconditionally.
|
|
106
|
+
`link_sequencing_sample_extraction` is still there for a single late link. See `examples/example_haem_20_999.py`.
|
|
107
|
+
|
|
93
108
|
## BAMs and CRAMs
|
|
94
109
|
|
|
95
110
|
A sequencing sample can have several alignment files, eg a BAM and its recalibrated BAM, or a BAM plus a CRAM.
|
|
@@ -114,13 +129,15 @@ file as `bam_file`, and a QC's first alignment file as its `bam_file`. A CRAM th
|
|
|
114
129
|
Servers of different ages accept different calls. The client asks the server which features it has
|
|
115
130
|
(`GET api/v1/capabilities`, fetched once on first use), and each call that needs a newer server
|
|
116
131
|
checks that list first: the patient / specimen / extraction calls, specimen measures,
|
|
117
|
-
`link_sequencing_sample_extraction`, the annotate flow
|
|
118
|
-
`download_annotated`, `annotate_vcf`), and `upload_file` with
|
|
119
|
-
capabilities endpoint counts as legacy and reports no features.
|
|
132
|
+
`link_sequencing_sample_extraction` and `link_sequencing_sample_extractions`, the annotate flow
|
|
133
|
+
(`poll_upload_status`, `wait_for_annotation`, `download_annotated`, `annotate_vcf`), and `upload_file` with
|
|
134
|
+
`metadata` or `file_type`. A server without the capabilities endpoint counts as legacy and reports no features.
|
|
120
135
|
|
|
121
136
|
By default an unsupported call raises `UnsupportedFeatureError`. To run the same code against old and new
|
|
122
|
-
servers, skip those calls instead: they log a warning and return `None`.
|
|
123
|
-
server
|
|
137
|
+
servers, skip those calls instead: they log a warning and return `None`. Two calls downgrade rather than skip
|
|
138
|
+
when the server has the older form of the feature: `upload_file` on a server without upload metadata drops the
|
|
139
|
+
`metadata` and still uploads the file, and `link_sequencing_sample_extractions` on a server with only
|
|
140
|
+
`link_extraction` makes one call per sample.
|
|
124
141
|
|
|
125
142
|
```
|
|
126
143
|
from variantgrid_api.api_client import VariantGridAPI, UnsupportedFeaturePolicy
|
|
@@ -163,10 +180,10 @@ specimen measures. See `examples/example_tso500.py` for a full run.
|
|
|
163
180
|
## Permission to write sequencing data
|
|
164
181
|
|
|
165
182
|
Posts to the server's `seqauto/api/` endpoints - the sequencing `create_*` calls from `create_experiment` through
|
|
166
|
-
`create_sequencing_data` and the QC calls, and `link_sequencing_sample_extraction` - need a
|
|
167
|
-
or one belonging to a member of the server's SeqAuto write group (`seqauto_api_write`
|
|
168
|
-
`SEQAUTO_API_WRITE_GROUP` setting says otherwise). Anyone else gets a 403
|
|
169
|
-
`sequencing_run_has_vcf()`, need only a valid token.
|
|
183
|
+
`create_sequencing_data` and the QC calls, and the `link_sequencing_sample_extraction(s)` calls - need a
|
|
184
|
+
superuser's API token, or one belonging to a member of the server's SeqAuto write group (`seqauto_api_write`
|
|
185
|
+
unless the server's `SEQAUTO_API_WRITE_GROUP` setting says otherwise). Anyone else gets a 403
|
|
186
|
+
(`requests.HTTPError`). Reads, such as `sequencing_run_has_vcf()`, need only a valid token.
|
|
170
187
|
|
|
171
188
|
## Testing
|
|
172
189
|
|
|
@@ -140,6 +140,8 @@ def _gated_calls(vg_objects, vcf):
|
|
|
140
140
|
"create_extraction": lambda api: api.create_extraction(vg_objects["dna_extraction"]),
|
|
141
141
|
"link_sequencing_sample_extraction": lambda api: api.link_sequencing_sample_extraction(
|
|
142
142
|
vg_objects["sequencing_sample_lookup_1"], "2600000001C"),
|
|
143
|
+
"link_sequencing_sample_extractions": lambda api: api.link_sequencing_sample_extractions(
|
|
144
|
+
vg_objects["sample_sheet_lookup"], {"fake_sample_1": "2600000001C"}),
|
|
143
145
|
"upload_file_file_type": lambda api: api.upload_file(vcf, path=None, file_type=CVO),
|
|
144
146
|
"poll_upload_status": lambda api: api.poll_upload_status(uploaded_file_id=1),
|
|
145
147
|
"wait_for_annotation": lambda api: api.wait_for_annotation(uploaded_file_id=1, sleep=lambda _: None),
|
|
@@ -149,6 +151,7 @@ def _gated_calls(vg_objects, vcf):
|
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
GATED = ["create_patient", "create_specimen", "create_extraction", "link_sequencing_sample_extraction",
|
|
154
|
+
"link_sequencing_sample_extractions",
|
|
152
155
|
"upload_file_file_type",
|
|
153
156
|
"poll_upload_status", "wait_for_annotation", "download_annotated", "annotate_vcf"]
|
|
154
157
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"""Patient / specimen / extraction, specimen measures, extraction links and upload metadata
|
|
2
2
|
(SACGF/variantgrid_api#20)"""
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
4
6
|
from urllib.parse import urlparse, parse_qs
|
|
5
7
|
|
|
6
8
|
import pytest
|
|
7
9
|
import responses
|
|
8
10
|
|
|
9
|
-
from variantgrid_api.
|
|
11
|
+
from variantgrid_api.api_client import VariantGridAPI, UnsupportedFeaturePolicy, UnsupportedFeatureError
|
|
12
|
+
from variantgrid_api.data_models import ExternalReference, ExternalPK, Extraction, ServerCapabilities, \
|
|
13
|
+
ServerFeature
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
@pytest.fixture
|
|
@@ -141,6 +145,126 @@ def test_link_sequencing_sample_extraction_pending_is_not_an_error(api, server,
|
|
|
141
145
|
assert out == pending
|
|
142
146
|
|
|
143
147
|
|
|
148
|
+
# ------------------------------------------------------------------ #
|
|
149
|
+
# Linking a sample sheet's sequencing samples in one call (#32) #
|
|
150
|
+
# ------------------------------------------------------------------ #
|
|
151
|
+
|
|
152
|
+
def _bulk_url(server):
|
|
153
|
+
return f"{server}/seqauto/api/v1/sequencing_sample/link_extractions"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _single_url(server):
|
|
157
|
+
return f"{server}/seqauto/api/v1/sequencing_sample/link_extraction"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _link_result(sample_name, extraction, match_status="Matched"):
|
|
161
|
+
return {"sequencing_sample": sample_name, "match_status": match_status, "match_error": None,
|
|
162
|
+
"extraction": extraction if match_status == "Matched" else None}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
EXTRACTIONS = {"fake_sample_1": "2600000001C", "fake_sample_2": "2600000002C"}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@responses.activate
|
|
169
|
+
def test_link_sequencing_sample_extractions(api, server, vg_objects):
|
|
170
|
+
result = [_link_result("fake_sample_1", "2600000001C"), _link_result("fake_sample_2", "2600000002C")]
|
|
171
|
+
responses.add(responses.POST, _bulk_url(server), json=result, status=200)
|
|
172
|
+
|
|
173
|
+
out = api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], EXTRACTIONS)
|
|
174
|
+
assert out == result
|
|
175
|
+
assert len(responses.calls) == 1
|
|
176
|
+
assert _last_json() == {
|
|
177
|
+
"sample_sheet": vg_objects["sample_sheet_lookup"].to_dict(),
|
|
178
|
+
"links": [
|
|
179
|
+
{"sample_name": "fake_sample_1", "extraction": "2600000001C"},
|
|
180
|
+
{"sample_name": "fake_sample_2", "extraction": "2600000002C"},
|
|
181
|
+
],
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@responses.activate
|
|
186
|
+
def test_link_sequencing_sample_extractions_external_reference(api, server, vg_objects):
|
|
187
|
+
""" Each link takes the same reference forms as the single call """
|
|
188
|
+
responses.add(responses.POST, _bulk_url(server), json=[], status=200)
|
|
189
|
+
lims = ExternalReference(code="LIMS-1", external_type="LabNo")
|
|
190
|
+
api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], {"fake_sample_1": lims})
|
|
191
|
+
assert _last_json()["links"] == [{"sample_name": "fake_sample_1", "extraction": lims.to_json_value()}]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@responses.activate
|
|
195
|
+
def test_link_sequencing_sample_extractions_pending_is_not_an_error(api, server, vg_objects):
|
|
196
|
+
""" match_status is per sample - a 202 with one 'Pending' still links the others """
|
|
197
|
+
result = [_link_result("fake_sample_1", "2600000001C"),
|
|
198
|
+
_link_result("fake_sample_2", None, match_status="Pending")]
|
|
199
|
+
responses.add(responses.POST, _bulk_url(server), json=result, status=202)
|
|
200
|
+
assert api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], EXTRACTIONS) == result
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@pytest.fixture
|
|
204
|
+
def link_extraction_only_api(server, api_token):
|
|
205
|
+
""" A server from before the bulk endpoint, with just the single-sample one """
|
|
206
|
+
api = VariantGridAPI(server, api_token)
|
|
207
|
+
api._capabilities = ServerCapabilities(version="old", features=frozenset([ServerFeature.LINK_EXTRACTION]))
|
|
208
|
+
return api
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@responses.activate
|
|
212
|
+
def test_link_sequencing_sample_extractions_falls_back_to_one_call_per_sample(link_extraction_only_api, server,
|
|
213
|
+
vg_objects):
|
|
214
|
+
""" So pipelines can use the bulk call unconditionally - the results are collected in order """
|
|
215
|
+
api = link_extraction_only_api
|
|
216
|
+
responses.add(responses.POST, _single_url(server), json=_link_result("fake_sample_1", "2600000001C"), status=200)
|
|
217
|
+
responses.add(responses.POST, _single_url(server), json=_link_result("fake_sample_2", None, "Pending"), status=202)
|
|
218
|
+
|
|
219
|
+
out = api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], EXTRACTIONS)
|
|
220
|
+
assert out == [_link_result("fake_sample_1", "2600000001C"), _link_result("fake_sample_2", None, "Pending")]
|
|
221
|
+
assert [c.request.url for c in responses.calls] == [_single_url(server)] * 2
|
|
222
|
+
sample_sheet = vg_objects["sample_sheet_lookup"].to_dict()
|
|
223
|
+
assert [json.loads(c.request.body) for c in responses.calls] == [
|
|
224
|
+
{"sequencing_sample": {"sample_sheet": sample_sheet, "sample_name": "fake_sample_1"},
|
|
225
|
+
"extraction": "2600000001C"},
|
|
226
|
+
{"sequencing_sample": {"sample_sheet": sample_sheet, "sample_name": "fake_sample_2"},
|
|
227
|
+
"extraction": "2600000002C"},
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@responses.activate
|
|
232
|
+
def test_link_sequencing_sample_extractions_policy_applies_only_without_link_extraction(server, api_token,
|
|
233
|
+
vg_objects, caplog):
|
|
234
|
+
""" unsupported_feature_policy applies only when the server has neither feature """
|
|
235
|
+
legacy = ServerCapabilities.LEGACY
|
|
236
|
+
api = VariantGridAPI(server, api_token)
|
|
237
|
+
api._capabilities = legacy
|
|
238
|
+
with pytest.raises(UnsupportedFeatureError):
|
|
239
|
+
api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], EXTRACTIONS)
|
|
240
|
+
|
|
241
|
+
skip_api = VariantGridAPI(server, api_token, unsupported_feature_policy=UnsupportedFeaturePolicy.SKIP)
|
|
242
|
+
skip_api._capabilities = legacy
|
|
243
|
+
with caplog.at_level(logging.WARNING):
|
|
244
|
+
assert skip_api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], EXTRACTIONS) is None
|
|
245
|
+
assert "Skipping" in caplog.text
|
|
246
|
+
assert not responses.calls
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@pytest.mark.parametrize("extractions, message", [
|
|
250
|
+
({}, "extractions: empty"),
|
|
251
|
+
({"fake_sample_1": "2600000001C", "fake_sample_2": None}, "extractions['fake_sample_2']: is None"),
|
|
252
|
+
({"fake_sample_1": ""}, "extractions['fake_sample_1']: empty string"),
|
|
253
|
+
({"": "2600000001C"}, "extractions: sample name: empty string"),
|
|
254
|
+
])
|
|
255
|
+
@responses.activate
|
|
256
|
+
def test_link_sequencing_sample_extractions_validation_names_the_sample(api, vg_objects, extractions, message):
|
|
257
|
+
with pytest.raises(ValueError, match=re.escape(message)):
|
|
258
|
+
api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], extractions)
|
|
259
|
+
assert not responses.calls
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@responses.activate
|
|
263
|
+
def test_link_sequencing_sample_extractions_requires_sample_sheet_lookup(api, vg_objects):
|
|
264
|
+
with pytest.raises(ValueError, match="sample_sheet_lookup: is None"):
|
|
265
|
+
api.link_sequencing_sample_extractions(None, EXTRACTIONS)
|
|
266
|
+
|
|
267
|
+
|
|
144
268
|
# ------------------------------------------------------------------ #
|
|
145
269
|
# Upload metadata #
|
|
146
270
|
# ------------------------------------------------------------------ #
|
|
@@ -208,6 +208,34 @@ def test_mock_link_sequencing_sample_extraction_pending_override(mock_api, vg_ob
|
|
|
208
208
|
mock_api.assert_called_once("link_sequencing_sample_extraction")
|
|
209
209
|
|
|
210
210
|
|
|
211
|
+
def test_mock_link_sequencing_sample_extractions(mock_api, vg_objects):
|
|
212
|
+
extractions = {"fake_sample_1": "2600000001C", "fake_sample_2": "2600000002C"}
|
|
213
|
+
result = mock_api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], extractions)
|
|
214
|
+
assert [r["sequencing_sample"] for r in result] == ["fake_sample_1", "fake_sample_2"]
|
|
215
|
+
assert all(r["match_status"] == "Matched" for r in result)
|
|
216
|
+
mock_api.assert_called_once("link_sequencing_sample_extractions")
|
|
217
|
+
mock_api.assert_not_called("link_sequencing_sample_extraction")
|
|
218
|
+
assert mock_api.get_calls("link_sequencing_sample_extractions") == [
|
|
219
|
+
((vg_objects["sample_sheet_lookup"], extractions), {})]
|
|
220
|
+
|
|
221
|
+
mock_api.set_return("link_sequencing_sample_extractions", [{"match_status": "Pending"}])
|
|
222
|
+
assert mock_api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], extractions) == [
|
|
223
|
+
{"match_status": "Pending"}]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_mock_link_sequencing_sample_extractions_falls_back_without_bulk_feature(vg_objects):
|
|
227
|
+
""" A server with only link_extraction gets (and records) one single-sample call per sample """
|
|
228
|
+
mock_api = MockVariantGridAPI(capabilities=ServerCapabilities(
|
|
229
|
+
version="old", features=frozenset([ServerFeature.LINK_EXTRACTION])))
|
|
230
|
+
extractions = {"fake_sample_1": "2600000001C", "fake_sample_2": "2600000002C"}
|
|
231
|
+
result = mock_api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"], extractions)
|
|
232
|
+
assert [r["sequencing_sample"] for r in result] == ["fake_sample_1", "fake_sample_2"]
|
|
233
|
+
mock_api.assert_not_called("link_sequencing_sample_extractions")
|
|
234
|
+
calls = mock_api.get_calls("link_sequencing_sample_extraction")
|
|
235
|
+
assert [(lookup.sample_name, extraction) for (lookup, extraction), _ in calls] == list(extractions.items())
|
|
236
|
+
assert all(lookup.sample_sheet_lookup == vg_objects["sample_sheet_lookup"] for (lookup, _), _ in calls)
|
|
237
|
+
|
|
238
|
+
|
|
211
239
|
def test_mock_upload_file_records_metadata_only_when_sent(mock_api):
|
|
212
240
|
mock_api.upload_file("a.vcf")
|
|
213
241
|
mock_api.upload_file("b.vcf", path=None, metadata={"genome_build": "GRCh37"})
|
|
@@ -246,9 +274,12 @@ def test_mock_legacy_skips_under_skip(vg_objects):
|
|
|
246
274
|
assert not mock_api.supports("patients")
|
|
247
275
|
assert mock_api.create_patient(vg_objects["patient"]) is None
|
|
248
276
|
assert mock_api.link_sequencing_sample_extraction(vg_objects["sequencing_sample_lookup_1"], "2600000001C") is None
|
|
277
|
+
assert mock_api.link_sequencing_sample_extractions(vg_objects["sample_sheet_lookup"],
|
|
278
|
+
{"fake_sample_1": "2600000001C"}) is None
|
|
249
279
|
assert mock_api.upload_file("cvo.tsv", path=None, file_type="dragen_tso500_combined_variant_output") is None
|
|
250
280
|
mock_api.assert_not_called("create_patient")
|
|
251
281
|
mock_api.assert_not_called("link_sequencing_sample_extraction")
|
|
282
|
+
mock_api.assert_not_called("link_sequencing_sample_extractions")
|
|
252
283
|
mock_api.assert_not_called("upload_file")
|
|
253
284
|
|
|
254
285
|
# Metadata is dropped but the file still uploads
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{variantgrid_api-1.9.0 → variantgrid_api-1.10.0}/src/variantgrid_api.egg-info/entry_points.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
|
|
File without changes
|