Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,6 @@ jobs:
run: |
pytest vetiver/tests/test_sklearn.py

test-pydantic-old:
name: "Test pydantic v1"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .[dev]
python -m pip install 'pydantic<2.0.0'

- name: Run tests
run: |
make test

typecheck:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ requires-python = ">=3.9"
dependencies =[
"numpy",
"pandas",
"fastapi",
"pydantic",
"fastapi>0.93",
"pydantic>2",
"joblib",
"uvicorn",
"scikit-learn",
Expand Down
8 changes: 4 additions & 4 deletions vetiver/prototype.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def _item(value):
# if its a numpy type, we have to take the Python type due to Pydantic

dict_data = {
f"{key}": (type(value.item()), Field(..., example=_item(value)))
f"{key}": (type(value.item()), Field(..., examples=[_item(value)]))
for key, value in dict_data.items()
}
prototype = create_prototype(**dict_data)
Expand All @@ -197,8 +197,8 @@ def _(data: dict):
dict_data.update(
{
key: (
type(value["example"]),
Field(..., example=value["example"]),
type(value["examples"]),
Field(..., examples=[value["examples"]]),
)
}
)
Expand Down Expand Up @@ -242,5 +242,5 @@ def _(data: NoneType):
def _to_field(data):
basemodel_input = dict()
for key, value in data.items():
basemodel_input[key] = (type(value), Field(..., example=value))
basemodel_input[key] = (type(value), Field(..., examples=[value]))
return basemodel_input
20 changes: 11 additions & 9 deletions vetiver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Callable, List, Union
from urllib.parse import urljoin
from warnings import warn
from contextlib import asynccontextmanager

import httpx
import pandas as pd
Expand Down Expand Up @@ -77,7 +78,7 @@ def __init__(
) -> None:
self.model = model
self.app_factory = app_factory
self.app = app_factory()
self.app = app_factory(lifespan=self._lifespan)
self.workbench_path = None

if "check_ptype" in kwargs:
Expand All @@ -97,18 +98,19 @@ def __init__(

self._init_app()

@asynccontextmanager
async def _lifespan(self, app: FastAPI):
logger = logging.getLogger("uvicorn.error")
if self.workbench_path:
logger.info(f"VetiverAPI starting at {self.workbench_path}")
else:
logger.info("VetiverAPI starting...")
yield

def _init_app(self):
app = self.app
app.openapi = self._custom_openapi

@app.on_event("startup")
async def startup_event():
logger = logging.getLogger("uvicorn.error")
if self.workbench_path:
logger.info(f"VetiverAPI starting at {self.workbench_path}")
else:
logger.info("VetiverAPI starting...")

@app.get("/", include_in_schema=False)
def docs_redirect():

Expand Down
2 changes: 1 addition & 1 deletion vetiver/templates/model_card.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ A [model card](https://doi.org/10.1145/3287560.3287596) provides brief, transpar
```{python}
#| echo: false
model_desc = v.description
num_features = len(v.prototype.construct().dict())
num_features = len(v.prototype.model_construct().model_dump())

display(Markdown(f"""
- A {model_desc} using {num_features} feature{'s'[:num_features^1]}.
Expand Down
4 changes: 2 additions & 2 deletions vetiver/tests/test_add_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def data() -> pd.DataFrame:


def test_endpoint_adds(client, data):
response = client.post("/sum/", data=data.to_json(orient="records"))
response = client.post("/sum/", content=data.to_json(orient="records"))

assert response.status_code == 200
assert response.json() == {"sum": [3, 6, 9]}
Expand All @@ -26,7 +26,7 @@ def test_endpoint_adds(client, data):
def test_endpoint_adds_no_prototype(client_no_prototype, data):

data = pd.DataFrame({"B": [1, 1, 1], "C": [2, 2, 2], "D": [3, 3, 3]})
response = client_no_prototype.post("/sum/", data=data.to_json(orient="records"))
response = client_no_prototype.post("/sum/", content=data.to_json(orient="records"))

assert response.status_code == 200
assert response.json() == {"sum": [3, 6, 9]}
4 changes: 2 additions & 2 deletions vetiver/tests/test_custom_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_custom_vetiver_model():
assert not v.metadata.required_pkgs
assert isinstance(v.model, sklearn.dummy.DummyRegressor)
# change to model_construct for pydantic v3
assert isinstance(v.prototype.construct(), pydantic.BaseModel)
assert isinstance(v.prototype.model_construct(), pydantic.BaseModel)


def test_custom_vetiver_model_no_ptype():
Expand All @@ -60,4 +60,4 @@ def test_custom_vetiver_model_no_ptype():
assert v.description == "A regression model for testing purposes"
assert isinstance(v.model, sklearn.dummy.DummyRegressor)
# change to model_construct for pydantic v3
assert isinstance(v.prototype.construct(), pydantic.BaseModel)
assert isinstance(v.prototype.model_construct(), pydantic.BaseModel)
2 changes: 1 addition & 1 deletion vetiver/tests/test_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import pytest

rng = pd.date_range("1/1/2012", periods=10, freq="S")
rng = pd.date_range("1/1/2012", periods=10, freq="s")
new = dict(x=range(len(rng)), y=range(len(rng)))
df = pd.DataFrame(new, index=rng)
td = timedelta(seconds=2)
Expand Down
48 changes: 34 additions & 14 deletions vetiver/tests/test_prepare_docker.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import pytest
import vetiver
import pins
import requests
from pathlib import Path
from tempfile import TemporaryDirectory
import pandas as pd
import numpy as np

DOCKER_URL = "http://0.0.0.0:8080/predict"
DOCKER_URL = "http://0.0.0.0:8080"

# uses GitHub Actions to deploy model into Docker
# see vetiver-python/script/setup-docker for files


@pytest.mark.docker
def test_deployed_dockerfile():
np.random.seed(500)

X, y = vetiver.mock.get_mock_data()
response = vetiver.predict(endpoint=DOCKER_URL, data=X)

assert isinstance(response, pd.DataFrame), response
assert response.iloc[0, 0] == 44.47
assert len(response) == 100


@pytest.fixture()
def create_vetiver_model():
X, y = vetiver.get_mock_data()
Expand All @@ -41,6 +30,38 @@ def test_warning_if_no_protocol(create_vetiver_model):
vetiver.get_board_pkgs(board)


@pytest.mark.docker
def test_prototype():
np.random.seed(500)

X, y = vetiver.mock.get_mock_data()
response = requests.get(endpoint=DOCKER_URL + "/prototype")
response = requests.get("/prototype")
assert response.status_code == 200, response.text
assert response.json() == {
"properties": {
"B": {"examples": [55], "type": "integer"},
"C": {"examples": [65], "type": "integer"},
"D": {"examples": [17], "type": "integer"},
},
"required": ["B", "C", "D"],
"title": "prototype",
"type": "object",
}


@pytest.mark.docker
def test_deployed_dockerfile():
np.random.seed(500)

X, y = vetiver.mock.get_mock_data()
response = vetiver.predict(endpoint=DOCKER_URL + "/predict", data=X)

assert isinstance(response, pd.DataFrame), response
assert response.iloc[0, 0] == 44.47
assert len(response) == 100


@pytest.mark.parametrize(
"prot,output",
[
Expand All @@ -49,7 +70,6 @@ def test_warning_if_no_protocol(create_vetiver_model):
(("gcs", "gs"), "gcsfs"),
],
)
@pytest.fixture(scope="module")
def test_get_board_pkgs(prot, output, create_vetiver_model):
board = pins.board_temp(allow_pickle_read=True)
board.fs.protocol = prot
Expand Down
26 changes: 25 additions & 1 deletion vetiver/tests/test_rsconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import sklearn
import pins
import requests
import pandas as pd
import numpy as np

Expand Down Expand Up @@ -84,13 +85,36 @@ def test_deploy(rsc_short):
extra_files=["requirements.txt"],
)

h = {"Authorization": f'Key {get_key("susan")}'}
# get url of where content lives
client = RSConnectClient(connect_server)
dicts = client.content_search()
rsc_api = list(filter(lambda x: x["title"] == "testapi", dicts))
content_url = rsc_api[0].get("content_url")

h = {"Authorization": f'Key {get_key("susan")}'}
# check that endpoint is alive
ping_response = requests.get(content_url + "/ping", headers=h)
assert ping_response.status_code == 200, ping_response.text

prototype_response = requests.get(content_url + "/prototype", headers=h)
assert prototype_response.status_code == 200, prototype_response.text
assert prototype_response.json() == {
"properties": {
"B": {"examples": [55], "type": "integer"},
"C": {"examples": [65], "type": "integer"},
"D": {"examples": [17], "type": "integer"},
},
"required": ["B", "C", "D"],
"title": "prototype",
"type": "object",
}

assert (
model.prototype.model_construct().model_dump()
== vetiver.vetiver_create_prototype(prototype_response.json())
.model_construct()
.model_dump()
)

endpoint = vetiver.vetiver_endpoint(content_url + "/predict")
response = vetiver.predict(endpoint, X_df, headers=h)
Expand Down
12 changes: 6 additions & 6 deletions vetiver/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class CustomPrototype(BaseModel):
v = VetiverModel(
model=model,
# move to model_construct for pydantic 3
prototype_data=CustomPrototype.construct(),
prototype_data=CustomPrototype.model_construct(),
model_name="my_model",
versioned=None,
description="A regression model for testing purposes",
Expand Down Expand Up @@ -82,18 +82,18 @@ def test_get_prototype(client, model):
assert response.status_code == 200, response.text
assert response.json() == {
"properties": {
"B": {"example": 55, "type": "integer"},
"C": {"example": 65, "type": "integer"},
"D": {"example": 17, "type": "integer"},
"B": {"examples": [55], "type": "integer"},
"C": {"examples": [65], "type": "integer"},
"D": {"examples": [17], "type": "integer"},
},
"required": ["B", "C", "D"],
"title": "prototype",
"type": "object",
}

assert (
model.prototype.construct().dict()
== vetiver_create_prototype(response.json()).construct().dict()
model.prototype.model_construct().model_dump()
== vetiver_create_prototype(response.json()).model_construct().model_dump()
)


Expand Down
4 changes: 2 additions & 2 deletions vetiver/tests/test_spacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def test_good_prototype_shape(data, spacy_model):
model_schema = v.prototype.model_json_schema()
expected = {
"properties": {
"col": {"example": "1", "title": "Col", "type": "string"},
"col": {"examples": ["1"], "title": "Col", "type": "string"},
},
"required": ["col"],
"title": "prototype",
Expand All @@ -77,7 +77,7 @@ def test_good_prototype_shape(data, spacy_model):
except AttributeError: # pydantic v1
model_schema = v.prototype.schema_json()
expected = '{"title": "prototype", "type": "object", "properties": \
{"col": {"title": "Col", "example": "1", "type": "string"}}, "required": ["col"]}'
{"col": {"title": "Col", "examples": ["1"], "type": "string"}}, "required": ["col"]}'

assert model_schema == expected

Expand Down
Loading
Loading