modal 0.73.76__py3-none-any.whl → 0.73.78__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
@@ -927,7 +927,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
927
927
  def _bind_parameters(
928
928
  self,
929
929
  obj: "modal.cls._Obj",
930
- options: Optional[api_pb2.FunctionOptions],
930
+ options: Optional["modal.cls._ServiceOptions"],
931
931
  args: Sized,
932
932
  kwargs: dict[str, Any],
933
933
  ) -> "_Function":
@@ -978,10 +978,35 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
978
978
 
979
979
  environment_name = _get_environment_name(None, resolver)
980
980
  assert parent is not None and parent.is_hydrated
981
+
982
+ if options:
983
+ volume_mounts = [
984
+ api_pb2.VolumeMount(
985
+ mount_path=path,
986
+ volume_id=volume.object_id,
987
+ allow_background_commits=True,
988
+ )
989
+ for path, volume in options.validated_volumes
990
+ ]
991
+ options_pb = api_pb2.FunctionOptions(
992
+ secret_ids=[s.object_id for s in options.secrets],
993
+ replace_secret_ids=bool(options.secrets),
994
+ resources=options.resources,
995
+ retry_policy=options.retry_policy,
996
+ concurrency_limit=options.concurrency_limit,
997
+ timeout_secs=options.timeout_secs,
998
+ task_idle_timeout_secs=options.task_idle_timeout_secs,
999
+ replace_volume_mounts=len(volume_mounts) > 0,
1000
+ volume_mounts=volume_mounts,
1001
+ target_concurrent_inputs=options.target_concurrent_inputs,
1002
+ )
1003
+ else:
1004
+ options_pb = None
1005
+
981
1006
  req = api_pb2.FunctionBindParamsRequest(
982
1007
  function_id=parent.object_id,
983
1008
  serialized_params=serialized_params,
984
- function_options=options,
1009
+ function_options=options_pb,
985
1010
  environment_name=environment_name
986
1011
  or "", # TODO: investigate shouldn't environment name always be specified here?
987
1012
  )
@@ -989,7 +1014,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
989
1014
  response = await retry_transient_errors(parent._client.stub.FunctionBindParams, req)
990
1015
  param_bound_func._hydrate(response.bound_function_id, parent._client, response.handle_metadata)
991
1016
 
992
- fun: _Function = _Function._from_loader(_load, "Function(parametrized)", hydrate_lazily=True)
1017
+ def _deps():
1018
+ if options:
1019
+ return [v for _, v in options.validated_volumes] + list(options.secrets)
1020
+ return []
1021
+
1022
+ fun: _Function = _Function._from_loader(_load, "Function(parametrized)", hydrate_lazily=True, deps=_deps)
993
1023
 
994
1024
  fun._info = self._info
995
1025
  fun._obj = obj
modal/client.pyi CHANGED
@@ -27,7 +27,7 @@ class _Client:
27
27
  _snapshotted: bool
28
28
 
29
29
  def __init__(
30
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.76"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.78"
31
31
  ): ...
32
32
  def is_closed(self) -> bool: ...
33
33
  @property
@@ -85,7 +85,7 @@ class Client:
85
85
  _snapshotted: bool
86
86
 
87
87
  def __init__(
88
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.76"
88
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.78"
89
89
  ): ...
90
90
  def is_closed(self) -> bool: ...
91
91
  @property
modal/cls.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # Copyright Modal Labs 2022
2
+ import dataclasses
2
3
  import inspect
3
4
  import os
4
5
  import typing
@@ -72,6 +73,18 @@ def _get_class_constructor_signature(user_cls: type) -> inspect.Signature:
72
73
  return inspect.Signature(constructor_parameters)
73
74
 
74
75
 
76
+ @dataclasses.dataclass()
77
+ class _ServiceOptions:
78
+ secrets: typing.Collection[_Secret]
79
+ resources: Optional[api_pb2.Resources]
80
+ retry_policy: Optional[api_pb2.FunctionRetryPolicy]
81
+ concurrency_limit: Optional[int]
82
+ timeout_secs: Optional[int]
83
+ task_idle_timeout_secs: Optional[int]
84
+ validated_volumes: typing.Sequence[tuple[str, _Volume]]
85
+ target_concurrent_inputs: Optional[int]
86
+
87
+
75
88
  def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name: str):
