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

Clinical-Genomics / cg / 10455843202

19 Aug 2024 03:01PM UTC coverage: 84.817%. First build
10455843202

Pull #3600

github

web-flow
Merge 69c669130 into 51aab0748
Pull Request #3600: (Improve order flow) Add sample application exists validation

31 of 32 new or added lines in 5 files covered. (96.88%)

21814 of 25719 relevant lines covered (84.82%)

0.85 hits per line

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

39.27
/cg/server/api.py
1
import json
1✔
2
import logging
1✔
3
import tempfile
1✔
4
from functools import wraps
1✔
5
from http import HTTPStatus
1✔
6
from pathlib import Path
1✔
7
from typing import Any
1✔
8

9
import cachecontrol
1✔
10
import requests
1✔
11
from flask import Blueprint, abort, current_app, g, jsonify, make_response, request
1✔
12
from google.auth import exceptions
1✔
13
from google.auth.transport import requests as google_requests
1✔
14
from google.oauth2 import id_token
1✔
15
from pydantic.v1 import ValidationError
1✔
16
from requests.exceptions import HTTPError
1✔
17
from sqlalchemy.exc import IntegrityError
1✔
18
from urllib3.exceptions import MaxRetryError, NewConnectionError
1✔
19
from werkzeug.utils import secure_filename
1✔
20

21
from cg.apps.orderform.excel_orderform_parser import ExcelOrderformParser
1✔
22
from cg.apps.orderform.json_orderform_parser import JsonOrderformParser
1✔
23
from cg.constants import ANALYSIS_SOURCES, METAGENOME_SOURCES
1✔
24
from cg.constants.constants import FileFormat
1✔
25
from cg.exc import (
1✔
26
    CaseNotFoundError,
27
    OrderError,
28
    OrderExistsError,
29
    OrderFormError,
30
    OrderMismatchError,
31
    OrderNotDeliverableError,
32
    OrderNotFoundError,
33
    TicketCreationError,
34
)
35
from cg.io.controller import WriteStream
1✔
36
from cg.meta.orders import OrdersAPI
1✔
37
from cg.meta.orders.ticket_handler import TicketHandler
1✔
38
from cg.models.orders.order import OrderIn, OrderType
1✔
39
from cg.models.orders.orderform_schema import Orderform
1✔
40
from cg.server.dto.delivery_message.delivery_message_request import (
1✔
41
    DeliveryMessageRequest,
42
)
43
from cg.server.dto.delivery_message.delivery_message_response import (
1✔
44
    DeliveryMessageResponse,
45
)
46
from cg.server.dto.orders.order_delivery_update_request import (
1✔
47
    OrderDeliveredUpdateRequest,
48
)
49
from cg.server.dto.orders.order_patch_request import OrderDeliveredPatch
1✔
50
from cg.server.dto.orders.orders_request import OrdersRequest
1✔
51
from cg.server.dto.orders.orders_response import Order, OrdersResponse
1✔
52
from cg.server.dto.sequencing_metrics.sequencing_metrics_request import (
1✔
53
    SequencingMetricsRequest,
54
)
55
from cg.server.ext import db, delivery_message_service, lims, order_service, osticket
1✔
56
from cg.server.utils import parse_metrics_into_request
1✔
57
from cg.store.models import (
1✔
58
    Analysis,
59
    Application,
60
    ApplicationLimitations,
61
    Case,
62
    Customer,
63
    IlluminaSampleSequencingMetrics,
64
    Pool,
65
    Sample,
66
    User,
67
)
68

69
LOG = logging.getLogger(__name__)
1✔
70
BLUEPRINT = Blueprint("api", __name__, url_prefix="/api/v1")
1✔
71

72

73
session = requests.session()
1✔
74
cached_session = cachecontrol.CacheControl(session)
1✔
75

76

77
def verify_google_token(token):
1✔
78
    request = google_requests.Request(session=cached_session)
×
79
    return id_token.verify_oauth2_token(id_token=token, request=request)
