Skip to content

Commit d405f4f

Browse files
authored
Merge pull request #166 from rstudio/pins-req
2 parents a265da0 + 238e967 commit d405f4f

File tree

8 files changed

+136
-51
lines changed

8 files changed

+136
-51
lines changed

vetiver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .server import VetiverAPI, vetiver_endpoint, predict # noqa
88
from .mock import get_mock_data, get_mock_model # noqa
99
from .pin_read_write import vetiver_pin_write # noqa
10-
from .attach_pkgs import load_pkgs # noqa
10+
from .attach_pkgs import load_pkgs, get_board_pkgs # noqa
1111
from .meta import VetiverMeta # noqa
1212
from .write_docker import write_docker, prepare_docker # noqa
1313
from .write_fastapi import write_app, vetiver_write_app # noqa

vetiver/attach_pkgs.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
import tempfile
22
import os
3+
import warnings
4+
from typing import List
35
from .vetiver_model import VetiverModel
46
from .meta import VetiverMeta
57

68

79
def load_pkgs(model: VetiverModel = None, packages: list = None, path=""):
810
"""Load packages necessary for predictions
911
10-
Args
11-
----
12-
model: VetiverModel
13-
VetiverModel to extract packages from
14-
packages: list
15-
List of extra packages to include
16-
path: str
17-
Where to save output file
12+
Parameters
13+
----------
14+
model: VetiverModel
15+
VetiverModel to extract packages from
16+
packages: list
17+
List of extra packages to include
18+
path: str
19+
Where to save output file
1820
"""
1921

2022
required_pkgs = ["vetiver"]
2123
if packages:
22-
required_pkgs = list(set(required_pkgs + packages))
24+
required_pkgs += packages
2325

2426
if isinstance(model.metadata, dict):
2527
model.metadata = VetiverMeta.from_dict(model.metadata)
2628

2729
if model.metadata.required_pkgs:
28-
required_pkgs = list(set(required_pkgs + model.metadata.required_pkgs))
30+
required_pkgs += model.metadata.required_pkgs
2931

3032
tmp = tempfile.NamedTemporaryFile(suffix=".in", delete=False)
3133
tmp.close()
@@ -36,3 +38,37 @@ def load_pkgs(model: VetiverModel = None, packages: list = None, path=""):
3638

3739
os.system(f"pip-compile {tmp.name} --output-file={path}requirements.txt")
3840
os.remove(tmp.name)
41+
42+
43+
def get_board_pkgs(board) -> List[str]:
44+
"""
45+
Extract packages required for pin board authorization
46+
47+
Parameters
48+
----------
49+
board:
50+
A pin board, created by `pins.board_folder()` or another `board_` function.
51+
52+
Returns
53+
--------
54+
list[str]
55+
"""
56+
prot = board.fs.protocol
57+
58+
if prot == "rsc":
59+
return ["rsconnect-python"]
60+
elif prot == "file":
61+
return []
62+
elif prot == ["s3", "s3a"]:
63+
return ["s3fs"]
64+
elif prot == "abfs":
65+
return ["adlfs"]
66+
elif prot == ("gcs", "gs"):
67+
return ["gcsfs"]
68+
else:
69+
warnings.warn(
70+
f"required packages unknown for board protocol: {prot}, "
71+
"add to model's metadata to export",
72+
UserWarning,
73+
)
74+
return []

