modal 1.1.5.dev43__py3-none-any.whl → 1.1.5.dev45__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

@@ -29,7 +29,7 @@ class FinalizedFunction:
29
29
  callable: Callable[..., Any]
30
30
  is_async: bool
31
31
  is_generator: bool
32
- data_format: int # api_pb2.DataFormat
32
+ supported_output_formats: Sequence["api_pb2.DataFormat.ValueType"]
33
33
  lifespan_manager: Optional["LifespanManager"] = None
34
34
 
35
35
 
@@ -108,6 +108,7 @@ class ImportedFunction(Service):
108
108
  is_generator = fun_def.function_type == api_pb2.Function.FUNCTION_TYPE_GENERATOR
109
109
 
110
110
  webhook_config = fun_def.webhook_config
111
+
111
112
  if not webhook_config.type:
112
113
  # for non-webhooks, the runnable is straight forward:
113
114
  return {
@@ -115,7 +116,10 @@ class ImportedFunction(Service):
115
116
  callable=self._user_defined_callable,
116
117
  is_async=is_async,
117
118
  is_generator=is_generator,
118
- data_format=api_pb2.DATA_FORMAT_PICKLE,
119
+ supported_output_formats=fun_def.supported_output_formats
120
+ # FIXME (elias): the following `or [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]` is only
121
+ # needed for tests
122
+ or [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR],
119
123
  )
120
124
  }
121
125
 
@@ -129,7 +133,8 @@ class ImportedFunction(Service):
129
133
  lifespan_manager=lifespan_manager,
130
134
  is_async=True,
131
135
  is_generator=True,
132
- data_format=api_pb2.DATA_FORMAT_ASGI,
136
+ # FIXME (elias): the following `or [api_pb2.DATA_FORMAT_ASGI]` is only needed for tests
137
+ supported_output_formats=fun_def.supported_output_formats or [api_pb2.DATA_FORMAT_ASGI],
133
138
  )
134
139
  }
135
140
 
@@ -154,6 +159,7 @@ class ImportedClass(Service):
154
159
  # Use the function definition for whether this is a generator (overriden by webhooks)
155
160
  is_generator = _partial.params.is_generator
156
161
  webhook_config = _partial.params.webhook_config
162
+ method_def = fun_def.method_definitions[method_name]
157
163
 
158
164
  bound_func = user_func.__get__(self.user_cls_instance)
159
165
 
@@ -163,7 +169,10 @@ class ImportedClass(Service):
163
169
  callable=bound_func,
164
170
  is_async=is_async,
165
171
  is_generator=bool(is_generator),
166
- data_format=api_pb2.DATA_FORMAT_PICKLE,
172
+ # FIXME (elias): the following `or [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]` is only
173
+ # needed for tests
174
+ supported_output_formats=method_def.supported_output_formats
175
+ or [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR],
167
176
  )
168
177
  else:
169
178
  web_callable, lifespan_manager = construct_webhook_callable(
@@ -174,7 +183,8 @@ class ImportedClass(Service):
174
183
  lifespan_manager=lifespan_manager,
175
184
  is_async=True,
176
185
  is_generator=True,
177
- data_format=api_pb2.DATA_FORMAT_ASGI,
186
+ # FIXME (elias): the following `or [api_pb2.DATA_FORMAT_ASGI]` is only needed for tests
187
+ supported_output_formats=method_def.supported_output_formats or [api_pb2.DATA_FORMAT_ASGI],
178
188
  )
179
189
  finalized_functions[method_name] = finalized_function
180
190
  return finalized_functions
modal/_serialization.py CHANGED
@@ -6,6 +6,14 @@ import typing
6
6
  from inspect import Parameter
7
7
  from typing import Any
8
8
 
9
+ from modal._traceback import extract_traceback
10
+ from modal.config import config
11
+
12
+ try:
13
+ import cbor2 # type: ignore
14
+ except ImportError: # pragma: no cover - optional dependency
15
+ cbor2 = None
16
+
9
17
  import google.protobuf.message
10
18
 
11
19
  from modal._utils.async_utils import synchronizer
@@ -15,7 +23,7 @@ from ._object import _Object
15
23
  from ._type_manager import parameter_serde_registry, schema_registry
16
24
  from ._vendor import cloudpickle
17
25
  from .config import logger
