4
4
import shutil
5
5
import tempfile
6
6
from typing import Any , Callable , Dict , List , Optional , TypeVar , Union
7
+ from zipfile import Zipfile
7
8
8
9
import cloudpickle
9
10
import requests
@@ -137,23 +138,55 @@ def register_endpoint_auth_decorator(self, endpoint_auth_decorator_fn):
137
138
"""
138
139
self .endpoint_auth_decorator_fn = endpoint_auth_decorator_fn
139
140
140
- def create_model_bundle_from_dir (
141
+ def create_model_bundle_from_dirs (
141
142
self ,
142
143
model_bundle_name : str ,
143
- base_path : str ,
144
+ base_paths : List [ str ] ,
144
145
requirements_path : str ,
145
146
env_params : Dict [str , str ],
146
147
load_predict_fn_module_path : str ,
147
148
load_model_fn_module_path : str ,
148
149
app_config : Optional [Union [Dict [str , Any ], str ]] = None ,
149
150
) -> ModelBundle :
150
151
"""
151
- Packages up code from a local filesystem folder and uploads that as a bundle to Scale Launch.
152
+ Packages up code from one or more local filesystem folders and uploads them as a bundle to Scale Launch.
152
153
In this mode, a bundle is just local code instead of a serialized object.
153
154
155
+ For example, if you have a directory structure like so, and your current working directory is also `my_root`:
156
+
157
+ ```
158
+ my_root/
159
+ my_module1/
160
+ __init__.py
161
+ ...files and directories
162
+ my_inference_file.py
163
+ my_module2/
164
+ __init__.py
165
+ ...files and directories
166
+ ```
167
+
168
+ then calling `create_model_bundle_from_dirs` with `base_paths=["my_module1", "my_module2"]` essentially
169
+ creates a zip file without the root directory, e.g.:
170
+
171
+ ```
172
+ my_module1/
173
+ __init__.py
174
+ ...files and directories
175
+ my_inference_file.py
176
+ my_module2/
177
+ __init__.py
178
+ ...files and directories
179
+ ```
180
+
181
+ and these contents will be unzipped relative to the server side `PYTHONPATH`. Bear these points in mind when
182
+ referencing Python module paths for this bundle. For instance, if `my_inference_file.py` has `def f(...)`
183
+ as the desired inference loading function, then the `load_predict_fn_module_path` argument should be
184
+ `my_module1.my_inference_file.f`.
185
+
186
+
154
187
Parameters:
155
188
model_bundle_name: Name of model bundle you want to create. This acts as a unique identifier.
156
- base_path : The path on the local filesystem where the bundle code lives.
189
+ base_paths : The paths on the local filesystem where the bundle code lives.
157
190
requirements_path: A path on the local filesystem where a requirements.txt file lives.
158
191
env_params: A dictionary that dictates environment information e.g.
159
192
the use of pytorch or tensorflow, which cuda/cudnn versions to use.
@@ -163,9 +196,9 @@ def create_model_bundle_from_dir(
163
196
"cuda_version": Version of cuda used, e.g. "11.0".
164
197
"cudnn_version" Version of cudnn used, e.g. "cudnn8-devel".
165
198
"tensorflow_version": Version of tensorflow, e.g. "2.3.0". Only applicable if framework_type is tensorflow
166
- load_predict_fn_module_path: A python module path within base_path for a function that, when called with the output of
199
+ load_predict_fn_module_path: A python module path for a function that, when called with the output of
167
200
load_model_fn_module_path, returns a function that carries out inference.
168
- load_model_fn_module_path: A python module path within base_path for a function that returns a model. The output feeds into
201
+ load_model_fn_module_path: A python module path for a function that returns a model. The output feeds into
169
202
the function located at load_predict_fn_module_path.
170
203
app_config: Either a Dictionary that represents a YAML file contents or a local path to a YAML file.
171
204
"""
@@ -174,20 +207,9 @@ def create_model_bundle_from_dir(
174
207
175
208
tmpdir = tempfile .mkdtemp ()
176
209
try :
177
- tmparchive = os .path .join (tmpdir , "bundle" )
178
- abs_base_path = os .path .abspath (base_path )
179
- root_dir = os .path .dirname (abs_base_path )
180
- base_dir = os .path .basename (abs_base_path )
181
-
182
- with open (
183
- shutil .make_archive (
184
- base_name = tmparchive ,
185
- format = "zip" ,
186
- root_dir = root_dir ,
187
- base_dir = base_dir ,
188
- ),
189
- "rb" ,
190
- ) as zip_f :
210
+ zip_path = os .path .join (tmpdir , "bundle.zip" )
211
+ _zip_directories (zip_path , base_paths )
212
+ with open (zip_path , "rb" ) as zip_f :
191
213
data = zip_f .read ()
192
214
finally :
193
215
shutil .rmtree (tmpdir )
@@ -771,3 +793,17 @@ def get_batch_async_response(self, batch_async_task_id: str):
771
793
TODO Something similar to a list of signed s3URLs
772
794
"""
773
795
raise NotImplementedError
796
+
797
+
798
+ def _zip_directory (zipf : zipfile .Zipfile , path : str ) -> None :
799
+ for root , _ , files in os .walk (path ):
800
+ for file in files :
801
+ zipf .write (filename = os .path .join (root , file ),
802
+ arcname = os .path .relpath (os .path .join (root , file ),
803
+ os .path .join (path , '..' )))
804
+
805
+
806
+ def _zip_directories (zip_path : str , dir_list : List [str ]) -> None :
807
+ with zipfile .ZipFile (zip_path , 'w' ) as zip_f :
808
+ for dir in dir_list :
809
+ zip_directory (zipf , dir )
0 commit comments