• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Clinical-Genomics / cg / 12294344407

12 Dec 2024 10:19AM UTC coverage: 80.834%. First build
12294344407

Pull #3936

github

web-flow
Merge 82f219ba6 into ab9ded5a4
Pull Request #3936: (Improve order flow) Minimal implementation FASTQSubmission

34 of 52 new or added lines in 11 files covered. (65.38%)

13648 of 16884 relevant lines covered (80.83%)

1.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.79
/cg/server/endpoints/orders.py
1
import json
1✔
2
import logging
1✔
3
import tempfile
1✔
4
from http import HTTPStatus
1✔
5
from pathlib import Path
1✔
6

7
from flask import Blueprint, abort, g, jsonify, make_response, request
1✔
8
from pydantic.v1 import ValidationError
1✔
9
from requests.exceptions import HTTPError
1✔
10
from sqlalchemy.exc import IntegrityError
1✔
11
from urllib3.exceptions import MaxRetryError, NewConnectionError
1✔
12
from werkzeug.utils import secure_filename
1✔
13

14
from cg.apps.orderform.excel_orderform_parser import ExcelOrderformParser
1✔
15
from cg.apps.orderform.json_orderform_parser import JsonOrderformParser
1✔
16
from cg.constants import ANALYSIS_SOURCES, METAGENOME_SOURCES
1✔
17
from cg.constants.constants import FileFormat
1✔
18
from cg.exc import (
1✔
19
    OrderError,
20
    OrderExistsError,
21
    OrderFormError,
22
    OrderNotDeliverableError,
23
    OrderNotFoundError,
24
    TicketCreationError,
25
)
26
from cg.io.controller import WriteStream
1✔
27
from cg.meta.orders import OrdersAPI
1✔
28
from cg.models.orders.order import OrderType
1✔
29
from cg.models.orders.orderform_schema import Orderform
1✔
30
from cg.server.dto.delivery_message.delivery_message_response import DeliveryMessageResponse
1✔
31
from cg.server.dto.orders.order_delivery_update_request import OrderOpenUpdateRequest
1✔
32
from cg.server.dto.orders.order_patch_request import OrderOpenPatch
1✔
33
from cg.server.dto.orders.orders_request import OrdersRequest
1✔
34
from cg.server.dto.orders.orders_response import Order, OrdersResponse
1✔
35
from cg.server.endpoints.utils import before_request
1✔
36
from cg.server.ext import (
1✔
37
    balsamic_umi_validation_service,
38
    balsamic_validation_service,
39
    db,
40
    delivery_message_service,
41
    fastq_validation_service,
42
    lims,
43
    metagenome_validation_service,
44
    microbial_fastq_validation_service,
45
    microsalt_validation_service,
46
    mip_dna_validation_service,
47
    mutant_validation_service,
48
    order_service,
49
    order_submitter_registry,
50
    pacbio_long_read_validation_service,
51
    rna_fusion_validation_service,
52
    taxprofiler_validation_service,
53
    ticket_handler,
54
    tomte_validation_service,
55
)
56
from cg.store.models import Application, Customer
1✔
57

58
ORDERS_BLUEPRINT = Blueprint("orders", __name__, url_prefix="/api/v1")
1✔
59
ORDERS_BLUEPRINT.before_request(before_request)
1✔
60

61
LOG = logging.getLogger(__name__)
1✔
62

63

64
@ORDERS_BLUEPRINT.route("/orders")
1✔
65
def get_orders():
1✔
66
    """Return the latest orders."""
67
    data = OrdersRequest.model_validate(request.args.to_dict())
1✔
68
    response: OrdersResponse = order_service.get_orders(data)
1✔
69
    return make_response(response.model_dump())
1✔
70

71

72
@ORDERS_BLUEPRINT.route("/orders/<order_id>")
1✔
73
def get_order(order_id: int):
1✔
74
    """Return an order."""
75
    try:
1✔
76
        response: Order = order_service.get_order(order_id)
1✔
77
        response_dict: dict = response.model_dump()
1✔
78
        return make_response(response_dict)
1✔
79
    except OrderNotFoundError as error:
1✔
80
        return make_response(jsonify(error=str(error)), HTTPStatus.NOT_FOUND)
1✔
81

82

83
@ORDERS_BLUEPRINT.route("/orders/<order_id>/open", methods=["PATCH"])
1✔
84
def set_order_open(order_id: int):
1✔
85
    try:
×
86
        request_data = OrderOpenPatch.model_validate(request.json)
×
87
        is_open: bool = request_data.open
×
88
        response_data: Order = order_service.set_open(order_id=order_id, open=is_open)
×
89
        return jsonify(response_data.model_dump()), HTTPStatus.OK
×
90
    except OrderNotFoundError as error:
