Skip to content

Commit 353f8ba

Browse files
authored
deploy/write-manifest tensorflow (#589)
* deploy/write-manifest tensorflow * avoid testing arbitrary bundle file contents
1 parent 6226be8 commit 353f8ba

File tree

4 files changed

+319
-202
lines changed

4 files changed

+319
-202
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Pending Next Release
88

9+
### Added
10+
- Added support for creating manifests for TensorFlow models.
11+
- Added support for deploying TensorFlow models.
12+
913
## [1.23.0] - 2024-04-04
1014

1115
### Added

rsconnect/bundle.py

Lines changed: 102 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -371,9 +371,9 @@ def discard_from_buffer(self, key: str):
371371
# noinspection SpellCheckingInspection
372372
def make_source_manifest(
373373
app_mode: AppMode,
374-
environment: Optional[Environment],
375-
entrypoint: Optional[str],
376-
quarto_inspection: Optional[QuartoInspectResult],
374+
environment: Optional[Environment] = None,
375+
entrypoint: Optional[str] = None,
376+
quarto_inspection: Optional[QuartoInspectResult] = None,
377377
image: Optional[str] = None,
378378
env_management_py: Optional[bool] = None,
379379
env_management_r: Optional[bool] = None,
@@ -701,9 +701,6 @@ def make_quarto_source_bundle(
701701

702702
def make_html_manifest(
703703
filename: str,
704-
image: Optional[str] = None,
705-
env_management_py: Optional[bool] = None,
706-
env_management_r: Optional[bool] = None,
707704
) -> ManifestData:
708705
# noinspection SpellCheckingInspection
709706
manifest: ManifestData = {
@@ -714,17 +711,6 @@ def make_html_manifest(
714711
},
715712
"files": {},
716713
}
717-
718-
if image or env_management_py is not None or env_management_r is not None:
719-
manifest["environment"] = {}
720-
if image:
721-
manifest["environment"]["image"] = image
722-
if env_management_py is not None or env_management_r is not None:
723-
manifest["environment"]["environment_management"] = {}
724-
if env_management_py is not None:
725-
manifest["environment"]["environment_management"]["python"] = env_management_py
726-
if env_management_r is not None:
727-
manifest["environment"]["environment_management"]["r"] = env_management_r
728714
return manifest
729715

730716

@@ -733,9 +719,6 @@ def make_notebook_html_bundle(
733719
python: str,
734720
hide_all_input: bool,
735721
hide_tagged_input: bool,
736-
image: Optional[str] = None,
737-
env_management_py: Optional[bool] = None,
738-
env_management_r: Optional[bool] = None,
739722
check_output: Callable[..., bytes] = subprocess.check_output,
740723
) -> typing.IO[bytes]:
741724
# noinspection SpellCheckingInspection
@@ -766,12 +749,11 @@ def make_notebook_html_bundle(
766749
filename = splitext(nb_name)[0] + ".html"
767750

768751
bundle_file = tempfile.TemporaryFile(prefix="rsc_bundle")
769-
770752
with tarfile.open(mode="w:gz", fileobj=bundle_file) as bundle:
771753
bundle_add_buffer(bundle, filename, output)
772754

773755
# manifest
774-
manifest = make_html_manifest(filename, image, env_management_py, env_management_r)
756+
manifest = make_html_manifest(filename)
775757
bundle_add_buffer(bundle, "manifest.json", json.dumps(manifest, indent=2))
776758

777759
# rewind file pointer
@@ -971,9 +953,6 @@ def create_html_manifest(
971953
entrypoint: Optional[str],
972954
extra_files: Sequence[str],
973955
excludes: Sequence[str],
974-
image: Optional[str] = None,
975-
env_management_py: Optional[bool] = None,
976-
env_management_r: Optional[bool] = None,
977956
) -> Manifest:
978957
"""
979958
Creates and writes a manifest.json file for the given path.
@@ -986,12 +965,6 @@ def create_html_manifest(
986965
portion of the entry point file name will be used to derive one. Previous default = None.
987966
:param extra_files: any extra files that should be included in the manifest. Previous default = None.
988967
:param excludes: a sequence of glob patterns that will exclude matched files.
989-
:param force_generate: bool indicating whether to force generate manifest and related environment files.
990-
:param image: the optional docker image to be specified for off-host execution. Default = None.
991-
:param env_management_py: False prevents Connect from managing the Python environment for this bundle.
992-
The server administrator is responsible for installing packages in the runtime environment. Default = None.
993-
:param env_management_r: False prevents Connect from managing the R environment for this bundle.
994-
The server administrator is responsible for installing packages in the runtime environment. Default = None.
995968
:return: the manifest data structure.
996969
"""
997970
if not path:
@@ -1023,9 +996,6 @@ def create_html_manifest(
1023996
app_mode=AppModes.STATIC,
1024997
entrypoint=entrypoint,
1025998
primary_html=entrypoint,
1026-
image=image,
1027-
env_management_py=env_management_py,
1028-
env_management_r=env_management_r,
1029999
)
10301000
manifest.deploy_dir = deploy_dir
10311001

@@ -1036,14 +1006,46 @@ def create_html_manifest(
10361006
return manifest
10371007

10381008

1009+
def make_tensorflow_manifest(
1010+
directory: str,
1011+
extra_files: Sequence[str],
1012+
excludes: Sequence[str],
1013+
image: Optional[str] = None,
1014+
) -> ManifestData:
1015+
"""
1016+
Creates and writes a manifest.json file for the given path.
1017+
1018+
:param directory the directory containing the TensorFlow model.
1019+
:param extra_files: any extra files that should be included in the manifest. Previous default = None.
1020+
:param excludes: a sequence of glob patterns that will exclude matched files.
1021+
:param image: the optional docker image to be specified for off-host execution. Default = None.
1022+
:return: the manifest data structure.
1023+
"""
1024+
if not directory:
1025+
raise RSConnectException("A valid directory must be provided.")
1026+
extra_files = list(extra_files) if extra_files else []
1027+
1028+
extra_files = validate_extra_files(directory, extra_files, use_abspath=True)
1029+
excludes = list(excludes) if excludes else []
1030+
excludes.extend(["manifest.json"])
1031+
excludes.extend(list_environment_dirs(directory))
1032+
1033+
manifest = make_source_manifest(
1034+
app_mode=AppModes.TENSORFLOW,
1035+
image=image,
1036+
)
1037+
1038+
file_list = create_file_list(directory, extra_files, excludes)
1039+
for rel_path in file_list:
1040+
manifest_add_file(manifest, rel_path, directory)
1041+
return manifest
1042+
1043+
10391044
def make_html_bundle(
10401045
path: str,
10411046
entrypoint: Optional[str],
10421047
extra_files: Sequence[str],
10431048
excludes: Sequence[str],
1044-
image: Optional[str] = None,
1045-
env_management_py: Optional[bool] = None,
1046-
env_management_r: Optional[bool] = None,
10471049
) -> typing.IO[bytes]:
10481050
"""
10491051
Create an html bundle, given a path and/or entrypoint.
@@ -1054,11 +1056,6 @@ def make_html_bundle(
10541056
:param entrypoint: the main entry point.
10551057
:param extra_files: a sequence of any extra files to include in the bundle.
10561058
:param excludes: a sequence of glob patterns that will exclude matched files.
1057-
:param image: the optional docker image to be specified for off-host execution. Default = None.
1058-
:param env_management_py: False prevents Connect from managing the Python environment for this bundle.
1059-
The server administrator is responsible for installing packages in the runtime environment. Default = None.
1060-
:param env_management_r: False prevents Connect from managing the R environment for this bundle.
1061-
The server administrator is responsible for installing packages in the runtime environment. Default = None.
10621059
:return: a file-like object containing the bundle tarball.
10631060
"""
10641061

@@ -1067,9 +1064,6 @@ def make_html_bundle(
10671064
entrypoint=entrypoint,
10681065
extra_files=extra_files,
10691066
excludes=excludes,
1070-
image=image,
1071-
env_management_py=env_management_py,
1072-
env_management_r=env_management_r,
10731067
)
10741068

10751069
if manifest.data.get("files") is None:
@@ -1091,6 +1085,43 @@ def make_html_bundle(
10911085
return bundle.to_file(manifest.deploy_dir)
10921086

10931087

1088+
def make_tensorflow_bundle(
1089+
directory: str,
1090+
extra_files: Sequence[str],
1091+
excludes: Sequence[str],
1092+
image: Optional[str] = None,
1093+
) -> typing.IO[bytes]:
1094+
"""
1095+
Create an html bundle, given a path and/or entrypoint.
1096+
1097+
The bundle contains a manifest.json file created for the given notebook entrypoint file.
1098+
1099+
:param directory: the directory containing the TensorFlow model.
1100+
:param extra_files: a sequence of any extra files to include in the bundle.
1101+
:param excludes: a sequence of glob patterns that will exclude matched files.
1102+
:param image: the optional docker image to be specified for off-host execution. Default = None.
1103+
:return: a file-like object containing the bundle tarball.
1104+
"""
1105+
1106+
manifest = make_tensorflow_manifest(
1107+
directory=directory,
1108+
extra_files=extra_files,
1109+
excludes=excludes,
1110+
image=image,
1111+
)
1112+
1113+
if not manifest.get("files"):
1114+
raise RSConnectException("No valid files were found for the manifest.")
1115+
1116+
bundle = Bundle()
1117+
for f in manifest["files"]:
1118+
bundle.add_file(join(directory, f))
1119+
1120+
bundle.add_to_buffer("manifest.json", json.dumps(manifest, indent=2))
1121+
1122+
return bundle.to_file(directory)
1123+
1124+
10941125
def create_file_list(
10951126
path: str,
10961127
extra_files: Sequence[str],
@@ -2173,6 +2204,32 @@ def write_quarto_manifest_json(
21732204
write_manifest_json(manifest_path, manifest)
21742205

21752206

2207+
def write_tensorflow_manifest_json(
2208+
directory: str,
2209+
extra_files: Sequence[str],
2210+
excludes: Sequence[str],
2211+
image: Optional[str] = None,
2212+
) -> None:
2213+
"""
2214+
Creates and writes a manifest.json file for the given TensorFlow content.
2215+
2216+
:param directory: The directory containing the TensorFlow model.
2217+
:param environment: The (optional) Python environment to use.
2218+
:param extra_files: Any extra files to include in the manifest.
2219+
:param excludes: A sequence of glob patterns to exclude when enumerating files to bundle.
2220+
:param image: the optional docker image to be specified for off-host execution. Default = None.
2221+
"""
2222+
2223+
manifest = make_tensorflow_manifest(
2224+
directory,
2225+
extra_files,
2226+
excludes,
2227+
image,
2228+
)
2229+
manifest_path = join(directory, "manifest.json")
2230+
write_manifest_json(manifest_path, manifest)
2231+
2232+
21762233
def write_manifest_json(manifest_path: str | Path, manifest: ManifestData) -> None:
21772234
"""
21782235
Write the manifest data as JSON to the named manifest.json with a trailing newline.

0 commit comments

Comments
 (0)