Beispiel #1
0
def find_cherrytrack_plate_from_barcode() -> FlaskResponse:
    logger.info("Finding cherry track plate from barcode")
    try:
        barcode = request.args.get("barcode") or ""

        assert len(barcode) > 0, "Include a barcode in the request"

        plate_type = request.args.get(ARG_TYPE) or ""

        logger.debug(f"{plate_type} plate barcode to look for: {barcode}")

        if plate_type == ARG_TYPE_SOURCE:
            response = get_samples_from_source_plate_barcode_from_cherrytrack(
                barcode)
        elif plate_type == ARG_TYPE_DESTINATION:
            response = get_wells_from_destination_barcode_from_cherrytrack(
                barcode)
        else:
            raise AssertionError(
                f"Plate type needs to be either '{ARG_TYPE_SOURCE}' or '{ARG_TYPE_DESTINATION}'"
            )

        response_json = response.json()

        if response_json.get("errors") is not None:
            return internal_server_error(response_json.get("errors"))

        return ok(plate=response_json)
    except AssertionError as e:
        return bad_request(str(e))
    except Exception as e:
        logger.exception(e)
        return internal_server_error(f"Failed to lookup plate: {str(e)}")
Beispiel #2
0
def create_plate_event_endpoint() -> FlaskResponse:
    event_type = request.args.get(FIELD_EVENT_TYPE, type=str)
    if event_type is None or (event_type == ""):
        return bad_request([f"Unrecognised event type '{event_type}'"])

    if event_type in [
            Beckman.EVENT_SOURCE_UNRECOGNISED,
            Beckman.EVENT_SOURCE_COMPLETED,
            Beckman.EVENT_SOURCE_ALL_NEGATIVES,
            Beckman.EVENT_SOURCE_NO_PLATE_MAP_DATA,
    ]:
        return create_plate_event()

    return internal_server_error([f"Unrecognised event type '{event_type}'"])
Beispiel #3
0
def get_reports() -> FlaskResponse:
    """Gets a list of all the available reports.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: list of report details and an HTTP status code.
    """
    logger.info("Getting reports")
    try:
        reports = get_reports_details()

        return ok(reports=reports)
    except Exception as e:
        msg = f"{ERROR_UNEXPECTED} ({type(e).__name__})"
        logger.error(msg)
        logger.exception(e)

        return internal_server_error(msg)
Beispiel #4
0
def get_failure_types() -> FlaskResponse:
    """Get a list of the Beckman failure types.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: a list of failure types if found or a list of errors with the corresponding HTTP status code.
    """
    logger.info("Fetching Beckman failure type information")
    try:
        failure_types = Beckman.get_failure_types()

        return ok(errors=[], failure_types=failure_types)
    except Exception as e:
        logger.exception(e)

        return internal_server_error(
            f"{ERROR_UNEXPECTED} while fetching Beckman failure type information",
            failure_types=[])
Beispiel #5
0
def get_robots() -> FlaskResponse:
    """Find information about the Beckman robots. Currently, this information lives in config.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: the config for the robots configures with the corresponding HTTP status code.
    """
    logger.info("Fetching Beckman robot information")
    try:
        robots = Beckman.get_robots()

        return ok(errors=[], robots=robots)
    except Exception as e:
        logger.exception(e)

        return internal_server_error(
            f"{ERROR_UNEXPECTED} while fetching Beckman robot information",
            robots=[])
Beispiel #6
0
def create_report() -> FlaskResponse:
    """Creates a new report.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: details of the report just created or a list of errors with the corresponding HTTP status code.
    """
    logger.info("Creating a new report")
    try:
        report_name = create_report_job()

        report_details = get_reports_details(report_name)

        return created(reports=report_details)
    except Exception as e:
        msg = f"{ERROR_UNEXPECTED} ({type(e).__name__})"
        logger.error(msg)
        logger.exception(e)

        return internal_server_error(msg)
