From 0d3e6c656d2dbf2d9aab604254acae9341c999ad Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 09:23:09 -0500 Subject: [PATCH 1/5] Move vetiver requirements into pyproject.toml --- .github/workflows/main.yml | 3 +-- pyproject.toml | 8 ++++++++ vetiver-testing/vetiver-requirements.txt | 6 ------ 3 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 vetiver-testing/vetiver-requirements.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed5cc3af..c451056d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -187,8 +187,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/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 From 3f5716cc996dc5e64f58fcad06c97dd16dd4a25f Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 10:59:20 -0500 Subject: [PATCH 2/5] Work on moving vetiver tests to work with with-connect --- tests/test_main_system_caches.py | 21 +++++++++------ tests/test_vetiver_pins.py | 46 ++++++++++++-------------------- 2 files changed, 30 insertions(+), 37 deletions(-) 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..332da66e 100644 --- a/tests/test_vetiver_pins.py +++ b/tests/test_vetiver_pins.py @@ -2,7 +2,7 @@ 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 @@ -12,29 +12,11 @@ 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 - -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) +os.environ["CONNECT_CONTENT_BUILD_DIR"] = "vetiver-test-build" # noqa def rsc_delete_user_content(rsc): @@ -47,7 +29,10 @@ def rsc_delete_user_content(rsc): @pytest.fixture(scope="function") def rsc_short(): # tears down content after each test - fs_susan = rsc_fs_from_key("susan") + server_url = require_connect() + api_key = require_api_key() + rsc = RsConnectApi(server_url, api_key) + fs_susan = RsConnectFs(rsc) # delete any content that might already exist rsc_delete_user_content(fs_susan.api) @@ -57,24 +42,27 @@ def rsc_short(): 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( + vetiver.deploy_connect( connect_server=connect_server, board=board, - pin_name="susan/model", + pin_name=modelname, title="testapivetiver", extra_files=["requirements.txt"], ) @@ -85,7 +73,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) From 9b3e79e462baa8d1d23195a176b5b5a96e5cb594 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 15:03:06 -0500 Subject: [PATCH 3/5] Run it in with-connect --- .github/workflows/main.yml | 20 ++++++++++++++++++++ tests/test_vetiver_pins.py | 26 -------------------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c451056d..63cfcbf2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -176,6 +176,26 @@ 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: | + pytest -m 'vetiver' + test-dev-connect: name: "Integration tests against dev Connect" runs-on: ubuntu-latest diff --git a/tests/test_vetiver_pins.py b/tests/test_vetiver_pins.py index 332da66e..a5fd9c14 100644 --- a/tests/test_vetiver_pins.py +++ b/tests/test_vetiver_pins.py @@ -7,9 +7,6 @@ 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 from .utils import require_api_key, require_connect # noqa @@ -19,29 +16,6 @@ os.environ["CONNECT_CONTENT_BUILD_DIR"] = "vetiver-test-build" # noqa -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 - server_url = require_connect() - api_key = require_api_key() - rsc = RsConnectApi(server_url, api_key) - fs_susan = RsConnectFs(rsc) - - # 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(): server_url = require_connect() np.random.seed(500) From 91b19f3ac3242ffc391c07a32d833673988d5d7a Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 15:29:12 -0500 Subject: [PATCH 4/5] deploy_rsconnect; deploy_connect is not yet released --- tests/test_vetiver_pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_vetiver_pins.py b/tests/test_vetiver_pins.py index a5fd9c14..6e9cc4ee 100644 --- a/tests/test_vetiver_pins.py +++ b/tests/test_vetiver_pins.py @@ -33,7 +33,7 @@ def test_deploy(): vetiver.vetiver_pin_write(board=board, model=v) connect_server = RSConnectServer(url=server_url, api_key=require_api_key()) - vetiver.deploy_connect( + vetiver.deploy_rsconnect( connect_server=connect_server, board=board, pin_name=modelname, From aea7847821c161fbca55e990ee95694223a41365 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 15:36:09 -0500 Subject: [PATCH 5/5] Does it need this? --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 63cfcbf2..efd01263 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -194,6 +194,7 @@ jobs: version: "release" license: ${{ secrets.CONNECT_LICENSE_FILE }} command: | + pip freeze > requirements.txt pytest -m 'vetiver' test-dev-connect: