variantgrid-api 1.0.0__tar.gz → 1.1.1__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.0.0/src/variantgrid_api.egg-info → variantgrid_api-1.1.1}/PKG-INFO +32 -2
- variantgrid_api-1.1.1/README.md +32 -0
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/pyproject.toml +4 -2
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/src/variantgrid_api/api_client.py +82 -7
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/src/variantgrid_api/data_models.py +20 -0
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1/src/variantgrid_api.egg-info}/PKG-INFO +32 -2
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/src/variantgrid_api.egg-info/SOURCES.txt +5 -1
- variantgrid_api-1.1.1/src/variantgrid_api.egg-info/requires.txt +7 -0
- variantgrid_api-1.1.1/tests/test_api_client.py +228 -0
- variantgrid_api-1.1.1/tests/test_api_client_bulk.py +46 -0
- variantgrid_api-1.1.1/tests/test_api_client_validation.py +75 -0
- variantgrid_api-1.1.1/tests/test_data_models.py +11 -0
- variantgrid_api-1.0.0/README.md +0 -5
- variantgrid_api-1.0.0/src/variantgrid_api.egg-info/requires.txt +0 -3
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/LICENSE +0 -0
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/setup.cfg +0 -0
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/src/variantgrid_api.egg-info/dependency_links.txt +0 -0
- {variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/src/variantgrid_api.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: variantgrid_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: A Python API client for VariantGrid
|
|
5
5
|
Author-email: Dave Lawrence <davmlaw@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -35,11 +35,41 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
License-File: LICENSE
|
|
36
36
|
Requires-Dist: requests
|
|
37
37
|
Requires-Dist: dataclasses-json
|
|
38
|
-
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-cov>=5; extra == "test"
|
|
41
|
+
Requires-Dist: responses; extra == "test"
|
|
39
42
|
Dynamic: license-file
|
|
40
43
|
|
|
41
44
|
# variantgrid_api
|
|
42
45
|
|
|
46
|
+
[](https://pypi.org/project/variantgrid_api/) [](https://pypi.org/project/variantgrid_api/)
|
|
47
|
+
|
|
43
48
|
Python API client for [VariantGrid](https://github.com/SACGF/variantgrid) Open source Variant database and analysis platform
|
|
44
49
|
|
|
45
50
|
See [changelog](https://github.com/SACGF/variantgrid_api/blob/main/CHANGELOG.md)
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
python3 -m pip install variantgrid_api
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Example
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
from variantgrid_api.api_client import VariantGridAPI
|
|
62
|
+
from variantgrid_api.data_models import EnrichmentKit
|
|
63
|
+
|
|
64
|
+
api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
|
|
65
|
+
enrichment_kit = EnrichmentKit(name="idt_haem", version=1)
|
|
66
|
+
result = api.create_enrichment_kit(enrichment_kit)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Testing
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
# Install required testing packages
|
|
73
|
+
python3 -m pip install -e ".[test]"
|
|
74
|
+
python3 -m pytest --cov=variantgrid_api
|
|
75
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
## Testing
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
# Install required testing packages
|
|
30
|
+
python3 -m pip install -e ".[test]"
|
|
31
|
+
python3 -m pytest --cov=variantgrid_api
|
|
32
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "variantgrid_api"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.1"
|
|
8
8
|
description = "A Python API client for VariantGrid"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Dave Lawrence", email = "davmlaw@gmail.com" }
|
|
@@ -21,9 +21,11 @@ classifiers = [
|
|
|
21
21
|
dependencies = [
|
|
22
22
|
"requests",
|
|
23
23
|
"dataclasses-json",
|
|
24
|
-
"samshee"
|
|
25
24
|
]
|
|
26
25
|
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
test = ["pytest>=8", "pytest-cov>=5", "responses"]
|
|
28
|
+
|
|
27
29
|
[project.urls]
|
|
28
30
|
"Homepage" = "https://github.com/SACGF/variantgrid_api"
|
|
29
31
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
5
|
-
from
|
|
4
|
+
import urllib
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import List, Optional, Callable
|
|
6
7
|
|
|
7
8
|
import requests
|
|
8
9
|
|
|
9
10
|
from variantgrid_api.data_models import EnrichmentKit, SequencingRun, SampleSheet, SampleSheetCombinedVCFFile, \
|
|
10
|
-
SampleSheetLookup, SequencingFile, QCGeneList, QCExecStats, QCGeneCoverage
|
|
11
|
+
SampleSheetLookup, SequencingFile, QCGeneList, QCExecStats, QCGeneCoverage, SequencerModel, Sequencer
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class DateTimeEncoder(json.JSONEncoder):
|
|
13
15
|
def default(self,o):
|
|
@@ -16,18 +18,35 @@ class DateTimeEncoder(json.JSONEncoder):
|
|
|
16
18
|
return super().default(o)
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
class EmptyInputPolicy(Enum):
|
|
22
|
+
IGNORE = "ignore"
|
|
23
|
+
WARN = "warn"
|
|
24
|
+
ERROR = "error"
|
|
25
|
+
|
|
19
26
|
|
|
20
27
|
class VariantGridAPI:
|
|
21
|
-
def __init__(self, server, api_token
|
|
28
|
+
def __init__(self, server, api_token,
|
|
29
|
+
empty_input_policy=EmptyInputPolicy.ERROR,
|
|
30
|
+
logger: Optional[logging.Logger] = None,
|
|
31
|
+
log_request=False, log_response=False
|
|
32
|
+
):
|
|
22
33
|
self.server = server
|
|
23
34
|
self.headers = {"Authorization": f"Token {api_token}"}
|
|
35
|
+
if logger is None:
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
self.logger = logger
|
|
38
|
+
self.validation_handler = self._get_validation_handler(empty_input_policy, logger)
|
|
39
|
+
self.log_request = log_request
|
|
40
|
+
self.log_response = log_response
|
|
24
41
|
|
|
25
42
|
def _get_url(self, url):
|
|
26
|
-
return
|
|
43
|
+
return urllib.parse.urljoin(self.server, url)
|
|
27
44
|
|
|
28
45
|
def _post(self, path, json_data):
|
|
29
46
|
url = self._get_url(path)
|
|
30
47
|
json_string = json.dumps(json_data, cls=DateTimeEncoder)
|
|
48
|
+
if self.log_request:
|
|
49
|
+
self.logger.info("POST to '%s', JSON: %s", url, json_string)
|
|
31
50
|
response = requests.post(url,
|
|
32
51
|
headers={**self.headers, "Content-Type": "application/json"},
|
|
33
52
|
data=json_string)
|
|
@@ -38,30 +57,78 @@ class VariantGridAPI:
|
|
|
38
57
|
def _handle_json_response(self, response, extra_error_message: Optional[str] = None):
|
|
39
58
|
try:
|
|
40
59
|
json_response = response.json()
|
|
60
|
+
if self.log_response:
|
|
61
|
+
self.logger.info("Response from '%s', JSON: %s", response.url, json_response)
|
|
41
62
|
except Exception as e:
|
|
42
63
|
json_response = f"Couldn't convert JSON: {e}"
|
|
43
64
|
if not response.ok:
|
|
44
65
|
if extra_error_message:
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
self.logger.error(extra_error_message)
|
|
67
|
+
self.logger.error("Response: %s", json_response)
|
|
47
68
|
response.raise_for_status()
|
|
48
69
|
return json_response
|
|
49
70
|
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _get_validation_handler(empty_input_policy: EmptyInputPolicy, logger: logging.Logger) -> Callable:
|
|
73
|
+
def _ignore(msg):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def _warn(msg):
|
|
77
|
+
logger.warning(msg)
|
|
78
|
+
|
|
79
|
+
def _error(msg):
|
|
80
|
+
raise ValueError(msg)
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
EmptyInputPolicy.IGNORE: _ignore,
|
|
84
|
+
EmptyInputPolicy.WARN: _warn,
|
|
85
|
+
EmptyInputPolicy.ERROR: _error,
|
|
86
|
+
}[empty_input_policy]
|
|
87
|
+
|
|
88
|
+
def _validate_string(self, info: str, s: Optional[str]) -> None:
|
|
89
|
+
if s is None:
|
|
90
|
+
self.validation_handler(f"{info}: is None")
|
|
91
|
+
elif s == "":
|
|
92
|
+
self.validation_handler(f"{info}: empty string")
|
|
93
|
+
|
|
94
|
+
def _validate_object(self, info: str, obj):
|
|
95
|
+
if obj is None:
|
|
96
|
+
self.validation_handler(f"{info}: is None")
|
|
97
|
+
|
|
98
|
+
def _validate_list(self, info: str, list_obj: List):
|
|
99
|
+
if not list_obj:
|
|
100
|
+
self.validation_handler(f"{info}: empty list")
|
|
101
|
+
|
|
50
102
|
def create_experiment(self, experiment: str):
|
|
103
|
+
self._validate_string("experiment", experiment)
|
|
51
104
|
json_data = {
|
|
52
105
|
"name": experiment
|
|
53
106
|
}
|
|
54
107
|
return self._post("seqauto/api/v1/experiment/", json_data)
|
|
55
108
|
|
|
56
109
|
def create_enrichment_kit(self, enrichment_kit: EnrichmentKit):
|
|
110
|
+
self._validate_object("enrichment_kit", enrichment_kit)
|
|
57
111
|
return self._post("seqauto/api/v1/enrichment_kit/",
|
|
58
112
|
enrichment_kit.to_dict())
|
|
59
113
|
|
|
114
|
+
|
|
115
|
+
def create_sequencer_model(self, sequencer_model: SequencerModel):
|
|
116
|
+
self._validate_object("sequencer_model", sequencer_model)
|
|
117
|
+
return self._post("seqauto/api/v1/sequencer_model/",
|
|
118
|
+
sequencer_model.to_dict())
|
|
119
|
+
|
|
120
|
+
def create_sequencer(self, sequencer: Sequencer):
|
|
121
|
+
self._validate_object("sequencer", sequencer)
|
|
122
|
+
return self._post("seqauto/api/v1/sequencer/",
|
|
123
|
+
sequencer.to_dict())
|
|
124
|
+
|
|
60
125
|
def create_sequencing_run(self, sequencing_run: SequencingRun):
|
|
126
|
+
self._validate_object("sequencing_run", sequencing_run)
|
|
61
127
|
return self._post("seqauto/api/v1/sequencing_run/",
|
|
62
128
|
sequencing_run.to_dict())
|
|
63
129
|
|
|
64
130
|
def create_sample_sheet(self, sample_sheet: SampleSheet):
|
|
131
|
+
self._validate_object("sample_sheet", sample_sheet)
|
|
65
132
|
json_data = sample_sheet.to_dict()
|
|
66
133
|
# We don't want all sequencing_run just the name
|
|
67
134
|
sequencing_run = json_data.pop("sequencing_run")
|
|
@@ -70,11 +137,14 @@ class VariantGridAPI:
|
|
|
70
137
|
json_data)
|
|
71
138
|
|
|
72
139
|
def create_sample_sheet_combined_vcf_file(self, sample_sheet_combined_vcf_file: SampleSheetCombinedVCFFile):
|
|
140
|
+
self._validate_object("sample_sheet_combined_vcf_file", sample_sheet_combined_vcf_file)
|
|
73
141
|
json_data = sample_sheet_combined_vcf_file.to_dict()
|
|
74
142
|
return self._post("seqauto/api/v1/sample_sheet_combined_vcf_file/",
|
|
75
143
|
json_data)
|
|
76
144
|
|
|
77
145
|
def create_sequencing_data(self, sample_sheet_lookup: SampleSheetLookup, sequencing_files: List[SequencingFile]):
|
|
146
|
+
self._validate_object("sample_sheet_lookup", sample_sheet_lookup)
|
|
147
|
+
self._validate_list("sequencing_files", sequencing_files)
|
|
78
148
|
records = []
|
|
79
149
|
for sf in sequencing_files:
|
|
80
150
|
data = sf.to_dict()
|
|
@@ -95,12 +165,14 @@ class VariantGridAPI:
|
|
|
95
165
|
json_data)
|
|
96
166
|
|
|
97
167
|
def create_qc_gene_list(self, qc_gene_list: QCGeneList):
|
|
168
|
+
self._validate_object("qc_gene_list", qc_gene_list)
|
|
98
169
|
json_data = qc_gene_list.to_dict()
|
|
99
170
|
return self._post("seqauto/api/v1/qc_gene_list/",
|
|
100
171
|
json_data)
|
|
101
172
|
|
|
102
173
|
|
|
103
174
|
def create_multiple_qc_gene_lists(self, qc_gene_lists: List[QCGeneList]):
|
|
175
|
+
self._validate_list("qc_gene_lists", qc_gene_lists)
|
|
104
176
|
json_data = {
|
|
105
177
|
"records": [
|
|
106
178
|
qcgl.to_dict() for qcgl in qc_gene_lists
|
|
@@ -110,11 +182,13 @@ class VariantGridAPI:
|
|
|
110
182
|
json_data)
|
|
111
183
|
|
|
112
184
|
def create_qc_exec_stats(self, qc_exec_stats: QCExecStats):
|
|
185
|
+
self._validate_object("qc_exec_stats", qc_exec_stats)
|
|
113
186
|
json_data = qc_exec_stats.to_dict()
|
|
114
187
|
return self._post("seqauto/api/v1/qc_exec_summary/",
|
|
115
188
|
json_data)
|
|
116
189
|
|
|
117
190
|
def create_multiple_qc_exec_stats(self, qc_exec_stats: List[QCExecStats]):
|
|
191
|
+
self._validate_list("qc_exec_stats", qc_exec_stats)
|
|
118
192
|
json_data = {
|
|
119
193
|
"records": [
|
|
120
194
|
qces.to_dict() for qces in qc_exec_stats
|
|
@@ -124,6 +198,7 @@ class VariantGridAPI:
|
|
|
124
198
|
json_data)
|
|
125
199
|
|
|
126
200
|
def create_multiple_qc_gene_coverage(self, qc_gene_coverage_list: List[QCGeneCoverage]):
|
|
201
|
+
self._validate_list("qc_gene_coverage_list", qc_gene_coverage_list)
|
|
127
202
|
json_data = {
|
|
128
203
|
"records": [
|
|
129
204
|
qcgc.to_dict() for qcgc in qc_gene_coverage_list
|
|
@@ -13,6 +13,26 @@ class EnrichmentKit:
|
|
|
13
13
|
version: int
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
@dataclass_json
|
|
17
|
+
@dataclass
|
|
18
|
+
class Manufacturer:
|
|
19
|
+
name: str
|
|
20
|
+
|
|
21
|
+
@dataclass_json
|
|
22
|
+
@dataclass
|
|
23
|
+
class SequencerModel:
|
|
24
|
+
model: str
|
|
25
|
+
manufacturer: Manufacturer
|
|
26
|
+
data_naming_convention: str # 'M' or 'H' for (HiSeq only)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass_json
|
|
30
|
+
@dataclass
|
|
31
|
+
class Sequencer:
|
|
32
|
+
name: str
|
|
33
|
+
sequencer_model: SequencerModel
|
|
34
|
+
|
|
35
|
+
|
|
16
36
|
@dataclass_json
|
|
17
37
|
@dataclass
|
|
18
38
|
class SequencingRun:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: variantgrid_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: A Python API client for VariantGrid
|
|
5
5
|
Author-email: Dave Lawrence <davmlaw@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -35,11 +35,41 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
License-File: LICENSE
|
|
36
36
|
Requires-Dist: requests
|
|
37
37
|
Requires-Dist: dataclasses-json
|
|
38
|
-
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-cov>=5; extra == "test"
|
|
41
|
+
Requires-Dist: responses; extra == "test"
|
|
39
42
|
Dynamic: license-file
|
|
40
43
|
|
|
41
44
|
# variantgrid_api
|
|
42
45
|
|
|
46
|
+
[](https://pypi.org/project/variantgrid_api/) [](https://pypi.org/project/variantgrid_api/)
|
|
47
|
+
|
|
43
48
|
Python API client for [VariantGrid](https://github.com/SACGF/variantgrid) Open source Variant database and analysis platform
|
|
44
49
|
|
|
45
50
|
See [changelog](https://github.com/SACGF/variantgrid_api/blob/main/CHANGELOG.md)
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
python3 -m pip install variantgrid_api
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Example
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
from variantgrid_api.api_client import VariantGridAPI
|
|
62
|
+
from variantgrid_api.data_models import EnrichmentKit
|
|
63
|
+
|
|
64
|
+
api = VariantGridAPI(server="https://variantgrid.com", api_token="YOUR_API_TOKEN")
|
|
65
|
+
enrichment_kit = EnrichmentKit(name="idt_haem", version=1)
|
|
66
|
+
result = api.create_enrichment_kit(enrichment_kit)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Testing
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
# Install required testing packages
|
|
73
|
+
python3 -m pip install -e ".[test]"
|
|
74
|
+
python3 -m pytest --cov=variantgrid_api
|
|
75
|
+
```
|
|
@@ -7,4 +7,8 @@ src/variantgrid_api.egg-info/PKG-INFO
|
|
|
7
7
|
src/variantgrid_api.egg-info/SOURCES.txt
|
|
8
8
|
src/variantgrid_api.egg-info/dependency_links.txt
|
|
9
9
|
src/variantgrid_api.egg-info/requires.txt
|
|
10
|
-
src/variantgrid_api.egg-info/top_level.txt
|
|
10
|
+
src/variantgrid_api.egg-info/top_level.txt
|
|
11
|
+
tests/test_api_client.py
|
|
12
|
+
tests/test_api_client_bulk.py
|
|
13
|
+
tests/test_api_client_validation.py
|
|
14
|
+
tests/test_data_models.py
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import responses
|
|
7
|
+
|
|
8
|
+
from variantgrid_api.api_client import VariantGridAPI, DateTimeEncoder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _last_json():
|
|
12
|
+
r = responses.calls[-1].request
|
|
13
|
+
return json.loads(r.body)
|
|
14
|
+
|
|
15
|
+
@responses.activate
|
|
16
|
+
def test_create_experiment(api, server, vg_objects):
|
|
17
|
+
url = f"{server}/seqauto/api/v1/experiment/"
|
|
18
|
+
responses.add(responses.POST, url, json={"id": 1}, status=200)
|
|
19
|
+
out = api.create_experiment(vg_objects["experiment"])
|
|
20
|
+
assert out == {"id": 1}
|
|
21
|
+
assert responses.calls[-1].request.headers["Authorization"] == "Token TKN"
|
|
22
|
+
|
|
23
|
+
@responses.activate
|
|
24
|
+
def test_create_sample_sheet_rewrites_sequencing_run_to_name(api, server, vg_objects):
|
|
25
|
+
url = f"{server}/seqauto/api/v1/sample_sheet/"
|
|
26
|
+
responses.add(responses.POST, url, json={"id": 2}, status=200)
|
|
27
|
+
api.create_sample_sheet(vg_objects["sample_sheet"])
|
|
28
|
+
body = _last_json()
|
|
29
|
+
assert isinstance(body["sequencing_run"], str)
|
|
30
|
+
|
|
31
|
+
@responses.activate
|
|
32
|
+
def test_create_sequencing_data_builds_unaligned_reads(api, server, vg_objects):
|
|
33
|
+
url = f"{server}/seqauto/api/v1/sequencing_files/bulk_create"
|
|
34
|
+
responses.add(responses.POST, url, json={"created": 2}, status=200)
|
|
35
|
+
api.create_sequencing_data(vg_objects["sample_sheet_lookup"], vg_objects["sequencing_files"])
|
|
36
|
+
body = _last_json()
|
|
37
|
+
assert "records" in body and len(body["records"]) == 2
|
|
38
|
+
r0 = body["records"][0]
|
|
39
|
+
assert "unaligned_reads" in r0
|
|
40
|
+
assert "fastq_r1" in r0["unaligned_reads"] and "path" in r0["unaligned_reads"]["fastq_r1"]
|
|
41
|
+
|
|
42
|
+
def assert_post(api_call, url):
|
|
43
|
+
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
44
|
+
out = api_call()
|
|
45
|
+
assert out == {"ok": True}
|
|
46
|
+
body = json.loads(responses.calls[-1].request.body)
|
|
47
|
+
assert body
|
|
48
|
+
return body
|
|
49
|
+
|
|
50
|
+
@responses.activate
|
|
51
|
+
def test_create_enrichment_kit_posts_json(api, server, vg_objects):
|
|
52
|
+
url = f"{server}/seqauto/api/v1/enrichment_kit/"
|
|
53
|
+
body = assert_post(
|
|
54
|
+
lambda: api.create_enrichment_kit(vg_objects["enrichment_kit"]),
|
|
55
|
+
url
|
|
56
|
+
)
|
|
57
|
+
assert body["name"] == "idt_haem"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@responses.activate
|
|
61
|
+
def test_create_sequencer_model_posts_json(api, server, vg_objects):
|
|
62
|
+
url = f"{server}/seqauto/api/v1/sequencer_model/"
|
|
63
|
+
body = assert_post(
|
|
64
|
+
lambda: api.create_sequencer_model(vg_objects["sequencer_model"]),
|
|
65
|
+
url
|
|
66
|
+
)
|
|
67
|
+
assert body["model"] == "HiSeq 2500"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@responses.activate
|
|
71
|
+
def test_create_sequencer_posts_json(api, server, vg_objects):
|
|
72
|
+
url = f"{server}/seqauto/api/v1/sequencer/"
|
|
73
|
+
body = assert_post(
|
|
74
|
+
lambda: api.create_sequencer(vg_objects["sequencer"]),
|
|
75
|
+
url
|
|
76
|
+
)
|
|
77
|
+
assert body["name"] == "SN1101"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@responses.activate
|
|
81
|
+
def test_create_sequencing_run_posts_json(api, server, vg_objects):
|
|
82
|
+
url = f"{server}/seqauto/api/v1/sequencing_run/"
|
|
83
|
+
body = assert_post(
|
|
84
|
+
lambda: api.create_sequencing_run(vg_objects["sequencing_run"]),
|
|
85
|
+
url
|
|
86
|
+
)
|
|
87
|
+
assert body["name"] == vg_objects["SEQUENCING_RUN_NAME"]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@responses.activate
|
|
91
|
+
def test_create_sample_sheet_combined_vcf_file_posts_json(api, server, vg_objects):
|
|
92
|
+
url = f"{server}/seqauto/api/v1/sample_sheet_combined_vcf_file/"
|
|
93
|
+
body = assert_post(
|
|
94
|
+
lambda: api.create_sample_sheet_combined_vcf_file(
|
|
95
|
+
vg_objects["sample_sheet_combined_vcf_file"]
|
|
96
|
+
),
|
|
97
|
+
url
|
|
98
|
+
)
|
|
99
|
+
assert body["path"].endswith(".vcf.gz")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@responses.activate
|
|
103
|
+
def test_upload_file_calls_expected_url_and_query_param(api, server, vg_objects):
|
|
104
|
+
filename = vg_objects["qc_gene_coverage_list"][0].path
|
|
105
|
+
url = f"{server}/upload/api/v1/file_upload"
|
|
106
|
+
responses.add(responses.POST, url, json={"uploaded": True}, status=200)
|
|
107
|
+
out = api.upload_file(filename)
|
|
108
|
+
assert out == {"uploaded": True}
|
|
109
|
+
assert responses.calls[-1].request.url.startswith(url)
|
|
110
|
+
assert "path=" in responses.calls[-1].request.url
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@responses.activate
|
|
114
|
+
def test_sequencing_run_has_vcf(api, server, vg_objects):
|
|
115
|
+
sequencing_run = vg_objects["sequencing_run"]
|
|
116
|
+
name = sequencing_run.name
|
|
117
|
+
url = f"{server}/seqauto/api/v1/sequencing_run/{name}/"
|
|
118
|
+
responses.add(responses.GET, url, json={"vcf_set":[{"path":"/x/a.vcf.gz"}]}, status=200)
|
|
119
|
+
|
|
120
|
+
assert api.sequencing_run_has_vcf(sequencing_run) is True
|
|
121
|
+
assert api.sequencing_run_has_vcf(sequencing_run, "a.vcf.gz") is True
|
|
122
|
+
assert api.sequencing_run_has_vcf(sequencing_run, "no_vcf_here") is False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@responses.activate
|
|
126
|
+
def test_create_qc_gene_list_posts(api, server, vg_objects):
|
|
127
|
+
url = f"{server}/seqauto/api/v1/qc_gene_list/"
|
|
128
|
+
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
129
|
+
|
|
130
|
+
out = api.create_qc_gene_list(vg_objects["qc_gene_lists"][0])
|
|
131
|
+
assert out == {"ok": True}
|
|
132
|
+
body = _last_json()
|
|
133
|
+
assert "gene_list" in body and len(body["gene_list"]) > 0
|
|
134
|
+
|
|
135
|
+
@responses.activate
|
|
136
|
+
def test_create_qc_exec_stats_posts(api, server, vg_objects):
|
|
137
|
+
url = f"{server}/seqauto/api/v1/qc_exec_summary/"
|
|
138
|
+
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
139
|
+
|
|
140
|
+
out = api.create_qc_exec_stats(vg_objects["qc_exec_stats"][0])
|
|
141
|
+
assert out == {"ok": True}
|
|
142
|
+
body = _last_json()
|
|
143
|
+
assert "reads" in body
|
|
144
|
+
|
|
145
|
+
import responses
|
|
146
|
+
|
|
147
|
+
@responses.activate
|
|
148
|
+
def test_log_request_emits_log(server, api_token, vg_objects, caplog):
|
|
149
|
+
logger = logging.getLogger("variantgrid_api.test")
|
|
150
|
+
caplog.set_level(logging.INFO, logger=logger.name)
|
|
151
|
+
|
|
152
|
+
api = VariantGridAPI(
|
|
153
|
+
server, api_token,
|
|
154
|
+
logger=logger,
|
|
155
|
+
log_request=True
|
|
156
|
+
)
|
|
157
|
+
url = f"{server}/seqauto/api/v1/experiment/"
|
|
158
|
+
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
159
|
+
|
|
160
|
+
api.create_experiment(vg_objects["experiment"])
|
|
161
|
+
|
|
162
|
+
assert any("POST to" in r.message for r in caplog.records)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@responses.activate
|
|
167
|
+
def test_log_response_emits_log(server, api_token, vg_objects, caplog):
|
|
168
|
+
logger = logging.getLogger("variantgrid_api.test")
|
|
169
|
+
caplog.set_level(logging.INFO, logger=logger.name)
|
|
170
|
+
|
|
171
|
+
api = VariantGridAPI(
|
|
172
|
+
server, api_token,
|
|
173
|
+
logger=logger,
|
|
174
|
+
log_response=True
|
|
175
|
+
)
|
|
176
|
+
url = f"{server}/seqauto/api/v1/experiment/"
|
|
177
|
+
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
178
|
+
|
|
179
|
+
api.create_experiment(vg_objects["experiment"])
|
|
180
|
+
|
|
181
|
+
assert any("Response from" in r.message for r in caplog.records)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@responses.activate
|
|
185
|
+
def test_bad_response_raises_and_logs(server, api_token, vg_objects, caplog):
|
|
186
|
+
logger = logging.getLogger("variantgrid_api.test")
|
|
187
|
+
caplog.set_level(logging.INFO, logger=logger.name)
|
|
188
|
+
|
|
189
|
+
api = VariantGridAPI(server, api_token, logger=logger)
|
|
190
|
+
url = f"{server}/seqauto/api/v1/experiment/"
|
|
191
|
+
responses.add(
|
|
192
|
+
responses.POST,
|
|
193
|
+
url,
|
|
194
|
+
json={"detail": "bad request"},
|
|
195
|
+
status=400
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
with pytest.raises(Exception):
|
|
199
|
+
api.create_experiment(vg_objects["experiment"])
|
|
200
|
+
|
|
201
|
+
assert any("Response:" in r.message for r in caplog.records)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@responses.activate
|
|
205
|
+
def test_bad_response_non_json_body(server, api_token, vg_objects):
|
|
206
|
+
api = VariantGridAPI(server, api_token)
|
|
207
|
+
url = f"{server}/seqauto/api/v1/experiment/"
|
|
208
|
+
responses.add(
|
|
209
|
+
responses.POST,
|
|
210
|
+
url,
|
|
211
|
+
body="not json",
|
|
212
|
+
status=500,
|
|
213
|
+
content_type="text/plain"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
with pytest.raises(Exception):
|
|
217
|
+
api.create_experiment(vg_objects["experiment"])
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_datetime_encoder_handles_date_and_rejects_unknown():
|
|
221
|
+
s = json.dumps(
|
|
222
|
+
{"d": datetime.date(2026, 1, 14)},
|
|
223
|
+
cls=DateTimeEncoder
|
|
224
|
+
)
|
|
225
|
+
assert s == '{"d": "2026-01-14"}'
|
|
226
|
+
|
|
227
|
+
with pytest.raises(TypeError):
|
|
228
|
+
json.dumps({"x": object()}, cls=DateTimeEncoder)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import json, responses
|
|
2
|
+
|
|
3
|
+
def assert_bulk_post(api_call, url, n):
|
|
4
|
+
responses.add(responses.POST, url, json={"ok": True}, status=200)
|
|
5
|
+
out = api_call()
|
|
6
|
+
assert out == {"ok": True}
|
|
7
|
+
body = json.loads(responses.calls[-1].request.body)
|
|
8
|
+
assert "records" in body
|
|
9
|
+
assert isinstance(body["records"], list)
|
|
10
|
+
assert len(body["records"]) == n
|
|
11
|
+
assert all(isinstance(r, dict) and r for r in body["records"])
|
|
12
|
+
return body["records"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@responses.activate
|
|
16
|
+
def test_create_multiple_qc_gene_lists(api, server, vg_objects):
|
|
17
|
+
url = f"{server}/seqauto/api/v1/qc_gene_list/bulk_create"
|
|
18
|
+
records = assert_bulk_post(
|
|
19
|
+
lambda: api.create_multiple_qc_gene_lists(vg_objects["qc_gene_lists"]),
|
|
20
|
+
url,
|
|
21
|
+
n=len(vg_objects["qc_gene_lists"])
|
|
22
|
+
)
|
|
23
|
+
assert "gene_list" in records[0]
|
|
24
|
+
assert len(records[0]["gene_list"]) > 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@responses.activate
|
|
28
|
+
def test_create_multiple_qc_exec_stats(api, server, vg_objects):
|
|
29
|
+
url = f"{server}/seqauto/api/v1/qc_exec_summary/bulk_create"
|
|
30
|
+
records = assert_bulk_post(
|
|
31
|
+
lambda: api.create_multiple_qc_exec_stats(vg_objects["qc_exec_stats"]),
|
|
32
|
+
url,
|
|
33
|
+
n=len(vg_objects["qc_exec_stats"])
|
|
34
|
+
)
|
|
35
|
+
assert "reads" in records[0]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@responses.activate
|
|
39
|
+
def test_create_multiple_qc_gene_coverage(api, server, vg_objects):
|
|
40
|
+
url = f"{server}/seqauto/api/v1/qc_gene_coverage/bulk_create"
|
|
41
|
+
records = assert_bulk_post(
|
|
42
|
+
lambda: api.create_multiple_qc_gene_coverage(vg_objects["qc_gene_coverage_list"]),
|
|
43
|
+
url,
|
|
44
|
+
n=len(vg_objects["qc_gene_coverage_list"])
|
|
45
|
+
)
|
|
46
|
+
assert "path" in records[0]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import pytest, responses
|
|
2
|
+
from variantgrid_api.api_client import VariantGridAPI, EmptyInputPolicy
|
|
3
|
+
|
|
4
|
+
import responses
|
|
5
|
+
|
|
6
|
+
def run_validation_call(api_call, url=None, method=responses.POST, response_json={"ok": True}):
|
|
7
|
+
if url:
|
|
8
|
+
responses.add(method, url, json=response_json, status=200)
|
|
9
|
+
return api_call()
|
|
10
|
+
|
|
11
|
+
@responses.activate
|
|
12
|
+
def test_error_policy_empty_list_raises(server, api_token):
|
|
13
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.ERROR)
|
|
14
|
+
|
|
15
|
+
with pytest.raises(ValueError):
|
|
16
|
+
run_validation_call(
|
|
17
|
+
lambda: api.create_multiple_qc_gene_lists([])
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
assert len(responses.calls) == 0
|
|
21
|
+
|
|
22
|
+
@responses.activate
|
|
23
|
+
def test_warn_policy_empty_list_logs_and_posts(server, api_token, caplog):
|
|
24
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.WARN)
|
|
25
|
+
url = f"{server}/seqauto/api/v1/qc_gene_list/bulk_create"
|
|
26
|
+
|
|
27
|
+
out = run_validation_call(
|
|
28
|
+
lambda: api.create_multiple_qc_gene_lists([]),
|
|
29
|
+
url=url
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
assert out == {"ok": True}
|
|
33
|
+
assert len(responses.calls) == 1
|
|
34
|
+
assert any("empty list" in r.message for r in caplog.records)
|
|
35
|
+
|
|
36
|
+
@responses.activate
|
|
37
|
+
def test_ignore_policy_empty_list_posts_silently(server, api_token, caplog):
|
|
38
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.IGNORE)
|
|
39
|
+
url = f"{server}/seqauto/api/v1/qc_gene_list/bulk_create"
|
|
40
|
+
|
|
41
|
+
out = run_validation_call(
|
|
42
|
+
lambda: api.create_multiple_qc_gene_lists([]),
|
|
43
|
+
url=url
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assert out == {"ok": True}
|
|
47
|
+
assert len(responses.calls) == 1
|
|
48
|
+
assert not caplog.records
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
import pytest, responses
|
|
52
|
+
from variantgrid_api.api_client import VariantGridAPI, EmptyInputPolicy
|
|
53
|
+
|
|
54
|
+
@responses.activate
|
|
55
|
+
def test_validate_string_empty_raises(server, api_token):
|
|
56
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.ERROR)
|
|
57
|
+
with pytest.raises(ValueError):
|
|
58
|
+
api.create_experiment("")
|
|
59
|
+
assert len(responses.calls) == 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@responses.activate
|
|
63
|
+
def test_validate_string_none_raises(server, api_token):
|
|
64
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.ERROR)
|
|
65
|
+
with pytest.raises(ValueError):
|
|
66
|
+
api.create_experiment(None)
|
|
67
|
+
assert len(responses.calls) == 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@responses.activate
|
|
71
|
+
def test_validate_object_none_raises(server, api_token):
|
|
72
|
+
api = VariantGridAPI(server, api_token, empty_input_policy=EmptyInputPolicy.ERROR)
|
|
73
|
+
with pytest.raises(ValueError):
|
|
74
|
+
api.create_enrichment_kit(None)
|
|
75
|
+
assert len(responses.calls) == 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from variantgrid_api.data_models import SequencingRun, SampleSheetLookup
|
|
2
|
+
|
|
3
|
+
def test_get_date_from_name(vg_objects):
|
|
4
|
+
d = SequencingRun.get_date_from_name(vg_objects["SEQUENCING_RUN_NAME"])
|
|
5
|
+
assert d is not None
|
|
6
|
+
|
|
7
|
+
def test_sample_sheet_lookup_from_sample_sheet(vg_objects):
|
|
8
|
+
ss = vg_objects["sample_sheet"]
|
|
9
|
+
ssl = SampleSheetLookup.from_sample_sheet(ss)
|
|
10
|
+
d = ssl.to_dict()
|
|
11
|
+
assert d
|
variantgrid_api-1.0.0/README.md
DELETED
|
File without changes
|
|
File without changes
|
{variantgrid_api-1.0.0 → variantgrid_api-1.1.1}/src/variantgrid_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|