From 0143698257c1b5d4b78499fdda4749bee5d2bf69 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Thu, 8 Dec 2022 10:34:10 -0500 Subject: [PATCH 1/9] ptype_data to prototype_data --- vetiver/pin_read_write.py | 2 +- vetiver/ptype.py | 10 +++++----- vetiver/rsconnect.py | 2 +- vetiver/server.py | 22 +++++++++++----------- vetiver/tests/test_add_endpoint.py | 2 +- vetiver/tests/test_build_api.py | 2 +- vetiver/tests/test_build_vetiver_model.py | 10 +++++----- vetiver/tests/test_custom_handler.py | 8 ++++---- vetiver/tests/test_no_handler.py | 2 +- vetiver/tests/test_pin_write.py | 8 ++++++-- vetiver/tests/test_ping_server.py | 2 +- vetiver/tests/test_predict.py | 2 +- vetiver/tests/test_pytorch.py | 8 ++++---- vetiver/tests/test_rsconnect.py | 2 +- vetiver/tests/test_sklearn.py | 2 +- vetiver/tests/test_write_app.py | 2 +- vetiver/vetiver_model.py | 22 +++++++++++++++++----- vetiver/write_docker.py | 2 +- vetiver/write_fastapi.py | 2 +- 19 files changed, 64 insertions(+), 48 deletions(-) diff --git a/vetiver/pin_read_write.py b/vetiver/pin_read_write.py index 375c1055..bb114a54 100644 --- a/vetiver/pin_read_write.py +++ b/vetiver/pin_read_write.py @@ -38,7 +38,7 @@ def vetiver_pin_write(board, model: VetiverModel, versioned: bool = True): >>> model_board = board_temp(versioned = True, allow_pickle_read = True) >>> X, y = vetiver.get_mock_data() >>> model = vetiver.get_mock_model().fit(X, y) - >>> v = vetiver.VetiverModel(model = model, model_name = "my_model", ptype_data = X) + >>> v = vetiver.VetiverModel(model, "my_model", prototype_data = X) >>> vetiver.vetiver_pin_write(model_board, v) """ if not board.allow_pickle_read: diff --git a/vetiver/ptype.py b/vetiver/ptype.py index f27211c7..f0296c80 100644 --- a/vetiver/ptype.py +++ b/vetiver/ptype.py @@ -80,7 +80,7 @@ def vetiver_create_ptype(data): @vetiver_create_ptype.register def _(data: pd.DataFrame): """ - Create ptype for a pandas dataframe + Create input data prototype for a pandas dataframe Parameters ---------- @@ -124,7 +124,7 @@ def _(data: pd.DataFrame): @vetiver_create_ptype.register def _(data: np.ndarray): """ - Create ptype for a numpy array + Create input data prototype for a numpy array Parameters ---------- @@ -163,7 +163,7 @@ def _item(value): @vetiver_create_ptype.register def _(data: dict): """ - Create ptype for a dict + Create input data prototype for a dict Parameters ---------- @@ -176,7 +176,7 @@ def _(data: dict): @vetiver_create_ptype.register def _(data: BaseModel): """ - Create ptype for a pydantic BaseModel object + Create input data prototype for a pydantic BaseModel object Parameters ---------- @@ -189,7 +189,7 @@ def _(data: BaseModel): @vetiver_create_ptype.register def _(data: NoneType): """ - Create ptype for None + Create input data prototype for None Parameters ---------- diff --git a/vetiver/rsconnect.py b/vetiver/rsconnect.py index 993c2890..ed62299c 100644 --- a/vetiver/rsconnect.py +++ b/vetiver/rsconnect.py @@ -67,7 +67,7 @@ def deploy_rsconnect( >>> model = vetiver.get_mock_model().fit(X, y) >>> v = vetiver.VetiverModel(model = model, ... model_name = "my_model", - ... ptype_data = X) + ... prototype_data = X) >>> vetiver.deploy_rsconnect( ... connect_server = connect_server, ... board = board, diff --git a/vetiver/server.py b/vetiver/server.py index 88054826..f97d5f15 100644 --- a/vetiver/server.py +++ b/vetiver/server.py @@ -27,11 +27,11 @@ class VetiverAPI: Example ------- - >>> import vetiver - >>> X, y = vetiver.get_mock_data() - >>> model = vetiver.get_mock_model().fit(X, y) - >>> v = vetiver.VetiverModel(model = model, model_name = "my_model", ptype_data = X) - >>> v_api = vetiver.VetiverAPI(model = v, check_ptype = True) + >>> import vetiver as vt + >>> X, y = vt.get_mock_data() + >>> model = vt.get_mock_model().fit(X, y) + >>> v = vt.VetiverModel(model = model, model_name = "my_model", prototype_data = X) + >>> v_api = vt.VetiverAPI(model = v, check_ptype = True) """ app = None @@ -129,7 +129,7 @@ def vetiver_post(self, endpoint_fx: Callable, endpoint_name: str = None, **kw): >>> import vetiver as vt >>> X, y = vt.get_mock_data() >>> model = vt.get_mock_model().fit(X, y) - >>> v = vt.VetiverModel(model = model, model_name = "model", ptype_data = X) + >>> v = vt.VetiverModel(model = model, model_name = "model", prototype_data = X) >>> v_api = vt.VetiverAPI(model = v, check_ptype = True) >>> def sum_values(x): ... return x.sum() @@ -175,11 +175,11 @@ def run(self, port: int = 8000, host: str = "127.0.0.1", **kw): Example ------- - >>> import vetiver - >>> X, y = vetiver.get_mock_data() - >>> model = vetiver.get_mock_model().fit(X, y) - >>> v = vetiver.VetiverModel(model = model, model_name = "model", ptype_data = X) - >>> v_api = vetiver.VetiverAPI(model = v, check_ptype = True) + >>> import vetiver as vt + >>> X, y = vt.get_mock_data() + >>> model = vt.get_mock_model().fit(X, y) + >>> v = vt.VetiverModel(model = model, model_name = "model", prototype_data = X) + >>> v_api = vt.VetiverAPI(model = v, check_ptype = True) >>> v_api.run() # doctest: +SKIP """ _jupyter_nb() diff --git a/vetiver/tests/test_add_endpoint.py b/vetiver/tests/test_add_endpoint.py index af876c3f..08983257 100644 --- a/vetiver/tests/test_add_endpoint.py +++ b/vetiver/tests/test_add_endpoint.py @@ -15,7 +15,7 @@ def vetiver_model(): model = mock.get_mock_model().fit(X, y) v = VetiverModel( model=model, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_build_api.py b/vetiver/tests/test_build_api.py index 8d83e94a..fd18a2de 100644 --- a/vetiver/tests/test_build_api.py +++ b/vetiver/tests/test_build_api.py @@ -7,7 +7,7 @@ def _build_v(): model = mock.get_mock_model().fit(X, y) v = VetiverModel( model=model, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_build_vetiver_model.py b/vetiver/tests/test_build_vetiver_model.py index 400e2638..5e42ac4d 100644 --- a/vetiver/tests/test_build_vetiver_model.py +++ b/vetiver/tests/test_build_vetiver_model.py @@ -17,7 +17,7 @@ def test_vetiver_model_array_ptype(): # build VetiverModel, no ptype vt1 = vt.VetiverModel( model=model, - ptype_data=X_array, + prototype_data=X_array, model_name="iris", versioned=None, description=None, @@ -33,7 +33,7 @@ def test_vetiver_model_df_ptype(): # build VetiverModel, df ptype_data vt2 = vt.VetiverModel( model=model, - ptype_data=X_df, + prototype_data=X_df, model_name="iris", versioned=None, description=None, @@ -49,7 +49,7 @@ def test_vetiver_model_dict_ptype(): dict_data = {"B": 0, "C": 0, "D": 0} vt3 = vt.VetiverModel( model=model, - ptype_data=dict_data, + prototype_data=dict_data, model_name="iris", versioned=None, description=None, @@ -65,7 +65,7 @@ def test_vetiver_model_no_ptype(): # build VetiverModel, no ptype vt4 = vt.VetiverModel( model=model, - ptype_data=None, + prototype_data=None, model_name="iris", versioned=None, description=None, @@ -80,7 +80,7 @@ def test_vetiver_model_from_pin(): v = vt.VetiverModel( model=model, - ptype_data=X_df, + prototype_data=X_df, model_name="model", versioned=None, description=None, diff --git a/vetiver/tests/test_custom_handler.py b/vetiver/tests/test_custom_handler.py index baab1f83..f8e612b6 100644 --- a/vetiver/tests/test_custom_handler.py +++ b/vetiver/tests/test_custom_handler.py @@ -6,8 +6,8 @@ class CustomHandler(BaseHandler): - def __init__(self, model, ptype_data): - super().__init__(model, ptype_data) + def __init__(self, model, prototype_data): + super().__init__(model, prototype_data) model_type = staticmethod(lambda: sklearn.dummy.DummyRegressor) @@ -32,7 +32,7 @@ def test_custom_vetiver_model(): v = VetiverModel( model=custom_handler, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", @@ -50,7 +50,7 @@ def test_custom_vetiver_model_no_ptype(): v = VetiverModel( model=custom_handler, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_no_handler.py b/vetiver/tests/test_no_handler.py index 510b30e8..bb01352c 100644 --- a/vetiver/tests/test_no_handler.py +++ b/vetiver/tests/test_no_handler.py @@ -9,7 +9,7 @@ def test_not_implemented_error(): with pytest.raises(InvalidModelError): VetiverModel( model=y, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_pin_write.py b/vetiver/tests/test_pin_write.py index b4b83ae3..bc3f5fb1 100644 --- a/vetiver/tests/test_pin_write.py +++ b/vetiver/tests/test_pin_write.py @@ -12,14 +12,18 @@ def test_board_pin_write_error(): - v = VetiverModel(model=model, ptype_data=X_df, model_name="model", versioned=None) + v = VetiverModel( + model=model, prototype_data=X_df, model_name="model", versioned=None + ) board = pins.board_temp() with pytest.raises(NotImplementedError): vetiver_pin_write(board=board, model=v) def test_board_pin_write(): - v = VetiverModel(model=model, ptype_data=X_df, model_name="model", versioned=None) + v = VetiverModel( + model=model, prototype_data=X_df, model_name="model", versioned=None + ) board = pins.board_temp(allow_pickle_read=True) vetiver_pin_write(board=board, model=v) assert isinstance(board.pin_read("model"), sklearn.dummy.DummyRegressor) diff --git a/vetiver/tests/test_ping_server.py b/vetiver/tests/test_ping_server.py index 5c42df89..491ab070 100644 --- a/vetiver/tests/test_ping_server.py +++ b/vetiver/tests/test_ping_server.py @@ -7,7 +7,7 @@ def _start_application(): model = mock.get_mock_model().fit(X, y) v = VetiverModel( model=model, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_predict.py b/vetiver/tests/test_predict.py index 06c5349e..a2257416 100644 --- a/vetiver/tests/test_predict.py +++ b/vetiver/tests/test_predict.py @@ -16,7 +16,7 @@ def vetiver_model(): model = mock.get_mock_model().fit(X, y) v = VetiverModel( model=model, - ptype_data=X, + prototype_data=X, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_pytorch.py b/vetiver/tests/test_pytorch.py index ab781a43..97e55ad0 100644 --- a/vetiver/tests/test_pytorch.py +++ b/vetiver/tests/test_pytorch.py @@ -45,7 +45,7 @@ def test_vetiver_build(): vt2 = VetiverModel( model=torch_model, - ptype_data=x_train, + prototype_data=x_train, model_name="torch", versioned=None, description=None, @@ -58,7 +58,7 @@ def test_vetiver_build(): def test_torch_predict_ptype(): torch.manual_seed(3) x_train, torch_model = _build_torch_v() - v = VetiverModel(torch_model, model_name="torch", ptype_data=x_train) + v = VetiverModel(torch_model, model_name="torch", prototype_data=x_train) v_api = VetiverAPI(v) client = TestClient(v_api.app) @@ -72,7 +72,7 @@ def test_torch_predict_ptype(): def test_torch_predict_ptype_batch(): torch.manual_seed(3) x_train, torch_model = _build_torch_v() - v = VetiverModel(torch_model, model_name="torch", ptype_data=x_train) + v = VetiverModel(torch_model, model_name="torch", prototype_data=x_train) v_api = VetiverAPI(v) client = TestClient(v_api.app) @@ -88,7 +88,7 @@ def test_torch_predict_ptype_batch(): def test_torch_predict_ptype_error(): x_train, torch_model = _build_torch_v() - v = VetiverModel(torch_model, model_name="torch", ptype_data=x_train) + v = VetiverModel(torch_model, model_name="torch", prototype_data=x_train) v_api = VetiverAPI(v) client = TestClient(v_api.app) diff --git a/vetiver/tests/test_rsconnect.py b/vetiver/tests/test_rsconnect.py index 3ce1656b..59ebcec4 100644 --- a/vetiver/tests/test_rsconnect.py +++ b/vetiver/tests/test_rsconnect.py @@ -66,7 +66,7 @@ def test_deploy(rsc_short): X_df, y = vetiver.mock.get_mock_data() model = vetiver.mock.get_mock_model().fit(X_df, y) - v = vetiver.VetiverModel(model=model, ptype_data=X_df, model_name="susan/model") + v = vetiver.VetiverModel(model=model, prototype_data=X_df, model_name="susan/model") board = pins.board_rsconnect( server_url=RSC_SERVER_URL, api_key=get_key("susan"), allow_pickle_read=True diff --git a/vetiver/tests/test_sklearn.py b/vetiver/tests/test_sklearn.py index 58b0f32b..91cbf97e 100644 --- a/vetiver/tests/test_sklearn.py +++ b/vetiver/tests/test_sklearn.py @@ -9,7 +9,7 @@ def _start_application(save_ptype: bool = True): model = mock.get_mock_model().fit(X, y) v = VetiverModel( model=model, - ptype_data=X if save_ptype else None, + prototype_data=X if save_ptype else None, model_name="my_model", versioned=None, description="A regression model for testing purposes", diff --git a/vetiver/tests/test_write_app.py b/vetiver/tests/test_write_app.py index a16ff8cf..088801f7 100644 --- a/vetiver/tests/test_write_app.py +++ b/vetiver/tests/test_write_app.py @@ -10,7 +10,7 @@ def test_write_app(): file = "app.py" v = vetiver.VetiverModel( - model=model, ptype_data=X_df, model_name="model", versioned=None + model=model, prototype_data=X_df, model_name="model", versioned=None ) model_board = pins.board_folder(path=".", versioned=True, allow_pickle_read=True) vetiver.vetiver_pin_write(board=model_board, model=v) diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index 4f1aa9de..ff0ded52 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -1,5 +1,5 @@ import json - +from warnings import warn from vetiver.handlers.base import create_handler from .meta import _model_meta @@ -27,8 +27,10 @@ class VetiverModel: A trained model, such as an sklearn or torch model model_name : string Model name or ID - ptype_data : pd.DataFrame, np.array + prototype_data : pd.DataFrame, np.array Sample of data model should expect when it is being served + ptype_data : pd.DataFrame, np.array + Deprecated, in favor of prototype_data versioned : Should the model be versioned when created? description : str @@ -54,7 +56,7 @@ class VetiverModel: >>> from vetiver import mock, VetiverModel >>> X, y = mock.get_mock_data() >>> model = mock.get_mock_model().fit(X, y) - >>> v = VetiverModel(model = model, model_name = "my_model", ptype_data = X) + >>> v = VetiverModel(model = model, model_name = "my_model", prototype_data = X) >>> v.description "Scikit-learn model" """ @@ -63,13 +65,23 @@ def __init__( self, model, model_name: str, + prototype_data=None, ptype_data=None, versioned=None, description: str = None, metadata: dict = None, **kwargs ): - translator = create_handler(model, ptype_data) + if ptype_data is not None: + prototype_data = ptype_data + warn( + "argument for saving input data prototype has changed to\ + `save_prototype`, from `save_ptype", + DeprecationWarning, + stacklevel=2, + ) + + translator = create_handler(model, prototype_data) self.model = translator.model self.ptype = translator.construct_ptype() @@ -99,7 +111,7 @@ def from_pin(cls, board, name: str, version: str = None): url=meta.local.get("url"), # None all the time, besides Connect required_pkgs=meta.user.get("required_pkgs"), ), - ptype_data=json.loads(meta.user.get("ptype")) + prototype_data=json.loads(meta.user.get("ptype")) if meta.user.get("ptype") else None, versioned=True, diff --git a/vetiver/write_docker.py b/vetiver/write_docker.py index 9e2743d9..225edd86 100644 --- a/vetiver/write_docker.py +++ b/vetiver/write_docker.py @@ -56,7 +56,7 @@ def write_docker( >>> board = pins.board_temp(allow_pickle_read=True) >>> X, y = vetiver.get_mock_data() >>> model = vetiver.get_mock_model().fit(X, y) - >>> v = vetiver.VetiverModel(model = model, model_name = "my_model", ptype_data = X) + >>> v = vetiver.VetiverModel(model, "my_model", prototype_data = X) >>> vetiver.vetiver_pin_write(board, v) >>> vetiver.write_app(board, ... "my_model", diff --git a/vetiver/write_fastapi.py b/vetiver/write_fastapi.py index f6a8dd68..60ffb992 100644 --- a/vetiver/write_fastapi.py +++ b/vetiver/write_fastapi.py @@ -76,7 +76,7 @@ def write_app( >>> board = pins.board_temp(allow_pickle_read=True) >>> X, y = vetiver.get_mock_data() >>> model = vetiver.get_mock_model().fit(X, y) - >>> v = vetiver.VetiverModel(model = model, model_name = "my_model", ptype_data = X) + >>> v = vetiver.VetiverModel(model, "my_model", prototype_data = X) >>> vetiver.vetiver_pin_write(board, v) >>> vetiver.write_app(board, ... "my_model", From 61fa10fc5429f7ddaef011e605a7fe85a3dc3c12 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Mon, 12 Dec 2022 16:10:58 -0500 Subject: [PATCH 2/9] check_ptype to check_prototype --- vetiver/handlers/sklearn.py | 4 ++-- vetiver/handlers/statsmodels.py | 5 +---- vetiver/handlers/torch.py | 4 ++-- vetiver/handlers/xgboost.py | 2 +- vetiver/server.py | 31 +++++++++++++++++++++--------- vetiver/tests/test_add_endpoint.py | 8 ++++---- vetiver/tests/test_predict.py | 6 +++--- vetiver/tests/test_pytorch.py | 4 ++-- vetiver/tests/test_sklearn.py | 2 +- vetiver/tests/test_statsmodels.py | 8 ++++---- vetiver/tests/test_xgboost.py | 8 ++++---- vetiver/vetiver_model.py | 4 ++-- 12 files changed, 48 insertions(+), 38 deletions(-) diff --git a/vetiver/handlers/sklearn.py b/vetiver/handlers/sklearn.py index b6a17de0..d1449872 100644 --- a/vetiver/handlers/sklearn.py +++ b/vetiver/handlers/sklearn.py @@ -33,7 +33,7 @@ def create_meta( return meta - def handler_predict(self, input_data, check_ptype): + def handler_predict(self, input_data, check_prototype): """Generates method for /predict endpoint in VetiverAPI The `handler_predict` function executes at each API call. Use this @@ -51,7 +51,7 @@ def handler_predict(self, input_data, check_ptype): Prediction from model """ - if not check_ptype or isinstance(input_data, pd.DataFrame): + if not check_prototype or isinstance(input_data, pd.DataFrame): prediction = self.model.predict(input_data) else: prediction = self.model.predict([input_data]) diff --git a/vetiver/handlers/statsmodels.py b/vetiver/handlers/statsmodels.py index 3c300499..8675485e 100644 --- a/vetiver/handlers/statsmodels.py +++ b/vetiver/handlers/statsmodels.py @@ -21,9 +21,6 @@ class StatsmodelsHandler(BaseHandler): model_class = staticmethod(lambda: statsmodels.base.wrapper.ResultsWrapper) - def __init__(self, model, ptype_data): - super().__init__(model, ptype_data) - def describe(self): """Create description for statsmodels model""" desc = f"Statsmodels {self.model.__class__} model." @@ -41,7 +38,7 @@ def create_meta( return meta - def handler_predict(self, input_data, check_ptype): + def handler_predict(self, input_data, check_prototype): """Generates method for /predict endpoint in VetiverAPI The `handler_predict` function executes at each API call. Use this diff --git a/vetiver/handlers/torch.py b/vetiver/handlers/torch.py index 4f96b108..df622921 100644 --- a/vetiver/handlers/torch.py +++ b/vetiver/handlers/torch.py @@ -38,7 +38,7 @@ def create_meta( return meta - def handler_predict(self, input_data, check_ptype): + def handler_predict(self, input_data, check_prototype): """Generates method for /predict endpoint in VetiverAPI The `handler_predict` function executes at each API call. Use this @@ -57,7 +57,7 @@ def handler_predict(self, input_data, check_ptype): """ if not torch_exists: raise ImportError("Cannot import `torch`.") - if check_ptype: + if check_prototype: input_data = np.array(input_data, dtype=np.array(self.ptype_data).dtype) prediction = self.model(torch.from_numpy(input_data)) diff --git a/vetiver/handlers/xgboost.py b/vetiver/handlers/xgboost.py index 0d61159f..f719c9a1 100644 --- a/vetiver/handlers/xgboost.py +++ b/vetiver/handlers/xgboost.py @@ -38,7 +38,7 @@ def create_meta( return meta - def handler_predict(self, input_data, check_ptype): + def handler_predict(self, input_data, check_prototype): """Generates method for /predict endpoint in VetiverAPI The `handler_predict` function executes at each API call. Use this diff --git a/vetiver/server.py b/vetiver/server.py index f97d5f15..ac6a6063 100644 --- a/vetiver/server.py +++ b/vetiver/server.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Union, Any, Dict +from typing import Any, Callable, Dict, List, Union from urllib.parse import urljoin import httpx @@ -8,6 +8,7 @@ from fastapi import FastAPI, Request, testclient from fastapi.openapi.utils import get_openapi from fastapi.responses import HTMLResponse, RedirectResponse +from warnings import warn from .utils import _jupyter_nb from .vetiver_model import VetiverModel @@ -20,8 +21,10 @@ class VetiverAPI: ---------- model : VetiverModel Model to be deployed in API - check_ptype : bool + check_prototype : bool Determine if data prototype should be enforced + check_ptype : bool + Deprecated in favor of check_prototype app_factory : Type of API to be deployed @@ -31,7 +34,7 @@ class VetiverAPI: >>> X, y = vt.get_mock_data() >>> model = vt.get_mock_model().fit(X, y) >>> v = vt.VetiverModel(model = model, model_name = "my_model", prototype_data = X) - >>> v_api = vt.VetiverAPI(model = v, check_ptype = True) + >>> v_api = vt.VetiverAPI(model = v, check_prototype = True) """ app = None @@ -39,14 +42,24 @@ class VetiverAPI: def __init__( self, model: VetiverModel, - check_ptype: bool = True, + check_prototype: bool = True, + check_ptype: bool = None, app_factory=FastAPI, ) -> None: self.model = model - self.check_ptype = check_ptype self.app_factory = app_factory self.app = app_factory() + if check_ptype is not None: + check_prototype = check_ptype + warn( + "argument for checking input data prototype has changed to " + "check_prototype, from check_ptype", + DeprecationWarning, + stacklevel=2, + ) + self.check_prototype = check_prototype + self._init_app() def _init_app(self): @@ -71,7 +84,7 @@ async def ping(): return {"ping": "pong"} self.vetiver_post( - self.model.handler_predict, "predict", check_ptype=self.check_ptype + self.model.handler_predict, "predict", check_prototype=self.check_prototype ) @app.get("/__docs__", response_class=HTMLResponse, include_in_schema=False) @@ -130,7 +143,7 @@ def vetiver_post(self, endpoint_fx: Callable, endpoint_name: str = None, **kw): >>> X, y = vt.get_mock_data() >>> model = vt.get_mock_model().fit(X, y) >>> v = vt.VetiverModel(model = model, model_name = "model", prototype_data = X) - >>> v_api = vt.VetiverAPI(model = v, check_ptype = True) + >>> v_api = vt.VetiverAPI(model = v, check_prototype = True) >>> def sum_values(x): ... return x.sum() >>> v_api.vetiver_post(sum_values, "sums") @@ -138,7 +151,7 @@ def vetiver_post(self, endpoint_fx: Callable, endpoint_name: str = None, **kw): if not endpoint_name: endpoint_name = endpoint_fx.__name__ - if self.check_ptype is True: + if self.check_prototype is True: @self.app.post(urljoin("/", endpoint_name), name=endpoint_name) async def custom_endpoint( @@ -179,7 +192,7 @@ def run(self, port: int = 8000, host: str = "127.0.0.1", **kw): >>> X, y = vt.get_mock_data() >>> model = vt.get_mock_model().fit(X, y) >>> v = vt.VetiverModel(model = model, model_name = "model", prototype_data = X) - >>> v_api = vt.VetiverAPI(model = v, check_ptype = True) + >>> v_api = vt.VetiverAPI(model = v, check_prototype = True) >>> v_api.run() # doctest: +SKIP """ _jupyter_nb() diff --git a/vetiver/tests/test_add_endpoint.py b/vetiver/tests/test_add_endpoint.py index 08983257..949e8149 100644 --- a/vetiver/tests/test_add_endpoint.py +++ b/vetiver/tests/test_add_endpoint.py @@ -34,9 +34,9 @@ def sum_dict(x): @pytest.fixture -def vetiver_client(vetiver_model): # With check_ptype=True +def vetiver_client(vetiver_model): # With check_prototype=True - app = VetiverAPI(vetiver_model, check_ptype=True) + app = VetiverAPI(vetiver_model, check_prototype=True) app.vetiver_post(sum_values, "sum") app.app.root_path = "/sum" @@ -46,9 +46,9 @@ def vetiver_client(vetiver_model): # With check_ptype=True @pytest.fixture -def vetiver_client_check_ptype_false(vetiver_model): # With check_ptype=False +def vetiver_client_check_ptype_false(vetiver_model): # With check_prototype=False - app = VetiverAPI(vetiver_model, check_ptype=False) + app = VetiverAPI(vetiver_model, check_prototype=False) app.vetiver_post(sum_dict, "sum") app.app.root_path = "/sum" diff --git a/vetiver/tests/test_predict.py b/vetiver/tests/test_predict.py index a2257416..e3d0b683 100644 --- a/vetiver/tests/test_predict.py +++ b/vetiver/tests/test_predict.py @@ -27,7 +27,7 @@ def vetiver_model(): @pytest.fixture def vetiver_client(vetiver_model): # With check_ptype=True - app = VetiverAPI(vetiver_model, check_ptype=True) + app = VetiverAPI(vetiver_model, check_prototype=True) app.app.root_path = "/predict" client = TestClient(app.app) @@ -36,7 +36,7 @@ def vetiver_client(vetiver_model): # With check_ptype=True @pytest.fixture def vetiver_client_check_ptype_false(vetiver_model): # With check_ptype=False - app = VetiverAPI(vetiver_model, check_ptype=False) + app = VetiverAPI(vetiver_model, check_prototype=False) app.app.root_path = "/predict" client = TestClient(app.app) @@ -90,7 +90,7 @@ def test_predict_sklearn_type_error(data, vetiver_client): def test_predict_server_error(vetiver_model): X, y = mock.get_mock_data() - app = VetiverAPI(vetiver_model, check_ptype=True) + app = VetiverAPI(vetiver_model, check_prototype=True) app.app.root_path = "/i_do_not_exists" client = TestClient(app.app) diff --git a/vetiver/tests/test_pytorch.py b/vetiver/tests/test_pytorch.py index 97e55ad0..e57c4676 100644 --- a/vetiver/tests/test_pytorch.py +++ b/vetiver/tests/test_pytorch.py @@ -102,7 +102,7 @@ def test_torch_predict_no_ptype_batch(): torch.manual_seed(3) x_train, torch_model = _build_torch_v() v = VetiverModel(torch_model, model_name="torch") - v_api = VetiverAPI(v, check_ptype=False) + v_api = VetiverAPI(v, check_prototype=False) client = TestClient(v_api.app) data = [[3.3], [3.3]] @@ -117,7 +117,7 @@ def test_torch_predict_no_ptype(): torch.manual_seed(3) x_train, torch_model = _build_torch_v() v = VetiverModel(torch_model, model_name="torch") - v_api = VetiverAPI(v, check_ptype=False) + v_api = VetiverAPI(v, check_prototype=False) client = TestClient(v_api.app) data = [[3.3]] diff --git a/vetiver/tests/test_sklearn.py b/vetiver/tests/test_sklearn.py index 91cbf97e..aa60d666 100644 --- a/vetiver/tests/test_sklearn.py +++ b/vetiver/tests/test_sklearn.py @@ -15,7 +15,7 @@ def _start_application(save_ptype: bool = True): description="A regression model for testing purposes", ) - app = VetiverAPI(v, check_ptype=save_ptype) + app = VetiverAPI(v, check_prototype=save_ptype) return app diff --git a/vetiver/tests/test_statsmodels.py b/vetiver/tests/test_statsmodels.py index eb828b93..e666c05c 100644 --- a/vetiver/tests/test_statsmodels.py +++ b/vetiver/tests/test_statsmodels.py @@ -24,8 +24,8 @@ def sm_model(): @pytest.fixture -def vetiver_client(sm_model): # With check_ptype=True - app = vetiver.VetiverAPI(sm_model, check_ptype=True) +def vetiver_client(sm_model): # With check_prototype=True + app = vetiver.VetiverAPI(sm_model, check_prototype=True) app.app.root_path = "/predict" client = TestClient(app.app) @@ -33,8 +33,8 @@ def vetiver_client(sm_model): # With check_ptype=True @pytest.fixture -def vetiver_client_check_ptype_false(sm_model): # With check_ptype=True - app = vetiver.VetiverAPI(sm_model, check_ptype=False) +def vetiver_client_check_ptype_false(sm_model): # With check_prototype=True + app = vetiver.VetiverAPI(sm_model, check_prototype=False) app.app.root_path = "/predict" client = TestClient(app.app) diff --git a/vetiver/tests/test_xgboost.py b/vetiver/tests/test_xgboost.py index efdcb30e..c708237a 100644 --- a/vetiver/tests/test_xgboost.py +++ b/vetiver/tests/test_xgboost.py @@ -28,8 +28,8 @@ def xgb_model(): @pytest.fixture -def vetiver_client(xgb_model): # With check_ptype=True - app = vetiver.VetiverAPI(xgb_model, check_ptype=True) +def vetiver_client(xgb_model): # With check_prototype=True + app = vetiver.VetiverAPI(xgb_model, check_prototype=True) app.app.root_path = "/predict" client = TestClient(app.app) @@ -37,8 +37,8 @@ def vetiver_client(xgb_model): # With check_ptype=True @pytest.fixture -def vetiver_client_check_ptype_false(xgb_model): # With check_ptype=True - app = vetiver.VetiverAPI(xgb_model, check_ptype=False) +def vetiver_client_check_ptype_false(xgb_model): # With check_prototype=True + app = vetiver.VetiverAPI(xgb_model, check_prototype=False) app.app.root_path = "/predict" client = TestClient(app.app) diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index ff0ded52..cdb6a906 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -75,8 +75,8 @@ def __init__( if ptype_data is not None: prototype_data = ptype_data warn( - "argument for saving input data prototype has changed to\ - `save_prototype`, from `save_ptype", + "argument for saving input data prototype has changed to " + "save_prototype, from save_ptype", DeprecationWarning, stacklevel=2, ) From 16a92284eb98334ca2a7dc258c194ce8ca2ac852 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Tue, 13 Dec 2022 14:42:23 -0500 Subject: [PATCH 3/9] remove ptype attr for ptrototype --- vetiver/pin_read_write.py | 8 +++++++- vetiver/server.py | 5 ++++- vetiver/tests/test_build_vetiver_model.py | 24 +++++++++++------------ vetiver/tests/test_custom_handler.py | 4 ++-- vetiver/vetiver_model.py | 2 +- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/vetiver/pin_read_write.py b/vetiver/pin_read_write.py index bb114a54..65643e27 100644 --- a/vetiver/pin_read_write.py +++ b/vetiver/pin_read_write.py @@ -51,6 +51,10 @@ def vetiver_pin_write(board, model: VetiverModel, versioned: bool = True): "with vetiver.model_card()", ) + # convert older model's ptype to prototype + if hasattr(model, "ptype"): + model.prototype = model.ptype + board.pin_write( model.model, name=model.model_name, @@ -58,7 +62,9 @@ def vetiver_pin_write(board, model: VetiverModel, versioned: bool = True): description=model.description, metadata={ "required_pkgs": model.metadata.get("required_pkgs"), - "ptype": None if model.ptype is None else model.ptype().json(), + "ptype": None + if model.prototype is None + else model.prototype().json(), # ptype_change }, versioned=versioned, ) diff --git a/vetiver/server.py b/vetiver/server.py index ac6a6063..7c457a30 100644 --- a/vetiver/server.py +++ b/vetiver/server.py @@ -151,11 +151,14 @@ def vetiver_post(self, endpoint_fx: Callable, endpoint_name: str = None, **kw): if not endpoint_name: endpoint_name = endpoint_fx.__name__ + if hasattr(self.model, "ptype"): + self.model.prototype = self.model.ptype + if self.check_prototype is True: @self.app.post(urljoin("/", endpoint_name), name=endpoint_name) async def custom_endpoint( - input_data: Union[self.model.ptype, List[self.model.ptype]] + input_data: Union[self.model.prototype, List[self.model.prototype]] ): if isinstance(input_data, List): diff --git a/vetiver/tests/test_build_vetiver_model.py b/vetiver/tests/test_build_vetiver_model.py index 5e42ac4d..a517a5bf 100644 --- a/vetiver/tests/test_build_vetiver_model.py +++ b/vetiver/tests/test_build_vetiver_model.py @@ -18,15 +18,15 @@ def test_vetiver_model_array_ptype(): vt1 = vt.VetiverModel( model=model, prototype_data=X_array, - model_name="iris", + model_name="model", versioned=None, description=None, metadata=None, ) assert vt1.model == model - assert isinstance(vt1.ptype.construct(), pydantic.BaseModel) - assert list(vt1.ptype.__fields__.values())[0].type_ == int + assert isinstance(vt1.prototype.construct(), pydantic.BaseModel) + assert list(vt1.prototype.__fields__.values())[0].type_ == int def test_vetiver_model_df_ptype(): @@ -34,15 +34,15 @@ def test_vetiver_model_df_ptype(): vt2 = vt.VetiverModel( model=model, prototype_data=X_df, - model_name="iris", + model_name="model", versioned=None, description=None, metadata=None, ) assert vt2.model == model - assert isinstance(vt2.ptype.construct(), pydantic.BaseModel) - assert list(vt2.ptype.__fields__.values())[0].type_ == int + assert isinstance(vt2.prototype.construct(), pydantic.BaseModel) + assert list(vt2.prototype.__fields__.values())[0].type_ == int def test_vetiver_model_dict_ptype(): @@ -50,15 +50,15 @@ def test_vetiver_model_dict_ptype(): vt3 = vt.VetiverModel( model=model, prototype_data=dict_data, - model_name="iris", + model_name="model", versioned=None, description=None, metadata=None, ) assert vt3.model == model - assert isinstance(vt3.ptype.construct(), pydantic.BaseModel) - assert list(vt3.ptype.__fields__.values())[0].type_ == int + assert isinstance(vt3.prototype.construct(), pydantic.BaseModel) + assert list(vt3.prototype.__fields__.values())[0].type_ == int def test_vetiver_model_no_ptype(): @@ -66,14 +66,14 @@ def test_vetiver_model_no_ptype(): vt4 = vt.VetiverModel( model=model, prototype_data=None, - model_name="iris", + model_name="model", versioned=None, description=None, metadata=None, ) assert vt4.model == model - assert vt4.ptype is None + assert vt4.prototype is None def test_vetiver_model_from_pin(): @@ -91,5 +91,5 @@ def test_vetiver_model_from_pin(): v2 = vt.VetiverModel.from_pin(board, "model") assert isinstance(v2, vt.VetiverModel) assert isinstance(v2.model, sklearn.base.BaseEstimator) - assert isinstance(v2.ptype.construct(), pydantic.BaseModel) + assert isinstance(v2.prototype.construct(), pydantic.BaseModel) board.pin_delete("model") diff --git a/vetiver/tests/test_custom_handler.py b/vetiver/tests/test_custom_handler.py index f8e612b6..873c21d0 100644 --- a/vetiver/tests/test_custom_handler.py +++ b/vetiver/tests/test_custom_handler.py @@ -40,7 +40,7 @@ def test_custom_vetiver_model(): assert v.description == "A regression model for testing purposes" assert isinstance(v.model, sklearn.dummy.DummyRegressor) - assert isinstance(v.ptype.construct(), pydantic.BaseModel) + assert isinstance(v.prototype.construct(), pydantic.BaseModel) def test_custom_vetiver_model_no_ptype(): @@ -58,4 +58,4 @@ def test_custom_vetiver_model_no_ptype(): assert v.description == "A regression model for testing purposes" assert isinstance(v.model, sklearn.dummy.DummyRegressor) - assert isinstance(v.ptype.construct(), pydantic.BaseModel) + assert isinstance(v.prototype.construct(), pydantic.BaseModel) diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index cdb6a906..2c8358ed 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -84,7 +84,7 @@ def __init__( translator = create_handler(model, prototype_data) self.model = translator.model - self.ptype = translator.construct_ptype() + self.prototype = translator.construct_ptype() self.model_name = model_name self.description = description if description else translator.describe() self.versioned = versioned From 4e69722d486d5e3c812091ebab7ee26ac8305bf8 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Thu, 15 Dec 2022 17:24:39 -0500 Subject: [PATCH 4/9] updates from review --- README.md | 4 ++-- vetiver/pin_read_write.py | 4 +--- vetiver/tests/test_sklearn.py | 12 ++++++------ vetiver/vetiver_model.py | 11 ++++++++--- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f66d81c6..25fa12cb 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ from vetiver import mock, VetiverModel X, y = mock.get_mock_data() model = mock.get_mock_model().fit(X, y) -v = VetiverModel(model, save_ptype=True, ptype_data=X, model_name='mock_model') +v = VetiverModel(model, model_name='mock_model', prototype_data=X) ``` You can **version** and **share** your `VetiverModel()` by choosing a [pins](https://rstudio.github.io/pins-python/) "board" for it, including a local folder, RStudio Connect, Amazon S3, and more. @@ -60,7 +60,7 @@ You can **deploy** your pinned `VetiverModel()` using `VetiverAPI()`, an extensi ```python from vetiver import VetiverAPI -app = VetiverAPI(v, check_ptype = True) +app = VetiverAPI(v, check_prototype = True) ``` To start a server using this object, use `app.run(port = 8080)` or your port of choice. diff --git a/vetiver/pin_read_write.py b/vetiver/pin_read_write.py index 65643e27..7532da48 100644 --- a/vetiver/pin_read_write.py +++ b/vetiver/pin_read_write.py @@ -62,9 +62,7 @@ def vetiver_pin_write(board, model: VetiverModel, versioned: bool = True): description=model.description, metadata={ "required_pkgs": model.metadata.get("required_pkgs"), - "ptype": None - if model.prototype is None - else model.prototype().json(), # ptype_change + "prototype": None if model.prototype is None else model.prototype().json(), }, versioned=versioned, ) diff --git a/vetiver/tests/test_sklearn.py b/vetiver/tests/test_sklearn.py index aa60d666..29fe9fdf 100644 --- a/vetiver/tests/test_sklearn.py +++ b/vetiver/tests/test_sklearn.py @@ -4,18 +4,18 @@ import pytest -def _start_application(save_ptype: bool = True): +def _start_application(save_prototype: bool = True): X, y = mock.get_mock_data() model = mock.get_mock_model().fit(X, y) v = VetiverModel( model=model, - prototype_data=X if save_ptype else None, + prototype_data=X if save_prototype else None, model_name="my_model", versioned=None, description="A regression model for testing purposes", ) - app = VetiverAPI(v, check_prototype=save_ptype) + app = VetiverAPI(v, check_prototype=save_prototype) return app @@ -48,7 +48,7 @@ def test_predict_endpoint_ptype_error(): def test_predict_endpoint_no_ptype(): np.random.seed(500) - client = TestClient(_start_application(save_ptype=False).app) + client = TestClient(_start_application(save_prototype=False).app) data = [{"B": 0, "C": 0, "D": 0}] response = client.post("/predict", json=data) assert response.status_code == 200, response.text @@ -57,7 +57,7 @@ def test_predict_endpoint_no_ptype(): def test_predict_endpoint_no_ptype_batch(): np.random.seed(500) - client = TestClient(_start_application(save_ptype=False).app) + client = TestClient(_start_application(save_prototype=False).app) data = [[0, 0, 0], [0, 0, 0]] response = client.post("/predict", json=data) assert response.status_code == 200, response.text @@ -66,7 +66,7 @@ def test_predict_endpoint_no_ptype_batch(): def test_predict_endpoint_no_ptype_error(): np.random.seed(500) - client = TestClient(_start_application(save_ptype=False).app) + client = TestClient(_start_application(save_prototype=False).app) data = {"hell0", 9, 32.0} with pytest.raises(TypeError): client.post("/predict", json=data) diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index 2c8358ed..fc3fa181 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -101,6 +101,13 @@ def from_pin(cls, board, name: str, version: str = None): model = board.pin_read(name, version) meta = board.pin_meta(name, version) + if meta.user.get("ptype"): + get_prototype = meta.user.get("ptype") + elif meta.user.get("prototype"): + get_prototype = meta.user.get("prototype") + else: + get_prototype = None + return cls( model=model, model_name=name, @@ -111,8 +118,6 @@ def from_pin(cls, board, name: str, version: str = None): url=meta.local.get("url"), # None all the time, besides Connect required_pkgs=meta.user.get("required_pkgs"), ), - prototype_data=json.loads(meta.user.get("ptype")) - if meta.user.get("ptype") - else None, + prototype_data=json.loads(get_prototype) if get_prototype else None, versioned=True, ) From de2c222940b1974d25dda4135cacfe4604b4a898 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Fri, 16 Dec 2022 11:25:37 -0500 Subject: [PATCH 5/9] comments from review --- vetiver/handlers/base.py | 34 +++++++++++------------ vetiver/handlers/torch.py | 2 +- vetiver/server.py | 10 +++---- vetiver/tests/test_build_vetiver_model.py | 26 ++++++++++++----- vetiver/vetiver_model.py | 9 +++--- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/vetiver/handlers/base.py b/vetiver/handlers/base.py index b539f46d..600e7905 100644 --- a/vetiver/handlers/base.py +++ b/vetiver/handlers/base.py @@ -20,14 +20,14 @@ def __init__( @singledispatch -def create_handler(model, ptype_data): +def create_handler(model, prototype_data): """check for model type to handle prediction Parameters ---------- model: object Description of parameter `x`. - ptype_data : object + prototype_data : object An object with information (data) whose layout is to be determined. Returns @@ -63,7 +63,7 @@ class BaseHandler: ---------- model : a trained model - ptype_data : + prototype_data : An object with information (data) whose layout is to be determined. """ @@ -73,9 +73,9 @@ def __init_subclass__(cls, **kwargs): with suppress(AttributeError, NameError): create_handler.register(cls.model_class(), cls) - def __init__(self, model, ptype_data): + def __init__(self, model, prototype_data): self.model = model - self.ptype_data = ptype_data + self.prototype_data = prototype_data def describe(self): """Create description for model""" @@ -93,21 +93,21 @@ def create_meta( return meta - def construct_ptype(self): + def construct_prototype(self): """Create data prototype for a model Parameters ---------- - ptype_data : pd.DataFrame, np.ndarray, or None - Training data to create ptype + prototype_data : pd.DataFrame, np.ndarray, or None + Training data to create prototype Returns ------- - ptype : pd.DataFrame or None + prototype : pd.DataFrame or None Zero-row DataFrame for storing data types """ - ptype = vetiver_create_ptype(self.ptype_data) - return ptype + prototype = vetiver_create_ptype(self.prototype_data) + return prototype def handler_startup(): """Include required packages for prediction @@ -117,7 +117,7 @@ def handler_startup(): """ ... - def handler_predict(self, input_data, check_ptype): + def handler_predict(self, input_data, check_prototype): """Generates method for /predict endpoint in VetiverAPI The `handler_predict` function executes at each API call. Use this @@ -128,8 +128,8 @@ def handler_predict(self, input_data, check_ptype): ---------- input_data: Data used to generate prediction - check_ptype: - If type should be checked against `ptype` or not + check_prototype: + If type should be checked against `prototype` or not Returns ------- @@ -144,8 +144,8 @@ def handler_predict(self, input_data, check_ptype): @create_handler.register -def _(model: base.BaseHandler, ptype_data): - if model.ptype_data is None and ptype_data is not None: - model.ptype_data = ptype_data +def _(model: base.BaseHandler, prototype_data): + if model.prototype_data is None and prototype_data is not None: + model.prototype_data = prototype_data return model diff --git a/vetiver/handlers/torch.py b/vetiver/handlers/torch.py index df622921..788fb601 100644 --- a/vetiver/handlers/torch.py +++ b/vetiver/handlers/torch.py @@ -58,7 +58,7 @@ def handler_predict(self, input_data, check_prototype): if not torch_exists: raise ImportError("Cannot import `torch`.") if check_prototype: - input_data = np.array(input_data, dtype=np.array(self.ptype_data).dtype) + input_data = np.array(input_data, dtype=np.array(self.prototype_data).dtype) prediction = self.model(torch.from_numpy(input_data)) # do not check ptype diff --git a/vetiver/server.py b/vetiver/server.py index 7c457a30..1cb5af5b 100644 --- a/vetiver/server.py +++ b/vetiver/server.py @@ -43,21 +43,22 @@ def __init__( self, model: VetiverModel, check_prototype: bool = True, - check_ptype: bool = None, app_factory=FastAPI, + **kwargs, ) -> None: self.model = model self.app_factory = app_factory self.app = app_factory() - if check_ptype is not None: - check_prototype = check_ptype + if "check_ptype" in kwargs: + check_prototype = kwargs.pop("check_ptype") warn( "argument for checking input data prototype has changed to " "check_prototype, from check_ptype", DeprecationWarning, stacklevel=2, ) + self.check_prototype = check_prototype self._init_app() @@ -151,9 +152,6 @@ def vetiver_post(self, endpoint_fx: Callable, endpoint_name: str = None, **kw): if not endpoint_name: endpoint_name = endpoint_fx.__name__ - if hasattr(self.model, "ptype"): - self.model.prototype = self.model.ptype - if self.check_prototype is True: @self.app.post(urljoin("/", endpoint_name), name=endpoint_name) diff --git a/vetiver/tests/test_build_vetiver_model.py b/vetiver/tests/test_build_vetiver_model.py index a517a5bf..aa54d1eb 100644 --- a/vetiver/tests/test_build_vetiver_model.py +++ b/vetiver/tests/test_build_vetiver_model.py @@ -13,8 +13,7 @@ model = get_mock_model().fit(X_df, y) -def test_vetiver_model_array_ptype(): - # build VetiverModel, no ptype +def test_vetiver_model_array_prototype(): vt1 = vt.VetiverModel( model=model, prototype_data=X_array, @@ -29,8 +28,7 @@ def test_vetiver_model_array_ptype(): assert list(vt1.prototype.__fields__.values())[0].type_ == int -def test_vetiver_model_df_ptype(): - # build VetiverModel, df ptype_data +def test_vetiver_model_df_prototype(): vt2 = vt.VetiverModel( model=model, prototype_data=X_df, @@ -45,7 +43,7 @@ def test_vetiver_model_df_ptype(): assert list(vt2.prototype.__fields__.values())[0].type_ == int -def test_vetiver_model_dict_ptype(): +def test_vetiver_model_dict_prototype(): dict_data = {"B": 0, "C": 0, "D": 0} vt3 = vt.VetiverModel( model=model, @@ -61,8 +59,7 @@ def test_vetiver_model_dict_ptype(): assert list(vt3.prototype.__fields__.values())[0].type_ == int -def test_vetiver_model_no_ptype(): - # build VetiverModel, no ptype +def test_vetiver_model_no_prototype(): vt4 = vt.VetiverModel( model=model, prototype_data=None, @@ -76,6 +73,21 @@ def test_vetiver_model_no_ptype(): assert vt4.prototype is None +def test_vetiver_model_use_ptype(): + vt5 = vt.VetiverModel( + model=model, + ptype_data=X_df, + model_name="model", + versioned=None, + description=None, + metadata=None, + ) + + assert vt5.model == model + assert isinstance(vt5.prototype.construct(), pydantic.BaseModel) + assert list(vt5.prototype.__fields__.values())[0].type_ == int + + def test_vetiver_model_from_pin(): v = vt.VetiverModel( diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index fc3fa181..93a67e09 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -66,17 +66,16 @@ def __init__( model, model_name: str, prototype_data=None, - ptype_data=None, versioned=None, description: str = None, metadata: dict = None, **kwargs ): - if ptype_data is not None: - prototype_data = ptype_data + if "ptype_data" in kwargs: + prototype_data = kwargs.pop("ptype_data") warn( "argument for saving input data prototype has changed to " - "save_prototype, from save_ptype", + "prototype_data, from ptype_data", DeprecationWarning, stacklevel=2, ) @@ -84,7 +83,7 @@ def __init__( translator = create_handler(model, prototype_data) self.model = translator.model - self.prototype = translator.construct_ptype() + self.prototype = translator.construct_prototype() self.model_name = model_name self.description = description if description else translator.describe() self.versioned = versioned From 052488a16adad8fb5c974c50493439b9014d133e Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Fri, 16 Dec 2022 14:32:07 -0500 Subject: [PATCH 6/9] move to vetiver_create_prototype --- vetiver/__init__.py | 3 +- vetiver/handlers/base.py | 4 +- vetiver/{ptype.py => prototype.py} | 60 +++++++++++++++--------------- vetiver/server.py | 9 ++++- vetiver/types.py | 11 ++++++ vetiver/vetiver_model.py | 10 +++-- 6 files changed, 59 insertions(+), 38 deletions(-) rename vetiver/{ptype.py => prototype.py} (80%) create mode 100644 vetiver/types.py diff --git a/vetiver/__init__.py b/vetiver/__init__.py index dcd1d597..ea794476 100644 --- a/vetiver/__init__.py +++ b/vetiver/__init__.py @@ -2,7 +2,7 @@ # Change to import.metadata when minimum python>=3.8 from importlib_metadata import version as _version -from .ptype import * # noqa +from .prototype import * # noqa from .vetiver_model import VetiverModel # noqa from .server import VetiverAPI, vetiver_endpoint, predict # noqa from .mock import get_mock_data, get_mock_model # noqa @@ -19,6 +19,7 @@ from .rsconnect import deploy_rsconnect # noqa from .monitor import compute_metrics, pin_metrics, plot_metrics, _rolling_df # noqa from .model_card import model_card # noqa +from .types import create_prototype, Prototype # noqa __author__ = "Isabel Zimmerman " __all__ = [] diff --git a/vetiver/handlers/base.py b/vetiver/handlers/base.py index 600e7905..2ceda71d 100644 --- a/vetiver/handlers/base.py +++ b/vetiver/handlers/base.py @@ -2,7 +2,7 @@ from functools import singledispatch from contextlib import suppress -from ..ptype import vetiver_create_ptype +from ..prototype import vetiver_create_prototype from ..meta import _model_meta @@ -106,7 +106,7 @@ def construct_prototype(self): prototype : pd.DataFrame or None Zero-row DataFrame for storing data types """ - prototype = vetiver_create_ptype(self.prototype_data) + prototype = vetiver_create_prototype(self.prototype_data) return prototype def handler_startup(): diff --git a/vetiver/ptype.py b/vetiver/prototype.py similarity index 80% rename from vetiver/ptype.py rename to vetiver/prototype.py index f0296c80..1e60046d 100644 --- a/vetiver/ptype.py +++ b/vetiver/prototype.py @@ -8,21 +8,9 @@ import pandas as pd import numpy as np -from pydantic import BaseModel, create_model - - -class NoAvailablePTypeError(Exception): - """ - Throw an error if we cannot create - a 0 row input data prototype for `model` - """ - - def __init__( - self, - message="There is no method to create a 0-row input data prototype for `model`", - ): - self.message = message - super().__init__(self.message) +import pydantic +from warnings import warn +from .types import create_prototype class InvalidPTypeError(Exception): @@ -39,14 +27,14 @@ def __init__( CREATE_PTYPE_TPL = """\ -Failed to create a data prototype (ptype) from data of \ +Failed to create a data prototype from data of \ type {_data_type}. If your datatype is not one of \ (pd.DataFrame, pydantic.BaseModel, np.ndarry, dict), \ -you should write a function to create the ptype. Here is \ +you should write a function to create the prototype. Here is \ a template for such a function: \ from pydantic import create_model - from vetiver.ptype import vetiver_create_ptype + from vetiver.prototype import vetiver_create_ptype @vetiver_create_ptype.register def _(data: {_data_type}): @@ -59,8 +47,20 @@ def _(data: {_data_type}): """ -@singledispatch def vetiver_create_ptype(data): + + warn( + "argument for creating input data prototypes has changed to " + "vetiver_create_prototype, from vetiver_create_ptype", + DeprecationWarning, + stacklevel=2, + ) + + return vetiver_create_prototype(data) + + +@singledispatch +def vetiver_create_prototype(data): """Create zero row structure to save data types Parameters @@ -77,7 +77,7 @@ def vetiver_create_ptype(data): raise InvalidPTypeError(message=CREATE_PTYPE_TPL.format(_data_type=type(data))) -@vetiver_create_ptype.register +@vetiver_create_prototype.register def _(data: pd.DataFrame): """ Create input data prototype for a pandas dataframe @@ -117,11 +117,11 @@ def _(data: pd.DataFrame): True """ dict_data = data.iloc[0, :].to_dict() - ptype = create_model("ptype", **dict_data) - return ptype + prototype = create_prototype(**dict_data) + return prototype -@vetiver_create_ptype.register +@vetiver_create_prototype.register def _(data: np.ndarray): """ Create input data prototype for a numpy array @@ -156,11 +156,11 @@ def _item(value): dict_data = dict(enumerate(data[0], 0)) # pydantic requires strings as indicies dict_data = {f"{key}": _item(value) for key, value in dict_data.items()} - ptype = create_model("ptype", **dict_data) - return ptype + prototype = create_prototype(**dict_data) + return prototype -@vetiver_create_ptype.register +@vetiver_create_prototype.register def _(data: dict): """ Create input data prototype for a dict @@ -170,11 +170,11 @@ def _(data: dict): data : dict Dictionary """ - return create_model("ptype", **data) + return create_prototype(**data) -@vetiver_create_ptype.register -def _(data: BaseModel): +@vetiver_create_prototype.register +def _(data: pydantic.BaseModel): """ Create input data prototype for a pydantic BaseModel object @@ -186,7 +186,7 @@ def _(data: BaseModel): return data -@vetiver_create_ptype.register +@vetiver_create_prototype.register def _(data: NoneType): """ Create input data prototype for None diff --git a/vetiver/server.py b/vetiver/server.py index 1cb5af5b..819a5b62 100644 --- a/vetiver/server.py +++ b/vetiver/server.py @@ -23,10 +23,10 @@ class VetiverAPI: Model to be deployed in API check_prototype : bool Determine if data prototype should be enforced - check_ptype : bool - Deprecated in favor of check_prototype app_factory : Type of API to be deployed + **kwargs: dict + Deprecated parameters. Example ------- @@ -35,6 +35,11 @@ class VetiverAPI: >>> model = vt.get_mock_model().fit(X, y) >>> v = vt.VetiverModel(model = model, model_name = "my_model", prototype_data = X) >>> v_api = vt.VetiverAPI(model = v, check_prototype = True) + + Notes + ----- + Parameter `check_ptype` was changed to `check_prototype`. Handling of `check_ptype` + will be removed in a future version. """ app = None diff --git a/vetiver/types.py b/vetiver/types.py new file mode 100644 index 00000000..a0970274 --- /dev/null +++ b/vetiver/types.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel, create_model + +all = ["Prototype", "create_prototype"] + + +class Prototype(BaseModel): + pass + + +def create_prototype(**dict_data): + return create_model("prototype", __base__=Prototype, **dict_data) diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index 93a67e09..cd6508c6 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -29,8 +29,6 @@ class VetiverModel: Model name or ID prototype_data : pd.DataFrame, np.array Sample of data model should expect when it is being served - ptype_data : pd.DataFrame, np.array - Deprecated, in favor of prototype_data versioned : Should the model be versioned when created? description : str @@ -38,10 +36,12 @@ class VetiverModel: If omitted, a brief description will be generated. metadata : dict Other details to be saved and accessed for serving + **kwargs: dict + Deprecated parameters. Attributes ---------- - ptype : pydantic.main.BaseModel + prototype : pydantic.main.BaseModel Data prototype handler_predict: Method to make predictions from a trained model @@ -50,6 +50,10 @@ class VetiverModel: ----- VetiverModel can also take an initialized custom VetiverHandler as a model, for advanced use cases or non-supported model types. + Parameter `ptype_data` was changed to `prototype_data`. Handling of `ptype_data` + will be removed in a future version. + + Example ------- From 33ed494d643f8a036c4cccab51690c707afa8c15 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Fri, 16 Dec 2022 17:05:22 -0500 Subject: [PATCH 7/9] removing more references to ptype --- .github/workflows/tests.yml | 6 ++--- vetiver/prototype.py | 31 ++++++++++++----------- vetiver/tests/test_build_vetiver_model.py | 23 +++++++++++++++++ vetiver/vetiver_model.py | 4 +-- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc78a2ae..9f311cbd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,9 +78,9 @@ jobs: python-version: 3.8 - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - name: Run Docker + python -m pip install ".[dev]" + python -m pip install --upgrade git+https://github.com/rstudio/vetiver-python@${{ github.sha }} + - name: run Docker run: | python script/setup-docker/docker.py docker build -t mock . diff --git a/vetiver/prototype.py b/vetiver/prototype.py index 1e60046d..c3bc04fb 100644 --- a/vetiver/prototype.py +++ b/vetiver/prototype.py @@ -15,12 +15,13 @@ class InvalidPTypeError(Exception): """ - Throw an error if ptype cannot be recognised + Throw an error if prototype cannot be recognised """ def __init__( self, - message="`ptype_data` must be a pd.DataFrame, a pydantic BaseModel or np.ndarray", + message="`prototype_data` must be a pd.DataFrame, a pydantic BaseModel, dict or " + "np.ndarray", ): self.message = message super().__init__(self.message) @@ -33,14 +34,14 @@ def __init__( you should write a function to create the prototype. Here is \ a template for such a function: \ - from pydantic import create_model - from vetiver.prototype import vetiver_create_ptype + from vetiver.prototype import vetiver_create_prototype + from vetiver.types import create_prototype - @vetiver_create_ptype.register + @vetiver_create_prototype.register def _(data: {_data_type}): data_dict = ... # convert data to a dictionary - ptype = create_model("ptype", **data_dict) - return ptype + prototype = create_prototype(**data_dict) + return prototype If your datatype is a common type, please consider submitting \ a pull request. @@ -70,7 +71,7 @@ def vetiver_create_prototype(data): Returns ------- - ptype : pydantic.main.BaseModel + prototype : vetiver.Prototype Data prototype """ @@ -91,17 +92,17 @@ def _(data: pd.DataFrame): -------- >>> from pydantic import BaseModel >>> df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) - >>> prototype = vetiver_create_ptype(df) + >>> prototype = vetiver_create_prototype(df) >>> issubclass(prototype, BaseModel) True >>> prototype() - ptype(x=1, y=4) + prototype(x=1, y=4) The data prototype created for the dataframe is equivalent to: >>> class another_prototype(BaseModel): ... class Config: - ... title = 'ptype' + ... title = 'prototype' ... x: int = 1 ... y: int = 4 @@ -134,14 +135,14 @@ def _(data: np.ndarray): Examples -------- >>> arr = np.array([[1, 4], [2, 5], [3, 6]]) - >>> prototype = vetiver_create_ptype(arr) + >>> prototype = vetiver_create_prototype(arr) >>> prototype() - ptype(0=1, 1=4) + prototype(0=1, 1=4) >>> arr2 = np.array([[1, 'a'], [2, 'b'], [3, 'c']], dtype=object) - >>> prototype2 = vetiver_create_ptype(arr2) + >>> prototype2 = vetiver_create_prototype(arr2) >>> prototype2() - ptype(0=1, 1='a') + prototype(0=1, 1='a') """ def _item(value): diff --git a/vetiver/tests/test_build_vetiver_model.py b/vetiver/tests/test_build_vetiver_model.py index aa54d1eb..2d8df52c 100644 --- a/vetiver/tests/test_build_vetiver_model.py +++ b/vetiver/tests/test_build_vetiver_model.py @@ -13,6 +13,12 @@ model = get_mock_model().fit(X_df, y) +class MockPrototype(pydantic.BaseModel): + B: int + C: int + D: int + + def test_vetiver_model_array_prototype(): vt1 = vt.VetiverModel( model=model, @@ -24,6 +30,7 @@ def test_vetiver_model_array_prototype(): ) assert vt1.model == model + assert issubclass(vt1.prototype, vt.Prototype) assert isinstance(vt1.prototype.construct(), pydantic.BaseModel) assert list(vt1.prototype.__fields__.values())[0].type_ == int @@ -59,6 +66,22 @@ def test_vetiver_model_dict_prototype(): assert list(vt3.prototype.__fields__.values())[0].type_ == int +def test_vetiver_model_basemodel_prototype(): + + m = MockPrototype(B=4, C=0, D=0) + vt4 = vt.VetiverModel( + model=model, + prototype_data=m, + model_name="model", + versioned=None, + description=None, + metadata=None, + ) + + assert vt4.model == model + assert vt4.prototype is m + + def test_vetiver_model_no_prototype(): vt4 = vt.VetiverModel( model=model, diff --git a/vetiver/vetiver_model.py b/vetiver/vetiver_model.py index cd6508c6..843f607b 100644 --- a/vetiver/vetiver_model.py +++ b/vetiver/vetiver_model.py @@ -41,9 +41,9 @@ class VetiverModel: Attributes ---------- - prototype : pydantic.main.BaseModel + prototype : vetiver.Prototype Data prototype - handler_predict: + handler_predict: Callable Method to make predictions from a trained model Notes From 5eedbd4ec953bc1520a781ee2e4c0ff4613974a8 Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Fri, 16 Dec 2022 17:11:07 -0500 Subject: [PATCH 8/9] remove ptype if it exists at server --- vetiver/server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vetiver/server.py b/vetiver/server.py index 819a5b62..e621d404 100644 --- a/vetiver/server.py +++ b/vetiver/server.py @@ -63,6 +63,9 @@ def __init__( DeprecationWarning, stacklevel=2, ) + if hasattr(self.model, "ptype"): + self.model.prototype = self.model.ptype + delattr(self.model, "ptype") self.check_prototype = check_prototype From 6403b5728c944727ab7c5b4bc2ac7ab6c7c2f3dc Mon Sep 17 00:00:00 2001 From: isabelizimm Date: Fri, 16 Dec 2022 17:12:25 -0500 Subject: [PATCH 9/9] update tests --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f311cbd..4d8febf2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,6 +83,7 @@ jobs: - name: run Docker run: | python script/setup-docker/docker.py + pip freeze > vetiver_requirements.txt docker build -t mock . docker run -d -v $PWD/pinsboard:/vetiver/pinsboard -p 8080:8080 mock sleep 5