diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed5cc3af..efd01263 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -176,6 +176,27 @@ jobs: command: | make test-${{ env.python-version }} + test-vetiver: + name: "Vetiver integration test" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.12.4 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install '.[test,vetiver-testing]' + - name: Run tests + uses: posit-dev/with-connect@main + with: + version: "release" + license: ${{ secrets.CONNECT_LICENSE_FILE }} + command: | + pip freeze > requirements.txt + pytest -m 'vetiver' + test-dev-connect: name: "Integration tests against dev Connect" runs-on: ubuntu-latest @@ -187,8 +208,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -r vetiver-testing/vetiver-requirements.txt - python -m pip install '.[test]' + python -m pip install '.[test,vetiver-testing]' - name: Run Posit Connect run: | docker compose up --build -d diff --git a/pyproject.toml b/pyproject.toml index b163ffcd..5622feed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,14 @@ test = [ ] snowflake = ["snowflake-cli"] mcp = ["fastmcp==2.12.4; python_version >= '3.10'"] +vetiver-testing = [ + "pandas", + "numpy", + "pydantic", + "pytest", + "pins", + "vetiver", +] docs = [ "mkdocs-material", "mkdocs-click", diff --git a/tests/test_main_system_caches.py b/tests/test_main_system_caches.py index fe1f32b5..9563351a 100644 --- a/tests/test_main_system_caches.py +++ b/tests/test_main_system_caches.py @@ -5,9 +5,9 @@ from click.testing import CliRunner from rsconnect.main import cli +from .utils import require_connect -CONNECT_SERVER = "http://localhost:3939" CONNECT_KEYS_JSON = "vetiver-testing/rsconnect_api_keys.json" CONNECT_CACHE_DIR = "/data/python-environments/_packages_cache" @@ -64,11 +64,12 @@ def tearDownClass(cls): # Admins can list caches def test_system_caches_list_admin(self): + connect_server = require_connect() api_key = get_key("admin") runner = CliRunner() args = ["system", "caches", "list"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) result = runner.invoke(cli, args) self.assertEqual(result.exit_code, 0) @@ -79,11 +80,12 @@ def test_system_caches_list_admin(self): # Publishers cannot list caches def test_system_caches_list_publisher(self): + connect_server = require_connect() api_key = get_key("susan") runner = CliRunner() args = ["system", "caches", "list"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) result = runner.invoke(cli, args) self.assertEqual(result.exit_code, 1) @@ -106,11 +108,12 @@ def tearDownClass(cls): # Publishers cannot delete caches def test_system_caches_delete_publisher(self): + connect_server = require_connect() api_key = get_key("susan") runner = CliRunner() args = ["system", "caches", "delete", "--language", "Python", "--version", "1.2.3", "--image-name", "Local"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) result = runner.invoke(cli, args) self.assertEqual(result.exit_code, 1) @@ -119,11 +122,12 @@ def test_system_caches_delete_publisher(self): # Admins can delete caches that exist def test_system_caches_delete_admin(self): + connect_server = require_connect() api_key = get_key("admin") runner = CliRunner() args = ["system", "caches", "delete", "--language", "Python", "--version", "1.2.3", "--image-name", "Local"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) self.assertTrue(cache_dir_exists()) result = runner.invoke(cli, args) @@ -134,26 +138,27 @@ def test_system_caches_delete_admin(self): # --version and --language flags are required def test_system_caches_delete_required_flags(self): + connect_server = require_connect() api_key = get_key("admin") runner = CliRunner() # neither flag provided should fail args = ["system", "caches", "delete"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) result = runner.invoke(cli, args) self.assertEqual(result.exit_code, 2) self.assertRegex(result.output, "Error: Missing option '--language' / '-l'") # only --language flag provided should fail args = ["system", "caches", "delete", "--language", "Python"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) result = runner.invoke(cli, args) self.assertEqual(result.exit_code, 2) self.assertRegex(result.output, "Error: Missing option '--version' / '-V'") # only --version flag provided should fail args = ["system", "caches", "delete", "--version", "1.2.3"] - apply_common_args(args, server=CONNECT_SERVER, key=api_key) + apply_common_args(args, server=connect_server, key=api_key) result = runner.invoke(cli, args) self.assertEqual(result.exit_code, 2) self.assertRegex(result.output, "Error: Missing option '--language' / '-l'") diff --git a/tests/test_vetiver_pins.py b/tests/test_vetiver_pins.py index 5b187794..6e9cc4ee 100644 --- a/tests/test_vetiver_pins.py +++ b/tests/test_vetiver_pins.py @@ -2,79 +2,41 @@ vetiver = pytest.importorskip("vetiver", reason="vetiver library not installed") -import json # noqa +import os # noqa import pins # noqa import pandas as pd # noqa import numpy as np # noqa -from pins.boards import BoardRsConnect # noqa -from pins.rsconnect.api import RsConnectApi # noqa -from pins.rsconnect.fs import RsConnectFs # noqa from rsconnect.api import RSConnectServer, RSConnectClient # noqa -RSC_SERVER_URL = "http://localhost:3939" -RSC_KEYS_FNAME = "vetiver-testing/rsconnect_api_keys.json" +from .utils import require_api_key, require_connect # noqa pytestmark = pytest.mark.vetiver # noqa +os.environ["CONNECT_CONTENT_BUILD_DIR"] = "vetiver-test-build" # noqa -def get_key(name): - with open(RSC_KEYS_FNAME) as f: - api_key = json.load(f)[name] - return api_key - -def rsc_from_key(name): - with open(RSC_KEYS_FNAME) as f: - api_key = json.load(f)[name] - return RsConnectApi(RSC_SERVER_URL, api_key) - - -def rsc_fs_from_key(name): - - rsc = rsc_from_key(name) - - return RsConnectFs(rsc) - - -def rsc_delete_user_content(rsc): - guid = rsc.get_user()["guid"] - content = rsc.get_content(owner_guid=guid) - for entry in content: - rsc.delete_content_item(entry["guid"]) - - -@pytest.fixture(scope="function") -def rsc_short(): - # tears down content after each test - fs_susan = rsc_fs_from_key("susan") - - # delete any content that might already exist - rsc_delete_user_content(fs_susan.api) - - yield BoardRsConnect("", fs_susan, allow_pickle_read=True) # fs_susan.ls to list content - - rsc_delete_user_content(fs_susan.api) - - -def test_deploy(rsc_short): +def test_deploy(): + server_url = require_connect() np.random.seed(500) # Load data, model X_df, y = vetiver.mock.get_mock_data() model = vetiver.mock.get_mock_model().fit(X_df, y) - v = vetiver.VetiverModel(model=model, prototype_data=X_df, model_name="susan/model") + board = pins.board_rsconnect(server_url=server_url, api_key=require_api_key(), allow_pickle_read=True) + username = board.fs.api.get_user()["username"] + modelname = f"{username}/model" - board = pins.board_rsconnect(server_url=RSC_SERVER_URL, api_key=get_key("susan"), allow_pickle_read=True) + v = vetiver.VetiverModel(model=model, prototype_data=X_df, model_name=modelname) vetiver.vetiver_pin_write(board=board, model=v) - connect_server = RSConnectServer(url=RSC_SERVER_URL, api_key=get_key("susan")) + connect_server = RSConnectServer(url=server_url, api_key=require_api_key()) vetiver.deploy_rsconnect( connect_server=connect_server, board=board, - pin_name="susan/model", + pin_name=modelname, title="testapivetiver", extra_files=["requirements.txt"], ) @@ -85,7 +47,7 @@ def test_deploy(rsc_short): rsc_api = list(filter(lambda x: x["title"] == "testapivetiver", dicts)) content_url = rsc_api[0].get("content_url") - h = {"Authorization": "Key {}".format(get_key("susan"))} + h = {"Authorization": "Key {}".format(require_api_key())} endpoint = vetiver.vetiver_endpoint(content_url + "/predict") response = vetiver.predict(endpoint, X_df, headers=h) diff --git a/vetiver-testing/vetiver-requirements.txt b/vetiver-testing/vetiver-requirements.txt deleted file mode 100644 index cc8f66bc..00000000 --- a/vetiver-testing/vetiver-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -pandas -numpy -pydantic -pytest -pins -vetiver