Skip to content

Commit 2047a99

Browse files
authored
Merge pull request #70 from isabelizimm/wrap-rsconnect
deploy to rsconnect
2 parents 5200637 + 33c74d5 commit 2047a99

4 files changed

Lines changed: 141 additions & 11 deletions

File tree

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ install_requires =
3333
nest-asyncio
3434
requests
3535
pins
36+
rsconnect-python
3637

3738
[options.extras_require]
3839
dev =

vetiver/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import importlib
88
from .ptype import *
99
from .vetiver_model import VetiverModel
10-
from .server import VetiverAPI, vetiver_endpoint
10+
from .server import VetiverAPI, vetiver_endpoint, predict
1111
from .mock import get_mock_data, get_mock_model
1212
from .pin_read_write import vetiver_pin_write
1313
from .attach_pkgs import *
@@ -18,3 +18,4 @@
1818
from .handlers.base import VetiverHandler
1919
from .handlers.sklearn import SKLearnHandler
2020
from .handlers.torch import TorchHandler
21+
from .rsconnect import deploy_rsconnect

vetiver/rsconnect.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import tempfile
2+
import typing
3+
from rsconnect.actions import deploy_python_fastapi
4+
import shutil
5+
import os
6+
7+
from .write_fastapi import write_app
8+
9+
10+
def deploy_rsconnect(
11+
connect_server,
12+
board,
13+
pin_name: str,
14+
version: str = None,
15+
extra_files: typing.List[str] = None,
16+
new: bool = False,
17+
app_id: int = None,
18+
title: str = None,
19+
python: str = None,
20+
conda_mode: bool = False,
21+
force_generate: bool = False,
22+
log_callback: typing.Callable = None,
23+
image: str = None,
24+
):
25+
"""Deploy to RSConnect
26+
27+
Parameters
28+
----------
29+
connect_server:
30+
RSConnect Server
31+
board:
32+
Pins board
33+
pin_name: str
34+
Name of pin
35+
version: str
36+
Version of pin
37+
extra_files: typing.List[str]
38+
Any extra files to include in
39+
new:
40+
Force as a new deploy
41+
app_id:
42+
ID of an existing application to deploy new files for.
43+
title: str
44+
Optional title for the deploy.
45+
python: str
46+
Optional name of a Python executable
47+
conda_mode: bool
48+
Use conda to build an environment.yml
49+
force_generate: bool
50+
Force generating "requirements.txt" or "environment.yml"
51+
log_callback: typing.Callable
52+
Callback to use to write the log to
53+
image: str
54+
Docker image to be specified for off-host execution
55+
"""
56+
if board.fs.protocol == "rsc":
57+
raise NotImplementedError()
58+
59+
if not title:
60+
title = pin_name + "_vetiver"
61+
62+
with tempfile.TemporaryDirectory() as temp:
63+
if extra_files is not None:
64+
new_files = []
65+
for file in extra_files:
66+
filename = os.path.basename(file)
67+
shutil.copyfile(file, os.path.join(temp, filename))
68+
new_files = new_files + [os.path.join(temp, filename)]
69+
extra_files = new_files
70+
71+
tmp_app = temp + "/app.py"
72+
73+
write_app(
74+
board=board,
75+
pin_name=pin_name,
76+
version=version,
77+
file=tmp_app,
78+
overwrite=False,
79+
)
80+
81+
deploy_python_fastapi(
82+
connect_server=connect_server,
83+
directory=temp,
84+
extra_files=extra_files,
85+
excludes=None,
86+
entry_point="app:api",
87+
new=new,
88+
app_id=app_id,
89+
title=title,
90+
python=python,
91+
conda_mode=conda_mode,
92+
force_generate=force_generate,
93+
log_callback=log_callback,
94+
image=image,
95+
)

vetiver/tests/test_rsconnect.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
11
import pytest
22
import json
3-
from pins.rsconnect.fs import RsConnectFs
3+
import sklearn
44
from pins.boards import BoardRsConnect
55

6+
import vetiver
7+
from vetiver.rsconnect import deploy_rsconnect
8+
9+
# Load data, model
10+
X_df, y = vetiver.mock.get_mock_data()
11+
model = vetiver.mock.get_mock_model().fit(X_df, y)
12+
613
RSC_SERVER_URL = "http://localhost:3939"
714
RSC_KEYS_FNAME = "vetiver/tests/rsconnect_api_keys.json"
815

916
pytestmark = pytest.mark.rsc_test # noqa
1017

18+
19+
def server_from_key(name):
20+
from rsconnect.api import RSConnectServer
21+
22+
with open(RSC_KEYS_FNAME) as f:
23+
api_key = json.load(f)[name]
24+
return RSConnectServer(RSC_SERVER_URL, api_key)
25+
1126
def rsc_from_key(name):
1227
from pins.rsconnect.api import RsConnectApi
1328

1429
with open(RSC_KEYS_FNAME) as f:
1530
api_key = json.load(f)[name]
1631
return RsConnectApi(RSC_SERVER_URL, api_key)
1732

33+
1834
def rsc_fs_from_key(name):
1935
from pins.rsconnect.fs import RsConnectFs
2036

@@ -29,6 +45,7 @@ def rsc_delete_user_content(rsc):
2945
for entry in content:
3046
rsc.delete_content_item(entry["guid"])
3147

48+
3249
@pytest.fixture(scope="function")
3350
def rsc_short():
3451
# tears down content after each test
@@ -37,18 +54,34 @@ def rsc_short():
3754
# delete any content that might already exist
3855
rsc_delete_user_content(fs_susan.api)
3956

40-
yield BoardRsConnect("", fs_susan, allow_pickle_read=True) #fs_susan.ls to list content
57+
yield BoardRsConnect(
58+
"", fs_susan, allow_pickle_read=True
59+
) # fs_susan.ls to list content
4160

4261
rsc_delete_user_content(fs_susan.api)
4362

44-
from vetiver import VetiverModel, vetiver_pin_write, mock
45-
import sklearn
46-
# Load data, model
47-
X_df, y = mock.get_mock_data()
48-
model = mock.get_mock_model().fit(X_df, y)
4963

5064
def test_board_pin_write(rsc_short):
51-
v = VetiverModel(model=model, ptype_data=X_df,
52-
model_name="susan/model", versioned=None)
53-
vetiver_pin_write(board=rsc_short, model=v)
65+
v = vetiver.VetiverModel(
66+
model=model, ptype_data=X_df, model_name="susan/model", versioned=None
67+
)
68+
vetiver.vetiver_pin_write(board=rsc_short, model=v)
5469
assert isinstance(rsc_short.pin_read("susan/model"), sklearn.dummy.DummyRegressor)
70+
71+
@pytest.mark.xfail
72+
def test_deploy(rsc_short):
73+
# TODO: test in Dockerfile
74+
v = vetiver.VetiverModel(
75+
model=model, ptype_data=X_df, model_name="susan/model", versioned=None
76+
)
77+
78+
vetiver.vetiver_pin_write(board=rsc_short, model=v)
79+
80+
deploy_rsconnect(
81+
connect_server=server_from_key("susan"),
82+
board=rsc_short,
83+
pin_name="susan/model"
84+
)
85+
response = vetiver.predict(RSC_SERVER_URL + "/predict/", json=X_df)
86+
assert response.status_code == 200, response.text
87+
assert response.json() == {"prediction": [44.47, 44.47]}, response.json()

0 commit comments

Comments
 (0)