modal 0.66.44__py3-none-any.whl → 0.67.0__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/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._invocation_function_id()
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 be using another function id and supply the method name in the FunctionInput:
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
modal/image.py CHANGED
@@ -19,7 +19,6 @@ from typing import (
19
19
  Optional,
20
20
  Sequence,
21
21
  Set,
22
- Tuple,
23
22
  Union,
24
23
  cast,
25
24
  get_args,
@@ -36,6 +35,7 @@ from ._utils.async_utils import synchronize_api
36
35
  from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
37
36
  from ._utils.function_utils import FunctionInfo
38
37
  from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
38
+ from .client import _Client
39
39
  from .cloud_bucket_mount import _CloudBucketMount
40
40
  from .config import config, logger, user_config_path
41
41
  from .environments import _get_environment_cached
@@ -52,7 +52,6 @@ from .volume import _Volume
52
52
  if typing.TYPE_CHECKING:
53
53
  import modal.functions
54
54
 
55
-
56
55
  # This is used for both type checking and runtime validation
57
56
  ImageBuilderVersion = Literal["2023.12", "2024.04", "2024.10"]
58
57
 
@@ -147,8 +146,8 @@ def _get_modal_requirements_command(version: ImageBuilderVersion) -> str:
147
146
  return f"{prefix} -r {CONTAINER_REQUIREMENTS_PATH}"
148
147
 
149
148
 
150
- def _flatten_str_args(function_name: str, arg_name: str, args: Tuple[Union[str, List[str]], ...]) -> List[str]:
151
- """Takes a tuple of strings, or string lists, and flattens it.
149
+ def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[str, List[str]]]) -> List[str]:
150
+ """Takes a sequence of strings, or string lists, and flattens it.
152
151
 
153
152
  Raises an error if any of the elements are not strings or string lists.
154
153
  """
@@ -244,7 +243,7 @@ class _ImageRegistryConfig:
244
243
  def __init__(
245
244
  self,
246
245
  # TODO: change to _PUBLIC after worker starts handling it.
247
- registry_auth_type: int = api_pb2.REGISTRY_AUTH_TYPE_UNSPECIFIED,
246
+ registry_auth_type: "api_pb2.RegistryAuthType.ValueType" = api_pb2.REGISTRY_AUTH_TYPE_UNSPECIFIED,
248
247
  secret: Optional[_Secret] = None,
249
248
  ):
250
249
  self.registry_auth_type = registry_auth_type
@@ -253,7 +252,7 @@ class _ImageRegistryConfig:
253
252
  def get_proto(self) -> api_pb2.ImageRegistryConfig:
254
253
  return api_pb2.ImageRegistryConfig(
255
254
  registry_auth_type=self.registry_auth_type,
256
- secret_id=(self.secret.object_id if self.secret else None),
255
+ secret_id=(self.secret.object_id if self.secret else ""),
257
256
  )
258
257
 
259
258
 
@@ -264,6 +263,45 @@ class DockerfileSpec:
264
263
  context_files: Dict[str, str]
265
264
 
266
265
 
266
+ async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.ImageJoinStreamingResponse:
267
+ last_entry_id: str = ""
268
+ result_response: Optional[api_pb2.ImageJoinStreamingResponse] = None
269
+
270
+ async def join():
271
+ nonlocal last_entry_id, result_response
272
+
273
+ request = api_pb2.ImageJoinStreamingRequest(image_id=image_id, timeout=55, last_entry_id=last_entry_id)
274
+ async for response in client.stub.ImageJoinStreaming.unary_stream(request):
275
+ if response.entry_id:
276
+ last_entry_id = response.entry_id
277
+ if response.result.status:
278
+ result_response = response
279
+ # can't return yet, since there may still be logs streaming back in subsequent responses
280
+ for task_log in response.task_logs:
281
+ if task_log.task_progress.pos or task_log.task_progress.len:
282
+ assert task_log.task_progress.progress_type == api_pb2.IMAGE_SNAPSHOT_UPLOAD
283
+ if output_mgr := _get_output_manager():
284
+ output_mgr.update_snapshot_progress(image_id, task_log.task_progress)
285
+ elif task_log.data:
286
+ if output_mgr := _get_output_manager():
287
+ await output_mgr.put_log_content(task_log)
288
+ if output_mgr := _get_output_manager():
289
+ output_mgr.flush_lines()
290
+
291
+ # Handle up to n exceptions while fetching logs
292
+ retry_count = 0
293
+ while result_response is None:
294
+ try:
295
+ await join()
296
+ except (StreamTerminatedError, GRPCError) as exc:
297
+ if isinstance(exc, GRPCError) and exc.status not in RETRYABLE_GRPC_STATUS_CODES:
298
+ raise exc
299
+ retry_count += 1
300
+ if retry_count >= 3:
301
+ raise exc
302
+ return result_response
303
+
304
+
267
305
  class _Image(_Object, type_prefix="im"):
268
306
  """Base class for container images to run functions in.
269
307
 
@@ -292,7 +330,7 @@ class _Image(_Object, type_prefix="im"):
292
330
  self._serve_mounts = other._serve_mounts
293
331
  self._deferred_mounts = other._deferred_mounts
294
332
 
295
- def _hydrate_metadata(self, message: Optional[Message]):
333
+ def _hydrate_metadata(self, metadata: Optional[Message]):
296
334
  env_image_id = config.get("image_id") # set as an env var in containers
297
335
  if env_image_id == self.object_id:
298
336
  for exc in self.inside_exceptions:
@@ -300,9 +338,9 @@ class _Image(_Object, type_prefix="im"):
300
338
  # if the hydrated image is the one used by the container
301
339
  raise exc
302
340
 
303
- if message:
304
- assert isinstance(message, api_pb2.ImageMetadata)
305
- self._metadata = message
341
+ if metadata:
342
+ assert isinstance(metadata, api_pb2.ImageMetadata)
343
+ self._metadata = metadata
306
344
 
307
345
  def _add_mount_layer_or_copy(self, mount: _Mount, copy: bool = False):
308
346
  if copy:
@@ -318,7 +356,7 @@ class _Image(_Object, type_prefix="im"):
318
356
  return _Image._from_loader(_load, "Image(local files)", deps=lambda: [base_image, mount])
319
357
 
320
358
  @property
321
- def _mount_layers(self) -> typing.Tuple[_Mount]:
359
+ def _mount_layers(self) -> typing.Sequence[_Mount]:
322
360
  """Non-evaluated mount layers on the image
323
361
 
324
362
  When the image is used by a Modal container, these mounts need to be attached as well to
@@ -362,7 +400,7 @@ class _Image(_Object, type_prefix="im"):
362
400
  context_mount: Optional[_Mount] = None,
363
401
  force_build: bool = False,
364
402
  # For internal use only.
365
- _namespace: int = api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
403
+ _namespace: "api_pb2.DeploymentNamespace.ValueType" = api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
366
404
  _do_assert_no_mount_layers: bool = True,
367
405
  ):
368
406
  if base_images is None:
@@ -382,14 +420,14 @@ class _Image(_Object, type_prefix="im"):
382
420
  if build_function and len(base_images) != 1:
383
421
  raise InvalidError("Cannot run a build function with multiple base images!")
384
422
 
385
- def _deps() -> List[_Object]:
386
- deps: List[_Object] = list(base_images.values()) + list(secrets)
423
+ def _deps() -> Sequence[_Object]:
424
+ deps = tuple(base_images.values()) + tuple(secrets)
387
425
  if build_function:
388
- deps.append(build_function)
426
+ deps += (build_function,)
389
427
  if context_mount:
390
- deps.append(context_mount)
391
- if image_registry_config.secret:
392
- deps.append(image_registry_config.secret)
428
+ deps += (context_mount,)
429
+ if image_registry_config and image_registry_config.secret:
430
+ deps += (image_registry_config.secret,)
393
431
  return deps
394
432
 
395
433
  async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
@@ -398,6 +436,7 @@ class _Image(_Object, type_prefix="im"):
398
436
  # base images can't have
399
437
  image._assert_no_mount_layers()
400
438
 
439
+ assert resolver.app_id # type narrowing
401
440
  environment = await _get_environment_cached(resolver.environment_name or "", resolver.client)
402
441
  # A bit hacky,but assume that the environment provides a valid builder version
403
442
  image_builder_version = cast(ImageBuilderVersion, environment._settings.image_builder_version)
@@ -432,7 +471,6 @@ class _Image(_Object, type_prefix="im"):
432
471
 
433
472
  if build_function:
434
473
  build_function_id = build_function.object_id
435
-
436
474
  globals = build_function._get_info().get_globals()
437
475
  attrs = build_function._get_info().get_cls_var_attrs()
438
476
  globals = {**globals, **attrs}
@@ -454,14 +492,14 @@ class _Image(_Object, type_prefix="im"):
454
492
 
455
493
  # Cloudpickle function serialization produces unstable values.
456
494
  # TODO: better way to filter out types that don't have a stable hash?
457
- build_function_globals = serialize(filtered_globals) if filtered_globals else None
495
+ build_function_globals = serialize(filtered_globals) if filtered_globals else b""
458
496
  _build_function = api_pb2.BuildFunction(
459
497
  definition=build_function.get_build_def(),
460
498
  globals=build_function_globals,
461
499
  input=build_function_input,
462
500
  )
463
501
  else:
464
- build_function_id = None
502
+ build_function_id = ""
465
503
  _build_function = None
466
504
 
467
505
  image_definition = api_pb2.Image(
@@ -470,7 +508,7 @@ class _Image(_Object, type_prefix="im"):
470
508
  context_files=context_file_pb2s,
471
509
  secret_ids=[secret.object_id for secret in secrets],
472
510
  gpu=bool(gpu_config.type), # Note: as of 2023-01-27, server still uses this
473
- context_mount_id=(context_mount.object_id if context_mount else None),
511
+ context_mount_id=(context_mount.object_id if context_mount else ""),
474
512
  gpu_config=gpu_config, # Note: as of 2023-01-27, server ignores this
475
513
  image_registry_config=image_registry_config.get_proto(),
476
514
  runtime=config.get("function_runtime"),
@@ -481,7 +519,7 @@ class _Image(_Object, type_prefix="im"):
481
519
  req = api_pb2.ImageGetOrCreateRequest(
482
520
  app_id=resolver.app_id,
483
521
  image=image_definition,
484
- existing_image_id=existing_object_id, # TODO: ignored
522
+ existing_image_id=existing_object_id or "", # TODO: ignored
485
523
  build_function_id=build_function_id,
486
524
  force_build=config.get("force_build") or force_build,
487
525
  namespace=_namespace,
@@ -492,46 +530,22 @@ class _Image(_Object, type_prefix="im"):
492
530
  )
493
531
  resp = await retry_transient_errors(resolver.client.stub.ImageGetOrCreate, req)
494
532
  image_id = resp.image_id
533
+ result: api_pb2.GenericResult
534
+ metadata: Optional[api_pb2.ImageMetadata] = None
535
+
536
+ if resp.result.status:
537
+ # image already built
538
+ result = resp.result
539
+ if resp.HasField("metadata"):
540
+ metadata = resp.metadata
541
+ else:
542
+ # not built or in the process of building - wait for build
543
+ logger.debug("Waiting for image %s" % image_id)
544
+ resp = await _image_await_build_result(image_id, resolver.client)
545
+ result = resp.result
546
+ if resp.HasField("metadata"):
547
+ metadata = resp.metadata
495
548
 
496
- logger.debug("Waiting for image %s" % image_id)
497
- last_entry_id: Optional[str] = None
498
- result_response: Optional[api_pb2.ImageJoinStreamingResponse] = None
499
-
500
- async def join():
501
- nonlocal last_entry_id, result_response
502
-
503
- request = api_pb2.ImageJoinStreamingRequest(image_id=image_id, timeout=55, last_entry_id=last_entry_id)
504
-
505
- async for response in resolver.client.stub.ImageJoinStreaming.unary_stream(request):
506
- if response.entry_id:
507
- last_entry_id = response.entry_id
508
- if response.result.status:
509
- result_response = response
510
- # can't return yet, since there may still be logs streaming back in subsequent responses
511
- for task_log in response.task_logs:
512
- if task_log.task_progress.pos or task_log.task_progress.len:
513
- assert task_log.task_progress.progress_type == api_pb2.IMAGE_SNAPSHOT_UPLOAD
514
- if output_mgr := _get_output_manager():
515
- output_mgr.update_snapshot_progress(image_id, task_log.task_progress)
516
- elif task_log.data:
517
- if output_mgr := _get_output_manager():
518
- await output_mgr.put_log_content(task_log)
519
- if output_mgr := _get_output_manager():
520
- output_mgr.flush_lines()
521
-
522
- # Handle up to n exceptions while fetching logs
523
- retry_count = 0
524
- while result_response is None:
525
- try:
526
- await join()
527
- except (StreamTerminatedError, GRPCError) as exc:
528
- if isinstance(exc, GRPCError) and exc.status not in RETRYABLE_GRPC_STATUS_CODES:
529
- raise exc
530
- retry_count += 1
531
- if retry_count >= 3:
532
- raise exc
533
-
534
- result = result_response.result
535
549
  if result.status == api_pb2.GenericResult.GENERIC_STATUS_FAILURE:
536
550
  raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
537
551
  elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
@@ -545,7 +559,7 @@ class _Image(_Object, type_prefix="im"):
545
559
  else:
546
560
  raise RemoteError("Unknown status %s!" % result.status)
547
561
 
548
- self._hydrate(image_id, resolver.client, result_response.metadata)
562
+ self._hydrate(image_id, resolver.client, metadata)
549
563
  local_mounts = set()
550
564
  for base in base_images.values():
551
565
  local_mounts |= base._serve_mounts
@@ -666,7 +680,7 @@ class _Image(_Object, type_prefix="im"):
666
680
  context_mount=mount,
667
681
  )
668
682
 
669
- def _add_local_python_packages(self, *packages: Union[str, Path], copy: bool = False) -> "_Image":
683
+ def _add_local_python_packages(self, *packages: str, copy: bool = False) -> "_Image":
670
684
  """Adds Python package files to containers
671
685
 
672
686
  Adds all files from the specified Python packages to containers running the Image.
@@ -1632,7 +1646,7 @@ class _Image(_Object, type_prefix="im"):
1632
1646
  function = _Function.from_args(
1633
1647
  info,
1634
1648
  app=None,
1635
- image=self,
1649
+ image=self, # type: ignore[reportArgumentType] # TODO: probably conflict with type stub?
1636
1650
  secrets=secrets,
1637
1651
  gpu=gpu,
1638
1652
  mounts=mounts,
@@ -1700,7 +1714,7 @@ class _Image(_Object, type_prefix="im"):
1700
1714
  """
1701
1715
 
1702
1716
  def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
1703
- commands = ["FROM base", f"WORKDIR {shlex.quote(path)}"]
1717
+ commands = ["FROM base", f"WORKDIR {shlex.quote(str(path))}"]
1704
1718
  return DockerfileSpec(commands=commands, context_files={})
1705
1719
 
1706
1720
  return _Image._from_args(
@@ -1744,7 +1758,7 @@ class _Image(_Object, type_prefix="im"):
1744
1758
 
1745
1759
  This method is considered private since its interface may change - use it at your own risk!
1746
1760
  """
1747
- last_entry_id: Optional[str] = None
1761
+ last_entry_id: str = ""
1748
1762
 
1749
1763
  request = api_pb2.ImageJoinStreamingRequest(
1750
1764
  image_id=self._object_id, timeout=55, last_entry_id=last_entry_id, include_logs_for_finished=True
modal/image.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import google.protobuf.message
2
+ import modal.client
2
3
  import modal.cloud_bucket_mount
3
4
  import modal.functions
4
5
  import modal.gpu
@@ -28,7 +29,7 @@ def _get_modal_requirements_path(
28
29
  ) -> str: ...
29
30
  def _get_modal_requirements_command(version: typing.Literal["2023.12", "2024.04", "2024.10"]) -> str: ...
30
31
  def _flatten_str_args(
31
- function_name: str, arg_name: str, args: typing.Tuple[typing.Union[str, typing.List[str]], ...]
32
+ function_name: str, arg_name: str, args: typing.Sequence[typing.Union[str, typing.List[str]]]
32
33
  ) -> typing.List[str]: ...
33
34
  def _validate_packages(packages: typing.List[str]) -> bool: ...
34
35
  def _warn_invalid_packages(old_command: str) -> None: ...
@@ -55,6 +56,10 @@ class DockerfileSpec:
55
56
  def __repr__(self): ...
56
57
  def __eq__(self, other): ...
57
58
 
59
+ async def _image_await_build_result(
60
+ image_id: str, client: modal.client._Client
61
+ ) -> modal_proto.api_pb2.ImageJoinStreamingResponse: ...
62
+
58
63
  class _Image(modal.object._Object):
59
64
  force_build: bool
60
65
  inside_exceptions: typing.List[Exception]
@@ -64,10 +69,10 @@ class _Image(modal.object._Object):
64
69
 
65
70
  def _initialize_from_empty(self): ...
66
71
  def _initialize_from_other(self, other: _Image): ...
67
- def _hydrate_metadata(self, message: typing.Optional[google.protobuf.message.Message]): ...
72
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
68
73
  def _add_mount_layer_or_copy(self, mount: modal.mount._Mount, copy: bool = False): ...
69
74
  @property
70
- def _mount_layers(self) -> typing.Tuple[modal.mount._Mount]: ...
75
+ def _mount_layers(self) -> typing.Sequence[modal.mount._Mount]: ...
71
76
  def _assert_no_mount_layers(self): ...
72
77
  @staticmethod
73
78
  def _from_args(
@@ -109,7 +114,7 @@ class _Image(modal.object._Object):
109
114
  def copy_local_file(
110
115
  self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
111
116
  ) -> _Image: ...
112
- def _add_local_python_packages(self, *module_names, copy: bool = False) -> _Image: ...
117
+ def _add_local_python_packages(self, *module_names: str, copy: bool = False) -> _Image: ...
113
118
  def copy_local_dir(
114
119
  self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "."
115
120
  ) -> _Image: ...
@@ -321,10 +326,10 @@ class Image(modal.object.Object):
321
326
  def __init__(self, *args, **kwargs): ...
322
327
  def _initialize_from_empty(self): ...
323
328
  def _initialize_from_other(self, other: Image): ...
324
- def _hydrate_metadata(self, message: typing.Optional[google.protobuf.message.Message]): ...
329
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
325
330
  def _add_mount_layer_or_copy(self, mount: modal.mount.Mount, copy: bool = False): ...
326
331
  @property
327
- def _mount_layers(self) -> typing.Tuple[modal.mount.Mount]: ...
332
+ def _mount_layers(self) -> typing.Sequence[modal.mount.Mount]: ...
328
333
  def _assert_no_mount_layers(self): ...
329
334
  @staticmethod
330
335
  def _from_args(
@@ -366,7 +371,7 @@ class Image(modal.object.Object):
366
371
  def copy_local_file(
367
372
  self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
368
373
  ) -> Image: ...
369
- def _add_local_python_packages(self, *module_names, copy: bool = False) -> Image: ...
374
+ def _add_local_python_packages(self, *module_names: str, copy: bool = False) -> Image: ...
370
375
  def copy_local_dir(
371
376
  self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "."
372
377
  ) -> Image: ...
modal/object.py CHANGED
@@ -17,7 +17,7 @@ O = TypeVar("O", bound="_Object")
17
17
 
18
18
  _BLOCKING_O = synchronize_api(O)
19
19
 
20
- EPHEMERAL_OBJECT_HEARTBEAT_SLEEP = 300
20
+ EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
21
21
 
22
22
 
23
23
  def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
@@ -205,7 +205,7 @@ class _Object:
205
205
  return self._local_uuid
206
206
 
207
207
  @property
208
- def object_id(self):
208
+ def object_id(self) -> str:
209
209
  """mdmd:hidden"""
210
210
  return self._object_id
211
211
 
modal/object.pyi CHANGED
@@ -86,7 +86,7 @@ class _Object:
86
86
  @property
87
87
  def local_uuid(self): ...
88
88
  @property
89
- def object_id(self): ...
89
+ def object_id(self) -> str: ...
90
90
  @property
91
91
  def is_hydrated(self) -> bool: ...
92
92
  @property
@@ -188,7 +188,7 @@ class Object:
188
188
  @property
189
189
  def local_uuid(self): ...
190
190
  @property
191
- def object_id(self): ...
191
+ def object_id(self) -> str: ...
192
192
  @property
193
193
  def is_hydrated(self) -> bool: ...
194
194
  @property
@@ -202,3 +202,5 @@ class Object:
202
202
 
203
203
  def live_method(method): ...
204
204
  def live_method_gen(method): ...
205
+
206
+ EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int
modal/parallel_map.py CHANGED
@@ -78,7 +78,7 @@ async def _map_invocation(
78
78
  ):
79
79
  assert client.stub
80
80
  request = api_pb2.FunctionMapRequest(
81
- function_id=function._invocation_function_id(),
81
+ function_id=function.object_id,
82
82
  parent_input_id=current_input_id() or "",
83
83
  function_call_type=api_pb2.FUNCTION_CALL_TYPE_MAP,
84
84
  return_exceptions=return_exceptions,
@@ -131,7 +131,7 @@ async def _map_invocation(
131
131
  nonlocal have_all_inputs, num_inputs
132
132
  async for items in queue_batch_iterator(input_queue, MAP_INVOCATION_CHUNK_SIZE):
133
133
  request = api_pb2.FunctionPutInputsRequest(
134
- function_id=function._invocation_function_id(), inputs=items, function_call_id=function_call_id
134
+ function_id=function.object_id, inputs=items, function_call_id=function_call_id
135
135
  )
136
136
  logger.debug(
137
137
  f"Pushing {len(items)} inputs to server. Num queued inputs awaiting push is {input_queue.qsize()}."