×
80

81

82
def is_public(route_function):
1✔
83
    @wraps(route_function)
1✔
84
    def public_endpoint(*args, **kwargs):
1✔
85
        return route_function(*args, **kwargs)
×
86

87
    public_endpoint.is_public = True
1✔
88
    return public_endpoint
1✔
89

90

91
@BLUEPRINT.before_request
1✔
92
def before_request():
1✔
93
    """Authorize API routes with JSON Web Tokens."""
94
    if not request.is_secure:
×
95
        return abort(
×
96
            make_response(jsonify(message="Only https requests accepted"), HTTPStatus.FORBIDDEN)
97
        )
98

99
    if request.method == "OPTIONS":
×
100
        return make_response(jsonify(ok=True), HTTPStatus.NO_CONTENT)
×
101

102
    endpoint_func = current_app.view_functions[request.endpoint]
×
103
    if getattr(endpoint_func, "is_public", None):
×
104
        return
×
105

106
    auth_header = request.headers.get("Authorization")
×
107
    if not auth_header:
×
108
        return abort(
×
109
            make_response(jsonify(message="no JWT token found on request"), HTTPStatus.UNAUTHORIZED)
110
        )
111

112
    jwt_token = auth_header.split("Bearer ")[-1]
×
113
    try:
×
114
        user_data = verify_google_token(jwt_token)
×
115
    except (exceptions.OAuthError, ValueError) as e:
×
116
        LOG.error(f"Error {e} occurred while decoding JWT token: {jwt_token}")
×
117
        return abort(
×
118
            make_response(jsonify(message="outdated login certificate"), HTTPStatus.UNAUTHORIZED)
119
        )
120

121
    user: User = db.get_user_by_email(user_data["email"])
×
122
    if user is None or not user.order_portal_login:
×
123
        message = f"{user_data['email']} doesn't have access"
×
124
        LOG.error(message)
×
125
        return abort(make_response(jsonify(message=message), HTTPStatus.FORBIDDEN))
×
126

127
    g.current_user = user
×
128

129

130
@BLUEPRINT.route("/submit_order/<order_type>", methods=["POST"])
1✔
131
def submit_order(order_type):
1✔
132
    """Submit an order for samples."""
133
    api = OrdersAPI(lims=lims, status=db, osticket=osticket)
×
134
    error_message: str
135
    try:
×
136
        request_json = request.get_json()
×
137
        LOG.info(
×
138
            "processing order: %s",
139
            WriteStream.write_stream_from_content(
140
                content=request_json, file_format=FileFormat.JSON
141
            ),
142
        )
143
        project = OrderType(order_type)
×
144
        order_in = OrderIn.parse_obj(request_json, project=project)
×
145
        existing_ticket: str | None = TicketHandler.parse_ticket_number(order_in.name)
×
146
        if existing_ticket and order_service.store.get_order_by_ticket_id(existing_ticket):
×
147
            raise OrderExistsError(f"Order with ticket id {existing_ticket} already exists.")
×
148

149
        result: dict = api.submit(
×
150
            project=project,
151
            order_in=order_in,
152
            user_name=g.current_user.name,
153
            user_mail=g.current_user.email,
154
        )
NEW
155
        order_service.create_tomte_order(order_in)
×
156

157
    except (  # user misbehaviour
×
158
        OrderError,
159
        OrderExistsError,
160
        OrderFormError,
161
        ValidationError,
162
        ValueError,
163
    ) as error:
164
        error_message = error.message if hasattr(error, "message") else str(error)
×
165
        http_error_response = HTTPStatus.BAD_REQUEST
×
166
        LOG.error(error_message)
×
167
    except (  # system misbehaviour
×
168
        AttributeError,
169
        ConnectionError,
170
        HTTPError,
171
        IntegrityError,
172
        KeyError,
173
        NewConnectionError,
174
        MaxRetryError,
175
        TimeoutError,
176
        TicketCreationError,
177
        TypeError,
178
    ) as error:
179
        LOG.exception(error)
×
180
        error_message = error.message if hasattr(error, "message") else str(error)
×
181
        http_error_response = HTTPStatus.INTERNAL_SERVER_ERROR
×
182
    else:
183
        return jsonify(
×
184
            project=result["project"], records=[record.to_dict() for record in result["records"]]
185
        )
186

187
    if error_message:
×
188
        return abort(make_response(jsonify(message=error_message), http_error_response))
×
189

190

191
@BLUEPRINT.route("/cases")
1✔
192
def get_cases():
1✔
193
    """Return cases with links for a customer from the database."""
194
    enquiry: str = request.args.get("enquiry")
×
195
    action: str = request.args.get("action")
×
196

197
    customers: list[Customer] = _get_current_customers()
×
198
    cases: list[Case] = _get_cases(enquiry=enquiry, action=action, customers=customers)
×
199

200
    nr_cases: int = len(cases)
×
201
    cases_with_links: list[dict] = [case.to_dict(links=True) for case in cases]
×
202
    return jsonify(families=cases_with_links, total=nr_cases)
×
203

204

205
def _get_current_customers() -> list[Customer] | None:
1✔
206
    """Return customers if the current user is not an admin."""
207
    return g.current_user.customers if not g.current_user.is_admin else None
×
208

209

210
def _get_cases(
1✔
211
    enquiry: str | None, action: str | None, customers: list[Customer] | None
212
) -> list[Case]:
213
    """Get cases based on the provided filters."""
214
    return db.get_cases_by_customers_action_and_case_search(
×
215
        case_search=enquiry,
216
        customers=customers,
217
        action=action,
218
    )
219

220

221
@BLUEPRINT.route("/cases/<case_id>")
1✔
222
def parse_case(case_id):
1✔
223
    """Return a case with links."""
224
    case: Case = db.get_case_by_internal_id(internal_id=case_id)
×
225
    if case is None:
×
226
        return abort(HTTPStatus.NOT_FOUND)
×
227
    if not g.current_user.is_admin and (case.customer not in g.current_user.customers):
×
228
        return abort(HTTPStatus.FORBIDDEN)
×
229
    return jsonify(**case.to_dict(links=True, analyses=True))
×
230

231

232
@BLUEPRINT.route("/cases/delivery_message", methods=["GET"])
1✔
233
def get_cases_delivery_message():
1✔
234
    delivery_message_request = DeliveryMessageRequest.model_validate(request.args)
1✔
235
    try:
1✔
236
        response: DeliveryMessageResponse = delivery_message_service.get_cases_message(
1✔
237
            delivery_message_request
238
        )
239
        return jsonify(response.model_dump()), HTTPStatus.OK
1✔
240
    except (CaseNotFoundError, OrderMismatchError) as error:
×
241
        return jsonify({"error": str(error)}), HTTPStatus.BAD_REQUEST
×
242

243

244
@BLUEPRINT.route("/cases/<case_id>/delivery_message", methods=["GET"])
1✔
245
def get_case_delivery_message(case_id: str):
1✔
246
    delivery_message_request = DeliveryMessageRequest(case_ids=[case_id])
1✔
247
    try:
1✔
248
        response: DeliveryMessageResponse = delivery_message_service.get_cases_message(
1✔
249
            delivery_message_request
250
        )
251
        return jsonify(response.model_dump()), HTTPStatus.OK
1✔
252
    except CaseNotFoundError as error:
×
253
        return jsonify({"error": str(error)}), HTTPStatus.BAD_REQUEST
×
254

255

256
@BLUEPRINT.route("/families_in_collaboration")
1✔
257
def parse_families_in_collaboration():
1✔
258
    """Return cases in collaboration."""
259

260
    customer_internal_id = request.args.get("customer")
×
261
    workflow = request.args.get("data_analysis")
×
262
    case_search_pattern = request.args.get("enquiry")
×
263