18
- from .exception import DeserializationError, ExecutionError, InvalidError
26
+ from .exception import DeserializationError, ExecutionError, InvalidError, SerializationError
19
27
  from .object import Object
20
28
 
21
29
  if typing.TYPE_CHECKING:
@@ -346,6 +354,12 @@ def _deserialize_asgi(asgi: api_pb2.Asgi) -> Any:
346
354
  return None
347
355
 
348
356
 
357
+ def get_preferred_payload_format() -> "api_pb2.DataFormat.ValueType":
358
+ payload_format = (config.get("payload_format") or "pickle").lower()
359
+ data_format = api_pb2.DATA_FORMAT_CBOR if payload_format == "cbor" else api_pb2.DATA_FORMAT_PICKLE
360
+ return data_format
361
+
362
+
349
363
  def serialize_data_format(obj: Any, data_format: int) -> bytes:
350
364
  """Similar to serialize(), but supports other data formats."""
351
365
  if data_format == api_pb2.DATA_FORMAT_PICKLE:
@@ -355,6 +369,21 @@ def serialize_data_format(obj: Any, data_format: int) -> bytes:
355
369
  elif data_format == api_pb2.DATA_FORMAT_GENERATOR_DONE:
356
370
  assert isinstance(obj, api_pb2.GeneratorDone)
357
371
  return obj.SerializeToString(deterministic=True)
372
+ elif data_format == api_pb2.DATA_FORMAT_CBOR:
373
+ if cbor2 is None:
374
+ raise InvalidError("CBOR support requires the 'cbor2' package to be installed.")
375
+ try:
376
+ return cbor2.dumps(obj)
377
+ except cbor2.CBOREncodeTypeError:
378
+ try:
379
+ typename = f"{type(obj).__module__}.{type(obj).__name__}"
380
+ except Exception:
381
+ typename = str(type(obj))
382
+ raise SerializationError(
383
+ # TODO (elias): add documentation link for more information on this
384
+ f"Can not serialize type {typename} as cbor. If you need to use a custom data type, "
385
+ "try to serialize it yourself e.g. by using pickle.dumps(my_data)"
386
+ )
358
387
  else:
359
388
  raise InvalidError(f"Unknown data format {data_format!r}")
360
389
 
@@ -366,6 +395,10 @@ def deserialize_data_format(s: bytes, data_format: int, client) -> Any:
366
395
  return _deserialize_asgi(api_pb2.Asgi.FromString(s))
367
396
  elif data_format == api_pb2.DATA_FORMAT_GENERATOR_DONE:
368
397
  return api_pb2.GeneratorDone.FromString(s)
398
+ elif data_format == api_pb2.DATA_FORMAT_CBOR:
399
+ if cbor2 is None:
400
+ raise InvalidError("CBOR support requires the 'cbor2' package to be installed.")
401
+ return cbor2.loads(s)
369
402
  else:
370
403
  raise InvalidError(f"Unknown data format {data_format!r}")
371
404
 
@@ -579,3 +612,26 @@ def get_callable_schema(
579
612
  arguments=arguments,
580
613
  return_type=return_type_proto,
581
614
  )
615
+
616
+
617
+ def pickle_exception(exc: BaseException) -> bytes:
618
+ try:
619
+ return serialize(exc)
620
+ except Exception as serialization_exc:
621
+ # We can't always serialize exceptions.
622
+ err = f"Failed to serialize exception {exc} of type {type(exc)}: {serialization_exc}"
623
+ logger.info(err)
624
+ return serialize(SerializationError(err))
625
+
626
+
627
+ def pickle_traceback(exc: BaseException, task_id: str) -> tuple[bytes, bytes]:
628
+ serialized_tb, tb_line_cache = b"", b""
629
+
630
+ try:
631
+ tb_dict, line_cache = extract_traceback(exc, task_id)
632
+ serialized_tb = serialize(tb_dict)
633
+ tb_line_cache = serialize(line_cache)
634
+ except Exception:
635
+ logger.info("Failed to serialize exception traceback.")
636
+
637
+ return serialized_tb, tb_line_cache
@@ -300,6 +300,10 @@ async def blob_upload(payload: bytes, stub: ModalClientModal) -> str:
300
300
  return blob_id
301
301
 
302
302
 
