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)}")
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)
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=[])
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=[])
def delete_reports() -> FlaskResponse: """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()
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__}")
def create_plate_event() -> FlaskResponse: """/v3/plate-events/create beckman endpoint to publish a plate event message to the RabbitMQ broker. Returns: FlaskResponse: if successful, return an empty list of errors and an OK status; otherwise, a list of errors and the corresponding HTTP status code. """ required_params = (FIELD_EVENT_TYPE, FIELD_EVENT_ROBOT, FIELD_EVENT_USER_ID) write_exception_error = False plate_event = None try: event = create_event_dict(request, required_params) automation_system = AutomationSystem.AutomationSystemEnum.BECKMAN # Assume we only receive one event beckman = Beckman() event_type = event.get(FIELD_EVENT_TYPE) if event_type is None or not isinstance(event_type, str) or not event_type: raise Exception("Cannot determine event type in hook") logger.info( f"Attempting to process a '{event_type}' plate event message received from {automation_system.name}" ) plate_event = beckman.get_plate_event(event_type) plate_event.initialize_event(event) write_exception_error = True if plate_event.is_valid(): plate_event.process_event() plate_event.process_errors() if len(plate_event.errors) > 0: write_exception_error = False raise Exception("The processing of the event failed.") return ok(errors=[]) except Exception as e: if write_exception_error: if plate_event is not None: plate_event.process_exception(e) message = str(e) issues: Any = None if plate_event is not None and hasattr(plate_event, "errors"): issues = plate_event.errors else: issues = [message] abort( make_response( jsonify({ "_status": "ERR", "_issues": issues, "_error": { "code": HTTPStatus.INTERNAL_SERVER_ERROR, "message": message, }, }), HTTPStatus.INTERNAL_SERVER_ERROR, ))
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)