76
89
  """Binds an "instance service function" to a specific method using metadata for that method
77
90
 
@@ -97,11 +110,14 @@ def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name:
97
110
  hydrate_from_instance_service_function(fun)
98
111
 
99
112
  def _deps():
100
- if service_function.is_hydrated:
101
- # without this check, the common service_function will be reloaded by all methods
102
- # TODO(elias): Investigate if we can fix this multi-loader in the resolver - feels like a bug?
103
- return []
104
- return [service_function]
113
+ unhydrated_deps = []
114
+ # without this check, the common service_function will be reloaded by all methods
115
+ # TODO(elias): Investigate if we can fix this multi-loader in the resolver - feels like a bug?
116
+ if not cls.is_hydrated:
117
+ unhydrated_deps.append(cls)
118
+ if not service_function.is_hydrated:
119
+ unhydrated_deps.append(service_function)
120
+ return unhydrated_deps
105
121
 
106
122
  rep = f"Method({cls._name}.{method_name})"
107
123
 
@@ -144,12 +160,13 @@ class _Obj:
144
160
  _kwargs: dict[str, Any]
145
161
 
146
162
  _instance_service_function: Optional[_Function] = None # this gets set lazily
163
+ _options: Optional[_ServiceOptions]
147
164
 
148
165
  def __init__(
149
166
  self,
150
167
  cls: "_Cls",
151
168
  user_cls: Optional[type], # this would be None in case of lookups
152
- options: Optional[api_pb2.FunctionOptions],
169
+ options: Optional[_ServiceOptions],
153
170
  args,
154
171
  kwargs,
155
172
  ):
@@ -354,7 +371,7 @@ class _Cls(_Object, type_prefix="cs"):
354
371
  """
355
372
 
356
373
  _class_service_function: Optional[_Function] # The _Function (read "service") serving *all* methods of the class
357
- _options: Optional[api_pb2.FunctionOptions]
374
+ _options: Optional[_ServiceOptions]
358
375
 
359
376
  _app: Optional["modal.app._App"] = None # not set for lookups
360
377
  _name: Optional[str]
@@ -595,28 +612,33 @@ class _Cls(_Object, type_prefix="cs"):
595
612
  else:
596
613
  resources = None
597
614
 
