-
Notifications
You must be signed in to change notification settings - Fork 2
/
conftest.py
347 lines (264 loc) · 10.8 KB
/
conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#!/usr/bin/env python3
# Copyright (c) TeselaGen Biotechnology, Inc. and its affiliates. All Rights Reserved
# License: MIT
"""PyTest configuration file."""
from __future__ import annotations
from pprint import pprint as pp
from typing import TYPE_CHECKING
import warnings
import pytest
from teselagen.api.client import TeselaGenClient
from teselagen.utils import get_test_configuration_path, get_default_host_name
from teselagen.utils import load_from_json
TEST_API_TOKEN_EXPIRATION_TIME = '30m'
if TYPE_CHECKING:
from typing import Any, Dict, List, Literal, Optional, Set, TypedDict, Union
import typing
from _pytest.config import ExitCode
# from _pytest.config.argparsing import OptionGroup
# from _pytest.config.argparsing import Parser
ImportStatusValue = Union[str, Literal['FINISHED', 'INPROGRESS']]
class Assay(TypedDict, total=True): # noqa: H601
"""Assay `TypedDict`."""
id: str
name: str
class AssayWithExperiment(TypedDict, total=True): # noqa: H601
"""Assay With Experiment `TypedDict`."""
id: str
name: str
experiment: Experiment
class Experiment(TypedDict, total=True): # noqa: H601
"""Experiment `TypedDict`."""
id: str
name: str
class File(TypedDict, total=True): # noqa: H601
"""File `TypedDict`."""
id: str
name: str
importStatus: ImportStatusValue # noqa: N815
experiment: Optional[Experiment] # None
assay: Optional[Assay] # None
Files = Union[List[File], List[Dict[str, Any]]]
Assays = Union[List[Assay], List[AssayWithExperiment], List[Dict[str, Any]]]
# https://pypi.org/project/pytest-xdist/#making-session-scoped-fixtures-execute-only-once
def get_test_configuration() -> dict[str, str]:
"""Loads test configuration and updates with it the default conf.
Default configuration is defined here (see source code below) and it will look for CLI endpoints at a local port.
The configuration file should be a json file with name ".test_configuration" at the lib root folders.
Examples of configuration files:
```json
{
"host_url": "http://host.docker.internal:3000",
"api_token_name": "x-tg-api-token"
}
```
```json
{
"host_url": "http://platform.teselagen.com",
"api_token_name": "x-tg-api-token"
}
```
```json
{
"host_url": "http://platform.teselagen.com",
}
```
"""
DEFAULT_CONFIGURATION: dict[str, str] = {
'host_url': get_default_host_name(),
'api_token_name': 'x-tg-api-token',
}
DEFAULT_CONFIGURATION['host_url'] = DEFAULT_CONFIGURATION['host_url'].strip('/')
configuration = DEFAULT_CONFIGURATION.copy()
# Update if file is found
configuration_filepath = get_test_configuration_path()
if configuration_filepath.is_file():
# Load file
file_conf: dict = load_from_json(filepath=configuration_filepath.absolute())
assert isinstance(file_conf, dict), 'Configuration file should be a JSON file.'
# Check keys are ok
for key in file_conf:
assert key in configuration, f'The key {key} should not be in the test configuration file.'
# Update values
configuration.update(file_conf)
if configuration['host_url'] != DEFAULT_CONFIGURATION['host_url']:
print(f"Host URL was set to: {configuration['host_url']}")
return configuration
def get_host_url(_test_configuration: dict[str, str]) -> str:
return _test_configuration['host_url'].strip('/')
def get_api_token_name(_test_configuration: dict[str, str]) -> str:
"""Returns the name of the token to be used in the API calls."""
return _test_configuration['api_token_name']
def get_client(
host_url: str,
api_token_name: str,
) -> TeselaGenClient:
"""A TeselaGen client instance.
Returns:
(TeselaGenClient) : An instance of TeselaGen client.
"""
return TeselaGenClient(
api_token_name=api_token_name,
host_url=host_url,
)
def get_logged_client(
client: TeselaGenClient,
expiration_time: str,
) -> TeselaGenClient:
"""A logged TEST client instance.
Returns:
(TeselaGenClient) : An instance of the TEST client.
"""
# set-up
client.login(expiration_time=expiration_time)
return client
@pytest.fixture(scope='session')
def test_configuration() -> dict[str, str]:
return get_test_configuration()
@pytest.fixture(scope='session')
def host_url(test_configuration: dict[str, str]) -> str:
"""Returns the host URL."""
return get_host_url(_test_configuration=test_configuration)
@pytest.fixture(scope='session')
def api_token_name(test_configuration: dict[str, str]) -> str:
"""Returns the name of the token to be used in the API calls."""
return get_api_token_name(_test_configuration=test_configuration)
@pytest.fixture(scope='session')
def expiration_time() -> str:
"""Expiration time for API tokens."""
return TEST_API_TOKEN_EXPIRATION_TIME
@pytest.fixture
def client(
host_url: str,
api_token_name: str,
) -> TeselaGenClient:
"""A TeselaGen client instance.
Returns:
(TeselaGenClient) : An instance of TeselaGen client.
"""
return get_client(host_url=host_url, api_token_name=api_token_name)
@pytest.fixture
def logged_client(
client: TeselaGenClient,
expiration_time: str,
) -> typing.Generator[TeselaGenClient, None, None]:
"""A logged TEST client instance.
Returns:
(TeselaGenClient) : An instance of the TEST client.
"""
# set-up
client = get_logged_client(client=client, expiration_time=expiration_time)
# yield
yield client
# tear-down
client.logout()
def clean_test_module_used_for_testing() -> None:
"""Cleanup files and assays."""
print("Starting clean_test_module_used_for_testing")
_test_configuration: dict[str, str] = get_test_configuration()
client: TeselaGenClient = get_logged_client(client=get_client(
host_url=get_host_url(_test_configuration),
api_token_name=get_api_token_name(_test_configuration=_test_configuration)),
expiration_time=TEST_API_TOKEN_EXPIRATION_TIME)
LAB_NAME: str = 'The Test Lab' # noqa: N806
client.select_laboratory(lab_name=LAB_NAME)
# FILES
files: Files = client.test.get_files_info()
filenames_to_remove: Set[str] = {
'EDD_experiment_description_file_BE_designs.csv',
'EDD_experiment_description_file_WT.csv',
'TEST_OD_WT.csv',
'TEST_experiment_description_file_BE_designs.csv',
'TEST_experiment_description_file_WT.csv',
'TEST_external_metabolites_WT.csv',
'TEST_isoprenol_production.csv',
'TEST_metabolomics_WTSM.csv',
'TEST_proteomics_WTSM.csv',
'TEST_transcriptomics_WTSM.csv',
}
if files is not None and len(files) > 0:
# map filenames to file ids, to remove them by name
filename2ids: Dict[str, List[str]] = {}
for file in files:
if file['name'] not in filename2ids:
filename2ids[file['name']] = []
filename2ids[file['name']].append(file['id'])
# remove files from previous tests if they exist in the lab
for filename, ids in filename2ids.items():
if filename in filenames_to_remove:
for id in ids:
pp(f'Deleting {filename} with id {id}')
client.test.delete_file(file_id=id)
REMOVE_ALL_DUPLICATED_FILES: bool = False # noqa: N806
if REMOVE_ALL_DUPLICATED_FILES:
for filename, ids in filename2ids.items():
if len(ids) > 1:
for id in ids:
pp(f'Deleting {filename} with id {id}')
client.test.delete_file(file_id=id)
# ASSAYS
assays: Assays = client.test.get_assays()
assay_names_to_remove: Set[str] = {
'Wild Type External Metabolites', 'Wild Type Optical Density', 'Wild Type Transcriptomics'
}
if assays is not None and len(assays) > 0:
# map assay names to assay ids, to remove them by name
assay_name2ids: Dict[str, List[str]] = {}
for assay in assays:
if assay['name'] not in assay_name2ids:
assay_name2ids[assay['name']] = []
assay_name2ids[assay['name']].append(assay['id'])
# remove assays from previous tests if they exist in the lab
for assay_name, ids in assay_name2ids.items():
if assay_name in assay_names_to_remove:
for id in ids:
pp(f'Deleting {assay_name} with id {id}')
client.test.delete_assay(assay_id=id)
REMOVE_ALL_DUPLICATED_ASSAYS: bool = False # noqa: N806
if REMOVE_ALL_DUPLICATED_ASSAYS:
for assay_name, ids in assay_name2ids.items():
if len(ids) > 1:
for id in ids:
pp(f'Deleting {assay_name} with id {id}')
client.test.delete_assay(assay_id=id)
# MODELS
# NOTE: Do not remove `Teselagen Example Evolutive Model` evolutive model.
model_type: str = 'predictive'
models = None
# models = client.discover.get_models_by_type(model_type=model_type) #TODO: Something is wrong at the API
if models is not None and len(models) > 0:
# map model ids to model names, to show their names if needed when removing them by id
id2model_name: Dict[str, str] = {
model['id']: model['name'] for model in models if model['name'].startswith('Model X times Y')
}
# remove models from previous tests if they exist in the lab
for id, name in id2model_name.items():
pp(f'Deleting {name} with id {id}')
client.discover.delete_model(model_id=id)
client.logout()
def pytest_sessionstart(session: pytest.Session) -> None:
"""A `PyTest` hook.
Called after the `Session` object has been created and before performing collection and entering the run test \
loop.
Args:
session (pytest.Session): The pytest session object.
References:
https://pytest.org/en/6.2.x/reference.html#pytest.hookspec.pytest_sessionstart
"""
clean_test_module_used_for_testing()
pass
def pytest_sessionfinish(
session: pytest.Session,
exitstatus: Union[int, ExitCode],
) -> None:
"""A `PyTest` hook.
Called after whole test run finished, right before returning the exit status to the system.
Args:
session (pytest.Session): The pytest session object.
exitstatus (Union[int, ExitCode]): The status which pytest will return to the system.
References:
https://docs.pytest.org/en/6.2.x/reference.html#pytest.hookspec.pytest_sessionfinish
"""
clean_test_module_used_for_testing()
print() # noqa: T001
print('run status code:', exitstatus) # noqa: T001