modal 0.66.48__py3-none-any.whl → 0.67.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modal/_resolver.py +7 -2
- modal/_runtime/container_io_manager.py +1 -1
- modal/app.py +13 -7
- modal/cli/config.py +3 -0
- modal/cli/import_refs.py +1 -1
- modal/cli/run.py +17 -4
- modal/client.pyi +2 -2
- modal/cls.py +49 -44
- modal/cls.pyi +2 -4
- modal/config.py +1 -1
- modal/functions.py +4 -98
- modal/functions.pyi +0 -10
- modal/image.py +81 -67
- modal/image.pyi +12 -7
- modal/object.py +2 -2
- modal/object.pyi +4 -2
- modal/parallel_map.py +2 -2
- modal/runner.py +1 -1
- modal/volume.py +13 -6
- modal/volume.pyi +10 -4
- {modal-0.66.48.dist-info → modal-0.67.1.dist-info}/METADATA +1 -1
- {modal-0.66.48.dist-info → modal-0.67.1.dist-info}/RECORD +30 -30
- modal_proto/api.proto +3 -3
- modal_proto/api_pb2.pyi +3 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.66.48.dist-info → modal-0.67.1.dist-info}/LICENSE +0 -0
- {modal-0.66.48.dist-info → modal-0.67.1.dist-info}/WHEEL +0 -0
- {modal-0.66.48.dist-info → modal-0.67.1.dist-info}/entry_points.txt +0 -0
- {modal-0.66.48.dist-info → modal-0.67.1.dist-info}/top_level.txt +0 -0
modal/_resolver.py
CHANGED
@@ -130,9 +130,14 @@ class Resolver:
|
|
130
130
|
raise NotFoundError(exc.message)
|
131
131
|
raise
|
132
132
|
|
133
|
-
# Check that the id of functions
|
133
|
+
# Check that the id of functions didn't change
|
134
134
|
# Persisted refs are ignored because their life cycle is managed independently.
|
135
|
-
if
|
135
|
+
if (
|
136
|
+
not obj._is_another_app
|
137
|
+
and existing_object_id is not None
|
138
|
+
and existing_object_id.startswith("fu-")
|
139
|
+
and obj.object_id != existing_object_id
|
140
|
+
):
|
136
141
|
raise Exception(
|
137
142
|
f"Tried creating an object using existing id {existing_object_id}"
|
138
143
|
f" but it has id {obj.object_id}"
|
@@ -452,7 +452,7 @@ class _ContainerIOManager:
|
|
452
452
|
await asyncio.sleep(DYNAMIC_CONCURRENCY_INTERVAL_SECS)
|
453
453
|
|
454
454
|
async def get_app_objects(self) -> RunningApp:
|
455
|
-
req = api_pb2.AppGetObjectsRequest(app_id=self.app_id, include_unindexed=True)
|
455
|
+
req = api_pb2.AppGetObjectsRequest(app_id=self.app_id, include_unindexed=True, only_class_function=True)
|
456
456
|
resp = await retry_transient_errors(self._client.stub.AppGetObjects, req)
|
457
457
|
logger.debug(f"AppGetObjects received {len(resp.items)} objects for app {self.app_id}")
|
458
458
|
|
modal/app.py
CHANGED
@@ -492,13 +492,19 @@ class _App:
|
|
492
492
|
|
493
493
|
_App._container_app = running_app
|
494
494
|
|
495
|
-
# Hydrate objects on app
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
495
|
+
# Hydrate objects on app -- hydrating functions first so that when a class is being hydrated its
|
496
|
+
# corresponding class service function is already hydrated.
|
497
|
+
def hydrate_objects(objects_dict):
|
498
|
+
for tag, object_id in running_app.tag_to_object_id.items():
|
499
|
+
if tag in objects_dict:
|
500
|
+
obj = objects_dict[tag]
|
501
|
+
handle_metadata = running_app.object_handle_metadata[object_id]
|
502
|
+
obj._hydrate(object_id, client, handle_metadata)
|
503
|
+
|
504
|
+
# Hydrate function objects
|
505
|
+
hydrate_objects(self._functions)
|
506
|
+
# Hydrate class objects
|
507
|
+
hydrate_objects(self._classes)
|
502
508
|
|
503
509
|
@property
|
504
510
|
def registered_functions(self) -> Dict[str, _Function]:
|
modal/cli/config.py
CHANGED
@@ -3,6 +3,7 @@ import typer
|
|
3
3
|
from rich.console import Console
|
4
4
|
|
5
5
|
from modal.config import _profile, _store_user_config, config
|
6
|
+
from modal.environments import Environment
|
6
7
|
|
7
8
|
config_cli = typer.Typer(
|
8
9
|
name="config",
|
@@ -38,6 +39,8 @@ when running a command that requires an environment.
|
|
38
39
|
|
39
40
|
@config_cli.command(help=SET_DEFAULT_ENV_HELP)
|
40
41
|
def set_environment(environment_name: str):
|
42
|
+
# Confirm that the environment exists by looking it up
|
43
|
+
Environment.lookup(environment_name)
|
41
44
|
_store_user_config({"environment": environment_name})
|
42
45
|
typer.echo(f"New default environment for profile {_profile}: {environment_name}")
|
43
46
|
|
modal/cli/import_refs.py
CHANGED
@@ -110,7 +110,7 @@ def get_by_object_path(obj: Any, obj_path: str) -> Optional[Any]:
|
|
110
110
|
def _infer_function_or_help(
|
111
111
|
app: App, module, accept_local_entrypoint: bool, accept_webhook: bool
|
112
112
|
) -> Union[Function, LocalEntrypoint]:
|
113
|
-
function_choices = set(
|
113
|
+
function_choices = set(app.registered_functions)
|
114
114
|
if not accept_webhook:
|
115
115
|
function_choices -= set(app.registered_web_endpoints)
|
116
116
|
if accept_local_entrypoint:
|
modal/cli/run.py
CHANGED
@@ -136,7 +136,13 @@ def _get_clean_app_description(func_ref: str) -> str:
|
|
136
136
|
|
137
137
|
|
138
138
|
def _get_click_command_for_function(app: App, function_tag):
|
139
|
-
function = app.registered_functions
|
139
|
+
function = app.registered_functions.get(function_tag)
|
140
|
+
if not function or (isinstance(function, Function) and function.info.user_cls is not None):
|
141
|
+
# This is either a function_tag for a class method function (e.g MyClass.foo) or a function tag for a
|
142
|
+
# class service function (MyClass.*)
|
143
|
+
class_name, method_name = function_tag.rsplit(".", 1)
|
144
|
+
if not function:
|
145
|
+
function = app.registered_functions.get(f"{class_name}.*")
|
140
146
|
assert isinstance(function, Function)
|
141
147
|
function = typing.cast(Function, function)
|
142
148
|
if function.is_generator:
|
@@ -144,12 +150,19 @@ def _get_click_command_for_function(app: App, function_tag):
|
|
144
150
|
|
145
151
|
signature: Dict[str, ParameterMetadata]
|
146
152
|
cls: Optional[Cls] = None
|
147
|
-
method_name: Optional[str] = None
|
148
153
|
if function.info.user_cls is not None:
|
149
|
-
class_name, method_name = function_tag.rsplit(".", 1)
|
150
154
|
cls = typing.cast(Cls, app.registered_classes[class_name])
|
151
155
|
cls_signature = _get_signature(function.info.user_cls)
|
152
|
-
|
156
|
+
if method_name == "*":
|
157
|
+
method_names = list(cls._get_partial_functions().keys())
|
158
|
+
if len(method_names) == 1:
|
159
|
+
method_name = method_names[0]
|
160
|
+
else:
|
161
|
+
class_name = function.info.user_cls.__name__
|
162
|
+
raise click.UsageError(
|
163
|
+
f"Please specify a specific method of {class_name} to run, e.g. `modal run foo.py::MyClass.bar`" # noqa: E501
|
164
|
+
)
|
165
|
+
fun_signature = _get_signature(getattr(cls, method_name).info.raw_f, is_method=True)
|
153
166
|
signature = dict(**cls_signature, **fun_signature) # Pool all arguments
|
154
167
|
# TODO(erikbern): assert there's no overlap?
|
155
168
|
else:
|
modal/client.pyi
CHANGED
@@ -31,7 +31,7 @@ class _Client:
|
|
31
31
|
server_url: str,
|
32
32
|
client_type: int,
|
33
33
|
credentials: typing.Optional[typing.Tuple[str, str]],
|
34
|
-
version: str = "0.
|
34
|
+
version: str = "0.67.1",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -90,7 +90,7 @@ class Client:
|
|
90
90
|
server_url: str,
|
91
91
|
client_type: int,
|
92
92
|
credentials: typing.Optional[typing.Tuple[str, str]],
|
93
|
-
version: str = "0.
|
93
|
+
version: str = "0.67.1",
|
94
94
|
): ...
|
95
95
|
def is_closed(self) -> bool: ...
|
96
96
|
@property
|
modal/cls.py
CHANGED
@@ -244,7 +244,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
244
244
|
_class_service_function: Optional[
|
245
245
|
_Function
|
246
246
|
] # The _Function serving *all* methods of the class, used for version >=v0.63
|
247
|
-
_method_functions: Dict[str, _Function] # Placeholder _Functions for each method
|
247
|
+
_method_functions: Optional[Dict[str, _Function]] = None # Placeholder _Functions for each method
|
248
248
|
_options: Optional[api_pb2.FunctionOptions]
|
249
249
|
_callables: Dict[str, Callable[..., Any]]
|
250
250
|
_from_other_workspace: Optional[bool] # Functions require FunctionBindParams before invocation.
|
@@ -253,7 +253,6 @@ class _Cls(_Object, type_prefix="cs"):
|
|
253
253
|
def _initialize_from_empty(self):
|
254
254
|
self._user_cls = None
|
255
255
|
self._class_service_function = None
|
256
|
-
self._method_functions = {}
|
257
256
|
self._options = None
|
258
257
|
self._callables = {}
|
259
258
|
self._from_other_workspace = None
|
@@ -273,28 +272,46 @@ class _Cls(_Object, type_prefix="cs"):
|
|
273
272
|
|
274
273
|
def _hydrate_metadata(self, metadata: Message):
|
275
274
|
assert isinstance(metadata, api_pb2.ClassHandleMetadata)
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
)
|
275
|
+
if (
|
276
|
+
self._class_service_function
|
277
|
+
and self._class_service_function._method_handle_metadata
|
278
|
+
and len(self._class_service_function._method_handle_metadata)
|
279
|
+
):
|
280
|
+
# The class only has a class service service function and no method placeholders (v0.67+)
|
281
|
+
if self._method_functions:
|
282
|
+
# We're here when the Cls is loaded locally (e.g. _Cls.from_local) so the _method_functions mapping is
|
283
|
+
# populated with (un-hydrated) _Function objects
|
284
|
+
for (
|
285
|
+
method_name,
|
286
|
+
method_handle_metadata,
|
287
|
+
) in self._class_service_function._method_handle_metadata.items():
|
288
|
+
self._method_functions[method_name]._hydrate(
|
289
|
+
self._class_service_function.object_id, self._client, method_handle_metadata
|
290
|
+
)
|
284
291
|
else:
|
292
|
+
# We're here when the function is loaded remotely (e.g. _Cls.from_name)
|
293
|
+
self._method_functions = {}
|
294
|
+
for (
|
295
|
+
method_name,
|
296
|
+
method_handle_metadata,
|
297
|
+
) in self._class_service_function._method_handle_metadata.items():
|
298
|
+
self._method_functions[method_name] = _Function._new_hydrated(
|
299
|
+
self._class_service_function.object_id, self._client, method_handle_metadata
|
300
|
+
)
|
301
|
+
elif self._class_service_function:
|
302
|
+
# A class with a class service function and method placeholder functions
|
303
|
+
self._method_functions = {}
|
304
|
+
for method in metadata.methods:
|
285
305
|
self._method_functions[method.function_name] = _Function._new_hydrated(
|
286
|
-
|
306
|
+
self._class_service_function.object_id, self._client, method.function_handle_metadata
|
287
307
|
)
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
function_name=f_name, function_id=f.object_id, function_handle_metadata=f._get_metadata()
|
308
|
+
else:
|
309
|
+
# pre 0.63 class that does not have a class service function and only method functions
|
310
|
+
self._method_functions = {}
|
311
|
+
for method in metadata.methods:
|
312
|
+
self._method_functions[method.function_name] = _Function._new_hydrated(
|
313
|
+
method.function_id, self._client, method.function_handle_metadata
|
295
314
|
)
|
296
|
-
)
|
297
|
-
return class_handle_metadata
|
298
315
|
|
299
316
|
@staticmethod
|
300
317
|
def validate_construction_mechanism(user_cls):
|
@@ -327,16 +344,17 @@ class _Cls(_Object, type_prefix="cs"):
|
|
327
344
|
# validate signature
|
328
345
|
_Cls.validate_construction_mechanism(user_cls)
|
329
346
|
|
330
|
-
|
347
|
+
method_functions: Dict[str, _Function] = {}
|
331
348
|
partial_functions: Dict[str, _PartialFunction] = _find_partial_methods_for_user_cls(
|
332
349
|
user_cls, _PartialFunctionFlags.FUNCTION
|
333
350
|
)
|
334
351
|
|
335
352
|
for method_name, partial_function in partial_functions.items():
|
336
|
-
method_function = class_service_function.
|
337
|
-
|
353
|
+
method_function = class_service_function._bind_method(user_cls, method_name, partial_function)
|
354
|
+
if partial_function.webhook_config is not None:
|
355
|
+
app._web_endpoints.append(method_function.tag)
|
338
356
|
partial_function.wrapped = True
|
339
|
-
|
357
|
+
method_functions[method_name] = method_function
|
340
358
|
|
341
359
|
# Disable the warning that these are not wrapped
|
342
360
|
for partial_function in _find_partial_methods_for_user_cls(user_cls, ~_PartialFunctionFlags.FUNCTION).values():
|
@@ -344,31 +362,17 @@ class _Cls(_Object, type_prefix="cs"):
|
|
344
362
|
|
345
363
|
# Get all callables
|
346
364
|
callables: Dict[str, Callable] = {
|
347
|
-
k: pf.raw_f for k, pf in _find_partial_methods_for_user_cls(user_cls,
|
365
|
+
k: pf.raw_f for k, pf in _find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.all()).items()
|
348
366
|
}
|
349
367
|
|
350
368
|
def _deps() -> List[_Function]:
|
351
|
-
return [class_service_function]
|
369
|
+
return [class_service_function]
|
352
370
|
|
353
371
|
async def _load(self: "_Cls", resolver: Resolver, existing_object_id: Optional[str]):
|
354
|
-
req = api_pb2.ClassCreateRequest(
|
355
|
-
|
356
|
-
|
357
|
-
api_pb2.ClassMethod(
|
358
|
-
function_name=f_name, function_id=f.object_id, function_handle_metadata=f._get_metadata()
|
359
|
-
)
|
360
|
-
)
|
372
|
+
req = api_pb2.ClassCreateRequest(
|
373
|
+
app_id=resolver.app_id, existing_class_id=existing_object_id, only_class_function=True
|
374
|
+
)
|
361
375
|
resp = await resolver.client.stub.ClassCreate(req)
|
362
|
-
# Even though we already have the function_handle_metadata for this method locally,
|
363
|
-
# The RPC is going to replace it with function_handle_metadata derived from the server.
|
364
|
-
# We need to overwrite the definition_id sent back from the server here with the definition_id
|
365
|
-
# previously stored in function metadata, which may have been sent back from FunctionCreate.
|
366
|
-
# The problem is that this metadata propagates back and overwrites the metadata on the Function
|
367
|
-
# object itself. This is really messy. Maybe better to exclusively populate the method metadata
|
368
|
-
# from the function metadata we already have locally? Really a lot to clean up here...
|
369
|
-
for method in resp.handle_metadata.methods:
|
370
|
-
f_metadata = self._method_functions[method.function_name]._get_metadata()
|
371
|
-
method.function_handle_metadata.definition_id = f_metadata.definition_id
|
372
376
|
self._hydrate(resp.class_id, resolver.client, resp.handle_metadata)
|
373
377
|
|
374
378
|
rep = f"Cls({user_cls.__name__})"
|
@@ -376,7 +380,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
376
380
|
cls._app = app
|
377
381
|
cls._user_cls = user_cls
|
378
382
|
cls._class_service_function = class_service_function
|
379
|
-
cls._method_functions =
|
383
|
+
cls._method_functions = method_functions
|
380
384
|
cls._callables = callables
|
381
385
|
cls._from_other_workspace = False
|
382
386
|
return cls
|
@@ -415,6 +419,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
415
419
|
environment_name=_environment_name,
|
416
420
|
lookup_published=workspace is not None,
|
417
421
|
workspace_name=workspace,
|
422
|
+
only_class_function=True,
|
418
423
|
)
|
419
424
|
try:
|
420
425
|
response = await retry_transient_errors(resolver.client.stub.ClassGet, request)
|
modal/cls.pyi
CHANGED
@@ -86,7 +86,7 @@ class Obj:
|
|
86
86
|
class _Cls(modal.object._Object):
|
87
87
|
_user_cls: typing.Optional[type]
|
88
88
|
_class_service_function: typing.Optional[modal.functions._Function]
|
89
|
-
_method_functions: typing.Dict[str, modal.functions._Function]
|
89
|
+
_method_functions: typing.Optional[typing.Dict[str, modal.functions._Function]]
|
90
90
|
_options: typing.Optional[modal_proto.api_pb2.FunctionOptions]
|
91
91
|
_callables: typing.Dict[str, typing.Callable[..., typing.Any]]
|
92
92
|
_from_other_workspace: typing.Optional[bool]
|
@@ -96,7 +96,6 @@ class _Cls(modal.object._Object):
|
|
96
96
|
def _initialize_from_other(self, other: _Cls): ...
|
97
97
|
def _get_partial_functions(self) -> typing.Dict[str, modal.partial_function._PartialFunction]: ...
|
98
98
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
99
|
-
def _get_metadata(self) -> modal_proto.api_pb2.ClassHandleMetadata: ...
|
100
99
|
@staticmethod
|
101
100
|
def validate_construction_mechanism(user_cls): ...
|
102
101
|
@staticmethod
|
@@ -139,7 +138,7 @@ class _Cls(modal.object._Object):
|
|
139
138
|
class Cls(modal.object.Object):
|
140
139
|
_user_cls: typing.Optional[type]
|
141
140
|
_class_service_function: typing.Optional[modal.functions.Function]
|
142
|
-
_method_functions: typing.Dict[str, modal.functions.Function]
|
141
|
+
_method_functions: typing.Optional[typing.Dict[str, modal.functions.Function]]
|
143
142
|
_options: typing.Optional[modal_proto.api_pb2.FunctionOptions]
|
144
143
|
_callables: typing.Dict[str, typing.Callable[..., typing.Any]]
|
145
144
|
_from_other_workspace: typing.Optional[bool]
|
@@ -150,7 +149,6 @@ class Cls(modal.object.Object):
|
|
150
149
|
def _initialize_from_other(self, other: Cls): ...
|
151
150
|
def _get_partial_functions(self) -> typing.Dict[str, modal.partial_function.PartialFunction]: ...
|
152
151
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
153
|
-
def _get_metadata(self) -> modal_proto.api_pb2.ClassHandleMetadata: ...
|
154
152
|
@staticmethod
|
155
153
|
def validate_construction_mechanism(user_cls): ...
|
156
154
|
@staticmethod
|
modal/config.py
CHANGED
modal/functions.py
CHANGED
@@ -118,7 +118,7 @@ class _Invocation:
|
|
118
118
|
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
|
119
119
|
) -> "_Invocation":
|
120
120
|
assert client.stub
|
121
|
-
function_id = function.
|
121
|
+
function_id = function.object_id
|
122
122
|
item = await _create_input(args, kwargs, client, method_name=function._use_method_name)
|
123
123
|
|
124
124
|
request = api_pb2.FunctionMapRequest(
|
@@ -319,8 +319,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
319
319
|
_cluster_size: Optional[int] = None
|
320
320
|
|
321
321
|
# when this is the method of a class/object function, invocation of this function
|
322
|
-
# should
|
323
|
-
_use_function_id: str # The function to invoke
|
322
|
+
# should supply the method name in the FunctionInput:
|
324
323
|
_use_method_name: str = ""
|
325
324
|
|
326
325
|
_class_parameter_info: Optional["api_pb2.ClassParameterInfo"] = None
|
@@ -360,94 +359,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
360
359
|
fun._is_method = True
|
361
360
|
return fun
|
362
361
|
|
363
|
-
def _bind_method_old(
|
364
|
-
self,
|
365
|
-
user_cls,
|
366
|
-
method_name: str,
|
367
|
-
partial_function: "modal.partial_function._PartialFunction",
|
368
|
-
):
|
369
|
-
"""mdmd:hidden
|
370
|
-
|
371
|
-
Creates a function placeholder function that binds a specific method name to
|
372
|
-
this function for use when invoking the function.
|
373
|
-
|
374
|
-
Should only be used on "class service functions". For "instance service functions",
|
375
|
-
we don't create an actual backend function, and instead do client-side "fake-hydration"
|
376
|
-
only, see _bind_instance_method.
|
377
|
-
|
378
|
-
"""
|
379
|
-
class_service_function = self
|
380
|
-
assert class_service_function._info # has to be a local function to be able to "bind" it
|
381
|
-
assert not class_service_function._is_method # should not be used on an already bound method placeholder
|
382
|
-
assert not class_service_function._obj # should only be used on base function / class service function
|
383
|
-
full_name = f"{user_cls.__name__}.{method_name}"
|
384
|
-
function_type = get_function_type(partial_function.is_generator)
|
385
|
-
|
386
|
-
async def _load(method_bound_function: "_Function", resolver: Resolver, existing_object_id: Optional[str]):
|
387
|
-
function_definition = api_pb2.Function(
|
388
|
-
function_name=full_name,
|
389
|
-
webhook_config=partial_function.webhook_config,
|
390
|
-
function_type=function_type,
|
391
|
-
is_method=True,
|
392
|
-
use_function_id=class_service_function.object_id,
|
393
|
-
use_method_name=method_name,
|
394
|
-
batch_max_size=partial_function.batch_max_size or 0,
|
395
|
-
batch_linger_ms=partial_function.batch_wait_ms or 0,
|
396
|
-
)
|
397
|
-
assert resolver.app_id
|
398
|
-
request = api_pb2.FunctionCreateRequest(
|
399
|
-
app_id=resolver.app_id,
|
400
|
-
function=function_definition,
|
401
|
-
# method_bound_function.object_id usually gets set by preload
|
402
|
-
existing_function_id=existing_object_id or method_bound_function.object_id or "",
|
403
|
-
defer_updates=True,
|
404
|
-
)
|
405
|
-
assert resolver.client.stub is not None # client should be connected when load is called
|
406
|
-
with FunctionCreationStatus(resolver, full_name) as function_creation_status:
|
407
|
-
response = await resolver.client.stub.FunctionCreate(request)
|
408
|
-
method_bound_function._hydrate(
|
409
|
-
response.function_id,
|
410
|
-
resolver.client,
|
411
|
-
response.handle_metadata,
|
412
|
-
)
|
413
|
-
function_creation_status.set_response(response)
|
414
|
-
|
415
|
-
async def _preload(method_bound_function: "_Function", resolver: Resolver, existing_object_id: Optional[str]):
|
416
|
-
if class_service_function._use_method_name:
|
417
|
-
raise ExecutionError(f"Can't bind method to already bound {class_service_function}")
|
418
|
-
assert resolver.app_id
|
419
|
-
req = api_pb2.FunctionPrecreateRequest(
|
420
|
-
app_id=resolver.app_id,
|
421
|
-
function_name=full_name,
|
422
|
-
function_type=function_type,
|
423
|
-
webhook_config=partial_function.webhook_config,
|
424
|
-
use_function_id=class_service_function.object_id,
|
425
|
-
use_method_name=method_name,
|
426
|
-
existing_function_id=existing_object_id or "",
|
427
|
-
)
|
428
|
-
assert resolver.client.stub # client should be connected at this point
|
429
|
-
response = await retry_transient_errors(resolver.client.stub.FunctionPrecreate, req)
|
430
|
-
method_bound_function._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
431
|
-
|
432
|
-
def _deps():
|
433
|
-
return [class_service_function]
|
434
|
-
|
435
|
-
rep = f"Method({full_name})"
|
436
|
-
|
437
|
-
fun = _Function._from_loader(_load, rep, preload=_preload, deps=_deps)
|
438
|
-
fun._tag = full_name
|
439
|
-
fun._raw_f = partial_function.raw_f
|
440
|
-
fun._info = FunctionInfo(
|
441
|
-
partial_function.raw_f, user_cls=user_cls, serialized=class_service_function.info.is_serialized()
|
442
|
-
) # needed for .local()
|
443
|
-
fun._use_method_name = method_name
|
444
|
-
fun._app = class_service_function._app
|
445
|
-
fun._is_generator = partial_function.is_generator
|
446
|
-
fun._cluster_size = partial_function.cluster_size
|
447
|
-
fun._spec = class_service_function._spec
|
448
|
-
fun._is_method = True
|
449
|
-
return fun
|
450
|
-
|
451
362
|
def _bind_instance_method(self, class_bound_method: "_Function"):
|
452
363
|
"""mdmd:hidden
|
453
364
|
|
@@ -475,7 +386,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
475
386
|
method_placeholder_fun._is_generator = class_bound_method._is_generator
|
476
387
|
method_placeholder_fun._cluster_size = class_bound_method._cluster_size
|
477
388
|
method_placeholder_fun._use_method_name = method_name
|
478
|
-
method_placeholder_fun._use_function_id = instance_service_function.object_id
|
479
389
|
method_placeholder_fun._is_method = True
|
480
390
|
|
481
391
|
async def _load(fun: "_Function", resolver: Resolver, existing_object_id: Optional[str]):
|
@@ -848,6 +758,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
848
758
|
class_serialized=class_serialized or b"",
|
849
759
|
function_type=function_type,
|
850
760
|
webhook_config=webhook_config,
|
761
|
+
method_definitions=method_definitions,
|
762
|
+
method_definitions_set=True,
|
851
763
|
shared_volume_mounts=network_file_system_mount_protos(
|
852
764
|
validated_network_file_systems, allow_cross_region_volumes
|
853
765
|
),
|
@@ -1224,7 +1136,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1224
1136
|
self._web_url = None
|
1225
1137
|
self._function_name = None
|
1226
1138
|
self._info = None
|
1227
|
-
self._use_function_id = ""
|
1228
1139
|
self._serve_mounts = frozenset()
|
1229
1140
|
|
1230
1141
|
def _hydrate_metadata(self, metadata: Optional[Message]):
|
@@ -1234,15 +1145,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1234
1145
|
self._web_url = metadata.web_url
|
1235
1146
|
self._function_name = metadata.function_name
|
1236
1147
|
self._is_method = metadata.is_method
|
1237
|
-
self._use_function_id = metadata.use_function_id
|
1238
1148
|
self._use_method_name = metadata.use_method_name
|
1239
1149
|
self._class_parameter_info = metadata.class_parameter_info
|
1240
1150
|
self._method_handle_metadata = dict(metadata.method_handle_metadata)
|
1241
1151
|
self._definition_id = metadata.definition_id
|
1242
1152
|
|
1243
|
-
def _invocation_function_id(self) -> str:
|
1244
|
-
return self._use_function_id or self.object_id
|
1245
|
-
|
1246
1153
|
def _get_metadata(self):
|
1247
1154
|
# Overridden concrete implementation of base class method
|
1248
1155
|
assert self._function_name, f"Function name must be set before metadata can be retrieved for {self}"
|
@@ -1251,7 +1158,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1251
1158
|
function_type=get_function_type(self._is_generator),
|
1252
1159
|
web_url=self._web_url or "",
|
1253
1160
|
use_method_name=self._use_method_name,
|
1254
|
-
use_function_id=self._use_function_id,
|
1255
1161
|
is_method=self._is_method,
|
1256
1162
|
class_parameter_info=self._class_parameter_info,
|
1257
1163
|
definition_id=self._definition_id,
|
modal/functions.pyi
CHANGED
@@ -122,15 +122,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
122
122
|
_build_args: dict
|
123
123
|
_is_generator: typing.Optional[bool]
|
124
124
|
_cluster_size: typing.Optional[int]
|
125
|
-
_use_function_id: str
|
126
125
|
_use_method_name: str
|
127
126
|
_class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
|
128
127
|
_method_handle_metadata: typing.Optional[typing.Dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
|
129
128
|
|
130
129
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function._PartialFunction): ...
|
131
|
-
def _bind_method_old(
|
132
|
-
self, user_cls, method_name: str, partial_function: modal.partial_function._PartialFunction
|
133
|
-
): ...
|
134
130
|
def _bind_instance_method(self, class_bound_method: _Function): ...
|
135
131
|
@staticmethod
|
136
132
|
def from_args(
|
@@ -212,7 +208,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
212
208
|
def get_build_def(self) -> str: ...
|
213
209
|
def _initialize_from_empty(self): ...
|
214
210
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
215
|
-
def _invocation_function_id(self) -> str: ...
|
216
211
|
def _get_metadata(self): ...
|
217
212
|
def _check_no_web_url(self, fn_name: str): ...
|
218
213
|
@property
|
@@ -296,16 +291,12 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
296
291
|
_build_args: dict
|
297
292
|
_is_generator: typing.Optional[bool]
|
298
293
|
_cluster_size: typing.Optional[int]
|
299
|
-
_use_function_id: str
|
300
294
|
_use_method_name: str
|
301
295
|
_class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
|
302
296
|
_method_handle_metadata: typing.Optional[typing.Dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
|
303
297
|
|
304
298
|
def __init__(self, *args, **kwargs): ...
|
305
299
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction): ...
|
306
|
-
def _bind_method_old(
|
307
|
-
self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction
|
308
|
-
): ...
|
309
300
|
def _bind_instance_method(self, class_bound_method: Function): ...
|
310
301
|
@staticmethod
|
311
302
|
def from_args(
|
@@ -406,7 +397,6 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
406
397
|
def get_build_def(self) -> str: ...
|
407
398
|
def _initialize_from_empty(self): ...
|
408
399
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
409
|
-
def _invocation_function_id(self) -> str: ...
|
410
400
|
def _get_metadata(self): ...
|
411
401
|
def _check_no_web_url(self, fn_name: str): ...
|
412
402
|
@property
|