From b109ad6900bb2320fe5d2ddd7cc413431ada045a Mon Sep 17 00:00:00 2001 From: joshyam-k Date: Mon, 4 Aug 2025 12:25:54 -0700 Subject: [PATCH 1/5] add empty integrations array --- rsconnect/bundle.py | 19 ++++++ tests/test_Manifest.py | 5 ++ tests/test_bundle.py | 67 +++++++++++++++++++++- tests/testdata/Manifest/html_manifest.json | 5 +- 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index 274b7ee8..5138ff87 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -40,6 +40,7 @@ Sequence, Union, cast, + Any, ) # Even though TypedDict is available in Python 3.8, because it's used with NotRequired, @@ -123,6 +124,15 @@ class ManifestDataPythonPackageManager(TypedDict): package_file: str +class ManifestIntegrations(TypedDict): + guid: NotRequired[str] + name: NotRequired[str] + description: NotRequired[str] + authtype: NotRequired[str] + integrationtype: NotRequired[str] + config: NotRequired[dict[str, Any]] + + class ManifestData(TypedDict): version: int files: dict[str, ManifestDataFile] @@ -132,6 +142,7 @@ class ManifestData(TypedDict): quarto: NotRequired[ManifestDataQuarto] python: NotRequired[ManifestDataPython] environment: NotRequired[ManifestDataEnvironment] + integrations: NotRequired[list[ManifestIntegrations]] class Manifest: @@ -149,6 +160,7 @@ def __init__( primary_html: Optional[str] = None, metadata: Optional[ManifestDataMetadata] = None, files: Optional[dict[str, ManifestDataFile]] = None, + integrations: Optional[list[ManifestIntegrations]] = None, ) -> None: self.data: ManifestData = cast(ManifestData, {}) self.buffer: dict[str, str] = {} @@ -216,6 +228,13 @@ def __init__( if files: self.data["files"] = files + # Add integrations array if provided, or initialize with empty array for new manifests + if integrations is not None: + self.data["integrations"] = integrations + else: + # For new manifests, add an empty integrations array + self.data["integrations"] = [] + @classmethod def from_json(cls, json_str: str): return cls(**json.loads(json_str)) diff --git a/tests/test_Manifest.py b/tests/test_Manifest.py index 96a14719..1a10e7a8 100644 --- a/tests/test_Manifest.py +++ b/tests/test_Manifest.py @@ -19,6 +19,7 @@ def test_Manifest_from_json(): "test1.txt": {"checksum": "3e7705498e8be60520841409ebc69bc1"}, "test_folder1/testfoldertext1.txt": {"checksum": "0a576fd324b6985bac6aa934131d2f5c"}, }, + "integrations": [], } manifest_json_str = json.dumps(html_manifest_dict, indent=2) m = Manifest.from_json(manifest_json_str) @@ -41,6 +42,7 @@ def test_Manifest_properties(): "test1.txt": {"checksum": "3e7705498e8be60520841409ebc69bc1"}, "test_folder1/testfoldertext1.txt": {"checksum": "0a576fd324b6985bac6aa934131d2f5c"}, }, + "integrations": [], } manifest_json_str = json.dumps(html_manifest_dict, indent=2) m = Manifest.from_json(manifest_json_str) @@ -66,6 +68,7 @@ def test_Manifest_flattened_copy(): "checksum": "0a576fd324b6985bac6aa934131d2f5c" }, }, + "integrations": [], } start_json_str = json.dumps(start, indent=2) m = Manifest.from_json(start_json_str) @@ -80,6 +83,7 @@ def test_Manifest_flattened_copy(): "test1.txt": {"checksum": "3e7705498e8be60520841409ebc69bc1"}, "test_folder1/testfoldertext1.txt": {"checksum": "0a576fd324b6985bac6aa934131d2f5c"}, }, + "integrations": [], } assert m.get_flattened_copy().data == html_manifest_dict @@ -89,6 +93,7 @@ def test_Manifest_empty_init(): "version": 1, "metadata": {}, "files": {}, + "integrations": [], } m = Manifest() m.data == init diff --git a/tests/test_bundle.py b/tests/test_bundle.py index e300954e..74e688b9 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -138,6 +138,7 @@ def test_make_notebook_source_bundle1(self): }, "requirements.txt": {"checksum": "5f2a5e862fe7afe3def4a57bb5cfb214"}, }, + "integrations": [], }, ) @@ -224,6 +225,7 @@ def test_make_notebook_source_bundle2(self): }, "data.csv": {"checksum": data_csv_hash}, }, + "integrations": [], }, ) @@ -305,6 +307,7 @@ def test_make_quarto_source_bundle_from_simple_project(self): "myquarto.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -371,6 +374,7 @@ def test_make_quarto_source_bundle_from_complex_project(self): "config": [temp_proj + "/_quarto.yml"], "configResources": [], }, + "integrations": [], } with make_quarto_source_bundle( @@ -421,6 +425,7 @@ def test_make_quarto_source_bundle_from_complex_project(self): "about.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -504,6 +509,7 @@ def test_make_quarto_source_bundle_from_project_with_requirements(self): "myquarto.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -554,6 +560,7 @@ def test_make_quarto_source_bundle_from_file(self): "files": { "myquarto.qmd": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -632,6 +639,7 @@ def do_test_html_bundle(self, directory): "primary_html": "dummy.html", }, "files": {}, + "integrations": [], }, ) finally: @@ -670,7 +678,7 @@ def test_make_source_manifest(self): manifest = make_source_manifest(AppModes.PYTHON_API, None, None, None) self.assertEqual( manifest, - {"version": 1, "metadata": {"appmode": "python-api"}, "files": {}}, + {"version": 1, "metadata": {"appmode": "python-api"}, "files": {}, "integrations": []}, ) # include image parameter @@ -684,6 +692,7 @@ def test_make_source_manifest(self): "image": "rstudio/connect:bionic", }, "files": {}, + "integrations": [], }, ) @@ -696,6 +705,7 @@ def test_make_source_manifest(self): "metadata": {"appmode": "python-api"}, "environment": {"environment_management": {"python": False}}, "files": {}, + "integrations": [], }, ) @@ -708,6 +718,7 @@ def test_make_source_manifest(self): "metadata": {"appmode": "python-api"}, "environment": {"environment_management": {"r": False}}, "files": {}, + "integrations": [], }, ) @@ -731,6 +742,7 @@ def test_make_source_manifest(self): "environment_management": {"r": False, "python": False}, }, "files": {}, + "integrations": [], }, ) @@ -764,6 +776,7 @@ def test_make_source_manifest(self): "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, }, "files": {}, + "integrations": [], }, ) @@ -778,7 +791,7 @@ def test_make_source_manifest(self): # print(manifest) self.assertEqual( manifest, - {"version": 1, "metadata": {"appmode": "python-api", "entrypoint": "main.py"}, "files": {}}, + {"version": 1, "metadata": {"appmode": "python-api", "entrypoint": "main.py"}, "files": {}, "integrations": []}, ) # include quarto_inspection parameter @@ -803,6 +816,7 @@ def test_make_source_manifest(self): }, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, + "integrations": [], }, ) @@ -836,6 +850,7 @@ def test_make_quarto_manifest_project_no_opt_params(self): "metadata": {"appmode": "quarto-shiny"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, + "integrations": [], }, ) @@ -869,6 +884,7 @@ def test_make_quarto_manifest_doc_no_opt_params(self): "metadata": {"appmode": "quarto-static"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {basename(temp_doc): {"checksum": mock.ANY}}, + "integrations": [], }, ) @@ -897,6 +913,7 @@ def test_make_quarto_manifest_project_with_image(self): "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "environment": {"image": "rstudio/connect:bionic"}, "files": {}, + "integrations": [], }, ) @@ -947,6 +964,7 @@ def test_make_quarto_manifest_project_with_env(self): "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, }, "files": {"requirements.txt": {"checksum": mock.ANY}}, + "integrations": [], }, ) @@ -998,6 +1016,7 @@ def test_make_quarto_manifest_project_with_extra_files(self): "b": {"checksum": b_hash}, "c": {"checksum": c_hash}, }, + "integrations": [], }, ) @@ -1047,6 +1066,7 @@ def test_make_quarto_manifest_project_with_excludes(self): "e": {"checksum": mock.ANY}, "f": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -1059,6 +1079,7 @@ def test_make_tensorflow_manifest_empty(self): "version": 1, "metadata": {"appmode": "tensorflow-saved-model"}, "files": {}, + "integrations": [], }, ) @@ -1082,6 +1103,7 @@ def test_make_tensorflow_manifest(self): "files": { "1/saved_model.pb": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -1111,6 +1133,7 @@ def test_make_tensorflow_bundle(self): "files": { "1/saved_model.pb": {"checksum": mock.ANY}, }, + "integrations": [], }, ) @@ -1130,6 +1153,7 @@ def test_make_html_manifest(self): "primary_html": "abc.html", }, "files": {}, + "integrations": [], }, ) @@ -1298,6 +1322,7 @@ def test_create_voila_manifest_1(path, entrypoint): "requirements.txt": {"checksum": "9cce1aac313043abd5690f67f84338ed"}, "bqplot.ipynb": {"checksum": checksum_hash}, }, + "integrations": [], } manifest = Manifest() if (path, entrypoint) in ( @@ -1376,6 +1401,7 @@ def test_create_voila_manifest_2(path, entrypoint): "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integrations": [], } manifest = create_voila_manifest( path, @@ -1426,6 +1452,7 @@ def test_create_voila_manifest_extra(): "bqplot.ipynb": {"checksum": bqplot_checksum}, "dashboard.ipynb": {"checksum": dashboard_checksum}, }, + "integrations": [], } manifest = create_voila_manifest( dashboard_ipynb, @@ -1510,6 +1537,7 @@ def test_create_voila_manifest_multi_notebook(path, entrypoint): "bqplot/bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard/dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integrations": [], } manifest = Manifest() if (path, entrypoint) in ( @@ -1614,6 +1642,7 @@ def test_make_voila_bundle( "requirements.txt": {"checksum": "9395f3162b7779c57c86b187fa441d96"}, "bqplot.ipynb": {"checksum": checksum_hash}, }, + "integrations": [], } if (path, entrypoint) in ( (None, None), @@ -1726,6 +1755,7 @@ def test_make_voila_bundle_multi_notebook( "bqplot/bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard/dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integrations": [], } if (path, entrypoint) in ( (None, None), @@ -1817,6 +1847,7 @@ def test_make_voila_bundle_2( "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integrations": [], } with make_voila_bundle( path, @@ -1875,6 +1906,7 @@ def test_make_voila_bundle_extra(): "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integrations": [], } with make_voila_bundle( dashboard_ipynb, @@ -1945,6 +1977,7 @@ def test_create_html_manifest(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_hash}}, + "integrations": [], } manifest = create_html_manifest( single_file_index_file, @@ -1962,6 +1995,7 @@ def test_create_html_manifest(): "test1.txt": {"checksum": txt_hash}, "test_folder1/testfoldertext1.txt": {"checksum": folder_txt_hash}, }, + "integrations": [], } manifest = create_html_manifest( @@ -1984,6 +2018,7 @@ def test_create_html_manifest(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_hash}}, + "integrations": [], } manifest = create_html_manifest( @@ -2001,6 +2036,7 @@ def test_create_html_manifest(): "index.html": {"checksum": index_hash}, "main.html": {"checksum": index_hash}, }, + "integrations": [], } manifest = create_html_manifest( @@ -2023,6 +2059,7 @@ def test_create_html_manifest(): "version": 1, "metadata": {"appmode": "static", "primary_html": "b.html", "entrypoint": "b.html"}, "files": {"b.html": {"checksum": index_hash}}, + "integrations": [], } manifest = create_html_manifest( @@ -2040,6 +2077,7 @@ def test_create_html_manifest(): "a.html": {"checksum": index_hash}, "b.html": {"checksum": index_hash}, }, + "integrations": [], } manifest = create_html_manifest( @@ -2057,6 +2095,7 @@ def test_create_html_manifest(): "a.html": {"checksum": index_hash}, "b.html": {"checksum": index_hash}, }, + "integrations": [], } manifest = create_html_manifest( multi_file_nonindex_fileb, @@ -2073,6 +2112,7 @@ def test_create_html_manifest(): "index.html": {"checksum": index_hash}, "main.html": {"checksum": index_hash}, }, + "integrations": [], } manifest = create_html_manifest( @@ -2098,6 +2138,7 @@ def test_make_html_bundle(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_hash}}, + "integrations": [], } with make_html_bundle( single_file_index_file, @@ -2120,6 +2161,7 @@ def test_make_html_bundle(): "test1.txt": {"checksum": txt_hash}, "test_folder1/testfoldertext1.txt": {"checksum": folder_txt_hash}, }, + "integrations": [], } with make_html_bundle( single_file_index_dir, @@ -2160,6 +2202,7 @@ def test_make_html_bundle(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_checksum}}, + "integrations": [], } with make_html_bundle( @@ -2182,6 +2225,7 @@ def test_make_html_bundle(): "index.html": {"checksum": index_checksum}, "main.html": {"checksum": index_checksum}, }, + "integrations": [], } with make_html_bundle( multi_file_index_dir, @@ -2215,6 +2259,7 @@ def test_make_html_bundle(): "version": 1, "metadata": {"appmode": "static", "primary_html": "b.html", "entrypoint": "b.html"}, "files": {"b.html": {"checksum": index_checksum}}, + "integrations": [], } with make_html_bundle( multi_file_nonindex_fileb, @@ -2236,6 +2281,7 @@ def test_make_html_bundle(): "a.html": {"checksum": index_checksum}, "b.html": {"checksum": index_checksum}, }, + "integrations": [], } with make_html_bundle( multi_file_nonindex_dir, @@ -2260,6 +2306,7 @@ def test_make_html_bundle(): "a.html": {"checksum": index_checksum}, "b.html": {"checksum": index_checksum}, }, + "integrations": [], } with make_html_bundle( multi_file_nonindex_fileb, @@ -2284,6 +2331,7 @@ def test_make_html_bundle(): "index.html": {"checksum": index_checksum}, "main.html": {"checksum": index_checksum}, }, + "integrations": [], } with make_html_bundle( @@ -2324,6 +2372,7 @@ def test_make_api_manifest_fastapi(): "main.py": {"checksum": "a8d8820f25be4dc8e2bf51a5ba1690b6"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, + "integrations": [], } environment = Environment.create_python_environment( fastapi_dir, @@ -2356,6 +2405,7 @@ def test_make_api_bundle_fastapi(): "main.py": {"checksum": "a8d8820f25be4dc8e2bf51a5ba1690b6"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, + "integrations": [], } environment = Environment.create_python_environment( fastapi_dir, @@ -2400,6 +2450,7 @@ def test_make_api_manifest_flask(): "app.py": {"checksum": "9799c3b834b555cf02e5896ad2997674"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, + "integrations": [], } environment = Environment.create_python_environment( flask_dir, @@ -2432,6 +2483,7 @@ def test_make_api_bundle_flask(): "app.py": {"checksum": "9799c3b834b555cf02e5896ad2997674"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, + "integrations": [], } environment = Environment.create_python_environment( flask_dir, @@ -2476,6 +2528,7 @@ def test_make_api_manifest_streamlit(): "app1.py": {"checksum": "b203bc6d9512029a414ccbb63514e603"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } environment = Environment.create_python_environment( streamlit_dir, @@ -2507,6 +2560,7 @@ def test_make_api_bundle_streamlit(): "app1.py": {"checksum": "b203bc6d9512029a414ccbb63514e603"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } environment = Environment.create_python_environment( streamlit_dir, @@ -2552,6 +2606,7 @@ def test_make_api_manifest_dash(): "app2.py": {"checksum": "0cb6f0261685d29243977c7318d70d6d"}, "prices.csv": {"checksum": "3efb0ed7ad93bede9dc88f7a81ad4153"}, }, + "integrations": [], } environment = Environment.create_python_environment( dash_dir, @@ -2584,6 +2639,7 @@ def test_make_api_bundle_dash(): "app2.py": {"checksum": "0cb6f0261685d29243977c7318d70d6d"}, "prices.csv": {"checksum": "3efb0ed7ad93bede9dc88f7a81ad4153"}, }, + "integrations": [], } environment = Environment.create_python_environment( dash_dir, @@ -2628,6 +2684,7 @@ def test_make_api_manifest_bokeh(): "app3.py": {"checksum": "a5de7b460476a9ac4e02edfc2d52d9df"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } environment = Environment.create_python_environment( bokeh_dir, @@ -2660,6 +2717,7 @@ def test_make_api_bundle_bokeh(): "app3.py": {"checksum": "a5de7b460476a9ac4e02edfc2d52d9df"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } environment = Environment.create_python_environment( @@ -2705,6 +2763,7 @@ def test_make_api_manifest_shiny(): "app4.py": {"checksum": "f7e4b3b7ff0ada525ec388d037ff6c6a"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } environment = Environment.create_python_environment( shiny_dir, @@ -2737,6 +2796,7 @@ def test_make_api_bundle_shiny(): "app4.py": {"checksum": "f7e4b3b7ff0ada525ec388d037ff6c6a"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } environment = Environment.create_python_environment( shiny_dir, @@ -2781,6 +2841,7 @@ def test_make_manifest_bundle(): "app5.py": {"checksum": "f7e4b3b7ff0ada525ec388d037ff6c6a"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, + "integrations": [], } with make_manifest_bundle( pyshiny_manifest_file, @@ -2815,6 +2876,7 @@ def test_make_api_manifest_gradio(): "requirements.txt": {"checksum": "381ccadfb8d4848add470e33033b198f"}, "app.py": {"checksum": "22feec76e9c02ac6b5a34a083e2983b6"}, }, + "integrations": [], } environment = Environment.create_python_environment( gradio_dir, @@ -2845,6 +2907,7 @@ def test_make_api_bundle_gradio(): "requirements.txt": {"checksum": "381ccadfb8d4848add470e33033b198f"}, "app.py": {"checksum": "22feec76e9c02ac6b5a34a083e2983b6"}, }, + "integrations": [], } environment = Environment.create_python_environment( gradio_dir, diff --git a/tests/testdata/Manifest/html_manifest.json b/tests/testdata/Manifest/html_manifest.json index 1cbb0d97..a6fcc622 100644 --- a/tests/testdata/Manifest/html_manifest.json +++ b/tests/testdata/Manifest/html_manifest.json @@ -15,5 +15,6 @@ "test_folder1/testfoldertext1.txt": { "checksum": "0a576fd324b6985bac6aa934131d2f5c" } - } -} \ No newline at end of file + }, + "integrations": [] +} From aded65f6b36e7ae5704a2bf11873a5690754f987 Mon Sep 17 00:00:00 2001 From: joshyam-k Date: Mon, 4 Aug 2025 14:13:45 -0700 Subject: [PATCH 2/5] focus on just write-manifest --- rsconnect/bundle.py | 20 +- tests/test_Manifest.py | 5 - tests/test_bundle.py | 274 ++++++++++++++++----- tests/testdata/Manifest/html_manifest.json | 3 +- 4 files changed, 224 insertions(+), 78 deletions(-) diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index 5138ff87..8f286b81 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -160,7 +160,6 @@ def __init__( primary_html: Optional[str] = None, metadata: Optional[ManifestDataMetadata] = None, files: Optional[dict[str, ManifestDataFile]] = None, - integrations: Optional[list[ManifestIntegrations]] = None, ) -> None: self.data: ManifestData = cast(ManifestData, {}) self.buffer: dict[str, str] = {} @@ -228,13 +227,6 @@ def __init__( if files: self.data["files"] = files - # Add integrations array if provided, or initialize with empty array for new manifests - if integrations is not None: - self.data["integrations"] = integrations - else: - # For new manifests, add an empty integrations array - self.data["integrations"] = [] - @classmethod def from_json(cls, json_str: str): return cls(**json.loads(json_str)) @@ -1678,6 +1670,9 @@ def write_notebook_manifest_json( manifest_data = make_source_manifest( app_mode, environment, file_name, None, image, env_management_py, env_management_r ) + + manifest_data["integrations"] = [] + if hide_all_input or hide_tagged_input: if "jupyter" not in manifest_data: manifest_data["jupyter"] = {} @@ -1844,6 +1839,9 @@ def write_voila_manifest_json( manifest_flattened_copy_data = manifest.get_flattened_copy().data if multi_notebook and "metadata" in manifest_flattened_copy_data: manifest_flattened_copy_data["metadata"]["entrypoint"] = "" + + manifest_flattened_copy_data["integrations"] = [] + manifest_path = join(deploy_dir, "manifest.json") write_manifest_json(manifest_path, manifest_flattened_copy_data) return exists(manifest_path) @@ -1934,6 +1932,9 @@ def write_api_manifest_json( manifest, _ = make_api_manifest( directory, entry_point, app_mode, environment, extra_files, excludes, image, env_management_py, env_management_r ) + + manifest["integrations"] = [] + manifest_path = join(directory, "manifest.json") write_manifest_json(manifest_path, manifest) @@ -2019,6 +2020,8 @@ def write_quarto_manifest_json( image, ) + manifest["integrations"] = [] + base_dir = file_or_directory if not isdir(file_or_directory): base_dir = dirname(file_or_directory) @@ -2048,6 +2051,7 @@ def write_tensorflow_manifest_json( excludes, image, ) + manifest["integrations"] = [] manifest_path = join(directory, "manifest.json") write_manifest_json(manifest_path, manifest) diff --git a/tests/test_Manifest.py b/tests/test_Manifest.py index 1a10e7a8..96a14719 100644 --- a/tests/test_Manifest.py +++ b/tests/test_Manifest.py @@ -19,7 +19,6 @@ def test_Manifest_from_json(): "test1.txt": {"checksum": "3e7705498e8be60520841409ebc69bc1"}, "test_folder1/testfoldertext1.txt": {"checksum": "0a576fd324b6985bac6aa934131d2f5c"}, }, - "integrations": [], } manifest_json_str = json.dumps(html_manifest_dict, indent=2) m = Manifest.from_json(manifest_json_str) @@ -42,7 +41,6 @@ def test_Manifest_properties(): "test1.txt": {"checksum": "3e7705498e8be60520841409ebc69bc1"}, "test_folder1/testfoldertext1.txt": {"checksum": "0a576fd324b6985bac6aa934131d2f5c"}, }, - "integrations": [], } manifest_json_str = json.dumps(html_manifest_dict, indent=2) m = Manifest.from_json(manifest_json_str) @@ -68,7 +66,6 @@ def test_Manifest_flattened_copy(): "checksum": "0a576fd324b6985bac6aa934131d2f5c" }, }, - "integrations": [], } start_json_str = json.dumps(start, indent=2) m = Manifest.from_json(start_json_str) @@ -83,7 +80,6 @@ def test_Manifest_flattened_copy(): "test1.txt": {"checksum": "3e7705498e8be60520841409ebc69bc1"}, "test_folder1/testfoldertext1.txt": {"checksum": "0a576fd324b6985bac6aa934131d2f5c"}, }, - "integrations": [], } assert m.get_flattened_copy().data == html_manifest_dict @@ -93,7 +89,6 @@ def test_Manifest_empty_init(): "version": 1, "metadata": {}, "files": {}, - "integrations": [], } m = Manifest() m.data == init diff --git a/tests/test_bundle.py b/tests/test_bundle.py index 74e688b9..3d4e79ea 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -35,6 +35,11 @@ to_bytes, validate_entry_point, validate_extra_files, + write_tensorflow_manifest_json, + write_api_manifest_json, + write_notebook_manifest_json, + write_quarto_manifest_json, + write_voila_manifest_json, ) from rsconnect.environment import Environment from rsconnect.exception import RSConnectException @@ -138,7 +143,6 @@ def test_make_notebook_source_bundle1(self): }, "requirements.txt": {"checksum": "5f2a5e862fe7afe3def4a57bb5cfb214"}, }, - "integrations": [], }, ) @@ -225,7 +229,6 @@ def test_make_notebook_source_bundle2(self): }, "data.csv": {"checksum": data_csv_hash}, }, - "integrations": [], }, ) @@ -307,7 +310,6 @@ def test_make_quarto_source_bundle_from_simple_project(self): "myquarto.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, - "integrations": [], }, ) @@ -374,7 +376,6 @@ def test_make_quarto_source_bundle_from_complex_project(self): "config": [temp_proj + "/_quarto.yml"], "configResources": [], }, - "integrations": [], } with make_quarto_source_bundle( @@ -425,7 +426,6 @@ def test_make_quarto_source_bundle_from_complex_project(self): "about.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, - "integrations": [], }, ) @@ -509,7 +509,6 @@ def test_make_quarto_source_bundle_from_project_with_requirements(self): "myquarto.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, - "integrations": [], }, ) @@ -560,7 +559,6 @@ def test_make_quarto_source_bundle_from_file(self): "files": { "myquarto.qmd": {"checksum": mock.ANY}, }, - "integrations": [], }, ) @@ -639,7 +637,6 @@ def do_test_html_bundle(self, directory): "primary_html": "dummy.html", }, "files": {}, - "integrations": [], }, ) finally: @@ -678,7 +675,7 @@ def test_make_source_manifest(self): manifest = make_source_manifest(AppModes.PYTHON_API, None, None, None) self.assertEqual( manifest, - {"version": 1, "metadata": {"appmode": "python-api"}, "files": {}, "integrations": []}, + {"version": 1, "metadata": {"appmode": "python-api"}, "files": {}}, ) # include image parameter @@ -692,7 +689,6 @@ def test_make_source_manifest(self): "image": "rstudio/connect:bionic", }, "files": {}, - "integrations": [], }, ) @@ -705,7 +701,6 @@ def test_make_source_manifest(self): "metadata": {"appmode": "python-api"}, "environment": {"environment_management": {"python": False}}, "files": {}, - "integrations": [], }, ) @@ -718,7 +713,6 @@ def test_make_source_manifest(self): "metadata": {"appmode": "python-api"}, "environment": {"environment_management": {"r": False}}, "files": {}, - "integrations": [], }, ) @@ -742,7 +736,6 @@ def test_make_source_manifest(self): "environment_management": {"r": False, "python": False}, }, "files": {}, - "integrations": [], }, ) @@ -776,7 +769,6 @@ def test_make_source_manifest(self): "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, }, "files": {}, - "integrations": [], }, ) @@ -791,7 +783,11 @@ def test_make_source_manifest(self): # print(manifest) self.assertEqual( manifest, - {"version": 1, "metadata": {"appmode": "python-api", "entrypoint": "main.py"}, "files": {}, "integrations": []}, + { + "version": 1, + "metadata": {"appmode": "python-api", "entrypoint": "main.py"}, + "files": {}, + }, ) # include quarto_inspection parameter @@ -816,7 +812,6 @@ def test_make_source_manifest(self): }, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, - "integrations": [], }, ) @@ -850,7 +845,6 @@ def test_make_quarto_manifest_project_no_opt_params(self): "metadata": {"appmode": "quarto-shiny"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, - "integrations": [], }, ) @@ -884,7 +878,6 @@ def test_make_quarto_manifest_doc_no_opt_params(self): "metadata": {"appmode": "quarto-static"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {basename(temp_doc): {"checksum": mock.ANY}}, - "integrations": [], }, ) @@ -913,7 +906,6 @@ def test_make_quarto_manifest_project_with_image(self): "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "environment": {"image": "rstudio/connect:bionic"}, "files": {}, - "integrations": [], }, ) @@ -964,7 +956,6 @@ def test_make_quarto_manifest_project_with_env(self): "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, }, "files": {"requirements.txt": {"checksum": mock.ANY}}, - "integrations": [], }, ) @@ -1016,7 +1007,6 @@ def test_make_quarto_manifest_project_with_extra_files(self): "b": {"checksum": b_hash}, "c": {"checksum": c_hash}, }, - "integrations": [], }, ) @@ -1066,6 +1056,35 @@ def test_make_quarto_manifest_project_with_excludes(self): "e": {"checksum": mock.ANY}, "f": {"checksum": mock.ANY}, }, + }, + ) + + def test_write_quarto_manifest_json(self): + temp_proj = tempfile.mkdtemp() + # No optional parameters + write_quarto_manifest_json( + temp_proj, + { + "quarto": {"version": "0.9.16"}, + "engines": ["jupyter"], + "config": {"project": {"title": "quarto-proj-py"}, "editor": "visual", "language": {}}, + }, + AppModes.SHINY_QUARTO, + None, + [], + [], + None, + ) + manifest_path = join(temp_proj, "manifest.json") + with open(manifest_path) as f: + manifest_data = json.load(f) + self.assertEqual( + manifest_data, + { + "version": 1, + "metadata": {"appmode": "quarto-shiny"}, + "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, + "files": {}, "integrations": [], }, ) @@ -1079,7 +1098,6 @@ def test_make_tensorflow_manifest_empty(self): "version": 1, "metadata": {"appmode": "tensorflow-saved-model"}, "files": {}, - "integrations": [], }, ) @@ -1097,6 +1115,27 @@ def test_make_tensorflow_manifest(self): manifest = make_tensorflow_manifest(temp_proj, [], []) self.assertEqual( manifest, + { + "version": 1, + "metadata": {"appmode": "tensorflow-saved-model"}, + "files": { + "1/saved_model.pb": {"checksum": mock.ANY}, + }, + }, + ) + + def test_write_tensorflow_manifest_json(self): + temp_proj = tempfile.mkdtemp() + os.mkdir(join(temp_proj, "1")) + model_file = join(temp_proj, "1", "saved_model.pb") + with open(model_file, "w") as fp: + fp.write("fake model file\n") + write_tensorflow_manifest_json(temp_proj, [], []) + manifest_path = join(temp_proj, "manifest.json") + with open(manifest_path) as f: + manifest_data = json.load(f) + self.assertEqual( + manifest_data, { "version": 1, "metadata": {"appmode": "tensorflow-saved-model"}, @@ -1133,10 +1172,159 @@ def test_make_tensorflow_bundle(self): "files": { "1/saved_model.pb": {"checksum": mock.ANY}, }, - "integrations": [], }, ) + def test_write_api_manifest_json(self): + """Test that write_api_manifest_json includes empty integrations field""" + with tempfile.TemporaryDirectory() as temp_dir: + environment = Environment.from_dict( + dict( + contents="flask\npandas\n", + error=None, + filename="requirements.txt", + locale="en_US.UTF-8", + package_manager="pip", + pip="22.0.4", + python="3.9.12", + source="file", + ) + ) + + api_path = join(temp_dir, "app.py") + with open(api_path, "w") as f: + f.write("from flask import Flask\napp = Flask(__name__)") + + write_api_manifest_json( + temp_dir, + "app:app", + environment, + AppModes.PYTHON_API, + [], + [], + ) + manifest_path = join(temp_dir, "manifest.json") + with open(manifest_path) as f: + manifest_data = json.load(f) + self.assertEqual( + manifest_data, + { + "version": 1, + "locale": "en_US.UTF-8", + "metadata": {"appmode": "python-api", "entrypoint": "app:app"}, + "python": { + "version": "3.9.12", + "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, + }, + "files": { + "requirements.txt": {"checksum": "d108edd464af9a839226a62b967792eb"}, + "app.py": {"checksum": "fce2c868dd1689602160cb02bf40efc0"}, + }, + "integrations": [], + }, + ) + + def test_write_voila_manifest_json(self): + """Test that write_voila_manifest_json includes empty integrations field""" + with tempfile.TemporaryDirectory() as temp_dir: + environment = Environment.from_dict( + dict( + contents="numpy\npandas\n", + error=None, + filename="requirements.txt", + locale="en_US.UTF-8", + package_manager="pip", + pip="22.0.4", + python="3.9.12", + source="file", + ) + ) + + notebook_path = join(temp_dir, "notebook.ipynb") + with open(notebook_path, "w") as f: + f.write('{"cells": [], "metadata": {}}') + + write_voila_manifest_json( + notebook_path, + None, + environment, + [], + [], + True, + ) + + manifest_path = join(temp_dir, "manifest.json") + with open(manifest_path) as f: + manifest_data = json.load(f) + + self.assertEqual( + manifest_data, + { + "version": 1, + "locale": "en_US.UTF-8", + "metadata": {"appmode": "jupyter-voila", "entrypoint": "notebook.ipynb"}, + "python": { + "version": "3.9.12", + "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, + }, + "files": { + "requirements.txt": {"checksum": "1d2a079d610f8ec35da9af81527049d9"}, + "notebook.ipynb": {"checksum": "045b5aba58a3dd7def524be564274d15"}, + }, + "integrations": [], + }, + ) + + def test_write_notebook_manifest_json(self): + """Test that write_notebook_manifest_json includes empty integrations field""" + with tempfile.TemporaryDirectory() as temp_dir: + environment = Environment.from_dict( + dict( + contents="numpy\npandas\n", + error=None, + filename="requirements.txt", + locale="en_US.UTF-8", + package_manager="pip", + pip="22.0.4", + python="3.9.12", + source="file", + ) + ) + + notebook_path = join(temp_dir, "notebook.ipynb") + with open(notebook_path, "w") as f: + f.write('{"cells": [], "metadata": {}}') + + write_notebook_manifest_json( + notebook_path, + environment, + AppModes.JUPYTER_NOTEBOOK, + [], + False, + False, + ) + manifest_path = join(temp_dir, "manifest.json") + with open(manifest_path) as f: + manifest_data = json.load(f) + + self.assertEqual( + manifest_data, + { + "version": 1, + "locale": "en_US.UTF-8", + "metadata": {"appmode": "jupyter-static", "entrypoint": "notebook.ipynb"}, + "python": { + "package_manager": {"name": "pip", "package_file": "requirements.txt", "version": "22.0.4"}, + "version": "3.9.12", + }, + "files": { + "notebook.ipynb": {"checksum": "045b5aba58a3dd7def524be564274d15"}, + "requirements.txt": {"checksum": "1d2a079d610f8ec35da9af81527049d9"}, + }, + "integrations": [], + }, + ) + def test_make_html_manifest(self): # Verify the optional parameters # image=None, # type: str @@ -1153,7 +1341,6 @@ def test_make_html_manifest(self): "primary_html": "abc.html", }, "files": {}, - "integrations": [], }, ) @@ -1322,7 +1509,6 @@ def test_create_voila_manifest_1(path, entrypoint): "requirements.txt": {"checksum": "9cce1aac313043abd5690f67f84338ed"}, "bqplot.ipynb": {"checksum": checksum_hash}, }, - "integrations": [], } manifest = Manifest() if (path, entrypoint) in ( @@ -1401,7 +1587,6 @@ def test_create_voila_manifest_2(path, entrypoint): "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, - "integrations": [], } manifest = create_voila_manifest( path, @@ -1452,7 +1637,6 @@ def test_create_voila_manifest_extra(): "bqplot.ipynb": {"checksum": bqplot_checksum}, "dashboard.ipynb": {"checksum": dashboard_checksum}, }, - "integrations": [], } manifest = create_voila_manifest( dashboard_ipynb, @@ -1537,7 +1721,6 @@ def test_create_voila_manifest_multi_notebook(path, entrypoint): "bqplot/bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard/dashboard.ipynb": {"checksum": dashboard_hash}, }, - "integrations": [], } manifest = Manifest() if (path, entrypoint) in ( @@ -1642,7 +1825,6 @@ def test_make_voila_bundle( "requirements.txt": {"checksum": "9395f3162b7779c57c86b187fa441d96"}, "bqplot.ipynb": {"checksum": checksum_hash}, }, - "integrations": [], } if (path, entrypoint) in ( (None, None), @@ -1755,7 +1937,6 @@ def test_make_voila_bundle_multi_notebook( "bqplot/bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard/dashboard.ipynb": {"checksum": dashboard_hash}, }, - "integrations": [], } if (path, entrypoint) in ( (None, None), @@ -1847,7 +2028,6 @@ def test_make_voila_bundle_2( "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, - "integrations": [], } with make_voila_bundle( path, @@ -1906,7 +2086,6 @@ def test_make_voila_bundle_extra(): "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, - "integrations": [], } with make_voila_bundle( dashboard_ipynb, @@ -1977,7 +2156,6 @@ def test_create_html_manifest(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_hash}}, - "integrations": [], } manifest = create_html_manifest( single_file_index_file, @@ -1995,7 +2173,6 @@ def test_create_html_manifest(): "test1.txt": {"checksum": txt_hash}, "test_folder1/testfoldertext1.txt": {"checksum": folder_txt_hash}, }, - "integrations": [], } manifest = create_html_manifest( @@ -2018,7 +2195,6 @@ def test_create_html_manifest(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_hash}}, - "integrations": [], } manifest = create_html_manifest( @@ -2036,7 +2212,6 @@ def test_create_html_manifest(): "index.html": {"checksum": index_hash}, "main.html": {"checksum": index_hash}, }, - "integrations": [], } manifest = create_html_manifest( @@ -2059,7 +2234,6 @@ def test_create_html_manifest(): "version": 1, "metadata": {"appmode": "static", "primary_html": "b.html", "entrypoint": "b.html"}, "files": {"b.html": {"checksum": index_hash}}, - "integrations": [], } manifest = create_html_manifest( @@ -2077,7 +2251,6 @@ def test_create_html_manifest(): "a.html": {"checksum": index_hash}, "b.html": {"checksum": index_hash}, }, - "integrations": [], } manifest = create_html_manifest( @@ -2095,7 +2268,6 @@ def test_create_html_manifest(): "a.html": {"checksum": index_hash}, "b.html": {"checksum": index_hash}, }, - "integrations": [], } manifest = create_html_manifest( multi_file_nonindex_fileb, @@ -2112,7 +2284,6 @@ def test_create_html_manifest(): "index.html": {"checksum": index_hash}, "main.html": {"checksum": index_hash}, }, - "integrations": [], } manifest = create_html_manifest( @@ -2138,7 +2309,6 @@ def test_make_html_bundle(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_hash}}, - "integrations": [], } with make_html_bundle( single_file_index_file, @@ -2161,7 +2331,6 @@ def test_make_html_bundle(): "test1.txt": {"checksum": txt_hash}, "test_folder1/testfoldertext1.txt": {"checksum": folder_txt_hash}, }, - "integrations": [], } with make_html_bundle( single_file_index_dir, @@ -2202,7 +2371,6 @@ def test_make_html_bundle(): "version": 1, "metadata": {"appmode": "static", "primary_html": "index.html", "entrypoint": "index.html"}, "files": {"index.html": {"checksum": index_checksum}}, - "integrations": [], } with make_html_bundle( @@ -2225,7 +2393,6 @@ def test_make_html_bundle(): "index.html": {"checksum": index_checksum}, "main.html": {"checksum": index_checksum}, }, - "integrations": [], } with make_html_bundle( multi_file_index_dir, @@ -2259,7 +2426,6 @@ def test_make_html_bundle(): "version": 1, "metadata": {"appmode": "static", "primary_html": "b.html", "entrypoint": "b.html"}, "files": {"b.html": {"checksum": index_checksum}}, - "integrations": [], } with make_html_bundle( multi_file_nonindex_fileb, @@ -2281,7 +2447,6 @@ def test_make_html_bundle(): "a.html": {"checksum": index_checksum}, "b.html": {"checksum": index_checksum}, }, - "integrations": [], } with make_html_bundle( multi_file_nonindex_dir, @@ -2306,7 +2471,6 @@ def test_make_html_bundle(): "a.html": {"checksum": index_checksum}, "b.html": {"checksum": index_checksum}, }, - "integrations": [], } with make_html_bundle( multi_file_nonindex_fileb, @@ -2331,7 +2495,6 @@ def test_make_html_bundle(): "index.html": {"checksum": index_checksum}, "main.html": {"checksum": index_checksum}, }, - "integrations": [], } with make_html_bundle( @@ -2372,7 +2535,6 @@ def test_make_api_manifest_fastapi(): "main.py": {"checksum": "a8d8820f25be4dc8e2bf51a5ba1690b6"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, - "integrations": [], } environment = Environment.create_python_environment( fastapi_dir, @@ -2405,7 +2567,6 @@ def test_make_api_bundle_fastapi(): "main.py": {"checksum": "a8d8820f25be4dc8e2bf51a5ba1690b6"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, - "integrations": [], } environment = Environment.create_python_environment( fastapi_dir, @@ -2450,7 +2611,6 @@ def test_make_api_manifest_flask(): "app.py": {"checksum": "9799c3b834b555cf02e5896ad2997674"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, - "integrations": [], } environment = Environment.create_python_environment( flask_dir, @@ -2483,7 +2643,6 @@ def test_make_api_bundle_flask(): "app.py": {"checksum": "9799c3b834b555cf02e5896ad2997674"}, "prices.csv": {"checksum": "012afa636c426748177b38160135307a"}, }, - "integrations": [], } environment = Environment.create_python_environment( flask_dir, @@ -2528,7 +2687,6 @@ def test_make_api_manifest_streamlit(): "app1.py": {"checksum": "b203bc6d9512029a414ccbb63514e603"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } environment = Environment.create_python_environment( streamlit_dir, @@ -2560,7 +2718,6 @@ def test_make_api_bundle_streamlit(): "app1.py": {"checksum": "b203bc6d9512029a414ccbb63514e603"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } environment = Environment.create_python_environment( streamlit_dir, @@ -2606,7 +2763,6 @@ def test_make_api_manifest_dash(): "app2.py": {"checksum": "0cb6f0261685d29243977c7318d70d6d"}, "prices.csv": {"checksum": "3efb0ed7ad93bede9dc88f7a81ad4153"}, }, - "integrations": [], } environment = Environment.create_python_environment( dash_dir, @@ -2639,7 +2795,6 @@ def test_make_api_bundle_dash(): "app2.py": {"checksum": "0cb6f0261685d29243977c7318d70d6d"}, "prices.csv": {"checksum": "3efb0ed7ad93bede9dc88f7a81ad4153"}, }, - "integrations": [], } environment = Environment.create_python_environment( dash_dir, @@ -2684,7 +2839,6 @@ def test_make_api_manifest_bokeh(): "app3.py": {"checksum": "a5de7b460476a9ac4e02edfc2d52d9df"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } environment = Environment.create_python_environment( bokeh_dir, @@ -2717,7 +2871,6 @@ def test_make_api_bundle_bokeh(): "app3.py": {"checksum": "a5de7b460476a9ac4e02edfc2d52d9df"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } environment = Environment.create_python_environment( @@ -2763,7 +2916,6 @@ def test_make_api_manifest_shiny(): "app4.py": {"checksum": "f7e4b3b7ff0ada525ec388d037ff6c6a"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } environment = Environment.create_python_environment( shiny_dir, @@ -2796,7 +2948,6 @@ def test_make_api_bundle_shiny(): "app4.py": {"checksum": "f7e4b3b7ff0ada525ec388d037ff6c6a"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } environment = Environment.create_python_environment( shiny_dir, @@ -2841,7 +2992,6 @@ def test_make_manifest_bundle(): "app5.py": {"checksum": "f7e4b3b7ff0ada525ec388d037ff6c6a"}, "data.csv": {"checksum": "aabd9d1210246c69403532a6a9d24286"}, }, - "integrations": [], } with make_manifest_bundle( pyshiny_manifest_file, @@ -2876,7 +3026,6 @@ def test_make_api_manifest_gradio(): "requirements.txt": {"checksum": "381ccadfb8d4848add470e33033b198f"}, "app.py": {"checksum": "22feec76e9c02ac6b5a34a083e2983b6"}, }, - "integrations": [], } environment = Environment.create_python_environment( gradio_dir, @@ -2907,7 +3056,6 @@ def test_make_api_bundle_gradio(): "requirements.txt": {"checksum": "381ccadfb8d4848add470e33033b198f"}, "app.py": {"checksum": "22feec76e9c02ac6b5a34a083e2983b6"}, }, - "integrations": [], } environment = Environment.create_python_environment( gradio_dir, diff --git a/tests/testdata/Manifest/html_manifest.json b/tests/testdata/Manifest/html_manifest.json index a6fcc622..02330e34 100644 --- a/tests/testdata/Manifest/html_manifest.json +++ b/tests/testdata/Manifest/html_manifest.json @@ -15,6 +15,5 @@ "test_folder1/testfoldertext1.txt": { "checksum": "0a576fd324b6985bac6aa934131d2f5c" } - }, - "integrations": [] + } } From c6e57dbd65cdee96d63e1cba7a358d3fbab7fda9 Mon Sep 17 00:00:00 2001 From: joshyam-k Date: Mon, 4 Aug 2025 14:43:48 -0700 Subject: [PATCH 3/5] move away from checksums --- tests/test_bundle.py | 58 +++++--------------------------------------- 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/tests/test_bundle.py b/tests/test_bundle.py index 3d4e79ea..43017907 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -1206,23 +1206,8 @@ def test_write_api_manifest_json(self): manifest_path = join(temp_dir, "manifest.json") with open(manifest_path) as f: manifest_data = json.load(f) - self.assertEqual( - manifest_data, - { - "version": 1, - "locale": "en_US.UTF-8", - "metadata": {"appmode": "python-api", "entrypoint": "app:app"}, - "python": { - "version": "3.9.12", - "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, - }, - "files": { - "requirements.txt": {"checksum": "d108edd464af9a839226a62b967792eb"}, - "app.py": {"checksum": "fce2c868dd1689602160cb02bf40efc0"}, - }, - "integrations": [], - }, - ) + self.assertIn("integrations", manifest_data) + self.assertEqual(manifest_data["integrations"], []) def test_write_voila_manifest_json(self): """Test that write_voila_manifest_json includes empty integrations field""" @@ -1256,24 +1241,8 @@ def test_write_voila_manifest_json(self): manifest_path = join(temp_dir, "manifest.json") with open(manifest_path) as f: manifest_data = json.load(f) - - self.assertEqual( - manifest_data, - { - "version": 1, - "locale": "en_US.UTF-8", - "metadata": {"appmode": "jupyter-voila", "entrypoint": "notebook.ipynb"}, - "python": { - "version": "3.9.12", - "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, - }, - "files": { - "requirements.txt": {"checksum": "1d2a079d610f8ec35da9af81527049d9"}, - "notebook.ipynb": {"checksum": "045b5aba58a3dd7def524be564274d15"}, - }, - "integrations": [], - }, - ) + self.assertIn("integrations", manifest_data) + self.assertEqual(manifest_data["integrations"], []) def test_write_notebook_manifest_json(self): """Test that write_notebook_manifest_json includes empty integrations field""" @@ -1307,23 +1276,8 @@ def test_write_notebook_manifest_json(self): with open(manifest_path) as f: manifest_data = json.load(f) - self.assertEqual( - manifest_data, - { - "version": 1, - "locale": "en_US.UTF-8", - "metadata": {"appmode": "jupyter-static", "entrypoint": "notebook.ipynb"}, - "python": { - "package_manager": {"name": "pip", "package_file": "requirements.txt", "version": "22.0.4"}, - "version": "3.9.12", - }, - "files": { - "notebook.ipynb": {"checksum": "045b5aba58a3dd7def524be564274d15"}, - "requirements.txt": {"checksum": "1d2a079d610f8ec35da9af81527049d9"}, - }, - "integrations": [], - }, - ) + self.assertIn("integrations", manifest_data) + self.assertEqual(manifest_data["integrations"], []) def test_make_html_manifest(self): # Verify the optional parameters From b1b753d59a3b7f0cd079bb4da093b9478faa71a7 Mon Sep 17 00:00:00 2001 From: joshyam-k Date: Tue, 5 Aug 2025 09:15:07 -0700 Subject: [PATCH 4/5] move changes into --- rsconnect/bundle.py | 20 ++++++----------- tests/test_bundle.py | 51 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index 8f286b81..7f3b00fd 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -124,12 +124,12 @@ class ManifestDataPythonPackageManager(TypedDict): package_file: str -class ManifestIntegrations(TypedDict): +class ManifestIntegrationRequests(TypedDict): guid: NotRequired[str] name: NotRequired[str] description: NotRequired[str] - authtype: NotRequired[str] - integrationtype: NotRequired[str] + auth_type: NotRequired[str] + integration_type: NotRequired[str] config: NotRequired[dict[str, Any]] @@ -142,7 +142,7 @@ class ManifestData(TypedDict): quarto: NotRequired[ManifestDataQuarto] python: NotRequired[ManifestDataPython] environment: NotRequired[ManifestDataEnvironment] - integrations: NotRequired[list[ManifestIntegrations]] + integration_requests: NotRequired[list[ManifestIntegrationRequests]] class Manifest: @@ -408,6 +408,7 @@ def make_source_manifest( env_management_py=env_management_py, env_management_r=env_management_r, ) + manifest.data["integration_requests"] = [] return manifest.data @@ -1671,8 +1672,6 @@ def write_notebook_manifest_json( app_mode, environment, file_name, None, image, env_management_py, env_management_r ) - manifest_data["integrations"] = [] - if hide_all_input or hide_tagged_input: if "jupyter" not in manifest_data: manifest_data["jupyter"] = {} @@ -1775,6 +1774,7 @@ def create_voila_manifest( env_management_py=env_management_py, env_management_r=env_management_r, ) + manifest.data["integration_requests"] = [] manifest.deploy_dir = deploy_dir if entrypoint and isfile(entrypoint): validate_file_is_notebook(entrypoint) @@ -1840,8 +1840,6 @@ def write_voila_manifest_json( if multi_notebook and "metadata" in manifest_flattened_copy_data: manifest_flattened_copy_data["metadata"]["entrypoint"] = "" - manifest_flattened_copy_data["integrations"] = [] - manifest_path = join(deploy_dir, "manifest.json") write_manifest_json(manifest_path, manifest_flattened_copy_data) return exists(manifest_path) @@ -1933,8 +1931,6 @@ def write_api_manifest_json( directory, entry_point, app_mode, environment, extra_files, excludes, image, env_management_py, env_management_r ) - manifest["integrations"] = [] - manifest_path = join(directory, "manifest.json") write_manifest_json(manifest_path, manifest) @@ -2020,8 +2016,6 @@ def write_quarto_manifest_json( image, ) - manifest["integrations"] = [] - base_dir = file_or_directory if not isdir(file_or_directory): base_dir = dirname(file_or_directory) @@ -2051,7 +2045,7 @@ def write_tensorflow_manifest_json( excludes, image, ) - manifest["integrations"] = [] + manifest_path = join(directory, "manifest.json") write_manifest_json(manifest_path, manifest) diff --git a/tests/test_bundle.py b/tests/test_bundle.py index 43017907..49e6708e 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -143,6 +143,7 @@ def test_make_notebook_source_bundle1(self): }, "requirements.txt": {"checksum": "5f2a5e862fe7afe3def4a57bb5cfb214"}, }, + "integration_requests": [], }, ) @@ -229,6 +230,7 @@ def test_make_notebook_source_bundle2(self): }, "data.csv": {"checksum": data_csv_hash}, }, + "integration_requests": [], }, ) @@ -268,6 +270,7 @@ def test_make_quarto_source_bundle_from_simple_project(self): "config": [temp_proj + "/_quarto.yml"], "configResources": [], }, + "integration_requests": [], } with make_quarto_source_bundle( @@ -310,6 +313,7 @@ def test_make_quarto_source_bundle_from_simple_project(self): "myquarto.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -376,6 +380,7 @@ def test_make_quarto_source_bundle_from_complex_project(self): "config": [temp_proj + "/_quarto.yml"], "configResources": [], }, + "integration_requests": [], } with make_quarto_source_bundle( @@ -426,6 +431,7 @@ def test_make_quarto_source_bundle_from_complex_project(self): "about.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -467,6 +473,7 @@ def test_make_quarto_source_bundle_from_project_with_requirements(self): "config": [temp_proj + "/_quarto.yml"], "configResources": [], }, + "integration_requests": [], } with make_quarto_source_bundle( @@ -509,6 +516,7 @@ def test_make_quarto_source_bundle_from_project_with_requirements(self): "myquarto.qmd": {"checksum": mock.ANY}, "requirements.txt": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -559,6 +567,7 @@ def test_make_quarto_source_bundle_from_file(self): "files": { "myquarto.qmd": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -675,7 +684,7 @@ def test_make_source_manifest(self): manifest = make_source_manifest(AppModes.PYTHON_API, None, None, None) self.assertEqual( manifest, - {"version": 1, "metadata": {"appmode": "python-api"}, "files": {}}, + {"version": 1, "metadata": {"appmode": "python-api"}, "files": {}, "integration_requests": []}, ) # include image parameter @@ -689,6 +698,7 @@ def test_make_source_manifest(self): "image": "rstudio/connect:bionic", }, "files": {}, + "integration_requests": [], }, ) @@ -701,6 +711,7 @@ def test_make_source_manifest(self): "metadata": {"appmode": "python-api"}, "environment": {"environment_management": {"python": False}}, "files": {}, + "integration_requests": [], }, ) @@ -713,6 +724,7 @@ def test_make_source_manifest(self): "metadata": {"appmode": "python-api"}, "environment": {"environment_management": {"r": False}}, "files": {}, + "integration_requests": [], }, ) @@ -736,6 +748,7 @@ def test_make_source_manifest(self): "environment_management": {"r": False, "python": False}, }, "files": {}, + "integration_requests": [], }, ) @@ -769,6 +782,7 @@ def test_make_source_manifest(self): "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, }, "files": {}, + "integration_requests": [], }, ) @@ -787,6 +801,7 @@ def test_make_source_manifest(self): "version": 1, "metadata": {"appmode": "python-api", "entrypoint": "main.py"}, "files": {}, + "integration_requests": [], }, ) @@ -812,6 +827,7 @@ def test_make_source_manifest(self): }, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, + "integration_requests": [], }, ) @@ -845,6 +861,7 @@ def test_make_quarto_manifest_project_no_opt_params(self): "metadata": {"appmode": "quarto-shiny"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, + "integration_requests": [], }, ) @@ -878,6 +895,7 @@ def test_make_quarto_manifest_doc_no_opt_params(self): "metadata": {"appmode": "quarto-static"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {basename(temp_doc): {"checksum": mock.ANY}}, + "integration_requests": [], }, ) @@ -906,6 +924,7 @@ def test_make_quarto_manifest_project_with_image(self): "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "environment": {"image": "rstudio/connect:bionic"}, "files": {}, + "integration_requests": [], }, ) @@ -956,6 +975,7 @@ def test_make_quarto_manifest_project_with_env(self): "package_manager": {"name": "pip", "version": "22.0.4", "package_file": "requirements.txt"}, }, "files": {"requirements.txt": {"checksum": mock.ANY}}, + "integration_requests": [], }, ) @@ -1007,6 +1027,7 @@ def test_make_quarto_manifest_project_with_extra_files(self): "b": {"checksum": b_hash}, "c": {"checksum": c_hash}, }, + "integration_requests": [], }, ) @@ -1056,6 +1077,7 @@ def test_make_quarto_manifest_project_with_excludes(self): "e": {"checksum": mock.ANY}, "f": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -1085,7 +1107,7 @@ def test_write_quarto_manifest_json(self): "metadata": {"appmode": "quarto-shiny"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": {}, - "integrations": [], + "integration_requests": [], }, ) @@ -1098,6 +1120,7 @@ def test_make_tensorflow_manifest_empty(self): "version": 1, "metadata": {"appmode": "tensorflow-saved-model"}, "files": {}, + "integration_requests": [], }, ) @@ -1121,6 +1144,7 @@ def test_make_tensorflow_manifest(self): "files": { "1/saved_model.pb": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -1142,7 +1166,7 @@ def test_write_tensorflow_manifest_json(self): "files": { "1/saved_model.pb": {"checksum": mock.ANY}, }, - "integrations": [], + "integration_requests": [], }, ) @@ -1172,6 +1196,7 @@ def test_make_tensorflow_bundle(self): "files": { "1/saved_model.pb": {"checksum": mock.ANY}, }, + "integration_requests": [], }, ) @@ -1206,8 +1231,8 @@ def test_write_api_manifest_json(self): manifest_path = join(temp_dir, "manifest.json") with open(manifest_path) as f: manifest_data = json.load(f) - self.assertIn("integrations", manifest_data) - self.assertEqual(manifest_data["integrations"], []) + self.assertIn("integration_requests", manifest_data) + self.assertEqual(manifest_data["integration_requests"], []) def test_write_voila_manifest_json(self): """Test that write_voila_manifest_json includes empty integrations field""" @@ -1241,8 +1266,8 @@ def test_write_voila_manifest_json(self): manifest_path = join(temp_dir, "manifest.json") with open(manifest_path) as f: manifest_data = json.load(f) - self.assertIn("integrations", manifest_data) - self.assertEqual(manifest_data["integrations"], []) + self.assertIn("integration_requests", manifest_data) + self.assertEqual(manifest_data["integration_requests"], []) def test_write_notebook_manifest_json(self): """Test that write_notebook_manifest_json includes empty integrations field""" @@ -1276,8 +1301,8 @@ def test_write_notebook_manifest_json(self): with open(manifest_path) as f: manifest_data = json.load(f) - self.assertIn("integrations", manifest_data) - self.assertEqual(manifest_data["integrations"], []) + self.assertIn("integration_requests", manifest_data) + self.assertEqual(manifest_data["integration_requests"], []) def test_make_html_manifest(self): # Verify the optional parameters @@ -1463,6 +1488,7 @@ def test_create_voila_manifest_1(path, entrypoint): "requirements.txt": {"checksum": "9cce1aac313043abd5690f67f84338ed"}, "bqplot.ipynb": {"checksum": checksum_hash}, }, + "integration_requests": [], } manifest = Manifest() if (path, entrypoint) in ( @@ -1541,6 +1567,7 @@ def test_create_voila_manifest_2(path, entrypoint): "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integration_requests": [], } manifest = create_voila_manifest( path, @@ -1591,6 +1618,7 @@ def test_create_voila_manifest_extra(): "bqplot.ipynb": {"checksum": bqplot_checksum}, "dashboard.ipynb": {"checksum": dashboard_checksum}, }, + "integration_requests": [], } manifest = create_voila_manifest( dashboard_ipynb, @@ -1675,6 +1703,7 @@ def test_create_voila_manifest_multi_notebook(path, entrypoint): "bqplot/bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard/dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integration_requests": [], } manifest = Manifest() if (path, entrypoint) in ( @@ -1779,6 +1808,7 @@ def test_make_voila_bundle( "requirements.txt": {"checksum": "9395f3162b7779c57c86b187fa441d96"}, "bqplot.ipynb": {"checksum": checksum_hash}, }, + "integration_requests": [], } if (path, entrypoint) in ( (None, None), @@ -1891,6 +1921,7 @@ def test_make_voila_bundle_multi_notebook( "bqplot/bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard/dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integration_requests": [], } if (path, entrypoint) in ( (None, None), @@ -1982,6 +2013,7 @@ def test_make_voila_bundle_2( "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integration_requests": [], } with make_voila_bundle( path, @@ -2040,6 +2072,7 @@ def test_make_voila_bundle_extra(): "bqplot.ipynb": {"checksum": bqplot_hash}, "dashboard.ipynb": {"checksum": dashboard_hash}, }, + "integration_requests": [], } with make_voila_bundle( dashboard_ipynb, From 28f4b6b6b9b8a9b9074033bce443cdf8d8ac2bd8 Mon Sep 17 00:00:00 2001 From: joshyam-k Date: Fri, 22 Aug 2025 14:10:54 -0700 Subject: [PATCH 5/5] drop NotRequired --- rsconnect/bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index 7f3b00fd..4f14fc2b 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -142,7 +142,7 @@ class ManifestData(TypedDict): quarto: NotRequired[ManifestDataQuarto] python: NotRequired[ManifestDataPython] environment: NotRequired[ManifestDataEnvironment] - integration_requests: NotRequired[list[ManifestIntegrationRequests]] + integration_requests: list[ManifestIntegrationRequests] class Manifest: