From c41341bcb1a35065d8bcbae316f251828e9cf5cb Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 09:23:09 -0500 Subject: [PATCH 01/10] 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 3878fab2..a5655398 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -188,8 +188,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 d4a5dd98..ff63bd55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,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 7fb4a023..00000000 --- a/vetiver-testing/vetiver-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -pandas -numpy -pydantic<2 -pytest -pins -vetiver From 860cc7e6250177fb8154f9d77fddec33da1163c1 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 10:59:20 -0500 Subject: [PATCH 02/10] 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 2460221956d9fbfc4888a462dfeb76253690b423 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 15:03:06 -0500 Subject: [PATCH 03/10] 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 a5655398..f386eb6c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -177,6 +177,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 97e5cd9ec944301d1f89c32e8198653711a65666 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 15:29:12 -0500 Subject: [PATCH 04/10] 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 7a389c85255d36b086d181901ac584f5e7abfe6c Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Mon, 8 Dec 2025 15:36:09 -0500 Subject: [PATCH 05/10] 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 f386eb6c..55882ab1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -195,6 +195,7 @@ jobs: version: "release" license: ${{ secrets.CONNECT_LICENSE_FILE }} command: | + pip freeze > requirements.txt pytest -m 'vetiver' test-dev-connect: From 71d4bff9e43cc6ffa2203eb0218dc0f6b899ccd9 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Wed, 25 Feb 2026 07:43:34 -0500 Subject: [PATCH 06/10] Apply pydantic version ceiling --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff63bd55..034a9eed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ mcp = ["fastmcp==2.12.4; python_version >= '3.10'"] vetiver-testing = [ "pandas", "numpy", - "pydantic", + "pydantic<2", "pytest", "pins", "vetiver", From 3e7336f8bbc042ab8d4bc7d2f492b6ae6de3f0a5 Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Wed, 25 Feb 2026 07:46:56 -0500 Subject: [PATCH 07/10] Loosen fastmcp pin --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 034a9eed..f8622c39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,10 +38,10 @@ test = [ "setuptools_scm[toml]>=3.4", "twine", "types-Flask", - "fastmcp==2.12.4; python_version >= '3.10'", + "fastmcp<3; python_version >= '3.10'", ] snowflake = ["snowflake-cli"] -mcp = ["fastmcp==2.12.4; python_version >= '3.10'"] +mcp = ["fastmcp<3; python_version >= '3.10'"] vetiver-testing = [ "pandas", "numpy", From 56a0c17e525a1a0db05465cbff34f6c08c3b7b7c Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Wed, 25 Feb 2026 07:55:36 -0500 Subject: [PATCH 08/10] move fastmcp out of test --- .github/workflows/main.yml | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 55882ab1..77761c00 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - run: pip install '.[test]' + - run: pip install '.[test,mcp]' - run: pip freeze - run: make lint - run: rsconnect version diff --git a/pyproject.toml b/pyproject.toml index f8622c39..244e7b32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ test = [ "setuptools_scm[toml]>=3.4", "twine", "types-Flask", - "fastmcp<3; python_version >= '3.10'", ] snowflake = ["snowflake-cli"] mcp = ["fastmcp<3; python_version >= '3.10'"] From 566b7bde668e449985fb9b1b1f80671b6629300e Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Wed, 25 Feb 2026 08:16:53 -0500 Subject: [PATCH 09/10] prints --- tests/test_vetiver_pins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_vetiver_pins.py b/tests/test_vetiver_pins.py index 6e9cc4ee..e7efb40c 100644 --- a/tests/test_vetiver_pins.py +++ b/tests/test_vetiver_pins.py @@ -51,6 +51,8 @@ def test_deploy(): endpoint = vetiver.vetiver_endpoint(content_url + "/predict") response = vetiver.predict(endpoint, X_df, headers=h) + print(X_df) + print(response) assert isinstance(response, pd.DataFrame), response assert response.iloc[0, 0] == 44.47 From 33e3eb97dfebde5a7e8c4065574ba00ce9fa163d Mon Sep 17 00:00:00 2001 From: Neal Richardson Date: Wed, 25 Feb 2026 08:41:49 -0500 Subject: [PATCH 10/10] Try this? --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 77761c00..262ef6ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -192,7 +192,7 @@ jobs: - name: Run tests uses: posit-dev/with-connect@main with: - version: "release" + version: "jammy" license: ${{ secrets.CONNECT_LICENSE_FILE }} command: | pip freeze > requirements.txt @@ -225,6 +225,8 @@ jobs: # NOTE: edited to run checks for python package - name: Run tests + env: + CONNECT_SERVER: http://localhost:3939 run: | pytest tests/test_main_system_caches.py pytest -m 'vetiver'