264
    customer = db.get_customer_by_internal_id(customer_internal_id=customer_internal_id)
×
265

266
    cases = db.get_cases_by_customer_workflow_and_case_search(
×
267
        customer=customer, workflow=workflow, case_search=case_search_pattern
268
    )
269

270
    case_dicts = [case.to_dict(links=True) for case in cases]
×
271
    return jsonify(families=case_dicts, total=len(cases))
×
272

273

274
@BLUEPRINT.route("/families_in_collaboration/<family_id>")
1✔
275
def parse_family_in_collaboration(family_id):
1✔
276
    """Return a family with links."""
277
    case: Case = db.get_case_by_internal_id(internal_id=family_id)
×
278
    customer: Customer = db.get_customer_by_internal_id(
×
279
        customer_internal_id=request.args.get("customer")
280
    )
281
    if case.customer not in customer.collaborators:
×
282
        return abort(HTTPStatus.FORBIDDEN)
×
283
    return jsonify(**case.to_dict(links=True, analyses=True))
×
284

285

286
@BLUEPRINT.route("/samples")
1✔
287
def parse_samples():
1✔
288
    """Return samples."""
289
    if request.args.get("status") and not g.current_user.is_admin:
×
290
        return abort(HTTPStatus.FORBIDDEN)
×
291
    if request.args.get("status") == "incoming":
×
292
        samples: list[Sample] = db.get_samples_to_receive()
×
293
    elif request.args.get("status") == "labprep":
×
294
        samples: list[Sample] = db.get_samples_to_prepare()
×
295
    elif request.args.get("status") == "sequencing":
×
296
        samples: list[Sample] = db.get_samples_to_sequence()
×
297
    else:
298
        customers: list[Customer] | None = (
×
299
            None if g.current_user.is_admin else g.current_user.customers
300
        )
301
        samples: list[Sample] = db.get_samples_by_customer_id_and_pattern(
×
302
            pattern=request.args.get("enquiry"), customers=customers
303
        )
304
    limit = int(request.args.get("limit", 50))
×
305
    parsed_samples: list[dict] = [sample.to_dict() for sample in samples[:limit]]
×
306
    return jsonify(samples=parsed_samples, total=len(samples))
×
307

308

309
@BLUEPRINT.route("/samples_in_collaboration")
1✔
310
def parse_samples_in_collaboration():
1✔
311
    """Return samples in a customer group."""
312
    customer: Customer = db.get_customer_by_internal_id(
×
313
        customer_internal_id=request.args.get("customer")
314
    )
315
    samples: list[Sample] = db.get_samples_by_customer_id_and_pattern(
×
316
        pattern=request.args.get("enquiry"), customers=customer.collaborators
317
    )
318
    limit = int(request.args.get("limit", 50))
×
319
    parsed_samples: list[dict] = [sample.to_dict() for sample in samples[:limit]]
×
320
    return jsonify(samples=parsed_samples, total=len(samples))
×
321

322

323
@BLUEPRINT.route("/samples/<sample_id>")
1✔
324
def parse_sample(sample_id):
1✔
325
    """Return a single sample."""
326
    sample: Sample = db.get_sample_by_internal_id(sample_id)
×
327
    if sample is None:
×
328
        return abort(HTTPStatus.NOT_FOUND)
×
329
    if not g.current_user.is_admin and (sample.customer not in g.current_user.customers):
×
330
        return abort(HTTPStatus.FORBIDDEN)
×
331
    return jsonify(**sample.to_dict(links=True, flowcells=True))
×
332

333

334
@BLUEPRINT.route("/samples_in_collaboration/<sample_id>")
1✔
335
def parse_sample_in_collaboration(sample_id):
1✔
336
    """Return a single sample."""
337
    sample: Sample = db.get_sample_by_internal_id(sample_id)
×
338
    customer: Customer = db.get_customer_by_internal_id(
×
339
        customer_internal_id=request.args.get("customer")
340
    )
341
    if sample.customer not in customer.collaborators:
