Skip to content

Commit e7cb91d

Browse files
authored
Merge pull request #195 from rstudio/custom-docs
2 parents b9248cf + 0a1c1c3 commit e7cb91d

File tree

5 files changed

+205
-48
lines changed

5 files changed

+205
-48
lines changed

.github/workflows/docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
run: |
2424
python -m pip install --upgrade pip
2525
python -m pip install -e .[docs]
26+
python -m pip install griffe==0.32.3
2627
- name: Set up Quarto
2728
uses: quarto-dev/quarto-actions/setup@v2
2829
- name: build docs

docs/_quarto.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ website:
2424
file: reference/index.qmd
2525
- text: "Advanced Usage"
2626
menu:
27-
- custom_handler.md
27+
- custom_elements.qmd
2828
- text: "Changelog"
2929
file: changelog.md
3030
- text: "Learn more"

docs/custom_code.qmd

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Creating and deploying custom code
2+
3+
In some cases, you may need to create and deploy custom code as part of your MLOps workflow using vetiver. This could be necessary when you need to:
4+
5+
- deploy custom models in vetiver
6+
- deploy unsupported models in vetiver
7+
- include custom code in vetiver
8+
- deploy a vetiver model with a custom pipeline
9+
10+
You may also have custom code in a known framework, such as a column transformer for a scikit-learn model.
11+
12+
In these cases, extra steps will be required to successfully create and deploy a `VetiverModel` object.
13+
14+
# Making a custom model
15+
16+
Vetiver supports basic [scikit-learn](https://scikit-learn.org/), [torch](https://pytorch.org/), [statsmodels](https://www.statsmodels.org/stable/index.html), [xgboost](https://xgboost.readthedocs.io/en/stable/), and [spacy](https://spacy.io/) models. If you need to alter the usage of these models, or deploy a different type of model, you will likely need to create a new model handler.
17+
18+
To create a model handler, you should create a subclass of vetiver's `BaseHandler` class. This handler should include the following:
19+
20+
- `model_type`: A static method that declares the type of your model.
21+
- `handler_predict()`: A method that defines how predictions should be made for your model. This method is used at the /predict endpoint in the VetiverAPI.
22+
23+
Here's an example of a handler for a model of `newmodeltype` type. Once you have defined your handler, you can initialize it with your model and pass it to the `VetiverModel` class.
24+
25+
```python
26+
from vetiver.handlers.base import BaseHandler
27+
28+
class CustomHandler(BaseHandler):
29+
def __init__(self, model, prototype_data):
30+
super().__init__(model, prototype_data)
31+
32+
model_type = staticmethod(lambda: newmodeltype)
33+
pip_name = "scikit-learn" # package's installation name on pip
34+
35+
def handler_predict(self, input_data, check_prototype: bool):
36+
"""
37+
Your code for making predictions using the custom model
38+
39+
Parameters
40+
----------
41+
input_data:
42+
Data POSTed to API endpoint
43+
check_prototype: bool
44+
Whether the prototype should be enforced
45+
"""
46+
prediction = model.fancy_new_predict(input_data)
47+
48+
return prediction
49+
50+
new_model = CustomHandler(model, prototype_data)
51+
52+
VetiverModel(new_model, "custom_model")
53+
```
54+
55+
If your model is a common type, please consider [submitting a pull request](https://github.com/rstudio/vetiver-python/pulls).
56+
57+
To deploy custom code, you need to include the necessary source code in your deployment files. If your model or other elements can be imported from a Python package, you can include the relevant packages in a `requirements.txt` file for deployment. However, if you have custom source code in local files, you will need to include those files in the deployment process.
58+
59+
# Deploying custom elements
60+
61+
If your `VetiverModel` includes custom source code, you need to include that code in your deployment files to build an API in another location. The example below shows a user-created `FeatureSelector`, which is part of a scikit-learn pipeline.
62+
63+
```{.python filename="model.py"}
64+
from sklearn.base import BaseEstimator, TransformerMixin
65+
from sklearn.tree import DecisionTreeClassifier
66+
from sklearn.pipeline import Pipeline
67+
68+
# create custom data preprocessing
69+
class FeatureSelector(BaseEstimator, TransformerMixin):
70+
def __init__(self, columns):
71+
self.columns = columns
72+
73+
def fit(self, X, y=None):
74+
return self
75+
76+
def transform(self, X, y=None):
77+
return X[self.columns]
78+
79+
# create model
80+
model = Pipeline(steps=[
81+
('feature_selector', FeatureSelector(features)),
82+
('decision_tree', DecisionTreeClassifier())
83+
])
84+
85+
# create deployable model object
86+
from vetiver import VetiverModel, vetiver_pin_write
87+
88+
v = VetiverModel(model, "selected_decision_tree", protoype_data = X)
89+
90+
# pin model to some location, eg, Posit Connect
91+
import pins
92+
93+
board = pins.board_connect(allow_pickle_read=True)
94+
vetiver_pin_write(board, v)
95+
```
96+
97+
::: {.panel-tabset}
98+
## Docker
99+
100+
To generate files needed to start a Docker container, you can use the command `vetiver.prepare_docker`.
101+
102+
```{.python}
103+
vetiver.prepare_docker(board, "selected_decision_tree")
104+
```
105+
106+
When you run this line, 3 files are generated: a Dockerfile, an `app.py` file, and a `vetiver_requirements.txt`. In the `app.py` file, you'll need to add an import statement that is formatted `from {name of file, excluding .py, that has custom element} import {name of custom element}`.
107+
108+
```{.python filename="app.py"}
109+
from vetiver import VetiverModel
110+
import vetiver
111+
import pins
112+
from model import FeatureSelector # add this line to import your custom feature engineering
113+
114+
115+
b = pins.board_connect(allow_pickle_read=True)
116+
v = VetiverModel.from_pin(b, 'selected_decision_tree')
117+
118+
vetiver_api = vetiver.VetiverAPI(v)
119+
api = vetiver_api.app
120+
```
121+
122+
Add a line to your Dockerfile to copy your source file(s) into your Docker container. The format will be `COPY path/to/your/filename.py /vetiver/app/filename.py`, where the destination is always in the `/vetiver/app/` directory.
123+
124+
```{.bash filename="Dockerfile"}
125+
# # Generated by the vetiver package; edit with care
126+
# start with python base image
127+
FROM python:3.10
128+
129+
# create directory in container for vetiver files
130+
WORKDIR /vetiver
131+
132+
# copy and install requirements
133+
COPY vetiver_requirements.txt /vetiver/requirements.txt
134+
135+
#
136+
RUN pip install --no-cache-dir --upgrade -r /vetiver/requirements.txt
137+
138+
# copy app file
139+
COPY app.py /vetiver/app/app.py
140+
141+
# ADD THIS LINE to copy model source code
142+
COPY model.py /vetiver/app/model.py
143+
144+
# expose port
145+
EXPOSE 8080
146+
147+
# run vetiver API
148+
CMD ["uvicorn", "app.app:api", "--host", "0.0.0.0", "--port", "8080"]
149+
```
150+
151+
## Posit Connect
152+
153+
To deploy custom code to Posit Connect, you'll first start with the command `vetiver.write_app`.
154+
155+
```{.python}
156+
vetiver.write_app(board, 'selected_decision_tree')
157+
```
158+
159+
This will generate an `app.py` file, where you'll need to add an import statement that is formatted `from {name of file, excluding .py, that has custom element} import {name of custom element}`.
160+
161+
```{.python filename=="app.py"}
162+
from vetiver import VetiverModel
163+
import vetiver
164+
import pins
165+
from model import FeatureSelector # add this line to import your custom feature engineering
166+
167+
168+
b = pins.board_connect(allow_pickle_read=True)
169+
v = VetiverModel.from_pin(b, 'selected_decision_tree')
170+
171+
vetiver_api = vetiver.VetiverAPI(v)
172+
api = vetiver_api.app
173+
```
174+
175+
After editing the `app.py` file, you can deploy it to Posit Connect using the `rsconnect` package. Use the `rsconnect.api.actions.deploy_python_fastapi()` function to deploy the API, specifying the Connect server URL, API key, directory containing the `app.py` and `model.py` files, and the entry point of the API.
176+
177+
```{.python}
178+
from rsconnect.api.actions import deploy_python_fastapi
179+
import rsconnect
180+
181+
url = "example.connect.com" # your Posit Connect server url
182+
api_key = os.environ(CONNECT_API_KEY) # your Posit Connect API key
183+
184+
connect_server = rsconnect.api.RSConnectServer(
185+
url = url,
186+
api_key = api_key
187+
)
188+
189+
rsconnect.actions.deploy_python_fastapi(
190+
connect_server = connect_server,
191+
directory = "./", # path to the directory containing the app.py and model.py files
192+
entry_point = "app:api" # the API is the app.py file, in a variable named api
193+
)
194+
195+
```
196+
197+
:::
198+
199+
Please note that the above steps are a general guide, and you may need to adapt them to your specific use case and deployment environment. If you have any questions, please consider [opening an issue](https://github.com/rstudio/vetiver-python/issues/new).

docs/custom_handler.md

Lines changed: 0 additions & 46 deletions
This file was deleted.

vetiver/server.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@ class VetiverAPI:
4646
4747
Notes
4848
-----
49-
This generates an API with either 2 or 3 GET endpoints and 1 POST endpoint.
49+
This generates an API with either 3 or 4 GET endpoints and 1 POST endpoint.
5050
51+
```
5152
├──/ping (GET)
5253
├──/metadata (GET)
54+
├──/prototype (GET)
5355
├──/pin-url (GET, if VetiverModel metadata `url` field is not None)
5456
└──/predict (POST)
57+
```
5558
5659
Parameter `check_ptype` was changed to `check_prototype`. Handling of `check_ptype`
5760
will be removed in a future version.

0 commit comments

Comments
 (0)