598
- volume_mounts = [
599
- api_pb2.VolumeMount(
600
- mount_path=path,
601
- volume_id=volume.object_id,
602
- allow_background_commits=True,
603
- )
604
- for path, volume in validate_volumes(volumes)
605
- ]
606
- replace_volume_mounts = len(volume_mounts) > 0
607
-
608
- cls = self.clone()
609
- cls._options = api_pb2.FunctionOptions(
610
- replace_secret_ids=bool(secrets),
611
- secret_ids=[secret.object_id for secret in secrets],
615
+ async def _load_from_base(new_cls, resolver, existing_object_id):
616
+ # this is a bit confusing, the cls will always have the same metadata
617
+ # since it has the same *class* service function (i.e. "template")
618
+ # But the (instance) service function for each Obj will be different
619
+ # since it will rebind to whatever `_options` have been assigned on
620
+ # the particular Cls parent
621
+ if not self.is_hydrated:
622
+ # this should only happen for Cls.from_name instances
623
+ # other classes should already be hydrated!
624
+ await resolver.load(self)
625
+
626
+ new_cls._initialize_from_other(self)
627
+
628
+ def _deps():
629
+ return []
630
+
631
+ cls = _Cls._from_loader(_load_from_base, rep=f"{self._name}.with_options(...)", is_another_app=True, deps=_deps)
632
+ cls._initialize_from_other(self)
633
+ cls._options = _ServiceOptions(
634
+ secrets=secrets,
612
635
  resources=resources,
613
636
  retry_policy=retry_policy,
614
637
  # TODO(michael) Update the protos to use the new terminology
615
638
  concurrency_limit=max_containers,
616
639
  task_idle_timeout_secs=scaledown_window,
617
640
  timeout_secs=timeout,
618
- replace_volume_mounts=replace_volume_mounts,
619
- volume_mounts=volume_mounts,
641
+ validated_volumes=validate_volumes(volumes),
620
642
  target_concurrent_inputs=allow_concurrent_inputs,
621
643
  )
622
644
 
modal/cls.pyi CHANGED
@@ -22,6 +22,31 @@ T = typing.TypeVar("T")
22
22
 
23
23
  def _use_annotation_parameters(user_cls: type) -> bool: ...
24
24
  def _get_class_constructor_signature(user_cls: type) -> inspect.Signature: ...
25
+
26
+ class _ServiceOptions:
27
+ secrets: typing.Collection[modal.secret._Secret]
28
+ resources: typing.Optional[modal_proto.api_pb2.Resources]
29
+ retry_policy: typing.Optional[modal_proto.api_pb2.FunctionRetryPolicy]
30
+ concurrency_limit: typing.Optional[int]
31
+ timeout_secs: typing.Optional[int]
32
+ task_idle_timeout_secs: typing.Optional[int]
33
+ validated_volumes: typing.Sequence[tuple[str, modal.volume._Volume]]
34
+ target_concurrent_inputs: typing.Optional[int]
35
+
36
+ def __init__(
37
+ self,
38
+ secrets: typing.Collection[modal.secret._Secret],
39
+ resources: typing.Optional[modal_proto.api_pb2.Resources],
40
+ retry_policy: typing.Optional[modal_proto.api_pb2.FunctionRetryPolicy],
41
+ concurrency_limit: typing.Optional[int],
42
+ timeout_secs: typing.Optional[int],
43
+ task_idle_timeout_secs: typing.Optional[int],
44
+ validated_volumes: typing.Sequence[tuple[str, modal.volume._Volume]],
45
+ target_concurrent_inputs: typing.Optional[int],
46
+ ) -> None: ...
47
+ def __repr__(self): ...
48
+ def __eq__(self, other): ...
49
+
25
50
  def _bind_instance_method(cls: _Cls, service_function: modal._functions._Function, method_name: str): ...
26
51
 
27
52
  class _Obj:
@@ -32,14 +57,10 @@ class _Obj:
32
57
  _args: tuple[typing.Any, ...]
33
58
  _kwargs: dict[str, typing.Any]
34
59
  _instance_service_function: typing.Optional[modal._functions._Function]
60
+ _options: typing.Optional[_ServiceOptions]
35
61
 
36
62
  def __init__(
37
- self,
38
- cls: _Cls,
39
- user_cls: typing.Optional[type],
40
- options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
41
- args,
42
- kwargs,
63
+ self, cls: _Cls, user_cls: typing.Optional[type], options: typing.Optional[_ServiceOptions], args, kwargs
43
64
  ): ...
44
65
  def _cached_service_function(self) -> modal._functions._Function: ...
45
66
  def _get_parameter_values(self) -> dict[str, typing.Any]: ...
@@ -64,14 +85,10 @@ class Obj:
64
85
  _args: tuple[typing.Any, ...]
65
86
  _kwargs: dict[str, typing.Any]
66
87
  _instance_service_function: typing.Optional[modal.functions.Function]
88
+ _options: typing.Optional[_ServiceOptions]
67
89
 
68
90
  def __init__(
69
- self,
70
- cls: Cls,
71
- user_cls: typing.Optional[type],
72
- options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
73
- args,
74
- kwargs,
91
+ self, cls: Cls, user_cls: typing.Optional[type], options: typing.Optional[_ServiceOptions], args, kwargs
75
92
  ): ...
76
93
  def _cached_service_function(self) -> modal.functions.Function: ...
77
94
  def _get_parameter_values(self) -> dict[str, typing.Any]: ...
@@ -94,7 +111,7 @@ class Obj:
94
111
 
95
112
  class _Cls(modal._object._Object):
96
113
  _class_service_function: typing.Optional[modal._functions._Function]
97
- _options: typing.Optional[modal_proto.api_pb2.FunctionOptions]
114
+ _options: typing.Optional[_ServiceOptions]
98
115
  _app: typing.Optional[modal.app._App]
99
116
  _name: typing.Optional[str]
100
117
  _method_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
@@ -154,7 +171,7 @@ class _Cls(modal._object._Object):
154
171
 
155
172
  class Cls(modal.object.Object):
156
173
  _class_service_function: typing.Optional[modal.functions.Function]
157
- _options: typing.Optional[modal_proto.api_pb2.FunctionOptions]
174
+ _options: typing.Optional[_ServiceOptions]
158
175
  _app: typing.Optional[modal.app.App]
159
176
  _name: typing.Optional[str]
160
177
  _method_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
modal/functions.pyi CHANGED
@@ -103,7 +103,7 @@ class Function(
103
103
  def _bind_parameters(
104
104
  self,
105
105
  obj: modal.cls.Obj,
106
- options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
106
+ options: typing.Optional[modal.cls._ServiceOptions],
107
107
  args: collections.abc.Sized,
108
108
  kwargs: dict[str, typing.Any],
109
109
  ) -> Function: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: modal
3
- Version: 0.73.76
3
+ Version: 0.73.78
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=CgIjP8m1xJjjd4AXc-delmR6LdBCZclw2A_V38CFIio,2870
3
3
  modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
4
4
  modal/_clustered_functions.pyi,sha256=vllkegc99A0jrUOWa8mdlSbdp6uz36TsHhGxysAOpaQ,771
5
5
  modal/_container_entrypoint.py,sha256=arhkIoF8nQNfa4iwYGSoqN3QMDg5M38QNAODXC8TlKc,29301
6
- modal/_functions.py,sha256=LkzRfcex420bHWN2s5Aqbtm9cg7PaAcuG-H2jNz1JfE,71459
6
+ modal/_functions.py,sha256=OR76wIl-Hq5Q-mId_FzcEhpUdU6Lq40FDvnMLvAzgUM,72740
7
7
  modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
8
8
  modal/_location.py,sha256=joiX-0ZeutEUDTrrqLF1GHXCdVLF-rHzstocbMcd_-k,366
9
9
  modal/_object.py,sha256=ItQcsMNkz9Y3kdTsvfNarbW-paJ2qabDyQ7njaqY0XI,11359
@@ -22,11 +22,11 @@ modal/app.py,sha256=kF3frIt4eRKVYYCjusMMhKJpO_lDdm2z37HOXPwpjT8,45506
22
22
  modal/app.pyi,sha256=tZFbcsu20SuvfB2puxCyuXLFNJ9bQulzag55rVpgZmc,26827
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=8SQawr7P1PNUCq1UmJMUQXG2jIo4Nmdcs311XqrNLRE,15276
25
- modal/client.pyi,sha256=BY_85JYs5yMmzZbJ4XMfajA48crezjBUTGtZ8RUi-gc,7593
25
+ modal/client.pyi,sha256=nsP8_keaoKGSvTKUjg-5lvGIkudDje81JI4blMbvPA0,7593
26
26
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
27
27
  modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
28
- modal/cls.py,sha256=pQqLU_vlgHD2aglDQAoVuzt0BS3pUkRfC7oHG_fsbV4,30700
29
- modal/cls.pyi,sha256=MAx5J72UhD5l2qbH_sTpZDP34PlI4P71OBLSWOqvE68,9300
28
+ modal/cls.py,sha256=5DjpSBP1IyROKZm5ItDiEGdbRnfTT6K1Ul0jEvEKw_Q,31695
29
+ modal/cls.pyi,sha256=ZJUwtRaQBGlM6tphvnv49FHBVDSgttMdD_LnYyRSKJM,10302
30
30
  modal/config.py,sha256=Boz1bPzaG-k5Grjq6y6fAELH1N_gTuYDnpB6FODzCPo,11710
31
31
  modal/container_process.py,sha256=WTqLn01dJPVkPpwR_0w_JH96ceN5mV4TGtiu1ZR2RRA,6108
32
32
  modal/container_process.pyi,sha256=Hf0J5JyDdCCXBJSKx6gvkPOo0XrztCm78xzxamtzUjQ,2828
@@ -41,7 +41,7 @@ modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
41
41
  modal/file_io.pyi,sha256=NTRft1tbPSWf9TlWVeZmTlgB5AZ_Zhu2srWIrWr7brk,9445
42
42
  modal/file_pattern_matcher.py,sha256=trosX-Bp7dOubudN1bLLhRAoidWy1TcoaR4Pv8CedWw,6497
43
43
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
44
- modal/functions.pyi,sha256=7m3qSR7dKUPcSZycZ4Y1VaySjdBZI0_MmR8pRrbHzxE,14387
44
+ modal/functions.pyi,sha256=ujc6eIYyNmMn__4dpxEy85-vZmAniZv56D2A4uBgs6U,14377
45
45
  modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
46
46
  modal/image.py,sha256=adMUpS7WrCu-M78BWslz2r6GPviy4qPvd5Dh-dBIrrk,90257
47
47
  modal/image.pyi,sha256=L7aZUOElSGtNHmFHz1RgKP1cG5paiXt_EzylrwBwzVk,25004
@@ -168,10 +168,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
168
168
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
169
169
  modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
170
170
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
171
- modal_version/_version_generated.py,sha256=jmVn-W1r2Lr0q91iDTY2ZmvlYDwd2coi9KKtw_i03Jk,149
172
- modal-0.73.76.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
173
- modal-0.73.76.dist-info/METADATA,sha256=XZV1Q5CvoLdg2NmRCtLXu5CfAd8SISK_6cRFO_bySec,2452
174
- modal-0.73.76.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
175
- modal-0.73.76.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
176
- modal-0.73.76.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
177
- modal-0.73.76.dist-info/RECORD,,
171
+ modal_version/_version_generated.py,sha256=SVM-j7lp7Izmro-RLUeewxt817YY4EwwPw1pMBXAJso,149
172
+ modal-0.73.78.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
173
+ modal-0.73.78.dist-info/METADATA,sha256=X3t3uscecWYwdrRCFn-rrPcuGE664EzvBTfL9PseXMk,2452
174
+ modal-0.73.78.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
175
+ modal-0.73.78.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
176
+ modal-0.73.78.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
177
+ modal-0.73.78.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 76 # git: d55968d
4
+ build_number = 78 # git: 8254f56