vetiver/pin_read_write.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ def vetiver_pin_write(board, model: VetiverModel, versioned: bool = True):
4343
>>> vetiver.vetiver_pin_write(model_board, v)
4444
"""
4545
if not board.allow_pickle_read:
46-
raise NotImplementedError # must be pickle-able
46+
raise ValueError(
47+
"board does not allow pickled models. Set "
48+
"allow_pickle_read to True on board creation."
49+
)
4750

4851
inform(
4952
_log,

vetiver/tests/test_pin_write.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_board_pin_write_error():
1616
model=model, prototype_data=X_df, model_name="model", versioned=None
1717
)
1818
board = pins.board_temp()
19-
with pytest.raises(NotImplementedError):
19+
with pytest.raises(ValueError):
2020
vetiver_pin_write(board=board, model=v)
2121

2222

vetiver/tests/test_prepare_docker.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import pytest
22
import vetiver
3+
import pins
4+
from pathlib import Path
5+
from tempfile import TemporaryDirectory
36
import pandas as pd
47
import numpy as np
58

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

8-
pytestmark = pytest.mark.docker # noqa
11+
# uses GitHub Actions to deploy model into Docker
12+
# see vetiver-python/script/setup-docker for files
913

1014

11-
def test_predict_sklearn_df_check_ptype():
15+
@pytest.mark.docker
16+
def test_deployed_dockerfile():
1217
np.random.seed(500)
1318

1419
X, y = vetiver.mock.get_mock_data()
@@ -17,3 +22,42 @@ def test_predict_sklearn_df_check_ptype():
1722
assert isinstance(response, pd.DataFrame), response
1823
assert response.iloc[0, 0] == 44.47
1924
assert len(response) == 100
25+
26+
27+
@pytest.fixture()
28+
def create_vetiver_model():
29+
X, y = vetiver.get_mock_data()
30+
model = vetiver.get_mock_model()
31+
32+
return vetiver.VetiverModel(model.fit(X, y), "model", prototype_data=X)
33+
34+
35+
@pytest.fixture(scope="module")
36+
def test_warning_if_no_protocol(create_vetiver_model):
37+
with pytest.warns(UserWarning):
38+
board = pins.board_temp(allow_pickle_read=True)
39+
board.fs.protocol = "abc"
40+
41+
vetiver.get_board_pkgs(board)
42+
43+
44+
@pytest.mark.parametrize(
45+
"prot,output",
46+
[
47+
(["s3", "s3a"], "s3fs"),
48+
("abfs", "adlfs"),
49+
(("gcs", "gs"), "gcsfs"),
50+
],
51+
)
52+
@pytest.fixture(scope="module")
53+
def test_get_board_pkgs(prot, output, create_vetiver_model):
54+
board = pins.board_temp(allow_pickle_read=True)
55+
board.fs.protocol = prot
56+
57+
vetiver.vetiver_pin_write(board, create_vetiver_model)
58+
59+
with TemporaryDirectory() as tempdir:
60+
vetiver.prepare_docker(board, "model", path=tempdir)
61+
file = Path(tempdir, "vetiver_requirements.txt")
62+
contents = open(file).read()
63+
assert f"{output}==" in contents

vetiver/tests/test_write_app.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,39 @@
1-
import os
21
import pins
32
import vetiver
3+
import pytest
4+
from tempfile import TemporaryDirectory
5+
from pathlib import Path
46

5-
# Load data, model
6-
X_df, y = vetiver.get_mock_data()
7-
model = vetiver.get_mock_model().fit(X_df, y)
87

8+
@pytest.fixture
9+
def vetiver_model_creation():
10+
X_df, y = vetiver.get_mock_data()
11+
model = vetiver.get_mock_model().fit(X_df, y)
12+
return vetiver.VetiverModel(model, "model")
913

10-
def test_write_app():
11-
file = "app.py"
12-
v = vetiver.VetiverModel(
13-
model=model, prototype_data=X_df, model_name="model", versioned=None
14-
)
15-
model_board = pins.board_folder(path=".", versioned=True, allow_pickle_read=True)
16-
vetiver.vetiver_pin_write(board=model_board, model=v)
17-
vetiver.write_app(model_board, "model", file="app.py")
18-
contents = open(file).read()
19-
os.remove(file)
20-
version = model_board.pin_versions("model").sort_values(
21-
by="created", ascending=False
22-
)
23-
version = version.version[0]
24-
assert (
25-
contents
26-
== f"""from vetiver import VetiverModel
14+
15+
def test_write_app(vetiver_model_creation):
16+
with TemporaryDirectory() as tempdir:
17+
file = Path(tempdir, "app.py")
18+
model_board = pins.board_folder(path=tempdir, allow_pickle_read=True)
19+
vetiver.vetiver_pin_write(model_board, vetiver_model_creation)
20+
vetiver.write_app(model_board, "model", file=file)
21+
contents = open(file).read()
22+
version = model_board.pin_versions("model").sort_values(
23+
by="created", ascending=False
24+
)
25+
version = version.version[0]
26+
assert (
27+
contents
28+
== f"""from vetiver import VetiverModel
2729
import vetiver
2830
import pins
2931
3032
31-
b = pins.board_folder('.', allow_pickle_read=True)
32-
v = VetiverModel.from_pin(b, 'model', version = '{version}')
33+
b = pins.board_folder({repr(tempdir)}, allow_pickle_read=True)
34+
v = VetiverModel.from_pin(b, 'model', version = {repr(version)})
3335
3436
vetiver_api = vetiver.VetiverAPI(v)
3537
api = vetiver_api.app
3638
"""
37-
)
38-
model_board.pin_delete("model")
39+
)

vetiver/vetiver_model.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ class VetiverModel:
5252
Parameter `ptype_data` was changed to `prototype_data`. Handling of `ptype_data`
5353
will be removed in a future version.
5454
55-
56-
5755
Examples
5856
-------
5957
>>> from vetiver import mock, VetiverModel
@@ -104,15 +102,9 @@ def from_pin(cls, board, name: str, version: str = None):
104102
required_pkgs = meta.user.get("vetiver_meta").get("required_pkgs", None)
105103
python_version = meta.user.get("vetiver_meta").get("python_version", None)
106104
meta.user.pop("vetiver_meta")
105+
# old pin type
107106
else:
108-
# ptype = meta.user.get("ptype", None)
109-
110107
get_prototype = meta.user.get("ptype")
111-
# elif meta.user.get("prototype"):
112-
# get_prototype = meta.user.get("prototype")
113-
# else:
114-
# get_prototype = None
115-
116108
required_pkgs = meta.user.get("required_pkgs")
117109
python_version = meta.user.get("python_version")
118110

vetiver/write_docker.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44

55
from .write_fastapi import write_app
6-
from .attach_pkgs import load_pkgs
6+
from .attach_pkgs import load_pkgs, get_board_pkgs
77
from .vetiver_model import VetiverModel
88

99

@@ -137,11 +137,20 @@ def prepare_docker(
137137
>>> v = vetiver.VetiverModel(model, "my_model", prototype_data = X)
138138
>>> vetiver.vetiver_pin_write(board, v)
139139
>>> vetiver.prepare_docker(board = board, pin_name = "my_model", path = tmp.name)
140+
141+
Notes
142+
------
143+
This function uses `vetiver.get_board_pkgs(board)` for generating requirements.
144+
For more complex use cases, call `write_docker()`, `load_pkgs()`, and
145+
`write_app()` individually.
140146
"""
141147

142148
v = VetiverModel.from_pin(board=board, name=pin_name, version=version)
149+
143150
write_app(
144151
board=board, pin_name=pin_name, version=version, file=Path(path, "app.py")
145152
)
146-
load_pkgs(v, path=Path(path, "vetiver_"))
153+
154+
load_pkgs(v, path=Path(path, "vetiver_"), packages=get_board_pkgs(board))
155+
147156
write_docker(path=path, rspm_env=rspm_env, host=host, port=port)

0 commit comments

Comments
 (0)