303
+ async def format_blob_data(data: bytes, api_stub: ModalClientModal) -> dict[str, Any]:
304
+ return {"data_blob_id": await blob_upload(data, api_stub)} if len(data) > MAX_OBJECT_SIZE_BYTES else {"data": data}
305
+
306
+
303
307
  async def blob_upload_file(
304
308
  file_obj: BinaryIO,
305
309
  stub: ModalClientModal,
@@ -2,6 +2,7 @@
2
2
  import asyncio
3
3
  import inspect
4
4
  import os
5
+ import typing
5
6
  from collections.abc import AsyncGenerator
6
7
  from enum import Enum
7
8
  from pathlib import Path, PurePosixPath
@@ -17,7 +18,9 @@ from modal_proto.modal_api_grpc import ModalClientModal
17
18
  from .._serialization import (
18
19
  deserialize,
19
20
  deserialize_data_format,
21
+ get_preferred_payload_format,
20
22
  serialize,
23
+ serialize_data_format as _serialize_data_format,
21
24
  signature_to_parameter_specs,
22
25
  )
23
26
  from .._traceback import append_modal_tb
@@ -38,6 +41,9 @@ from .blob_utils import (
38
41
  )
39
42
  from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
40
43
 
44
+ if typing.TYPE_CHECKING:
45
+ import modal._functions
46
+
41
47
 
42
48
  class FunctionInfoType(Enum):
43
49
  PACKAGE = "package"
@@ -486,7 +492,7 @@ async def _process_result(result: api_pb2.GenericResult, data_format: int, stub,
486
492
  elif result.status == api_pb2.GenericResult.GENERIC_STATUS_INTERNAL_FAILURE:
487
493
  raise InternalFailure(result.exception)
488
494
  elif result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
489
- if data:
495
+ if data and data_format == api_pb2.DATA_FORMAT_PICKLE:
490
496
  try:
491
497
  exc = deserialize(data, client)
492
498
  except DeserializationError as deser_exc:
@@ -549,27 +555,35 @@ async def _create_input(
549
555
  kwargs,
550
556
  stub: ModalClientModal,
551
557
  *,
552
- max_object_size_bytes: int,
558
+ function: "modal._functions._Function",
553
559
  idx: Optional[int] = None,
554
- method_name: Optional[str] = None,
555
560
  function_call_invocation_type: Optional["api_pb2.FunctionCallInvocationType.ValueType"] = None,
556
561
  ) -> api_pb2.FunctionPutInputsItem:
557
562
  """Serialize function arguments and create a FunctionInput protobuf,
558
563
  uploading to blob storage if needed.
559
564
  """
565
+ method_name = function._use_method_name
566
+ max_object_size_bytes = function._max_object_size_bytes
567
+
560
568
  if idx is None:
561
569
  idx = 0
562
- if method_name is None:
563
- method_name = "" # proto compatible
564
570
 
565
- args_serialized = serialize((args, kwargs))
571
+ data_format = get_preferred_payload_format()
572
+ if not function._metadata:
573
+ raise ExecutionError("Attempted to call function that has not been hydrated with metadata")
574
+
575
+ supported_input_formats = function._metadata.supported_input_formats or [api_pb2.DATA_FORMAT_PICKLE]
576
+ if data_format not in supported_input_formats:
577
+ data_format = supported_input_formats[0]
578
+
579
+ args_serialized = _serialize_data_format((args, kwargs), data_format)
566
580
 
567
581
  if should_upload(len(args_serialized), max_object_size_bytes, function_call_invocation_type):
568
582
  args_blob_id, r2_failed, r2_throughput_bytes_s = await blob_upload_with_r2_failure_info(args_serialized, stub)
569
583
  return api_pb2.FunctionPutInputsItem(
570
584
  input=api_pb2.FunctionInput(
571
585
  args_blob_id=args_blob_id,
572
- data_format=api_pb2.DATA_FORMAT_PICKLE,
586
+ data_format=data_format,
573
587
  method_name=method_name,
574
588
  ),
575
589
  idx=idx,
@@ -580,7 +594,7 @@ async def _create_input(
580
594
  return api_pb2.FunctionPutInputsItem(
581
595
  input=api_pb2.FunctionInput(
582
596
  args=args_serialized,
583
- data_format=api_pb2.DATA_FORMAT_PICKLE,
597
+ data_format=data_format,
584
598
  method_name=method_name,
585
599
  ),
586
600
  idx=idx,
modal/app.py CHANGED
@@ -667,6 +667,7 @@ class _App:
667
667
  ] = None, # Experimental controls over fine-grained scheduling (alpha).
668
668
  _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
669
669
  _experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
670
+ _experimental_restrict_output: bool = False, # Don't use pickle for return values
670
671
  # Parameters below here are deprecated. Please update your code as suggested
671
672
  keep_warm: Optional[int] = None, # Replaced with `min_containers`
672
673
  concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
@@ -835,6 +836,7 @@ class _App:
835
836
  include_source=include_source if include_source is not None else self._include_source_default,
836
837
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
837
838
  _experimental_proxy_ip=_experimental_proxy_ip,
839
+ restrict_output=_experimental_restrict_output,
838
840
  )
839
841
 
840
842
  self._add_function(function, webhook_config is not None)
@@ -895,6 +897,7 @@ class _App:
895
897
  ] = None, # Experimental controls over fine-grained scheduling (alpha).
896
898
  _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
897
899
  _experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
900
+ _experimental_restrict_output: bool = False, # Don't use pickle for return values
898
901
  # Parameters below here are deprecated. Please update your code as suggested
899
902
  keep_warm: Optional[int] = None, # Replaced with `min_containers`
900
903
  concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
@@ -1028,6 +1031,7 @@ class _App:
1028
1031
  experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
1029
1032
  _experimental_proxy_ip=_experimental_proxy_ip,
1030
1033
  _experimental_custom_scaling_factor=_experimental_custom_scaling_factor,
1034
+ restrict_output=_experimental_restrict_output,
1031
1035
  )
1032
1036
 
1033
1037
  self._add_function(cls_func, is_web_endpoint=False)
modal/app.pyi CHANGED
@@ -427,6 +427,7 @@ class _App:
427
427
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
428
428
  _experimental_proxy_ip: typing.Optional[str] = None,
429
429
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
430
+ _experimental_restrict_output: bool = False,
430
431
  keep_warm: typing.Optional[int] = None,
431
432
  concurrency_limit: typing.Optional[int] = None,
432
433
  container_idle_timeout: typing.Optional[int] = None,
@@ -480,6 +481,7 @@ class _App:
480
481
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
481
482
  _experimental_proxy_ip: typing.Optional[str] = None,
482
483
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
484
+ _experimental_restrict_output: bool = False,
483
485
  keep_warm: typing.Optional[int] = None,
484
486
  concurrency_limit: typing.Optional[int] = None,
485
487
  container_idle_timeout: typing.Optional[int] = None,
@@ -1034,6 +1036,7 @@ class App:
1034
1036
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1035
1037
  _experimental_proxy_ip: typing.Optional[str] = None,
1036
1038
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1039
+ _experimental_restrict_output: bool = False,
1037
1040
  keep_warm: typing.Optional[int] = None,
1038
1041
  concurrency_limit: typing.Optional[int] = None,
1039
1042
  container_idle_timeout: typing.Optional[int] = None,
@@ -1087,6 +1090,7 @@ class App:
1087
1090
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1088
1091
  _experimental_proxy_ip: typing.Optional[str] = None,
1089
1092
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1093
+ _experimental_restrict_output: bool = False,
1090
1094
  keep_warm: typing.Optional[int] = None,
1091
1095
  concurrency_limit: typing.Optional[int] = None,
1092
1096
  container_idle_timeout: typing.Optional[int] = None,
modal/client.pyi CHANGED
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.5.dev43",
36
+ version: str = "1.1.5.dev45",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.5.dev43",
167
+ version: str = "1.1.5.dev45",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
modal/config.py CHANGED
@@ -239,6 +239,11 @@ _SETTINGS = {
239
239
  "snapshot_debug": _Setting(False, transform=_to_boolean),
240
240
  "cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
241
241
  "build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),
242
+ # Payload format for function inputs/outputs: 'pickle' (default) or 'cbor'
243
+ "payload_format": _Setting(
244
+ "pickle",
245
+ transform=lambda s: _check_value(["pickle", "cbor"])(s.lower()),
246
+ ),
242
247
  }
243
248
 
244
249
 
modal/dict.py CHANGED
@@ -30,6 +30,14 @@ from .config import logger
30
30
  from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError
31
31
 
32
32
 
33
+ class _NoDefaultSentinel:
34
+ def __repr__(self) -> str:
35
+ return "..."
36
+
37
+
38
+ _NO_DEFAULT = _NoDefaultSentinel()
39
+
40
+
33
41
  def _serialize_dict(data):
34
42
  return [api_pb2.DictEntry(key=serialize(k), value=serialize(v)) for k, v in data.items()]
35
43
 
@@ -543,11 +551,16 @@ class _Dict(_Object, type_prefix="di"):
543
551
  return await self.put(key, value)
544
552
 
545
553
  @live_method
546
- async def pop(self, key: Any) -> Any:
547
- """Remove a key from the Dict, returning the value if it exists."""
554
+ async def pop(self, key: Any, default: Any = _NO_DEFAULT) -> Any:
555
+ """Remove a key from the Dict, returning the value if it exists.
556
+
557
+ If key is not found, return default if provided, otherwise raise KeyError.
558
+ """
548
559
  req = api_pb2.DictPopRequest(dict_id=self.object_id, key=serialize(key))
549
560
  resp = await retry_transient_errors(self._client.stub.DictPop, req)
550
561
  if not resp.found:
562
+ if default is not _NO_DEFAULT:
563
+ return default
551
564
  raise KeyError(f"{key} not in dict {self.object_id}")
552
565
  return deserialize(resp.value, self._client)
553
566
 
modal/dict.pyi CHANGED
@@ -10,6 +10,13 @@ import synchronicity.combined_types
10
10
  import typing
11
11
  import typing_extensions
12
12
 
13
+ class _NoDefaultSentinel:
14
+ def __repr__(self) -> str:
15
+ """Return repr(self)."""
16
+ ...
17
+
18
+ _NO_DEFAULT: _NoDefaultSentinel
19
+
13
20
  def _serialize_dict(data): ...
14
21
 
15
22
  class DictInfo:
@@ -548,8 +555,11 @@ class _Dict(modal._object._Object):
548
555
  """
549
556
  ...
550
557
 
551
- async def pop(self, key: typing.Any) -> typing.Any:
552
- """Remove a key from the Dict, returning the value if it exists."""
558
+ async def pop(self, key: typing.Any, default: typing.Any = ...) -> typing.Any:
559
+ """Remove a key from the Dict, returning the value if it exists.
560
+
561
+ If key is not found, return default if provided, otherwise raise KeyError.
562
+ """
553
563
  ...
554
564
 
555
565
  async def __delitem__(self, key: typing.Any) -> typing.Any:
@@ -918,12 +928,18 @@ class Dict(modal.object.Object):
918
928
  __setitem__: ____setitem___spec[typing_extensions.Self]
919
929
 
920
930
  class __pop_spec(typing_extensions.Protocol[SUPERSELF]):
921
- def __call__(self, /, key: typing.Any) -> typing.Any:
922
- """Remove a key from the Dict, returning the value if it exists."""
931
+ def __call__(self, /, key: typing.Any, default: typing.Any = ...) -> typing.Any:
932
+ """Remove a key from the Dict, returning the value if it exists.
933
+
934
+ If key is not found, return default if provided, otherwise raise KeyError.
935
+ """
923
936
  ...
924
937
 
925
- async def aio(self, /, key: typing.Any) -> typing.Any:
926
- """Remove a key from the Dict, returning the value if it exists."""
938
+ async def aio(self, /, key: typing.Any, default: typing.Any = ...) -> typing.Any:
939
+ """Remove a key from the Dict, returning the value if it exists.
940
+
941
+ If key is not found, return default if provided, otherwise raise KeyError.
942
+ """
927
943
  ...
928
944
 
929
945
  pop: __pop_spec[typing_extensions.Self]
modal/functions.pyi CHANGED
@@ -111,6 +111,7 @@ class Function(
111
111
  experimental_options: typing.Optional[dict[str, str]] = None,
112
112
  _experimental_proxy_ip: typing.Optional[str] = None,
113
113
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
114
+ restrict_output: bool = False,
114
115
  ) -> Function:
115
116
  """mdmd:hidden
116
117
 
@@ -449,7 +450,7 @@ class Function(
449
450
 
450
451
  _call_generator: ___call_generator_spec[typing_extensions.Self]
451
452
 
452
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
453
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
453
454
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
454
455
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
455
456
  ...
@@ -458,7 +459,7 @@ class Function(
458
459
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
459
460
  ...
460
461
 
461
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
462
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
462
463
 
463
464
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
464
465
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -485,7 +486,7 @@ class Function(
485
486
  """
486
487
  ...
487
488
 
488
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
489
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
489
490
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
490
491
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
491
492
 
@@ -509,7 +510,7 @@ class Function(
509
510
  ...
510
511
 
511
512
  _experimental_spawn: ___experimental_spawn_spec[
512
- modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
513
+ modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
513
514
  ]
514
515
 
515
516
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -518,7 +519,7 @@ class Function(
518
519
 
519
520
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
520
521
 
521
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
522
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
522
523
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
523
524
  """Calls the function with the given arguments, without waiting for the results.
524
525
 
@@ -539,7 +540,7 @@ class Function(
539
540
  """
540
541
  ...
541
542
 
542
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
543
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
543
544
 
544
545
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
545
546
  """Return the inner Python object wrapped by this Modal Function."""
modal/image.py CHANGED
@@ -25,11 +25,12 @@ from google.protobuf.message import Message
25
25
  from grpclib.exceptions import GRPCError, StreamTerminatedError
26
26
  from typing_extensions import Self
27
27
 
28
+ from modal._serialization import serialize_data_format
28
29
  from modal_proto import api_pb2
29
30
 
30
31
  from ._object import _Object, live_method_gen
31
32
  from ._resolver import Resolver
32
- from ._serialization import serialize
33
+ from ._serialization import get_preferred_payload_format, serialize
33
34
  from ._utils.async_utils import synchronize_api
34
35
  from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
35
36
  from ._utils.deprecation import deprecation_warning
@@ -2318,13 +2319,19 @@ class _Image(_Object, type_prefix="im"):
2318
2319
  include_source=include_source,
2319
2320
  )
2320
2321
  if len(args) + len(kwargs) > 0:
2321
- args_serialized = serialize((args, kwargs))
2322
+ data_format = get_preferred_payload_format()
2323
+ args_serialized = serialize_data_format((args, kwargs), data_format)
2324
+
2322
2325
  if len(args_serialized) > MAX_OBJECT_SIZE_BYTES:
2323
2326
  raise InvalidError(
2324
2327
  f"Arguments to `run_function` are too large ({len(args_serialized)} bytes). "
2325
2328
  f"Maximum size is {MAX_OBJECT_SIZE_BYTES} bytes."
2326
2329
  )
2327
- build_function_input = api_pb2.FunctionInput(args=args_serialized, data_format=api_pb2.DATA_FORMAT_PICKLE)
2330
+
2331
+ build_function_input = api_pb2.FunctionInput(
2332
+ args=args_serialized,
2333
+ data_format=data_format,
2334
+ )
2328
2335
  else:
2329
2336
  build_function_input = None
2330
2337
  return _Image._from_args(
modal/parallel_map.py CHANGED
@@ -128,9 +128,8 @@ class InputPreprocessor:
128
128
  args,
129
129
  kwargs,
130
130
  self.client.stub,
131
- max_object_size_bytes=self.function._max_object_size_bytes,
132
131
  idx=idx,
133
- method_name=self.function._use_method_name,
132
+ function=self.function,
134
133
  )
135
134
 
136
135
  return create_input
@@ -728,9 +727,8 @@ async def _map_invocation_inputplane(
728
727
  args,
729
728
  kwargs,
730
729
  client.stub,
731
- max_object_size_bytes=function._max_object_size_bytes,
732
730
  idx=idx,
733
- method_name=function._use_method_name,
731
+ function=function,
734
732
  )
735
733
  return api_pb2.MapStartOrContinueItem(input=put_item)
736
734
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.5.dev43
3
+ Version: 1.1.5.dev45
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -17,6 +17,7 @@ Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: aiohttp
20
+ Requires-Dist: cbor2
20
21
  Requires-Dist: certifi
21
22
  Requires-Dist: click~=8.1
22
23
  Requires-Dist: grpclib<0.4.9,>=0.4.7