def from_inference_job( # pylint: disable=arguments-differ self, input_=None, input_file=None, **extra_args) -> Iterator[InferenceTask[str]]: ''' Generate InferenceTask from calling bentom_svc.run(input_=None, input_file=None) Parameters ---------- input_ : str The input value input_file : str The URI/path of the input file extra_args : dict Additional parameters ''' if input_file is not None: for d in input_file: uri = pathlib.Path(d).absolute().as_uri() yield InferenceTask(inference_job_args=extra_args, data=FileLike(uri=uri)) else: for d in input_: yield InferenceTask( inference_job_args=extra_args, data=FileLike(bytes_=d.encode()), )
def parse_cli_input(cli_args: Iterable[str]) -> Iterator[FileLike]: ''' Parse CLI args and iter each input in bytes. >>> parse_cli_input('--input {"input":1} {"input":2}'.split(' ')) OR >>> parse_cli_inputs("--input-file 1.jpg 2.jpg 3.jpg".split(' ')) ''' parser = argparse.ArgumentParser() input_g = parser.add_mutually_exclusive_group(required=True) input_g.add_argument('--input', nargs="+", type=str) input_g.add_argument('--input-file', nargs="+") parsed_args, _ = parser.parse_known_args(list(cli_args)) inputs = tuple(parsed_args.input if parsed_args.input_file is None else parsed_args.input_file) is_file = parsed_args.input_file is not None if is_file: for input_ in inputs: uri = pathlib.Path(input_).absolute().as_uri() yield FileLike(uri=uri) else: for input_ in inputs: rv = FileLike(bytes_=input_.encode()) yield rv return _
def from_aws_lambda_event(self, event: AwsLambdaEvent) -> InferenceTask[FileLike]: headers = HTTPHeaders.from_dict(event.get('headers', {})) if headers.content_type == "text/csv": f = FileLike(bytes_=event["body"].encode(), name="input.csv") else: # Optimistically assuming Content-Type to be "application/json" f = FileLike(bytes_=event["body"].encode(), name="input.json") return InferenceTask(aws_lambda_event=event, data=f,)
def from_inference_job( # pylint: disable=arguments-differ self, input_=None, input_file=None, **extra_args) -> Iterator[InferenceTask[FileLike]]: if input_file is not None: for ds in zip(*input_file): uris = (pathlib.Path(d).absolute().as_uri() for d in ds) fs = tuple(FileLike(uri=uri) for uri in uris) yield InferenceTask(data=fs, inference_job_args=extra_args) else: for ds in zip(*input_): fs = tuple(FileLike(bytes_=d.encode()) for d in ds) yield InferenceTask(data=fs, inference_job_args=extra_args)
def from_http_request(self, req: HTTPRequest) -> InferenceTask[FileLike]: if req.headers.content_type == 'multipart/form-data': _, _, files = HTTPRequest.parse_form_data(req) if len(files) != 1: task = InferenceTask(data=None) task.discard( http_status=400, err_msg= f"BentoML#{self.__class__.__name__} requires one and at" " least one file at a time, if you just upgraded from" " bentoml 0.7, you may need to use MultiFileAdapter instead", ) else: input_file = next(iter(files.values())) task = InferenceTask(http_headers=req.headers, data=input_file) elif req.body: task = InferenceTask( http_headers=req.headers, data=FileLike(bytes_=req.body), ) else: task = InferenceTask(data=None) task.discard( http_status=400, err_msg= f'BentoML#{self.__class__.__name__} unexpected HTTP request' ' format', ) return task
def parse(self, args: Sequence[str]) -> Iterator[Tuple[FileLike]]: try: parsed, _ = self.parser.parse_known_args(args) except SystemExit: parsed = None inputs = tuple(getattr(parsed, name, None) for name in self.arg_names) file_inputs = tuple( getattr(parsed, name, None) for name in self.file_arg_names) if any(inputs) and any(file_inputs): exit_cli(''' Conflict arguments: --input* and --input-file* should not be provided at same time ''') if not all(inputs) and not all(file_inputs): exit_cli(f''' Insufficient arguments: ({' '.join(self.arg_strs)}) or ({' '.join(self.file_arg_strs)}) are required ''') if all(inputs): if functools.reduce(lambda i, j: len(i) == len(j), inputs): for input_ in zip(*inputs): yield tuple(FileLike(bytes_=i.encode()) for i in input_) else: exit_cli(f''' Arguments length mismatch: Each ({' '.join(self.arg_strs)}) should have same amount of inputs ''') if all(file_inputs): if functools.reduce(lambda i, j: len(i) == len(j), file_inputs): for input_ in zip(*file_inputs): uris = (pathlib.Path(fpath).absolute().as_uri() for fpath in input_) yield tuple(FileLike(uri=uri) for uri in uris) else: exit_cli(f''' Arguments length mismatch: Each ({' '.join(self.file_arg_strs)}) should have same amount of inputs ''')
def from_inference_job( # pylint: disable=arguments-differ self, input_=None, input_file=None, **extra_args, ) -> Iterator[InferenceTask[str]]: ''' Generate InferenceTask from calling bentom_svc.run(input_=None, input_file=None) Parameters ---------- input_ : str The input value input_file : str The URI/path of the input file extra_args : dict Additional parameters ''' if input_file is not None: for d in input_file: uri = pathlib.Path(d).absolute().as_uri() bytes_ = FileLike(uri=uri).read() try: charset = chardet.detect(bytes_)['encoding'] or "utf-8" yield InferenceTask( inference_job_args=extra_args, data=bytes_.decode(charset), ) except UnicodeDecodeError: yield InferenceTask().discard( http_status=400, err_msg=f"{self.__class__.__name__}: " f"Try decoding with {charset} but failed with DecodeError.", ) except LookupError: yield InferenceTask().discard( http_status=400, err_msg=f"{self.__class__.__name__}: " f"Unsupported charset {charset}", ) else: for d in input_: yield InferenceTask(inference_job_args=extra_args, data=d)
def from_aws_lambda_event(self, event): if event["headers"].get("Content-Type", "").startswith("images/"): img_bytes = base64.b64decode(event["body"]) _, ext = event["headers"]["Content-Type"].split('/') f = FileLike(bytes_=img_bytes, name=f"default.{ext}") task = InferenceTask(data=(f, )) else: task = InferenceTask(data=None) task.discard( http_status=400, err_msg="BentoML currently doesn't support Content-Type: " "{content_type} for AWS Lambda".format( content_type=event["headers"]["Content-Type"]), ) return task
def from_http_request(self, req: HTTPRequest) -> MultiImgTask: if len(self.input_names) == 1: # broad parsing while single input if req.headers.content_type == 'multipart/form-data': _, _, files = HTTPRequest.parse_form_data(req) if not any(files): task = InferenceTask(data=None) task.discard( http_status=400, err_msg= f"BentoML#{self.__class__.__name__} requires inputs" f"fields {self.input_names}", ) else: f = next(iter(files.values())) task = InferenceTask( http_headers=req.headers, data=(f, ), ) elif req.headers.content_type.startswith('image/'): # for images/* _, ext = req.headers.content_type.split('/') task = InferenceTask( http_headers=req.headers, data=(FileLike(bytes_=req.body, name=f'default.{ext}'), ), ) else: task = InferenceTask( http_headers=req.headers, data=(FileLike(bytes_=req.body), ), ) elif req.headers.content_type == 'multipart/form-data': _, _, files = HTTPRequest.parse_form_data(req) files = tuple(files.get(k) for k in self.input_names) if not any(files): task = InferenceTask(data=None) task.discard( http_status=400, err_msg=f"BentoML#{self.__class__.__name__} requires inputs " f"fields {self.input_names}", ) elif not all(files) and not self.allow_none: task = InferenceTask(data=None) task.discard( http_status=400, err_msg=f"BentoML#{self.__class__.__name__} requires inputs " f"fields {self.input_names}", ) else: task = InferenceTask( http_headers=req.headers, data=files, ) else: task = InferenceTask(data=None) task.discard( http_status=400, err_msg= f"BentoML#{self.__class__.__name__} with multiple inputs " "accepts requests with Content-Type: multipart/form-data only", ) return task
def from_aws_lambda_event( self, event: AwsLambdaEvent) -> InferenceTask[FileLike]: f = FileLike(bytes_=base64.decodebytes(event.get('body', ""))) return InferenceTask(aws_lambda_event=event, data=f)