def handle_cli(self, args, func): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("-o", "--output", default="str", choices=["str", "json"]) parsed_args = parser.parse_args(args) file_path = parsed_args.input verify_image_format_or_raise(file_path, self.accept_image_formats) if not os.path.isabs(file_path): file_path = os.path.abspath(file_path) image_array = self.fastai_vision.open_image( fn=file_path, convert_mode=self.convert_mode, div=self.div, after_open=self.after_open, cls=self.cls or self.fastai_vision.Image, ) result = func(image_array) if parsed_args.output == "json": result = api_func_result_to_json(result) else: result = str(result) print(result)
def handle_aws_lambda_event(self, event, func): if event["headers"].get("Content-Type", "").startswith("images/"): image_data = self.imread( base64.decodebytes(event["body"]), pilmode=self.pilmode ) else: raise BadInput( "BentoML currently doesn't support Content-Type: {content_type} for " "AWS Lambda".format(content_type=event["headers"]["Content-Type"]) ) if self.after_open: image_data = self.after_open(image_data) image_data = self.fastai_vision.pil2tensor(image_data, np.float32) if self.div: image_data = image_data.div_(255) if self.cls: image_data = self.cls(image_data) else: image_data = self.fastai_vision.Image(image_data) result = func(image_data) json_output = api_func_result_to_json(result) return {"statusCode": 200, "body": json_output}
def handle_aws_lambda_event(self, event, func): if event["headers"].get("Content-Type", None) == "text/csv": df = pd.read_csv(event["body"]) else: # Optimistically assuming Content-Type to be "application/json" try: df = pd.read_json(event["body"], orient=self.orient, typ=self.typ, dtype=False) except ValueError: raise BadInput( "Failed parsing request data, only Content-Type application/json " "and text/csv are supported in BentoML DataframeHandler") if self.typ == "frame" and self.input_dtypes is not None: _check_dataframe_column_contains(self.input_dtypes, df) result = func(df) result = api_func_result_to_json( result, pandas_dataframe_orient=self.output_orient) # Allow disabling CORS by setting it to None if self.cors: return { "statusCode": 200, "body": result, "headers": { "Access-Control-Allow-Origin": self.cors }, } return {"statusCode": 200, "body": result}
def handle_batch_request(self, requests: Iterable[SimpleRequest], func) -> Iterable[SimpleResponse]: datas = [r.data for r in requests] headers = [{hk.lower(): hv for hk, hv in r.headers or tuple()} for r in requests] content_types = [ h.get('content-type', 'application/json') for h in headers ] # TODO: check content_type df_conc, slices = read_dataframes_from_json_n_csv(datas, content_types) result_conc = func(df_conc) # TODO: check length results = [s and result_conc[s] or BadResult for s in slices] responses = [SimpleResponse("bad request", None, 400)] * len(requests) for i, result in enumerate(results): if result is BadResult: continue json_output = api_func_result_to_json( result, pandas_dataframe_orient=self.output_orient) responses[i] = SimpleResponse( json_output, (("Content-Type", "application/json"), ), 200) return responses
def handle_cli(self, args, func): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True, nargs='+') parser.add_argument("-o", "--output", default="str", choices=["str", "json"]) parser.add_argument("--batch-size", default=None, type=int) parsed_args = parser.parse_args(args) file_paths = parsed_args.input batch_size = (parsed_args.batch_size if parsed_args.batch_size else len(file_paths)) for i in range(0, len(file_paths), batch_size): step_file_paths = file_paths[i:i + batch_size] image_arrays = [] for file_path in step_file_paths: verify_image_format_or_raise(file_path, self.accept_image_formats) if not os.path.isabs(file_path): file_path = os.path.abspath(file_path) image_arrays.append( self.imread(file_path, pilmode=self.pilmode)) results = func(image_arrays) for result in results: if parsed_args.output == "json": result = api_func_result_to_json(result) else: result = str(result) print(result)
def handle_batch_request(self, requests: Iterable[SimpleRequest], func: callable) -> Iterable[SimpleResponse]: """ Batch version of handle_request """ bad_resp = SimpleResponse(400, None, "Bad Input") responses = [bad_resp] * len(requests) input_datas = [] ids = [] for i, req in enumerate(requests): if not req.data: continue request = Request.from_values( input_stream=BytesIO(req.data), content_length=len(req.data), headers=req.headers, ) try: input_data = self._load_image_data(request) except BadInput as e: responses[i] = SimpleResponse(400, None, str(e)) continue input_datas.append(input_data) ids.append(i) results = func(input_datas) if input_datas else [] for i, result in zip(ids, results): responses[i] = SimpleResponse(200, None, api_func_result_to_json(result)) return responses
def handle_batch_request(self, requests: Iterable[SimpleRequest], func) -> Iterable[SimpleResponse]: datas = [r.data for r in requests] content_types = [ r.formated_headers.get('content-type', 'application/json') for r in requests ] # TODO: check content_type df_conc, slices = read_dataframes_from_json_n_csv(datas, content_types) result_conc = func(df_conc) # TODO: check length results = [result_conc[s] if s else BadResult for s in slices] responses = [SimpleResponse(400, None, "bad request")] * len(requests) for i, result in enumerate(results): if result is BadResult: continue json_output = api_func_result_to_json( result, pandas_dataframe_orient=self.output_orient) responses[i] = SimpleResponse( 200, (("Content-Type", "application/json"), ), json_output) return responses
def handle_request(self, request, func): if request.content_type == "text/csv": csv_string = StringIO(request.data.decode('utf-8')) df = pd.read_csv(csv_string) else: # Optimistically assuming Content-Type to be "application/json" try: df = pd.read_json( request.data.decode("utf-8"), orient=self.orient, typ=self.typ, dtype=False, ) except ValueError: raise BadInput( "Failed parsing request data, only Content-Type application/json " "and text/csv are supported in BentoML DataframeHandler") if self.typ == "frame" and self.input_dtypes is not None: _check_dataframe_column_contains(self.input_dtypes, df) result = func(df) json_output = api_func_result_to_json( result, pandas_dataframe_orient=self.output_orient) return Response(response=json_output, status=200, mimetype="application/json")
def handle_request(self, request, func): if request.content_type == "application/json": df = pd.read_json( request.data.decode("utf-8"), orient=self.orient, typ=self.typ, dtype=False, ) elif request.content_type == "text/csv": csv_string = StringIO(request.data.decode('utf-8')) df = pd.read_csv(csv_string) else: raise BadInput( "Request content-type not supported, only application/json and " "text/csv are supported") if self.typ == "frame" and self.input_dtypes is not None: _check_dataframe_column_contains(self.input_dtypes, df) result = func(df) json_output = api_func_result_to_json( result, pandas_dataframe_orient=self.output_orient) return Response(response=json_output, status=200, mimetype="application/json")
def handle_cli(self, args, func): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("-o", "--output", default="str", choices=["str", "json"]) parser.add_argument( "--orient", default=self.orient, choices=PANDAS_DATAFRAME_TO_DICT_ORIENT_OPTIONS, ) parser.add_argument( "--output_orient", default=self.output_orient, choices=PANDAS_DATAFRAME_TO_DICT_ORIENT_OPTIONS, ) parsed_args = parser.parse_args(args) orient = parsed_args.orient output_orient = parsed_args.output_orient cli_input = parsed_args.input if os.path.isfile(cli_input) or is_s3_url(cli_input) or is_url( cli_input): if cli_input.endswith(".csv"): df = pd.read_csv(cli_input) elif cli_input.endswith(".json"): df = pd.read_json(cli_input, orient=orient, typ=self.typ, dtype=False) else: raise BadInput( "Input file format not supported, BentoML cli only accepts .json " "and .csv file") else: # Assuming input string is JSON format try: df = pd.read_json(cli_input, orient=orient, typ=self.typ, dtype=False) except ValueError as e: raise BadInput( "Unexpected input format, BentoML DataframeHandler expects json " "string as input: {}".format(e)) if self.typ == "frame" and self.input_dtypes is not None: _check_dataframe_column_contains(self.input_dtypes, df) result = func(df) if parsed_args.output == 'json': result = api_func_result_to_json( result, pandas_dataframe_orient=output_orient) else: result = str(result) print(result)
def handle_aws_lambda_event(self, event, func): if event["headers"]["Content-Type"] == "application/json": parsed_json = json.loads(event["body"]) else: raise BadInput( "Request content-type must be 'application/json' for this " "BentoService API lambda endpoint") result = func(parsed_json) json_output = api_func_result_to_json(result) return {"statusCode": 200, "body": json_output}
def handle_request(self, request, func): if request.content_type == "application/json": parsed_json = json.loads(request.data.decode("utf-8")) else: raise BadInput( "Request content-type must be 'application/json' for this " "BentoService API" ) result = func(parsed_json) json_output = api_func_result_to_json(result) return Response(response=json_output, status=200, mimetype="application/json")
def handle_batch_request( self, requests: Iterable[SimpleRequest], func ) -> Iterable[SimpleResponse]: """ TODO(hrmthw): 1. check content type 1. specify batch dim 1. output str fromat """ import tensorflow as tf bad_resp = SimpleResponse(b"Bad Input", None, 400) instances_list = [None] * len(requests) responses = [bad_resp] * len(requests) for i, request in enumerate(requests): try: raw_str = request[0] # .decode("utf-8") parsed_json = json.loads(raw_str) if parsed_json.get("instances") is not None: instances = parsed_json.get("instances") if instances is None: continue instances = decode_b64_if_needed(instances) if not isinstance(instances, (list, tuple)): instances = [instances] instances_list[i] = instances elif parsed_json.get("inputs"): responses[i] = SimpleResponse( "Column format 'inputs' not implemented", None, 501, ) except (json.exceptions.JSONDecodeError, UnicodeDecodeError): import traceback traceback.print_exc() merged_instances, slices = concat_list(instances_list) parsed_tensor = tf.constant(merged_instances) merged_result = func(parsed_tensor) merged_result = decode_tf_if_needed(merged_result) assert isinstance(merged_result, (list, tuple)) results = [merged_result[s] for s in slices] for i, result in enumerate(results): result_str = api_func_result_to_json(result) responses[i] = SimpleResponse(result_str, dict(), 200) return responses
def handle_batch_request( self, requests: Iterable[SimpleRequest], func ) -> Iterable[SimpleResponse]: bad_resp = SimpleResponse(400, None, "Bad Input") instances_list = [None] * len(requests) responses = [bad_resp] * len(requests) batch_flags = [None] * len(requests) for i, request in enumerate(requests): batch_flags[i] = ( request.formated_headers.get( self._BATCH_REQUEST_HEADER.lower(), "true" if self.config.get('is_batch_input') else "false", ) == "true" ) try: raw_str = request.data parsed_json = json.loads(raw_str) if not batch_flags[i]: parsed_json = (parsed_json,) instances_list[i] = parsed_json except (json.JSONDecodeError, UnicodeDecodeError): responses[i] = SimpleResponse(400, None, "Not a valid json") except Exception: # pylint: disable=broad-except import traceback err = traceback.format_exc() responses[i] = SimpleResponse( 500, None, f"Internal Server Error: {err}" ) merged_instances, slices = concat_list(instances_list) merged_result = func(merged_instances) if not isinstance(merged_result, (list, tuple)) or len(merged_result) != len( merged_instances ): raise ValueError( "The return value with JsonHandler must be list of jsonable objects, " "and have same length as the inputs." ) for i, s in enumerate(slices): if s is None: continue result = merged_result[s] if not batch_flags[i]: result = result[0] result_str = api_func_result_to_json(result) responses[i] = SimpleResponse(200, dict(), result_str) return responses
def handle_aws_lambda_event(self, event, func): if event["headers"].get("Content-Type", "").startswith("images/"): image = self.imread(base64.decodebytes(event["body"]), pilmode=self.pilmode) else: raise BadInput( "BentoML currently doesn't support Content-Type: {content_type} for " "AWS Lambda".format( content_type=event["headers"]["Content-Type"])) result = func(image) json_output = api_func_result_to_json(result) return {"statusCode": 200, "body": json_output}
def handle_request(self, request, func): """Handle http request that has image file/s. It will convert image into a ndarray for the function to consume. Args: request: incoming request object. func: function that will take ndarray as its arg. options: configuration for handling request object. Return: response object """ if len(self.input_names) == 1 and len(request.files) == 1: # Ignore multipart form input name when LegacyImageHandler is intended # to accept only one image file at a time input_files = [file for _, file in request.files.items()] else: input_files = [ request.files.get(form_input_name) for form_input_name in self.input_names if form_input_name in request.files ] if input_files: file_names = [ secure_filename(file.filename) for file in input_files ] for file_name in file_names: verify_image_format_or_raise(file_name, self.accept_image_formats) input_streams = [ BytesIO(input_file.read()) for input_file in input_files ] else: data = request.get_data() if data: input_streams = (data, ) else: raise BadInput( "BentoML#LegacyImageHandler unexpected HTTP request format" ) input_data = tuple( self.imread(input_stream, pilmode=self.pilmode) for input_stream in input_streams) result = func(*input_data) json_output = api_func_result_to_json(result) return Response(response=json_output, status=200, mimetype="application/json")
def handle_request(self, request, func): """Handle http request that has one image file. It will convert image into a ndarray for the function to consume. Args: request: incoming request object. func: function that will take ndarray as its arg. options: configuration for handling request object. Return: response object """ input_data = self._load_image_data(request) result = func((input_data, ))[0] return Response( response=api_func_result_to_json(result), status=200, mimetype="application/json", )
def handle_cli(self, args, func): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("-o", "--output", default="str", choices=["str", "json"]) parsed_args = parser.parse_args(args) if os.path.isfile(parsed_args.input): with open(parsed_args.input, "r") as content_file: content = content_file.read() else: content = parsed_args.input input_json = json.loads(content) result = func(input_json) if parsed_args.output == 'json': result = api_func_result_to_json(result) else: result = str(result) print(result)
def handle_request(self, request, func): input_streams = [] for filename in self.input_names: file = request.files.get(filename) if file is not None: file_name = secure_filename(file.filename) verify_image_format_or_raise(file_name, self.accept_image_formats) input_streams.append(BytesIO(file.read())) if len(input_streams) == 0: data = request.get_data() if data: input_streams = (data, ) else: raise BadInput( "BentoML#ImageHandler unexpected HTTP request: %s" % request) input_data = [] for input_stream in input_streams: data = self.imread(input_stream, pilmode=self.convert_mode) if self.after_open: data = self.after_open(data) data = self.fastai_vision.pil2tensor(data, np.float32) if self.div: data = data.div_(255) if self.cls: data = self.cls(data) else: data = self.fastai_vision.Image(data) input_data.append(data) result = func(*input_data) json_output = api_func_result_to_json(result) return Response(response=json_output, status=200, mimetype="application/json")
def _handle_raw_str(self, raw_str, output_format, func): import tensorflow as tf parsed_json = json.loads(raw_str) if parsed_json.get("instances") is not None: instances = parsed_json.get("instances") instances = decode_b64_if_needed(instances) parsed_tensor = tf.constant(instances) result = func(parsed_tensor) result = decode_tf_if_needed(result) elif parsed_json.get("inputs"): raise NotImplementedError("column format 'inputs' is not implemented") if output_format == "json": result_str = api_func_result_to_json(result) elif output_format == "str": result_str = str(result) return result_str
def handle_aws_lambda_event(self, event, func): if event["headers"]["Content-Type"] == "application/json": df = pd.read_json(event["body"], orient=self.orient, typ=self.typ, dtype=False) elif event["headers"]["Content-Type"] == "text/csv": df = pd.read_csv(event["body"]) else: raise BadInput( "Request content-type not supported, only application/json and " "text/csv are supported") if self.typ == "frame" and self.input_dtypes is not None: _check_dataframe_column_contains(self.input_dtypes, df) result = func(df) result = api_func_result_to_json( result, pandas_dataframe_orient=self.output_orient) return {"statusCode": 200, "body": result}
def handle_cli(self, args, func): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("-o", "--output", default="str", choices=["str", "json"]) parsed_args = parser.parse_args(args) file_path = parsed_args.input verify_image_format_or_raise(file_path, self.accept_image_formats) if not os.path.isabs(file_path): file_path = os.path.abspath(file_path) image_array = self.imread(file_path, pilmode=self.pilmode) result = func(image_array) if parsed_args.output == "json": result = api_func_result_to_json(result) else: result = str(result) print(result)
def handle_batch_request(self, requests: Iterable[SimpleRequest], func) -> Iterable[SimpleResponse]: bad_resp = SimpleResponse(400, None, "Bad Input") instances_list = [None] * len(requests) responses = [bad_resp] * len(requests) for i, request in enumerate(requests): try: raw_str = request.data parsed_json = json.loads(raw_str) instances_list[i] = parsed_json except (json.JSONDecodeError, UnicodeDecodeError): responses[i] = SimpleResponse(400, None, "not a valid json input") except Exception: # pylint: disable=broad-except responses[i] = SimpleResponse(500, None, "internal server error") import traceback traceback.print_exc() merged_instances, slices = concat_list(instances_list) merged_result = func(merged_instances) if not isinstance( merged_result, (list, tuple)) or len(merged_result) != len(merged_instances): raise ValueError( "The return value with JsonHandler must be list of jsonable objects, " "and have same length as the inputs.") for i, s in enumerate(slices): if s is None: continue result_str = api_func_result_to_json(merged_result[s]) responses[i] = SimpleResponse(200, dict(), result_str) return responses
def handle_batch_request( self, requests: Iterable[SimpleRequest], func ) -> Iterable[SimpleResponse]: """ TODO(hrmthw): 1. specify batch dim 1. output str fromat """ import tensorflow as tf bad_resp = SimpleResponse(400, None, "input format error") instances_list = [None] * len(requests) responses = [bad_resp] * len(requests) batch_flags = [None] * len(requests) for i, request in enumerate(requests): try: raw_str = request.data batch_flags[i] = ( request.formated_headers.get( self._BATCH_REQUEST_HEADER.lower(), "true" if self.config.get("is_batch_input") else "false", ) == "true" ) parsed_json = json.loads(raw_str) if parsed_json.get("instances") is not None: instances = parsed_json.get("instances") if instances is None: continue instances = decode_b64_if_needed(instances) if not batch_flags[i]: instances = (instances,) instances_list[i] = instances elif parsed_json.get("inputs"): responses[i] = SimpleResponse( 501, None, "Column format 'inputs' not implemented" ) except (json.JSONDecodeError, UnicodeDecodeError): pass except Exception: # pylint: disable=broad-except import traceback err = traceback.format_exc() responses[i] = SimpleResponse( 500, None, f"Internal Server Error: {err}" ) merged_instances, slices = concat_list(instances_list) parsed_tensor = tf.constant(merged_instances) merged_result = func(parsed_tensor) merged_result = decode_tf_if_needed(merged_result) assert isinstance(merged_result, (list, tuple)) for i, s in enumerate(slices): if s is None: continue result = merged_result[s] if not batch_flags[i]: result = result[0] result_str = api_func_result_to_json(result) responses[i] = SimpleResponse(200, dict(), result_str) return responses