×
91
        return jsonify(error=str(error)), HTTPStatus.NOT_FOUND
×
92

93

94
@ORDERS_BLUEPRINT.route("/orders/<order_id>/update-open-status", methods=["POST"])
1✔
95
def update_order_open(order_id: int):
1✔
96
    """Update the is_open parameter of an order based on the number of delivered analyses."""
97
    try:
×
98
        request_data = OrderOpenUpdateRequest.model_validate(request.json)
×
99
        delivered_analyses: int = request_data.delivered_analyses_count
×
100
        order_service.update_is_open(order_id=order_id, delivered_analyses=delivered_analyses)
×
101
    except OrderNotFoundError as error:
×
102
        return jsonify(error=str(error)), HTTPStatus.NOT_FOUND
×
103

104

105
@ORDERS_BLUEPRINT.route("/orders/<order_id>/delivery_message")
1✔
106
def get_delivery_message_for_order(order_id: int):
1✔
107
    """Return the delivery message for an order."""
108
    try:
1✔
109
        response: DeliveryMessageResponse = delivery_message_service.get_order_message(order_id)
1✔
110
        response_dict: dict = response.model_dump()
1✔
111
        return make_response(response_dict)
1✔
112
    except OrderNotDeliverableError as error:
1✔
113
        return make_response(jsonify(error=str(error)), HTTPStatus.PRECONDITION_FAILED)
1✔
114
    except OrderNotFoundError as error:
×
115
        return make_response(jsonify(error=str(error))), HTTPStatus.NOT_FOUND
×
116

117

118
@ORDERS_BLUEPRINT.route("/orderform", methods=["POST"])
1✔
119
def create_order_from_form():
1✔
120
    """Parse an orderform/JSON export."""
121
    input_file = request.files.get("file")
×
122
    filename = secure_filename(input_file.filename)
×
123

124
    error_message: str
125
    try:
×
126
        if filename.lower().endswith(".xlsx"):
×
127
            temp_dir = Path(tempfile.gettempdir())
×
128
            saved_path = str(temp_dir / filename)
×
129
            input_file.save(saved_path)
×
130
            order_parser = ExcelOrderformParser()
×
131
            order_parser.parse_orderform(excel_path=saved_path)
×
132
        else:
133
            json_data = json.load(input_file.stream, strict=False)
×
134
            order_parser = JsonOrderformParser()
×
135
            order_parser.parse_orderform(order_data=json_data)
×
136
        parsed_order: Orderform = order_parser.generate_orderform()
×
137
    except (  # user misbehaviour
×
138
        AttributeError,
139
        OrderFormError,
140
        OverflowError,
141
        ValidationError,
142
        ValueError,
143
    ) as error:
144
        error_message = error.message if hasattr(error, "message") else str(error)
×
145
        LOG.error(error_message)
×
146
        http_error_response = HTTPStatus.BAD_REQUEST
×
147
    except (  # system misbehaviour
×
148
        NewConnectionError,
149
        MaxRetryError,
150
        TimeoutError,
151
        TypeError,
152
    ) as error:
153
        LOG.exception(error)
×
154
        error_message = error.message if hasattr(error, "message") else str(error)
×
155
        http_error_response = HTTPStatus.INTERNAL_SERVER_ERROR
×
156
    else:
157
        return jsonify(**parsed_order.model_dump())
×
158

159
    if error_message:
×
160
        return abort(make_response(jsonify(message=error_message), http_error_response))
×
161

162

163
@ORDERS_BLUEPRINT.route("/submit_order/<order_type>", methods=["POST"])
1✔
164
def submit_order(order_type: OrderType):
1✔
165
    """Submit an order for samples."""
166
    api = OrdersAPI(
×
167
        lims=lims,
168
        status=db,
169
        ticket_handler=ticket_handler,
170
        submitter_registry=order_submitter_registry,
171
    )
172
    error_message: str
173
    try:
×
174
        request_json = request.get_json()
×
175
        LOG.info(
×
176
            "processing order: %s",
177
            WriteStream.write_stream_from_content(
178
                content=request_json, file_format=FileFormat.JSON
179
            ),
180
        )
NEW
181
        request_json["delivery_type"] = request_json["data_delivery"]
×
NEW
182
        request_json["project_type"] = order_type
×
NEW
183
        request_json["user_id"] = g.current_user.id
×
184

185
        result: dict = api.submit(
×
186
            raw_order=request_json,
187
            order_type=order_type,
188
            user_name=g.current_user.name,
189
            user_mail=g.current_user.email,
190
        )
191

192
    except (  # user misbehaviour
×
193
        OrderError,
194
        OrderExistsError,
195
        OrderFormError,
196
        ValidationError,
197
        ValueError,
198
    ) as error:
