PK聜N5x7l l scikit_rest/__init__.py"""Automatically serve ML model as a REST API""" __version__ = "0.1.1" from typing import List, Dict, Union, Callable import pandas as pd import sklearn from flask import Flask from flask_restful import Api from scikit_rest.resource import Prediction from scikit_rest.types import numpy_dict from scikit_rest.validator import validate_col_types def infer(input_df: pd.DataFrame) -> [List[str], Dict[str, Union[List, type]]]: """ Automatically infer the column list and column types from the input DataFrame Args: input_df: DataFrame, where the column list and column types will be inferred from. Returns: col_list: List of Column names, where the order of the values will dictate the order within the pandas DataFrame col_types: Dictionary of Column Names and the type of the variable, used for input Validation. If the values of the dictionary is instead a list, We assume that any input for the variable can only be any of the ones listed within the list """ df = input_df.copy().infer_objects() col_list = df.columns.tolist() col_types = {} for key, value in df.dtypes.to_dict().items(): col_types[key] = numpy_dict[value.type] return col_list, col_types def serve( col_list: List[str], col_types: Dict[str, Union[List, type]], transform_fn: Callable, predict_fn: Union[Callable, sklearn.base.BaseEstimator], port: int = 1234, is_nullable: bool = False, name: str = "model", ): """ Setting up ML model as a REST API server Args: col_list: List of Column names, where the order of the values will dictate the order within the pandas DataFrame col_types: Dictionary of Column Names and the type of the variable, used for input Validation. If the values of the dictionary is instead a list, We assume that any input for the variable can only be any of the ones listed within the list transform_fn: Function which convert the input dataframe into test dataframe, where we can call model.predict upon to get the final result predict_fn: Function which convert the test dataframe into result. If a ML model instance is passed in, we will instead try to call model.predict_proba / model.predict to get the result port: Port Number where the REST API should be served upon is_nullable: Whether input API can be nullable name: Name of the program """ validate_col_types(col_types) app = Flask(name) api = Api(app) api.add_resource( Prediction, "/", resource_class_kwargs={ "col_list": col_list, "col_types": col_types, "transform_fn": transform_fn, "predict_fn": predict_fn, "is_nullable": is_nullable, }, ) app.config["BUNDLE_ERRORS"] = True app.run(host="0.0.0.0", port=port) PKtNxI scikit_rest/resource.pyimport numpy as np import pandas as pd from collections import OrderedDict from flask_restful import Resource, reqparse import sklearn from scikit_rest.validator import validate_args from scikit_rest.types import set_type from typing import List, Dict, Union, Any, Callable class Prediction(Resource): """class to handle ML prediction""" def __init__(self, **kwargs): self.col_list: List[str] = kwargs["col_list"] self.col_types: Dict[str, Union[List, type]] = kwargs["col_types"] self.transform_fn: Callable = kwargs["transform_fn"] self.predict_fn: Union[Callable, sklearn.base.BaseEstimator] = kwargs["predict_fn"] self.is_nullable: bool = kwargs["is_nullable"] self.parser: reqparse.RequestParser = reqparse.RequestParser() for col in self.col_list: self.parser.add_argument(col) self.args: Dict[str, Any] = self.parser.parse_args() self.result: Any = np.nan self.status_message: str = "Success" self.status_code: int = 200 is_success, status_message = validate_args(self.args, self.col_types, self.is_nullable) if not is_success: self.status_message = status_message self.status_code = 400 def output(self): return ( {"data": {"result": self.result}, "message": {"status_message": self.status_message, "args": self.args}}, self.status_code, ) def get(self): if self.status_code == 200: input_dict = OrderedDict() for col in self.col_list: value = set_type(self.args[col], self.col_types[col]) input_dict[col] = [value] input_df = pd.DataFrame(input_dict) transformed_df = self.transform_fn(input_df) if isinstance(self.predict_fn, sklearn.base.BaseEstimator): if hasattr(self.predict_fn, "predict_proba"): if len(transformed_df) == 1: self.result = self.predict_fn.predict_proba(transformed_df)[:, 1].item() else: self.result = self.predict_fn.predict_proba(transformed_df)[:, 1] elif hasattr(self.predict_fn, "predict"): if len(transformed_df) == 1: self.result = self.predict_fn.predict(transformed_df).item() else: self.result = self.predict_fn.predict(transformed_df) else: self.status_code = 401 self.status_message = "predict_fn does not have attributes predict or predict_proba" else: self.result = self.predict_fn(transformed_df) return self.output() PKtN[\Z  scikit_rest/types.pyimport datetime from dateutil.parser import parse from typing import Union, Any import numpy true_list = ["True", "true", "T", "t", "1"] false_list = ["False", "false", "F", "f", "0"] numpy_dict = { numpy.int64: int, numpy.float64: float, numpy.bool_: bool, numpy.object_: str, numpy.str_: str, numpy.datetime64: datetime.datetime, } def is_int(value: Union[str, float, int]) -> bool: """validate whether the value is integer""" try: value = float(value) return value.is_integer() except ValueError: return False def is_date(value: Union[str, datetime.datetime]) -> bool: """validate whether the value is datetime""" try: parse(str(value), fuzzy=False) return True except ValueError: return False def is_float(value: Union[str, float]) -> bool: """validate whether the value is float""" try: float(value) return True except ValueError: return False def is_str(value: Union[str]) -> bool: """validate whether the value is string""" return isinstance(value, str) def is_bool(value: Union[str, bool]) -> bool: """validate whether the value is boolean""" return (value in true_list + false_list) or (isinstance(value, bool)) def validate_type(value: Union[int, float, bool, str, datetime.datetime], value_type: Union[type]) -> bool: """ Validate the type of the value""" if value_type == int: result = is_int(value) elif value_type == float: result = is_float(value) elif value_type == bool: result = is_bool(value) elif value_type == datetime.datetime: result = is_date(value) elif value_type == str: result = is_str(value) elif isinstance(value_type, list): result = str(value) in value_type else: raise ValueError("data type not recognized : {}".format(value)) return result def set_type(value: Union[str, bool, int, float, datetime.datetime], value_type: Union[type, list]) -> Any: """Convert the value to be the appropriate type, based on the given value_type""" if value_type == int: value = int(value) elif value_type == float: value = float(value) elif value_type == bool: value = value in true_list elif value_type == datetime.datetime: value = parse(value) elif value_type == str: value = str(value) elif isinstance(value_type, list): value = str(value) else: raise ValueError("data type not recognized : {}".format(value)) return value PK聜Nd*scikit_rest/validator.pyimport datetime import pandas as pd from scikit_rest.types import validate_type from typing import Dict, Union, List def validate_col_types(col_types: Dict[str, Union[List, type]]): """assert that all the values within the dictionary is one of the following : [str, int, float, bool, datetime]""" for key, items in col_types.items(): if isinstance(items, type): assert items in [str, int, float, bool, datetime.datetime], ( "Key {} has an unapproved type {}. Type must be one of the following : " "[str, int, float, bool, datetime.datetime]".format(key, items) ) else: assert isinstance(items, list), "values of key {} should be a list if it is not a type".format(key) for item in items: assert isinstance(item, str), "if col_types {} is a list, values must be type str".format(key) def validate_args( args: Dict[str, Union[str, bool, int, float, datetime.datetime]], col_types: Dict[str, Union[List, type]], is_nullable: bool, ) -> [bool, Dict[str, str]]: """check whether the given arguments follows the specified types and follows the nullable rules""" status_message = dict() for col, col_type in col_types.items(): if col not in args.keys(): status_message[col] = "input {} cannot be found in the payload".format(col) elif pd.isnull(args[col]): if (not is_nullable) or (col not in is_nullable): status_message[col] = "input {} is not supposed to be null".format(col) else: if isinstance(col_type, list): if not validate_type(args[col], col_type): status_message[col] = "input {} has to be one of the following {}".format(col, col_type) else: if not validate_type(args[col], col_type): status_message[col] = "input {} has wrong format (supposed to be {})".format(col, col_type) is_success = len(status_message) == 0 return is_success, status_message PKN=%::#scikit_rest-0.1.1.dist-info/LICENSEMIT License Copyright (c) 2019 Aditya Kelvianto Sidharta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HMuSa!scikit_rest-0.1.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UD"PK!H {l $scikit_rest-0.1.1.dist-info/METADATAV[o6~8Cvo ,Mhd0(Dj$J%$'d.QkkRzSY|ts~MSUmR:mdЙ, y}$I狛^ORZ yBӓfd:9u dRgxN?NNCU zb*˔dK|o+>b\:5A0mj 9>qVJu4vP̈́NGOY-> ڇ HxxнI|r̎M4UyyXKK.1:.tFwX2/.FLj:qU] ml~]qUA Rcf: ;k}&X5}j(ETlbiu\] d ڄ0ܓfMpAlR R*6U(j<=! OȉLyv0 1[:m4um\(ү'\Fq/j]njC*TJhLz`DN[:{N91A>Q)t"scikit_rest-0.1.1.dist-info/RECORD}Ko@{ Z@=PY Rp!A]E__.&b&Ьi_ >uEuS|HmUv}J+)t"K4scikit_rest-0.1.1.dist-info/RECORDPKT6