Beispiel #7
0
def find_plate_from_barcode() -> FlaskResponse:
    """A route which returns information about a list of comma separated plates as specified
    in the 'barcodes' parameters. Default fields can be excluded from the response using the url
    param '_exclude'.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    ### Source plate example
    To fetch data for the source plates with barcodes '123' and '456' and exclude field 'picked_samples' from the
    output:

    #### Query:
    ```
    GET /plates?barcodes=123,456&_exclude=picked_samples
    ```

    #### Response:
    ```json
    {"plates":
        [
            {
                "plate_barcode": "123",
                "has_plate_map": true,
                "count_must_sequence": 0,
                "count_preferentially_sequence": 0,
                "count_filtered_positive": 2,
                "count_fit_to_pick_samples": 2,
            },
            {
                "plate_barcode": "456",
                "has_plate_map": false,
                "count_must_sequence": 0,
                "count_preferentially_sequence": 0,
                "count_filtered_positive": 4,
                "count_fit_to_pick_samples": 4,
            },
        ]
    }
    ```

    ### Destination plate example
    To fetch data for the destination plates with barcodes 'destination_123' and 'destination_456':

    #### Query:
    ```
    GET /plates?barcodes=destination_123,destination_456&_type=destination
    ```

    #### Response:
    ```json
    {"plates":
        [
            {
                "plate_barcode": "destination_123",
                "plate_exists": true,
            },
            {
                "plate_barcode": "destination_456",
                "plate_exists": false,
            },
        ]
    }
    ```

    Returns:
        FlaskResponse: the response body and HTTP status code
    """
    logger.info("Finding plate from barcode")
    try:
        barcodes_arg = request.args.get("barcodes")
        barcodes_list = barcodes_arg.split(",") if barcodes_arg else []

        assert len(
            barcodes_list
        ) > 0, "Include a list of barcodes separated by commas (,) in the request"

        plate_type = request.args.get(ARG_TYPE, ARG_TYPE_SOURCE)
        assert plate_type in (
            ARG_TYPE_SOURCE,
            ARG_TYPE_DESTINATION,
        ), f"Plate type needs to be either '{ARG_TYPE_SOURCE}' or '{ARG_TYPE_DESTINATION}'"

        exclude_props_arg = request.args.get(ARG_EXCLUDE)
        exclude_props = exclude_props_arg.split(
            ",") if exclude_props_arg else []

        logger.debug(
            f"{plate_type} plate(s) barcodes to look for: {barcodes_arg}")

        plates = [
            format_plate(barcode,
                         exclude_props=exclude_props,
                         plate_type=plate_type) for barcode in barcodes_list
        ]

        pretty(logger, plates)

        return ok(plates=plates)
    except AssertionError as e:
        return bad_request(str(e))
    except Exception as e:
        logger.exception(e)
        # We don't use str(e) here to fetch the exception summary, because the exceptions we're most likely to see here
        #   aren't end-user-friendly
        return internal_server_error(
            f"Failed to lookup plates: {type(e).__name__}")
Beispiel #8
0
            response_json = {
                "data": {
                    "plate_barcode":
                    fit_to_pick_samples[0][FIELD_PLATE_BARCODE],
                    "centre": centre_prefix,
                    "count_fit_to_pick_samples": count_fit_to_pick_samples,
                }
            }

            try:
                update_mlwh_with_cog_uk_ids(fit_to_pick_samples)
            except Exception as e:
                logger.error(ERROR_UPDATE_MLWH_WITH_COG_UK_IDS)
                logger.exception(e)

                return internal_server_error(ERROR_UPDATE_MLWH_WITH_COG_UK_IDS)
        else:
            response_json = response.json()

        # return the JSON and status code directly from Sequencescape (act as a proxy)
        return response_json, response.status_code
    except Exception as e:
        msg = f"{ERROR_UNEXPECTED_PLATES_CREATE} ({type(e).__name__})"
        logger.error(msg)
        logger.exception(e)

        return internal_server_error(msg)


