Skip to content

Commit 1fd50b0

Browse files
mogmikedhammar
andauthored
Make analysis config_path optional (#523)
## Description The `config_path` of an Analysis is only used when the workflow manager is Slurm and no such file is longer being created for most Nextflow pipelines, therefore it should not be required for analyses that does not have Slurm set as workflow manager. ### Added - Validation that checks that a config_path is set when Slurm is the workflow manager ### Changed - Made config_path optional when adding and persisting analyses in Trailblazer and the workflow manager is not Slurm. --------- Co-authored-by: Alfred Kedhammar <89784800+kedhammar@users.noreply.github.com>
1 parent 5f2b848 commit 1fd50b0

File tree

5 files changed

+128
-31
lines changed

5 files changed

+128
-31
lines changed
Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,103 @@
1+
import json
12
from http import HTTPStatus
23

34
from flask.testing import FlaskClient
5+
from werkzeug.test import TestResponse
46

5-
from trailblazer.constants import TrailblazerPriority, TrailblazerTypes
6-
from trailblazer.dto.create_analysis_request import CreateAnalysisRequest
7+
from trailblazer.store.models import Analysis
78
from trailblazer.store.store import Store
89

910
TYPE_JSON = "application/json"
1011

1112

1213
def test_adding_analysis(client: FlaskClient, store: Store):
1314
# GIVEN a valid request to add an analysis
14-
create_analysis_request = CreateAnalysisRequest(
15-
case_id="case_id",
16-
config_path="config_path",
17-
workflow=TrailblazerTypes.WGS,
18-
priority=TrailblazerPriority.NORMAL,
19-
out_dir="out_dir",
20-
ticket="ticket_id",
21-
type=TrailblazerTypes.WGS,
22-
order_id=123,
15+
data: str = json.dumps(
16+
{
17+
"case_id": "case_id",
18+
"config_path": "config_path",
19+
"order_id": 123,
20+
"out_dir": "out_dir",
21+
"priority": "normal",
22+
"ticket": "ticket_id",
23+
"tower_workflow_id": None,
24+
"type": "wgs",
25+
"workflow": "wgs",
26+
}
2327
)
24-
data: str = create_analysis_request.model_dump_json()
2528

2629
# WHEN sending the request
27-
response = client.post("/api/v1/add-pending-analysis", data=data, content_type=TYPE_JSON)
30+
response: TestResponse = client.post(
31+
"/api/v1/add-pending-analysis", data=data, content_type=TYPE_JSON
32+
)
2833

29-
# THEN it gives a success response
34+
# THEN it gives a success response with json contents
3035
assert response.status_code == HTTPStatus.CREATED
36+
assert response.json
3137

3238
# THEN the analysis was persisted
3339
analysis_id = int(response.json["id"])
3440
assert store.get_analysis_with_id(analysis_id)
41+
42+
43+
def test_adding_analysis_without_config_path(client: FlaskClient, store: Store):
44+
# GIVEN a request to add an analysis with tower as workflow manager and no config_path
45+
data: str = json.dumps(
46+
{
47+
"case_id": "case_id",
48+
"config_path": None,
49+
"order_id": 123,
50+
"out_dir": "out_dir",
51+
"priority": "normal",
52+
"ticket": "ticket_id",
53+
"tower_workflow_id": None,
54+
"type": "wgs",
55+
"workflow": "wgs",
56+
"workflow_manager": "nf_tower",
57+
}
58+
)
59+
60+
# WHEN sending the request
61+
response: TestResponse = client.post(
62+
"/api/v1/add-pending-analysis", data=data, content_type=TYPE_JSON
63+
)
64+
65+
# THEN it gives a success response with json contents
66+
assert response.status_code == HTTPStatus.CREATED
67+
assert response.json
68+
69+
# THEN the analysis was persisted without a config_path
70+
analysis_id = int(response.json["id"])
71+
analysis: Analysis = store.get_analysis_with_id(analysis_id)
72+
assert analysis.config_path is None
73+
74+
75+
def test_adding_analysis_without_config_path_and_slurm_workflow_manager_fails(
76+
client: FlaskClient,
77+
):
78+
# GIVEN a request to add an analysis with slurm as workflow manager and no config_path
79+
data: str = json.dumps(
80+
{
81+
"case_id": "case_id",
82+
"email": None,
83+
"is_hidden": None,
84+
"order_id": 123,
85+
"out_dir": "out_dir",
86+
"priority": "normal",
87+
"ticket": "ticket_id",
88+
"tower_workflow_id": None,
89+
"type": "wgs",
90+
"workflow": "wgs",
91+
"workflow_manager": "slurm",
92+
}
93+
)
94+
95+
# WHEN sending the request
96+
response: TestResponse = client.post(
97+
"/api/v1/add-pending-analysis", data=data, content_type=TYPE_JSON
98+
)
99+
100+
# THEN an error response is returned
101+
assert response.status_code == HTTPStatus.BAD_REQUEST
102+
assert response.json
103+
assert response.json["error"]

tests/store/crud/test_create.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from sqlalchemy import inspect
2+
13
from tests.mocks.store_mock import MockStore
4+
from trailblazer.constants import TrailblazerPriority, TrailblazerTypes
5+
from trailblazer.dto.create_analysis_request import CreateAnalysisRequest
26
from trailblazer.store.filters.user_filters import UserFilter, apply_user_filter
3-
from trailblazer.store.models import User
7+
from trailblazer.store.models import Analysis, User
48

59

610
def test_add_user(store: MockStore, user_email: str, username: str):
@@ -19,3 +23,19 @@ def test_add_user(store: MockStore, user_email: str, username: str):
1923
).first()
2024
assert new_user
2125
assert user == new_user
26+
27+
28+
def test_add_pending_analysis(store: MockStore):
29+
# GIVEN a valid CreateAnalysisRequest
30+
analysis_data = CreateAnalysisRequest(
31+
case_id="pending_analysis_case_id",
32+
out_dir="/out/dir",
33+
priority=TrailblazerPriority.LOW,
34+
type=TrailblazerTypes.WGS,
35+
)
36+
37+
# WHEN creating and storing a pending analysis
38+
analysis: Analysis = store.add_pending_analysis(analysis_data)
39+
40+
# THEN an analysis has been created and persisted to the database
41+
assert inspect(analysis).persistent

trailblazer/constants.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ class FileExtension(StrEnum):
2626
class WorkflowManager(StrEnum):
2727
"""Supported task managers."""
2828

29-
SLURM: str = "slurm"
30-
TOWER: str = "nf_tower"
29+
SLURM = "slurm"
30+
TOWER = "nf_tower"
3131

3232
@classmethod
3333
def list(cls) -> list:
@@ -89,22 +89,22 @@ class CharacterFormat(StrEnum):
8989
class TrailblazerTypes(StrEnum):
9090
"""Trailblazer analysis types."""
9191

92-
OTHER: str = "other"
93-
RNA: str = "rna"
94-
TGS: str = "tgs"
95-
WES: str = "wes"
96-
WGS: str = "wgs"
97-
WTS: str = "wts"
92+
OTHER = "other"
93+
RNA = "rna"
94+
TGS = "tgs"
95+
WES = "wes"
96+
WGS = "wgs"
97+
WTS = "wts"
9898

9999

100100
class TrailblazerPriority(StrEnum):
101101
"""Trailblazer analysis priorities."""
102102

103-
LOW: str = "low"
104-
NORMAL: str = "normal"
105-
HIGH: str = "high"
106-
EXPRESS: str = "express"
107-
MAINTENANCE: str = "maintenance"
103+
LOW = "low"
104+
NORMAL = "normal"
105+
HIGH = "high"
106+
EXPRESS = "express"
107+
MAINTENANCE = "maintenance"
108108

109109

110110
class TrailblazerStatus(StrEnum):

trailblazer/dto/create_analysis_request.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
from pydantic import BaseModel
1+
from typing import Self
2+
3+
from pydantic import BaseModel, model_validator
24

35
from trailblazer.constants import TrailblazerPriority, TrailblazerTypes, WorkflowManager
46

57

68
class CreateAnalysisRequest(BaseModel):
79
case_id: str
8-
config_path: str
10+
config_path: str | None = None
911
email: str | None = None
1012
is_hidden: bool | None = None
1113
order_id: int | None = None
@@ -16,3 +18,9 @@ class CreateAnalysisRequest(BaseModel):
1618
type: TrailblazerTypes
1719
workflow: str | None = None
1820
workflow_manager: WorkflowManager | None = None
21+
22+
@model_validator(mode="after")
23+
def check_config_path_set_for_slurm_workflow_manager(self) -> Self:
24+
if (self.workflow_manager == WorkflowManager.SLURM) and (not self.config_path):
25+
raise ValueError("config_path needs to be set when SLURM is workflow manager")
26+
return self

trailblazer/store/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class Analysis(Model):
7575
case_id = Column(types.String(128), nullable=False)
7676
comment = Column(types.Text)
7777
completed_at = Column(types.DateTime)
78-
config_path = Column(types.Text)
78+
config_path = Column(types.Text, nullable=True, default=None)
7979
id = Column(types.Integer, primary_key=True)
8080
is_visible = Column(types.Boolean, default=True)
8181
logged_at = Column(types.DateTime, default=datetime.datetime.now)

0 commit comments

Comments
 (0)