variantgrid-api 1.5.0__tar.gz → 1.6.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.5.0/src/variantgrid_api.egg-info → variantgrid_api-1.6.0}/PKG-INFO +1 -1
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/pyproject.toml +1 -1
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api/api_client.py +22 -19
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api/data_models.py +41 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api/mock_variantgrid_api.py +22 -21
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0/src/variantgrid_api.egg-info}/PKG-INFO +1 -1
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_api_client.py +29 -1
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_api_client_capabilities.py +28 -1
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_mock_variantgrid_api.py +10 -1
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/LICENSE +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/README.md +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/setup.cfg +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api/cli.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api.egg-info/SOURCES.txt +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api.egg-info/dependency_links.txt +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api.egg-info/entry_points.txt +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api.egg-info/requires.txt +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api.egg-info/top_level.txt +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_api_client_annotation.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_api_client_bulk.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_api_client_patients.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_api_client_validation.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_cli.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_data_models.py +0 -0
- {variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/tests/test_sequencer_model_from_name.py +0 -0
|
@@ -14,7 +14,7 @@ import requests
|
|
|
14
14
|
from variantgrid_api.data_models import EnrichmentKit, SequencingRun, SampleSheet, JointCalledVCF, \
|
|
15
15
|
SampleSheetLookup, SequencingFile, QCGeneList, QCExecStats, QCGeneCoverage, SequencerModel, Sequencer, \
|
|
16
16
|
SequencingSampleLookup, Patient, Specimen, Extraction, SpecimenMeasure, ExternalReference, ReferenceLike, \
|
|
17
|
-
reference_json, ServerCapabilities
|
|
17
|
+
reference_json, ServerCapabilities, ServerFeature, UploadFileType
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
_UNSET = object()
|
|
@@ -161,11 +161,11 @@ class VariantGridAPI:
|
|
|
161
161
|
self._capabilities = ServerCapabilities.from_json(data)
|
|
162
162
|
return self._capabilities
|
|
163
163
|
|
|
164
|
-
def supports(self, feature: str) -> bool:
|
|
164
|
+
def supports(self, feature: Union[ServerFeature, str]) -> bool:
|
|
165
165
|
return feature in self.capabilities.features
|
|
166
166
|
|
|
167
|
-
def accepts_upload(self, file_type: str) -> bool:
|
|
168
|
-
""" file_type is the server's UploadedFileTypes name in lower case
|
|
167
|
+
def accepts_upload(self, file_type: Union[UploadFileType, str]) -> bool:
|
|
168
|
+
""" file_type is an UploadFileType, or the server's UploadedFileTypes name in lower case """
|
|
169
169
|
return file_type in self.capabilities.upload_file_types
|
|
170
170
|
|
|
171
171
|
def _unsupported(self, message: str) -> bool:
|
|
@@ -177,11 +177,11 @@ class VariantGridAPI:
|
|
|
177
177
|
return False
|
|
178
178
|
raise UnsupportedFeatureError(message, capabilities)
|
|
179
179
|
|
|
180
|
-
def _require(self, feature: str) -> bool:
|
|
180
|
+
def _require(self, feature: Union[ServerFeature, str]) -> bool:
|
|
181
181
|
""" True if the server supports feature, otherwise applies unsupported_feature_policy """
|
|
182
182
|
return self.supports(feature) or self._unsupported(f"server doesn't support feature '{feature}'")
|
|
183
183
|
|
|
184
|
-
def _require_upload(self, file_type: str) -> bool:
|
|
184
|
+
def _require_upload(self, file_type: Union[UploadFileType, str]) -> bool:
|
|
185
185
|
return self.accepts_upload(file_type) or self._unsupported(f"server doesn't accept upload file type '{file_type}'")
|
|
186
186
|
|
|
187
187
|
def create_experiment(self, experiment: str):
|
|
@@ -241,6 +241,9 @@ class VariantGridAPI:
|
|
|
241
241
|
self._validate_list("sequencing_files", sequencing_files)
|
|
242
242
|
records = []
|
|
243
243
|
for sf in sequencing_files:
|
|
244
|
+
# The server requires both paths - catch it here, naming the record, rather than a 400 for the batch
|
|
245
|
+
self._validate_string(f"SequencingFile '{sf.sample_name}' bam_file.path", sf.bam_file and sf.bam_file.path)
|
|
246
|
+
self._validate_string(f"SequencingFile '{sf.sample_name}' vcf_file.path", sf.vcf_file and sf.vcf_file.path)
|
|
244
247
|
data = sf.to_dict()
|
|
245
248
|
# put into hierarchial JSON DRF expects
|
|
246
249
|
fastq_r1 = data.pop("fastq_r1", None)
|
|
@@ -310,28 +313,28 @@ class VariantGridAPI:
|
|
|
310
313
|
## Creates are upserts keyed on the identifiers sent, so re-posting returns the same rows
|
|
311
314
|
|
|
312
315
|
def create_patient(self, patient: Patient):
|
|
313
|
-
if not self._require(
|
|
316
|
+
if not self._require(ServerFeature.PATIENTS):
|
|
314
317
|
return None
|
|
315
318
|
self._validate_object("patient", patient)
|
|
316
319
|
return self._post("patients/api/v1/patient/", patient.to_dict())
|
|
317
320
|
|
|
318
321
|
def create_specimen(self, specimen: Specimen):
|
|
319
322
|
""" The specimen's patient must already exist on the server, otherwise this is a 400 """
|
|
320
|
-
if not self._require(
|
|
323
|
+
if not self._require(ServerFeature.PATIENTS):
|
|
321
324
|
return None
|
|
322
325
|
self._validate_object("specimen", specimen)
|
|
323
326
|
return self._post("patients/api/v1/specimen/", specimen.to_dict())
|
|
324
327
|
|
|
325
328
|
def create_extraction(self, extraction: Extraction):
|
|
326
329
|
""" The extraction's specimen must already exist on the server, otherwise this is a 400 """
|
|
327
|
-
if not self._require(
|
|
330
|
+
if not self._require(ServerFeature.PATIENTS):
|
|
328
331
|
return None
|
|
329
332
|
self._validate_object("extraction", extraction)
|
|
330
333
|
return self._post("patients/api/v1/extraction/", extraction.to_dict())
|
|
331
334
|
|
|
332
335
|
def create_specimen_measure(self, specimen_reference: ReferenceLike, measure: SpecimenMeasure):
|
|
333
336
|
""" An unknown specimen is a 400. Replaces any existing measure of the same type for the specimen """
|
|
334
|
-
if not self._require(
|
|
337
|
+
if not self._require(ServerFeature.SPECIMEN_MEASURES):
|
|
335
338
|
return None
|
|
336
339
|
self._validate_reference("specimen_reference", specimen_reference)
|
|
337
340
|
self._validate_object("measure", measure)
|
|
@@ -340,7 +343,7 @@ class VariantGridAPI:
|
|
|
340
343
|
|
|
341
344
|
def create_specimen_measures(self, specimen_reference: ReferenceLike, measures: List[SpecimenMeasure]):
|
|
342
345
|
""" A run's measures (TMB, MSI, GIS etc) against one specimen in one call """
|
|
343
|
-
if not self._require(
|
|
346
|
+
if not self._require(ServerFeature.SPECIMEN_MEASURES):
|
|
344
347
|
return None
|
|
345
348
|
self._validate_reference("specimen_reference", specimen_reference)
|
|
346
349
|
self._validate_list("measures", measures)
|
|
@@ -360,7 +363,7 @@ class VariantGridAPI:
|
|
|
360
363
|
sequencing sample is a 400, but an extraction the server doesn't have yet is not an error:
|
|
361
364
|
the response is a 202 with match_status 'Pending', and the link attaches itself once the
|
|
362
365
|
extraction is created - there's no need to re-send. """
|
|
363
|
-
if not self._require(
|
|
366
|
+
if not self._require(ServerFeature.LINK_EXTRACTION):
|
|
364
367
|
return None
|
|
365
368
|
self._validate_object("sequencing_sample_lookup", sequencing_sample_lookup)
|
|
366
369
|
self._validate_reference("extraction_reference", extraction_reference)
|
|
@@ -371,7 +374,7 @@ class VariantGridAPI:
|
|
|
371
374
|
return self._post("seqauto/api/v1/sequencing_sample/link_extraction", json_data)
|
|
372
375
|
|
|
373
376
|
def upload_file(self, filename: str, path=_UNSET, metadata: Optional[dict] = None,
|
|
374
|
-
file_type: Optional[str] = None):
|
|
377
|
+
file_type: Optional[Union[UploadFileType, str]] = None):
|
|
375
378
|
""" Upload a file via multipart POST to upload/api/v1/file_upload.
|
|
376
379
|
|
|
377
380
|
Returns {"uploaded_file_id": <id>, "sha256_hash": <hash>, ...}; identify the upload by
|
|
@@ -393,10 +396,10 @@ class VariantGridAPI:
|
|
|
393
396
|
Needs the server feature 'upload_metadata'. Without it, SKIP uploads the file without the
|
|
394
397
|
metadata (as older clients did) rather than not at all, and ERROR raises
|
|
395
398
|
|
|
396
|
-
file_type: the server's name for what this file is, eg
|
|
399
|
+
file_type: the server's name for what this file is, eg UploadFileType.DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT.
|
|
397
400
|
Not sent (the server decides from the filename) - if given, the upload only happens when
|
|
398
401
|
accepts_upload(file_type), so an older server doesn't mis-import it as something else """
|
|
399
|
-
if metadata and not self.supports(
|
|
402
|
+
if metadata and not self.supports(ServerFeature.UPLOAD_METADATA):
|
|
400
403
|
self._unsupported(f"upload metadata for '{filename}' (server doesn't support feature 'upload_metadata')")
|
|
401
404
|
metadata = None
|
|
402
405
|
if file_type and not self._require_upload(file_type):
|
|
@@ -473,7 +476,7 @@ class VariantGridAPI:
|
|
|
473
476
|
|
|
474
477
|
Keyed by uploaded_file_id (returned from upload_file) or the SHA-256 of the uploaded file.
|
|
475
478
|
See wait_for_annotation to block until annotation is complete. Needs server feature 'upload_status' """
|
|
476
|
-
if not self._require(
|
|
479
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
477
480
|
return None
|
|
478
481
|
segment = self._upload_key_segment(uploaded_file_id, sha256)
|
|
479
482
|
return self._get(f"upload/api/v1/upload_status/{segment}")
|
|
@@ -490,7 +493,7 @@ class VariantGridAPI:
|
|
|
490
493
|
server is still creating the upload record) are tolerated: up to 'max_transient_errors' *consecutive*
|
|
491
494
|
failures are retried before giving up. A 4xx response is treated as a real error and raised immediately.
|
|
492
495
|
The success counter resets whenever a poll succeeds. Needs server feature 'upload_status' """
|
|
493
|
-
if not self._require(
|
|
496
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
494
497
|
return None
|
|
495
498
|
deadline = time.monotonic() + timeout
|
|
496
499
|
transient_errors = 0
|
|
@@ -542,7 +545,7 @@ class VariantGridAPI:
|
|
|
542
545
|
otherwise it is treated as the full destination path. Returns the Path written.
|
|
543
546
|
|
|
544
547
|
Raises TimeoutError if the file isn't ready within 'timeout' seconds. Needs server feature 'upload_status' """
|
|
545
|
-
if not self._require(
|
|
548
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
546
549
|
return None
|
|
547
550
|
if export_type not in ("vcf", "csv"):
|
|
548
551
|
raise ValueError(f"export_type must be 'vcf' or 'csv', got {export_type!r}")
|
|
@@ -590,7 +593,7 @@ class VariantGridAPI:
|
|
|
590
593
|
|
|
591
594
|
Chains upload_file -> wait_for_annotation -> download_annotated and returns the Path written.
|
|
592
595
|
Needs server feature 'upload_status' - checked before uploading """
|
|
593
|
-
if not self._require(
|
|
596
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
594
597
|
return None
|
|
595
598
|
# path is SeqAuto-only and makes ad-hoc uploads fail the import - omit it for the annotate flow
|
|
596
599
|
upload = self.upload_file(filename, path=None)
|
|
@@ -442,6 +442,47 @@ class SpecimenMeasure:
|
|
|
442
442
|
extraction: Optional[ReferenceLike] = _reference_field(default=None) # the arm that produced it
|
|
443
443
|
|
|
444
444
|
|
|
445
|
+
############################################################
|
|
446
|
+
## Server capabilities (SACGF/variantgrid_api#22)
|
|
447
|
+
|
|
448
|
+
class _ServerName(str, Enum):
|
|
449
|
+
""" A name from the server's vocabulary. Subclasses str so plain strings and these are interchangeable,
|
|
450
|
+
and formats as its value so messages read 'patients' rather than 'ServerFeature.PATIENTS' """
|
|
451
|
+
|
|
452
|
+
def __str__(self):
|
|
453
|
+
return self.value
|
|
454
|
+
|
|
455
|
+
def __format__(self, format_spec):
|
|
456
|
+
return format(self.value, format_spec)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class ServerFeature(_ServerName):
|
|
460
|
+
""" Features a server reports in capabilities (API_FEATURES in the variantgrid repo's
|
|
461
|
+
variantgrid/views_rest.py). Names are never removed - an older server just doesn't list a newer one """
|
|
462
|
+
PATIENTS = "patients"
|
|
463
|
+
SPECIMEN_MEASURES = "specimen_measures"
|
|
464
|
+
LINK_EXTRACTION = "link_extraction"
|
|
465
|
+
UPLOAD_STATUS = "upload_status"
|
|
466
|
+
JOINT_CALLED_VCF_CROSS_RUN = "joint_called_vcf_cross_run"
|
|
467
|
+
UPLOAD_METADATA = "upload_metadata"
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class UploadFileType(_ServerName):
|
|
471
|
+
""" File types a server imports from an upload - the server's upload.models.UploadedFileTypes names in
|
|
472
|
+
lower case, less the internal ones it drives itself. A server only reports those it has an importer for """
|
|
473
|
+
BED = "bed"
|
|
474
|
+
DRAGEN_TSO500_ALL_FUSIONS = "dragen_tso500_all_fusions"
|
|
475
|
+
DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT = "dragen_tso500_combined_variant_output"
|
|
476
|
+
GENE_COVERAGE = "gene_coverage"
|
|
477
|
+
GENE_LIST = "gene_list"
|
|
478
|
+
GENE_LEVEL_CNV_VCF = "gene_level_cnv_vcf"
|
|
479
|
+
GENE_LEVEL_INSERT_VARIANTS_ONLY = "gene_level_insert_variants_only"
|
|
480
|
+
PATIENT_RECORDS = "patient_records"
|
|
481
|
+
PED = "ped"
|
|
482
|
+
VCF = "vcf"
|
|
483
|
+
VCF_INSERT_VARIANTS_ONLY = "vcf_insert_variants_only"
|
|
484
|
+
|
|
485
|
+
|
|
445
486
|
@dataclass(frozen=True)
|
|
446
487
|
class ServerCapabilities:
|
|
447
488
|
""" What a server accepts, from GET api/v1/capabilities (SACGF/variantgrid_sapath#443).
|
|
@@ -16,20 +16,21 @@ import copy
|
|
|
16
16
|
import logging
|
|
17
17
|
import warnings
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Any, List, Optional, Tuple
|
|
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, ServerCapabilities
|
|
22
|
+
from variantgrid_api.data_models import reference_json, ServerCapabilities, ServerFeature, UploadFileType
|
|
23
23
|
|
|
24
24
|
_UNSET = object()
|
|
25
25
|
|
|
26
26
|
# Every feature and upload file type the real client gates on - the shape of a current (VG4) server
|
|
27
27
|
MOCK_CAPABILITIES = ServerCapabilities(
|
|
28
28
|
version="mock",
|
|
29
|
-
features=frozenset(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
features=frozenset(ServerFeature),
|
|
30
|
+
upload_file_types=frozenset({UploadFileType.VCF, UploadFileType.GENE_COVERAGE,
|
|
31
|
+
UploadFileType.DRAGEN_TSO500_ALL_FUSIONS,
|
|
32
|
+
UploadFileType.DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT,
|
|
33
|
+
UploadFileType.GENE_LEVEL_CNV_VCF}),
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
@@ -86,10 +87,10 @@ class MockVariantGridAPI:
|
|
|
86
87
|
# Capabilities — same gating as VariantGridAPI #
|
|
87
88
|
# ------------------------------------------------------------------ #
|
|
88
89
|
|
|
89
|
-
def supports(self, feature: str) -> bool:
|
|
90
|
+
def supports(self, feature: Union[ServerFeature, str]) -> bool:
|
|
90
91
|
return feature in self.capabilities.features
|
|
91
92
|
|
|
92
|
-
def accepts_upload(self, file_type: str) -> bool:
|
|
93
|
+
def accepts_upload(self, file_type: Union[UploadFileType, str]) -> bool:
|
|
93
94
|
return file_type in self.capabilities.upload_file_types
|
|
94
95
|
|
|
95
96
|
def _unsupported(self, message: str) -> bool:
|
|
@@ -99,10 +100,10 @@ class MockVariantGridAPI:
|
|
|
99
100
|
return False
|
|
100
101
|
raise UnsupportedFeatureError(message, self.capabilities)
|
|
101
102
|
|
|
102
|
-
def _require(self, feature: str) -> bool:
|
|
103
|
+
def _require(self, feature: Union[ServerFeature, str]) -> bool:
|
|
103
104
|
return self.supports(feature) or self._unsupported(f"server doesn't support feature '{feature}'")
|
|
104
105
|
|
|
105
|
-
def _require_upload(self, file_type: str) -> bool:
|
|
106
|
+
def _require_upload(self, file_type: Union[UploadFileType, str]) -> bool:
|
|
106
107
|
return self.accepts_upload(file_type) or self._unsupported(f"server doesn't accept upload file type '{file_type}'")
|
|
107
108
|
|
|
108
109
|
# ------------------------------------------------------------------ #
|
|
@@ -181,39 +182,39 @@ class MockVariantGridAPI:
|
|
|
181
182
|
return self._ret("create_multiple_qc_gene_coverage", {"created": len(qc_gene_coverage_list)})
|
|
182
183
|
|
|
183
184
|
def create_patient(self, patient):
|
|
184
|
-
if not self._require(
|
|
185
|
+
if not self._require(ServerFeature.PATIENTS):
|
|
185
186
|
return None
|
|
186
187
|
self._record("create_patient", patient)
|
|
187
188
|
return self._ret("create_patient", {"id": 1, **patient.to_dict()})
|
|
188
189
|
|
|
189
190
|
def create_specimen(self, specimen):
|
|
190
|
-
if not self._require(
|
|
191
|
+
if not self._require(ServerFeature.PATIENTS):
|
|
191
192
|
return None
|
|
192
193
|
self._record("create_specimen", specimen)
|
|
193
194
|
return self._ret("create_specimen", {"id": 1, **specimen.to_dict()})
|
|
194
195
|
|
|
195
196
|
def create_extraction(self, extraction):
|
|
196
|
-
if not self._require(
|
|
197
|
+
if not self._require(ServerFeature.PATIENTS):
|
|
197
198
|
return None
|
|
198
199
|
self._record("create_extraction", extraction)
|
|
199
200
|
return self._ret("create_extraction", {"id": 1, **extraction.to_dict()})
|
|
200
201
|
|
|
201
202
|
def create_specimen_measure(self, specimen_reference, measure):
|
|
202
|
-
if not self._require(
|
|
203
|
+
if not self._require(ServerFeature.SPECIMEN_MEASURES):
|
|
203
204
|
return None
|
|
204
205
|
self._record("create_specimen_measure", specimen_reference, measure)
|
|
205
206
|
return self._ret("create_specimen_measure", {"id": 1, "specimen": reference_json(specimen_reference),
|
|
206
207
|
**measure.to_dict()})
|
|
207
208
|
|
|
208
209
|
def create_specimen_measures(self, specimen_reference, measures):
|
|
209
|
-
if not self._require(
|
|
210
|
+
if not self._require(ServerFeature.SPECIMEN_MEASURES):
|
|
210
211
|
return None
|
|
211
212
|
self._record("create_specimen_measures", specimen_reference, measures)
|
|
212
213
|
return self._ret("create_specimen_measures", {"specimen": reference_json(specimen_reference),
|
|
213
214
|
"measures": [m.to_dict() for m in measures]})
|
|
214
215
|
|
|
215
216
|
def link_sequencing_sample_extraction(self, sequencing_sample_lookup, extraction_reference):
|
|
216
|
-
if not self._require(
|
|
217
|
+
if not self._require(ServerFeature.LINK_EXTRACTION):
|
|
217
218
|
return None
|
|
218
219
|
self._record("link_sequencing_sample_extraction", sequencing_sample_lookup, extraction_reference)
|
|
219
220
|
return self._ret("link_sequencing_sample_extraction", {
|
|
@@ -224,7 +225,7 @@ class MockVariantGridAPI:
|
|
|
224
225
|
})
|
|
225
226
|
|
|
226
227
|
def upload_file(self, filename, path=_UNSET, metadata=None, file_type=None):
|
|
227
|
-
if metadata and not self.supports(
|
|
228
|
+
if metadata and not self.supports(ServerFeature.UPLOAD_METADATA):
|
|
228
229
|
self._unsupported(f"upload metadata for '{filename}' (server doesn't support feature 'upload_metadata')")
|
|
229
230
|
metadata = None
|
|
230
231
|
if file_type and not self._require_upload(file_type):
|
|
@@ -240,7 +241,7 @@ class MockVariantGridAPI:
|
|
|
240
241
|
"path": path, "status": "ok"})
|
|
241
242
|
|
|
242
243
|
def poll_upload_status(self, uploaded_file_id=None, sha256=None):
|
|
243
|
-
if not self._require(
|
|
244
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
244
245
|
return None
|
|
245
246
|
self._record("poll_upload_status", uploaded_file_id, sha256)
|
|
246
247
|
return self._ret("poll_upload_status", {
|
|
@@ -252,7 +253,7 @@ class MockVariantGridAPI:
|
|
|
252
253
|
|
|
253
254
|
def wait_for_annotation(self, uploaded_file_id=None, sha256=None,
|
|
254
255
|
timeout=3600, poll_interval=10, sleep=None, max_transient_errors=5):
|
|
255
|
-
if not self._require(
|
|
256
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
256
257
|
return None
|
|
257
258
|
self._record("wait_for_annotation", uploaded_file_id, sha256,
|
|
258
259
|
timeout=timeout, poll_interval=poll_interval, sleep=sleep,
|
|
@@ -265,7 +266,7 @@ class MockVariantGridAPI:
|
|
|
265
266
|
|
|
266
267
|
def download_annotated(self, uploaded_file_id=None, sha256=None, export_type="vcf",
|
|
267
268
|
dest_path=None, timeout=3600, poll_interval=10, sleep=None):
|
|
268
|
-
if not self._require(
|
|
269
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
269
270
|
return None
|
|
270
271
|
self._record("download_annotated", uploaded_file_id, sha256, export_type=export_type,
|
|
271
272
|
dest_path=dest_path, timeout=timeout, poll_interval=poll_interval, sleep=sleep)
|
|
@@ -274,7 +275,7 @@ class MockVariantGridAPI:
|
|
|
274
275
|
|
|
275
276
|
def annotate_vcf(self, filename, export_type="vcf", dest_path=None,
|
|
276
277
|
timeout=3600, poll_interval=10, sleep=None):
|
|
277
|
-
if not self._require(
|
|
278
|
+
if not self._require(ServerFeature.UPLOAD_STATUS):
|
|
278
279
|
return None
|
|
279
280
|
self._record("annotate_vcf", filename, export_type=export_type, dest_path=dest_path,
|
|
280
281
|
timeout=timeout, poll_interval=poll_interval, sleep=sleep)
|
|
@@ -6,7 +6,8 @@ import logging
|
|
|
6
6
|
import pytest
|
|
7
7
|
import responses
|
|
8
8
|
|
|
9
|
-
from variantgrid_api.api_client import VariantGridAPI, DateTimeEncoder
|
|
9
|
+
from variantgrid_api.api_client import VariantGridAPI, DateTimeEncoder, EmptyInputPolicy
|
|
10
|
+
from variantgrid_api.data_models import BamFile, SingleSampleVCF
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def _last_json():
|
|
@@ -66,6 +67,33 @@ def test_create_sequencing_data_fastq_r2_without_r1_raises(api, vg_objects):
|
|
|
66
67
|
with pytest.raises(ValueError):
|
|
67
68
|
api.create_sequencing_data(vg_objects["sample_sheet_lookup"], [sf])
|
|
68
69
|
|
|
70
|
+
@pytest.mark.parametrize("field, value", [
|
|
71
|
+
("vcf_file", None),
|
|
72
|
+
("vcf_file", SingleSampleVCF(path=None)),
|
|
73
|
+
("vcf_file", SingleSampleVCF(path="")),
|
|
74
|
+
("bam_file", None),
|
|
75
|
+
("bam_file", BamFile(path=None)),
|
|
76
|
+
])
|
|
77
|
+
@responses.activate
|
|
78
|
+
def test_create_sequencing_data_missing_path_names_record(api, vg_objects, field, value):
|
|
79
|
+
""" SACGF/variantgrid_api#23 - caught before sending, naming the record, rather than a 400 for the batch """
|
|
80
|
+
sequencing_files = list(vg_objects["sequencing_files"])
|
|
81
|
+
sequencing_files[1] = dataclasses.replace(sequencing_files[1], **{field: value})
|
|
82
|
+
with pytest.raises(ValueError, match=f"SequencingFile 'fake_sample_2' {field}.path"):
|
|
83
|
+
api.create_sequencing_data(vg_objects["sample_sheet_lookup"], sequencing_files)
|
|
84
|
+
assert len(responses.calls) == 0
|
|
85
|
+
|
|
86
|
+
@responses.activate
|
|
87
|
+
def test_create_sequencing_data_missing_vcf_path_warns_and_posts(server, api_token, vg_objects, caplog):
|
|
88
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.WARN)
|
|
89
|
+
url = f"{server}/seqauto/api/v1/sequencing_files/bulk_create"
|
|
90
|
+
responses.add(responses.POST, url, json={"created": 2}, status=200)
|
|
91
|
+
sequencing_files = list(vg_objects["sequencing_files"])
|
|
92
|
+
sequencing_files[0] = dataclasses.replace(sequencing_files[0], vcf_file=SingleSampleVCF(path=None))
|
|
93
|
+
api.create_sequencing_data(vg_objects["sample_sheet_lookup"], sequencing_files)
|
|
94
|
+
assert len(responses.calls) == 1
|
|
95
|
+
assert any("SequencingFile 'fake_sample_1' vcf_file.path" in r.message for r in caplog.records)
|
|
96
|
+
|
|
69
97
|
def assert_post(api_call, url):
|
|
70
98
|
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
71
99
|
out = api_call()
|
|
@@ -6,7 +6,7 @@ import requests
|
|
|
6
6
|
import responses
|
|
7
7
|
|
|
8
8
|
from variantgrid_api.api_client import VariantGridAPI, UnsupportedFeaturePolicy, UnsupportedFeatureError
|
|
9
|
-
from variantgrid_api.data_models import ServerCapabilities
|
|
9
|
+
from variantgrid_api.data_models import ServerCapabilities, ServerFeature, UploadFileType
|
|
10
10
|
|
|
11
11
|
CVO = "dragen_tso500_combined_variant_output"
|
|
12
12
|
|
|
@@ -53,6 +53,33 @@ def test_capabilities_parsed_and_fetched_once(api, capabilities_url, capabilitie
|
|
|
53
53
|
assert len(_capabilities_calls(capabilities_url)) == 1
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
@responses.activate
|
|
57
|
+
def test_capabilities_accept_enums(api, capabilities_url, capabilities_json):
|
|
58
|
+
""" SACGF/variantgrid_api#22 - the enums are str, so they match the plain names the server sends """
|
|
59
|
+
responses.add(responses.GET, capabilities_url, json=capabilities_json, status=200)
|
|
60
|
+
|
|
61
|
+
assert all(api.supports(feature) for feature in ServerFeature)
|
|
62
|
+
assert api.accepts_upload(UploadFileType.DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT)
|
|
63
|
+
assert not api.accepts_upload(UploadFileType.GENE_LIST)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_enums_are_their_server_names():
|
|
67
|
+
assert ServerFeature.PATIENTS == "patients"
|
|
68
|
+
assert str(UploadFileType.DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT) == CVO
|
|
69
|
+
assert f"'{ServerFeature.UPLOAD_METADATA}'" == "'upload_metadata'"
|
|
70
|
+
for enum_class in (ServerFeature, UploadFileType):
|
|
71
|
+
for member in enum_class:
|
|
72
|
+
assert member.value == member.name.lower()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@responses.activate
|
|
76
|
+
def test_unsupported_message_names_feature_value(api, capabilities_url, vg_objects):
|
|
77
|
+
responses.add(responses.GET, capabilities_url, json={"detail": "Not found."}, status=404)
|
|
78
|
+
|
|
79
|
+
with pytest.raises(UnsupportedFeatureError, match="feature 'patients'"):
|
|
80
|
+
api.create_patient(vg_objects["patient"])
|
|
81
|
+
|
|
82
|
+
|
|
56
83
|
@responses.activate
|
|
57
84
|
def test_capabilities_404_is_legacy(api, capabilities_url):
|
|
58
85
|
responses.add(responses.GET, capabilities_url, json={"detail": "Not found."}, status=404)
|
|
@@ -3,7 +3,7 @@ overrides, and assertion helpers work correctly."""
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
5
|
from variantgrid_api.api_client import UnsupportedFeaturePolicy, UnsupportedFeatureError
|
|
6
|
-
from variantgrid_api.data_models import ServerCapabilities
|
|
6
|
+
from variantgrid_api.data_models import ServerCapabilities, ServerFeature, UploadFileType
|
|
7
7
|
from variantgrid_api.mock_variantgrid_api import MockVariantGridAPI
|
|
8
8
|
|
|
9
9
|
|
|
@@ -240,6 +240,15 @@ def test_mock_default_capabilities_support_everything_gated(mock_api, vg_objects
|
|
|
240
240
|
(("cvo.tsv",), {"path": None, "file_type": "dragen_tso500_combined_variant_output"})]
|
|
241
241
|
|
|
242
242
|
|
|
243
|
+
def test_mock_default_capabilities_accept_enums(mock_api):
|
|
244
|
+
assert all(mock_api.supports(feature) for feature in ServerFeature)
|
|
245
|
+
assert mock_api.supports("upload_status")
|
|
246
|
+
assert mock_api.accepts_upload(UploadFileType.DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT)
|
|
247
|
+
assert not mock_api.accepts_upload(UploadFileType.PED)
|
|
248
|
+
mock_api.upload_file("cvo.tsv", path=None, file_type=UploadFileType.DRAGEN_TSO500_COMBINED_VARIANT_OUTPUT)
|
|
249
|
+
mock_api.assert_called_once("upload_file")
|
|
250
|
+
|
|
251
|
+
|
|
243
252
|
def test_mock_legacy_skips_under_skip(vg_objects):
|
|
244
253
|
mock_api = MockVariantGridAPI(capabilities=ServerCapabilities.LEGACY,
|
|
245
254
|
unsupported_feature_policy=UnsupportedFeaturePolicy.SKIP)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{variantgrid_api-1.5.0 → variantgrid_api-1.6.0}/src/variantgrid_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{variantgrid_api-1.5.0 → variantgrid_api-1.6.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
|