def find_plate_from_barcode() -> FlaskResponse:
    """A route which returns information about a list of comma separated plates as specified
Beispiel #9
0
    """A route which accepts a list of report filenames and then deletes them from the reports path.
    This endpoint should be JSON and the body should be in the format:

    `{"data":"filenames":["file1.xlsx","file2.xlsx", ...]}`

    This is a POST request but is a destructive action but this does not need to be delete as it is not a REST resource.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: empty response body for OK; otherwise details of any errors and the corresponding HTTP status
        code.
    """
    logger.info("Attempting to delete report(s)")
    try:
        if (request_json := request.get_json()) is not None:
            if (data := request_json.get("data")) is not None:
                if (filenames := data.get("filenames")) is not None:
                    delete_reports_helper(filenames)

                    return ok()

        raise Exception(
            "Endpoint expecting JSON->data->filenames in request body")
    except Exception as e:
        msg = f"{ERROR_UNEXPECTED} ({type(e).__name__})"
        logger.error(msg)
        logger.exception(e)

        return internal_server_error(msg)
Beispiel #10
0
def create_plate_from_barcode() -> FlaskResponse:  # noqa: C901
    """This endpoint attempts to create a plate in Sequencescape. The arguments provided extract data from the DART
    and mongo databases, add COG UK barcodes and then call Sequencescape to attempt to create a plate and samples.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: If the call to Sequencescape was made, this response acts as a proxy as it returns the response
        and HTTP status code which is received back. If any errors occur on the way, it returns with a list of errors
        and the corresponding HTTP code.
    """
    logger.info("Attempting to create a plate in Sequencescape")
    try:
        user_id, barcode, robot_serial_number = get_required_params(
            request, (ARG_USER_ID, ARG_BARCODE, ARG_ROBOT_SERIAL))
    except Exception as e:
        logger.error(f"{ERROR_CHERRYPICKED_CREATE} {ERROR_MISSING_PARAMETERS}")
        logger.exception(e)

        return bad_request(str(e))

    try:
        dart_samples = find_dart_source_samples_rows(barcode)
        if len(dart_samples) == 0:
            msg = f"{ERROR_SAMPLE_DATA_MISSING} {barcode}"
            logger.error(msg)

            return internal_server_error(msg)

        mongo_samples = find_samples(
            query_for_cherrypicked_samples(dart_samples))

        if not mongo_samples:
            return bad_request(f"No samples for this barcode: {barcode}")

        if not check_matching_sample_numbers(dart_samples, mongo_samples):
            msg = f"{ERROR_SAMPLE_DATA_MISMATCH} {barcode}"
            logger.error(msg)

            return internal_server_error(msg)

        # add COG barcodes to samples
        try:
            centre_prefix = add_cog_barcodes_from_different_centres(
                mongo_samples)
        except Exception as e:
            logger.exception(e)

            return bad_request(
                f"Failed to add COG barcodes to plate: {barcode}")

        samples = join_rows_with_samples(dart_samples, mongo_samples)

        all_samples = add_controls_to_samples(dart_samples, samples)

        mapped_samples = map_to_ss_columns(all_samples)

        source_plates = get_source_plates_for_samples(mongo_samples)

        if not source_plates:
            return bad_request(f"{ERROR_SAMPLES_MISSING_UUIDS} {barcode}")

        body = create_cherrypicked_post_body(user_id, barcode, mapped_samples,
                                             robot_serial_number,
                                             source_plates)

        response = send_to_ss_heron_plates(body)

        if response.ok:
            response_json = {
                "data": {
                    "plate_barcode": barcode,
                    "centre": centre_prefix,
                    "number_of_fit_to_pick": len(samples),
                }
            }

            try:
                update_mlwh_with_cog_uk_ids(mongo_samples)
            except Exception as e:
                logger.exception(e)

                return internal_server_error(ERROR_UPDATE_MLWH_WITH_COG_UK_IDS)
        else:
            response_json = response.json()

        # return the JSON and status code directly from Sequencescape (act as a proxy)
        return response_json, response.status_code
    except Exception as e:
        msg = f"{ERROR_UNEXPECTED_CHERRYPICKING_CREATE} ({type(e).__name__})"
        logger.error(msg)
        logger.exception(e)

        return internal_server_error(msg)
Beispiel #11
0
def fail_plate_from_barcode() -> FlaskResponse:
    """This endpoints attempts to publish an event to the event warehouse when a failure occurs when a destination plate
    is not created successfully.

    Note: This is the existing implementation, currently used for the v1 endpoint.

    Returns:
        FlaskResponse: If the message is published successfully return with an OK otherwise return the error messages
        and the corresponding HTTP status code.
    """
    logger.info(
        f"Attempting to publish a '{PE_BECKMAN_DESTINATION_FAILED}' message")
    try:
        required_args = (ARG_USER_ID, ARG_BARCODE, ARG_ROBOT_SERIAL,
                         ARG_FAILURE_TYPE)
        user_id, barcode, robot_serial_number, failure_type = get_required_params(
            request, required_args)
    except Exception as e:
        logger.error(
            f"{ERROR_CHERRYPICKED_FAILURE_RECORD} {ERROR_MISSING_PARAMETERS}")
        logger.exception(e)

        return bad_request(str(e))
    try:
        if failure_type not in app.config["ROBOT_FAILURE_TYPES"]:
            logger.error(
                f"{ERROR_CHERRYPICKED_FAILURE_RECORD} unknown failure type")

            return bad_request(
                f"'{failure_type}' is not a known cherrypicked plate failure type"
            )

        errors, message = construct_cherrypicking_plate_failed_message(
            barcode, user_id, robot_serial_number, failure_type)

        if message is None:
            logger.error(
                f"{ERROR_CHERRYPICKED_FAILURE_RECORD} error(s) constructing event message: {errors}"
            )

            return internal_server_error(errors)

        routing_key = get_routing_key(PE_BECKMAN_DESTINATION_FAILED)

        logger.info(
            "Attempting to publish the destination failed event message")
        broker = Broker()
        # To use the broker as a context manager we don't need these methods to be public but we still need to refactor
        #   these calls to use a context manager
        broker._connect()
        try:
            broker.publish(message, routing_key)
            broker._close_connection()
            logger.info(
                f"Successfully published a '{PE_BECKMAN_DESTINATION_FAILED}' message"
            )

            return ok(errors=errors)

        except Exception:
            broker._close_connection()
            raise
    except Exception as e:
        msg = f"{ERROR_UNEXPECTED_CHERRYPICKING_FAILURE} ({type(e).__name__})"
        logger.error(msg)
        logger.exception(e)

        return internal_server_error(msg)