×
342
        return abort(HTTPStatus.FORBIDDEN)
×
343
    return jsonify(**sample.to_dict(links=True, flowcells=True))
×
344

345

346
@BLUEPRINT.route("/pools")
1✔
347
def parse_pools():
1✔
348
    """Return pools."""
349
    customers: list[Customer] | None = (
×
350
        g.current_user.customers if not g.current_user.is_admin else None
351
    )
352
    pools: list[Pool] = db.get_pools_to_render(
×
353
        customers=customers, enquiry=request.args.get("enquiry")
354
    )
355
    parsed_pools: list[dict] = [pool_obj.to_dict() for pool_obj in pools[:30]]
×
356
    return jsonify(pools=parsed_pools, total=len(pools))
×
357

358

359
@BLUEPRINT.route("/pools/<pool_id>")
1✔
360
def parse_pool(pool_id):
1✔
361
    """Return a single pool."""
362
    pool: Pool = db.get_pool_by_entry_id(entry_id=pool_id)
×
363
    if pool is None:
×
364
        return abort(HTTPStatus.NOT_FOUND)
×
365
    if not g.current_user.is_admin and (pool.customer not in g.current_user.customers):
×
366
        return abort(HTTPStatus.FORBIDDEN)
×
367
    return jsonify(**pool.to_dict())
×
368

369

370
@BLUEPRINT.route("/flowcells/<flow_cell_name>/sequencing_metrics", methods=["GET"])
1✔
371
def get_sequencing_metrics(flow_cell_name: str):
1✔
372
    """Return sample lane sequencing metrics for a flow cell."""
373
    if not flow_cell_name:
×
374
        return jsonify({"error": "Invalid or missing flow cell id"}), HTTPStatus.BAD_REQUEST
×
375
    sequencing_metrics: list[IlluminaSampleSequencingMetrics] = (
×
376
        db.get_illumina_sequencing_run_by_device_internal_id(flow_cell_name).sample_metrics
377
    )
378
    if not sequencing_metrics:
×
379
        return (
×
380
            jsonify({"error": f"Sequencing metrics not found for flow cell {flow_cell_name}."}),
381
            HTTPStatus.NOT_FOUND,
382
        )
383
    metrics_dtos: list[SequencingMetricsRequest] = parse_metrics_into_request(sequencing_metrics)
×
384
    return jsonify([metric.model_dump() for metric in metrics_dtos])
×
385

386

387
@BLUEPRINT.route("/analyses")
1✔
388
def parse_analyses():
1✔
389
    """Return analyses."""
390
    if request.args.get("status") == "delivery":
×
391
        analyses: list[Analysis] = db.get_analyses_to_deliver_for_pipeline()
×
392
    elif request.args.get("status") == "upload":
×
393
        analyses: list[Analysis] = db.get_analyses_to_upload()
×
394
    else:
395
        analyses: list[Analysis] = db.get_analyses()
×
396
    parsed_analysis: list[dict] = [analysis_obj.to_dict() for analysis_obj in analyses[:30]]
×
397
    return jsonify(analyses=parsed_analysis, total=len(analyses))
×
398

399

400
@BLUEPRINT.route("/options")
1✔
401
def parse_options():
1✔
402
    """Return various options."""
403
    customers: list[Customer | None] = (
×
404
        db.get_customers() if g.current_user.is_admin else g.current_user.customers
405
    )
406

407
    app_tag_groups: dict[str, list[str]] = {"ext": []}
×
408
    applications: list[Application] = db.get_applications_is_not_archived()
×
409
    for application in applications:
×
410
        if not application.versions:
×
411
            LOG.debug(f"Skipping application {application} that doesn't have a price")
×
412
            continue
×
413
        if application.is_external:
×
414
            app_tag_groups["ext"].append(application.tag)
×
415
        if application.prep_category not in app_tag_groups:
×
416
            app_tag_groups[application.prep_category]: list[str] = []
×
417
        app_tag_groups[application.prep_category].append(application.tag)
×
418

419
    source_groups = {"metagenome": METAGENOME_SOURCES, "analysis": ANALYSIS_SOURCES}
×
420

421
    return jsonify(
×
422
        applications=app_tag_groups,
423
        beds=[bed.name for bed in db.get_active_beds()],
424
        customers=[
425
            {
426
                "text": f"{customer.name} ({customer.internal_id})",
427
                "value": customer.internal_id,
428
                "isTrusted": customer.is_trusted,
429
            }
430
            for customer in customers
431
        ],
432
        organisms=[
433
            {
434
                "name": organism.name,
435
                "reference_genome": organism.reference_genome,
436
                "internal_id": organism.internal_id,
437
                "verified": organism.verified,
438
            }
439
            for organism in db.get_all_organisms()
440
        ],
441
        panels=[panel.abbrev for panel in db.get_panels()],
442
        sources=source_groups,
443
    )
444

445

446
@BLUEPRINT.route("/me")
1✔
447
def parse_current_user_information():
1✔
448
    """Return information about current user."""
449
    if not g.current_user.is_admin and not g.current_user.customers:
×
450
        LOG.error(
×
451
            "%s is not admin and is not connected to any customers, aborting", g.current_user.email
452
        )
453
        return abort(HTTPStatus.FORBIDDEN)
×
454

455
    return jsonify(user=g.current_user.to_dict())
×
456

457

458
@BLUEPRINT.route("/applications")
1✔
459
@is_public
1✔
460
def parse_applications():
1✔
461
    """Return application tags."""
462
    applications: list[Application] = db.get_applications_is_not_archived()
×
463
    parsed_applications: list[dict] = [application.to_dict() for application in applications]
×
464
    return jsonify(applications=parsed_applications)
×
465

466

467
@BLUEPRINT.route("/applications/<tag>")
1✔
468
@is_public
1✔
469
def parse_application(tag: str):
1✔
470
    """Return an application tag."""
471
    application: Application = db.get_application_by_tag(tag=tag)
×
472
    if not application:
×
473
        return abort(make_response(jsonify(message="Application not found"), HTTPStatus.NOT_FOUND))
×
474

475
    application_limitations: list[ApplicationLimitations] = db.get_application_limitations_by_tag(
×
476
        tag
477
    )
478
    application_dict: dict[str, Any] = application.to_dict()
×
479
    application_dict["workflow_limitations"] = [
×
480
        limitation.to_dict() for limitation in application_limitations
481
    ]
482
    return jsonify(**application_dict)
×
483

484

485
@BLUEPRINT.route("/applications/<tag>/workflow_limitations")
1✔
486
@is_public
1✔
487
def get_application_workflow_limitations(tag: str):
1✔
488
    """Return application workflow specific limitations."""
489
    if application_limitations := db.get_application_limitations_by_tag(tag):
×
490
        return jsonify([limitation.to_dict() for limitation in application_limitations])
×
491
    else:
492
        return jsonify(message="Application limitations not found"), HTTPStatus.NOT_FOUND
×
493

494

495
@BLUEPRINT.route("/orders")
1✔
496
def get_orders():
1✔
497
    """Return the latest orders."""
498
    data = OrdersRequest.model_validate(request.args.to_dict())
1✔
499
    response: OrdersResponse = order_service.get_orders(data)
1✔
500
    return make_response(response.model_dump())
1✔
501

502

503
@BLUEPRINT.route("/orders/<order_id>")
1✔
504
def get_order(order_id: int):
1✔
505
    """Return an order."""
506
    try:
1✔
507
        response: Order = order_service.get_order(order_id)
1✔
508
        response_dict: dict = response.model_dump()
1✔
509
        return make_response(response_dict)
1✔
510
    except OrderNotFoundError as error:
1✔
511
        return make_response(jsonify(error=str(error)), HTTPStatus.NOT_FOUND)
1✔
512

513

514
@BLUEPRINT.route("/orders/<order_id>/delivered", methods=["PATCH"])
1✔
515
def set_order_delivered(order_id: int):
1✔
516
    try:
×
517
        request_data = OrderDeliveredPatch.model_validate(request.json)
×
518
        delivered: bool = request_data.delivered
×
519
        response_data: Order = order_service.set_delivery(order_id=order_id, delivered=delivered)
×
520
        return jsonify(response_data.model_dump()), HTTPStatus.OK
×
521
    except OrderNotFoundError as error:
×
522
        return jsonify(error=str(error)), HTTPStatus.NOT_FOUND
×
523

524

525
@BLUEPRINT.route("/orders/<order_id>/update-delivery-status", methods=["POST"])
1✔
526
def update_order_delivered(order_id: int):
1✔
527
    """Update the delivery status of an order based on the number of delivered analyses."""
528
    try:
×
529
        request_data = OrderDeliveredUpdateRequest.model_validate(request.json)
×
530
        delivered_analyses: int = request_data.delivered_analyses_count
×
531
        order_service.update_delivered(order_id=order_id, delivered_analyses=delivered_analyses)
×
532
    except OrderNotFoundError as error:
×
533
        return jsonify(error=str(error)), HTTPStatus.NOT_FOUND
×
534

535

536
@BLUEPRINT.route("/orders/<order_id>/delivery_message")
1✔
537
def get_delivery_message_for_order(order_id: int):
1✔
538
    """Return the delivery message for an order."""
539
    try:
1✔
540
        response: DeliveryMessageResponse = delivery_message_service.get_order_message(order_id)
1✔
541
        response_dict: dict = response.model_dump()
1✔
542
        return make_response(response_dict)
1✔
543
    except OrderNotDeliverableError as error:
1✔
544
        return make_response(jsonify(error=str(error)), HTTPStatus.PRECONDITION_FAILED)
1✔
545
    except OrderNotFoundError as error:
×
546
        return make_response(jsonify(error=str(error))), HTTPStatus.NOT_FOUND
×
547

548

549
@BLUEPRINT.route("/orderform", methods=["POST"])
1✔
550
def parse_orderform():
1✔
551
    """Parse an orderform/JSON export."""
552
    input_file = request.files.get("file")
×
553
    filename = secure_filename(input_file.filename)
×
554

555
    error_message: str
556
    try:
×
557
        if filename.lower().endswith(".xlsx"):
×
558
            temp_dir = Path(tempfile.gettempdir())
×
559
            saved_path = str(temp_dir / filename)
×
560
            input_file.save(saved_path)
×
561
            order_parser = ExcelOrderformParser()
×
562
            order_parser.parse_orderform(excel_path=saved_path)
×
563
        else:
564
            json_data = json.load(input_file.stream, strict=False)
×
565
            order_parser = JsonOrderformParser()
×
566
            order_parser.parse_orderform(order_data=json_data)
×
567
        parsed_order: Orderform = order_parser.generate_orderform()
×
568
    except (  # user misbehaviour
×
569
        AttributeError,
570
        OrderFormError,
571
        OverflowError,
572
        ValidationError,
573
        ValueError,
574
    ) as error:
575
        error_message = error.message if hasattr(error, "message") else str(error)
×
576
        LOG.error(error_message)
×
577
        http_error_response = HTTPStatus.BAD_REQUEST
×
578
    except (  # system misbehaviour
×
579
        NewConnectionError,
580
        MaxRetryError,
581
        TimeoutError,
582
        TypeError,
583
    ) as error:
584
        LOG.exception(error)
×
585
        error_message = error.message if hasattr(error, "message") else str(error)
×
586
        http_error_response = HTTPStatus.INTERNAL_SERVER_ERROR
×
587
    else:
588
        return jsonify(**parsed_order.model_dump())
×
589

590
    if error_message:
×
591
        return abort(make_response(jsonify(message=error_message), http_error_response))
×
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

© 2026 Coveralls, Inc