199
        error_message = error.message if hasattr(error, "message") else str(error)
×
200
        http_error_response = HTTPStatus.BAD_REQUEST
×
201
        LOG.error(error_message)
×
202
    except (  # system misbehaviour
×
203
        AttributeError,
204
        ConnectionError,
205
        HTTPError,
206
        IntegrityError,
207
        KeyError,
208
        NewConnectionError,
209
        MaxRetryError,
210
        TimeoutError,
211
        TicketCreationError,
212
        TypeError,
213
    ) as error:
214
        LOG.exception(error)
×
215
        error_message = error.message if hasattr(error, "message") else str(error)
×
216
        http_error_response = HTTPStatus.INTERNAL_SERVER_ERROR
×
217
    else:
218
        return jsonify(
×
219
            project=result["project"], records=[record.to_dict() for record in result["records"]]
220
        )
221

222
    if error_message:
×
223
        return abort(make_response(jsonify(message=error_message), http_error_response))
×
224

225

226
@ORDERS_BLUEPRINT.route("/options")
1✔
227
def get_options():
1✔
228
    """Return various options."""
229
    customers: list[Customer | None] = (
×
230
        db.get_customers() if g.current_user.is_admin else g.current_user.customers
231
    )
232

233
    app_tag_groups: dict[str, list[str]] = {"ext": []}
×
234
    applications: list[Application] = db.get_applications_is_not_archived()
×
235
    for application in applications:
×
236
        if not application.versions:
×
237
            LOG.debug(f"Skipping application {application} that doesn't have a price")
×
238
            continue
×
239
        if application.is_external:
×
240
            app_tag_groups["ext"].append(application.tag)
×
241
        if application.prep_category not in app_tag_groups:
×
242
            app_tag_groups[application.prep_category]: list[str] = []
×
243
        app_tag_groups[application.prep_category].append(application.tag)
×
244

245
    source_groups = {"metagenome": METAGENOME_SOURCES, "analysis": ANALYSIS_SOURCES}
×
246

247
    return jsonify(
×
248
        applications=app_tag_groups,
249
        beds=[bed.name for bed in db.get_active_beds()],
250
        customers=[
251
            {
252
                "text": f"{customer.name} ({customer.internal_id})",
253
                "value": customer.internal_id,
254
                "isTrusted": customer.is_trusted,
255
            }
256
            for customer in customers
257
        ],
258
        organisms=[
259
            {
260
                "name": organism.name,
261
                "reference_genome": organism.reference_genome,
262
                "internal_id": organism.internal_id,
263
                "verified": organism.verified,
264
            }
265
            for organism in db.get_all_organisms()
266
        ],
267
        panels=[panel.abbrev for panel in db.get_panels()],
268
        sources=source_groups,
269
    )
270

271

272
@ORDERS_BLUEPRINT.route("/validate_order/<order_type>", methods=["POST"])
1✔
273
def validate_order(order_type: OrderType):
1✔
274
    raw_order = request.get_json()
×
275
    raw_order["project_type"] = order_type
×
276
    raw_order["user_id"] = g.current_user.id
×
277
    response = {}
×
278
    if order_type == OrderType.BALSAMIC:
×
279
        response = balsamic_validation_service.validate(raw_order)
×
280
    elif order_type == OrderType.BALSAMIC_UMI:
×
281
        response = balsamic_umi_validation_service.validate(raw_order)
×
282
    elif order_type == OrderType.FASTQ:
×
283
        response = fastq_validation_service.validate(raw_order)
×
284
    elif order_type == OrderType.METAGENOME:
×
285
        response = metagenome_validation_service.validate(raw_order)
×
286
    elif order_type == OrderType.MICROBIAL_FASTQ:
×
287
        response = microbial_fastq_validation_service.validate(raw_order)
×
288
    elif order_type == OrderType.MICROSALT:
×
289
        response = microsalt_validation_service.validate(raw_order)
×
290
    elif order_type == OrderType.MIP_DNA:
×
291
        response = mip_dna_validation_service.validate(raw_order)
×
292
    elif order_type == OrderType.PACBIO_LONG_READ:
×
293
        response = pacbio_long_read_validation_service.validate(raw_order)
×
294
    elif order_type == OrderType.SARS_COV_2:
×
295
        response = mutant_validation_service.validate(raw_order)
×
296
    elif order_type == OrderType.RNAFUSION:
×
297
        response = rna_fusion_validation_service.validate(raw_order)
×
298
    elif order_type == OrderType.TAXPROFILER:
×
299
        response = taxprofiler_validation_service.validate(raw_order)
×
300
    elif order_type == OrderType.TOMTE:
×
301
        response = tomte_validation_service.validate(raw_order)
×
302
    return jsonify(response), HTTPStatus.OK
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc