modal 0.73.169__py3-none-any.whl → 0.73.171__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/cli/run.py CHANGED
@@ -7,9 +7,10 @@ import re
7
7
  import shlex
8
8
  import sys
9
9
  import time
10
+ import typing
10
11
  from dataclasses import dataclass
11
12
  from functools import partial
12
- from typing import Any, Callable, Optional, get_type_hints
13
+ from typing import Any, Callable, Optional
13
14
 
14
15
  import click
15
16
  import typer
@@ -18,6 +19,7 @@ from typing_extensions import TypedDict
18
19
 
19
20
  from .._functions import _FunctionSpec
20
21
  from ..app import App, LocalEntrypoint
22
+ from ..cls import _get_class_constructor_signature
21
23
  from ..config import config
22
24
  from ..environments import ensure_env
23
25
  from ..exception import ExecutionError, InvalidError, _CliUserExecutionError
@@ -42,7 +44,7 @@ class ParameterMetadata(TypedDict):
42
44
  name: str
43
45
  default: Any
44
46
  annotation: Any
45
- type_hint: Any
47
+ type_hint: Any # same as annotation but evaluated by typing.get_type_hints
46
48
  kind: Any
47
49
 
48
50
 
@@ -68,27 +70,24 @@ class NoParserAvailable(InvalidError):
68
70
 
69
71
 
70
72
  @dataclass
71
- class FnSignature:
73
+ class CliRunnableSignature:
72
74
  parameters: dict[str, ParameterMetadata]
73
75
  has_variadic_args: bool
74
76
 
75
77
 
76
- def _get_signature(f: Callable[..., Any], is_method: bool = False) -> FnSignature:
78
+ def safe_get_type_hints(func_or_cls: typing.Union[Callable[..., Any], type]) -> dict[str, type]:
77
79
  try:
78
- type_hints = get_type_hints(f)
80
+ return typing.get_type_hints(func_or_cls)
79
81
  except Exception as exc:
80
82
  # E.g., if entrypoint type hints cannot be evaluated by local Python runtime
81
- msg = "Unable to generate command line interface for app entrypoint. See traceback above for details."
83
+ msg = "Unable to generate command line interface for app entrypoint due to unparseable type hints:\n" + str(exc)
82
84
  raise ExecutionError(msg) from exc
83
85
 
84
- has_variadic_args = False
85
-
86
- if is_method:
87
- self = None # Dummy, doesn't matter
88
- f = functools.partial(f, self)
89
86
 
87
+ def _get_cli_runnable_signature(sig: inspect.Signature, type_hints: dict[str, type]) -> CliRunnableSignature:
88
+ has_variadic_args = False
90
89
  signature: dict[str, ParameterMetadata] = {}
91
- for param in inspect.signature(f).parameters.values():
90
+ for param in sig.parameters.values():
92
91
  if param.kind == inspect.Parameter.VAR_POSITIONAL:
93
92
  has_variadic_args = True
94
93
  else:
@@ -103,7 +102,7 @@ def _get_signature(f: Callable[..., Any], is_method: bool = False) -> FnSignatur
103
102
  if has_variadic_args and len(signature) > 0:
104
103
  raise InvalidError("Functions with variable-length positional arguments (*args) cannot have other parameters.")
105
104
 
106
- return FnSignature(signature, has_variadic_args)
105
+ return CliRunnableSignature(signature, has_variadic_args)
107
106
 
108
107
 
109
108
  def _get_param_type_as_str(annot: Any) -> str:
@@ -172,7 +171,7 @@ def _write_local_result(result_path: str, res: Any):
172
171
  fid.write(res)
173
172
 
174
173
 
175
- def _make_click_function(app, signature: FnSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
174
+ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
176
175
  @click.pass_context
177
176
  def f(ctx, **kwargs):
178
177
  if signature.has_variadic_args:
@@ -201,7 +200,9 @@ def _get_click_command_for_function(app: App, function: Function):
201
200
  if function.is_generator:
202
201
  raise InvalidError("`modal run` is not supported for generator functions")
203
202
 
204
- signature = _get_signature(function.info.raw_f)
203
+ sig: inspect.Signature = inspect.signature(function.info.raw_f)
204
+ type_hints = safe_get_type_hints(function.info.raw_f)
205
+ signature: CliRunnableSignature = _get_cli_runnable_signature(sig, type_hints)
205
206
 
206
207
  def _inner(args, click_kwargs):
207
208
  return function.remote(*args, **click_kwargs)
@@ -223,7 +224,10 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference):
223
224
  cls = method_ref.cls
224
225
  method_name = method_ref.method_name
225
226
 
226
- cls_signature = _get_signature(cls._get_user_cls())
227
+ user_cls = cls._get_user_cls()
228
+ type_hints = safe_get_type_hints(user_cls)
229
+ sig: inspect.Signature = _get_class_constructor_signature(user_cls)
230
+ cls_signature: CliRunnableSignature = _get_cli_runnable_signature(sig, type_hints)
227
231
 
228
232
  if cls_signature.has_variadic_args:
229
233
  raise InvalidError("Modal classes cannot have variable-length positional arguments (*args).")
@@ -241,7 +245,9 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference):
241
245
  )
242
246
 
243
247
  partial_function = partial_functions[method_name]
244
- fun_signature = _get_signature(partial_function._get_raw_f(), is_method=True)
248
+ raw_f = partial_function._get_raw_f()
249
+ sig_without_self = inspect.signature(functools.partial(raw_f, None))
250
+ fun_signature = _get_cli_runnable_signature(sig_without_self, safe_get_type_hints(raw_f))
245
251
 
246
252
  # TODO(erikbern): assert there's no overlap?
247
253
  parameters = dict(**cls_signature.parameters, **fun_signature.parameters) # Pool all arguments
@@ -271,7 +277,7 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
271
277
  func = entrypoint.info.raw_f
272
278
  isasync = inspect.iscoroutinefunction(func)
273
279
 
274
- signature = _get_signature(func)
280
+ signature = _get_cli_runnable_signature(inspect.signature(func), safe_get_type_hints(func))
275
281
 
276
282
  @click.pass_context
277
283
  def f(ctx, *args, **kwargs):
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[tuple[str, str]],
34
- version: str = "0.73.169",
34
+ version: str = "0.73.171",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -93,7 +93,7 @@ class Client:
93
93
  server_url: str,
94
94
  client_type: int,
95
95
  credentials: typing.Optional[tuple[str, str]],
96
- version: str = "0.73.169",
96
+ version: str = "0.73.171",
97
97
  ): ...
98
98
  def is_closed(self) -> bool: ...
99
99
  @property
modal/functions.pyi CHANGED
@@ -200,11 +200,11 @@ class Function(
200
200
 
201
201
  _call_generator_nowait: ___call_generator_nowait_spec[typing_extensions.Self]
202
202
 
203
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
203
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
204
204
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
205
205
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
206
206
 
207
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
207
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
208
208
 
209
209
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
210
210
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -219,19 +219,19 @@ class Function(
219
219
  self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
220
220
  ) -> modal._functions.OriginalReturnType: ...
221
221
 
222
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
222
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
223
223
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
224
224
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
225
225
 
226
226
  _experimental_spawn: ___experimental_spawn_spec[
227
- modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
227
+ modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
228
228
  ]
229
229
 
230
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
230
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
231
231
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
232
232
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
233
233
 
234
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
234
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
235
235
 
236
236
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
237
237
 
modal/sandbox.py CHANGED
@@ -108,9 +108,6 @@ class _Sandbox(_Object, type_prefix="sb"):
108
108
  ) -> "_Sandbox":
109
109
  """mdmd:hidden"""
110
110
 
111
- if len(entrypoint_args) == 0:
112
- raise InvalidError("entrypoint_args must not be empty")
113
-
114
111
  validated_network_file_systems = validate_network_file_systems(network_file_systems)
115
112
 
116
113
  scheduler_placement: Optional[SchedulerPlacement] = _experimental_scheduler_placement
@@ -260,12 +257,6 @@ class _Sandbox(_Object, type_prefix="sb"):
260
257
 
261
258
  environment_name = _get_environment_name(environment_name)
262
259
 
263
- # If there are no entrypoint args, we'll sleep forever so that the sandbox will stay
264
- # alive long enough for the user to interact with it.
265
- if len(entrypoint_args) == 0:
266
- max_sleep_time = 60 * 60 * 24 * 2 # 2 days is plenty since workers roll every 24h
267
- entrypoint_args = ("sleep", str(max_sleep_time))
268
-
269
260
  _validate_exec_args(entrypoint_args)
270
261
 
271
262
  # TODO(erikbern): Get rid of the `_new` method and create an already-hydrated object
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.73.169
3
+ Version: 0.73.171
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ modal/app.py,sha256=bJp7W3liuVG2VwWkG31tMFogDh84EKppzP8YJFWl3eQ,48140
22
22
  modal/app.pyi,sha256=SkqXNrdnGIZ4MmNNvpGtzNLoUdyuvi9IjQQR_DRiRHk,26968
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=U-YKSw0n7J1ZLREt9cbEJCtmHe5YoPKFxl0xlkan2yc,15565
25
- modal/client.pyi,sha256=OPhN2duy3E59Cd0XH_KxhMf2Asehk37vBruSo_mJEZ8,7661
25
+ modal/client.pyi,sha256=fIlYvk4weOmKH7jNlgfefTnQ-7gPjfUnSTntpLuIxRo,7661
26
26
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
27
27
  modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
28
28
  modal/cls.py,sha256=x3CUDPC-QK7pa9E2eiFsiCS2ULvVi8UzNZQw1i9drsU,32945
@@ -39,7 +39,7 @@ modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
39
39
  modal/file_io.pyi,sha256=NTRft1tbPSWf9TlWVeZmTlgB5AZ_Zhu2srWIrWr7brk,9445
40
40
  modal/file_pattern_matcher.py,sha256=trosX-Bp7dOubudN1bLLhRAoidWy1TcoaR4Pv8CedWw,6497
41
41
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
42
- modal/functions.pyi,sha256=Bg_zlYSHMYuEXQYLcFU-j1Kwq5zHQ9jggMA_5ZO_du8,14785
42
+ modal/functions.pyi,sha256=m1PL2pwO-lnGV0uZDVCmzZ_v7Mu8ISRtxmxS15aEIAQ,14785
43
43
  modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
44
44
  modal/image.py,sha256=I-9_YZL0SSfnuGPywa3-4PlxDmJ-53p7ce3gP74SrOA,92877
45
45
  modal/image.pyi,sha256=89zv12C1sFrJs7Es9SnX23_m208ASAdeNGCVTrhjzHI,25632
@@ -65,7 +65,7 @@ modal/retries.py,sha256=IvNLDM0f_GLUDD5VgEDoN09C88yoxSrCquinAuxT1Sc,5205
65
65
  modal/runner.py,sha256=V17Fb9OtTGplvILc4ogT-waHYjxyCnjf0PP4aYy_0ho,25036
66
66
  modal/runner.pyi,sha256=HW2pvC_PLwg1Es_EkrfQgMZsktIr9zzVEtmjOVFG6Dw,5351
67
67
  modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
68
- modal/sandbox.py,sha256=FRcMkVDfp39_ACbhR71iDJcT6xaqFXuCI3LKsLETHPc,32619
68
+ modal/sandbox.py,sha256=CX42Rrso7a_Gz6fWEEu-OYzFsWanWOyBy9iQ4nAfAkg,32162
69
69
  modal/sandbox.pyi,sha256=cLmSwI1ab-2DgEuXNf6S1PiK63wfUR9dHtxlZtSOuX8,22719
70
70
  modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
71
71
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
@@ -128,7 +128,7 @@ modal/cli/launch.py,sha256=0_sBu6bv2xJEPWi-rbGS6Ri9ggnkWQvrGlgpYSUBMyY,3097
128
128
  modal/cli/network_file_system.py,sha256=eq3JnwjbfFNsJodIyANHL06ByYc3BSavzdmu8C96cHA,7948
129
129
  modal/cli/profile.py,sha256=0TYhgRSGUvQZ5LH9nkl6iZllEvAjDniES264dE57wOM,3201
130
130
  modal/cli/queues.py,sha256=6gTu76dzBtPN5eQVsLrvQpuru5jI9ZCWK5Eh8J8XhaM,4498
131
- modal/cli/run.py,sha256=v1_yj6fJ-W8hPi3z5tHsogsXjtuvwjnjV-EjTIJqXr8,22947
131
+ modal/cli/run.py,sha256=NX2wWwj8HD6XUhnZRF808Qy9eeouv8KnvyOP57HqIXI,23637
132
132
  modal/cli/secret.py,sha256=WB_c-LE9-eDqleLpJxsJ9rZw62Eeza8ZFQFR10vNMEk,4197
133
133
  modal/cli/token.py,sha256=mxSgOWakXG6N71hQb1ko61XAR9ZGkTMZD-Txn7gmTac,1924
134
134
  modal/cli/utils.py,sha256=hZmjyzcPjDnQSkLvycZD2LhGdcsfdZshs_rOU78EpvI,3717
@@ -145,7 +145,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
145
145
  modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
146
146
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
147
147
  modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
148
- modal-0.73.169.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
148
+ modal-0.73.171.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
149
149
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
150
150
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
151
151
  modal_docs/gen_reference_docs.py,sha256=cvTgltucqYLLIX84QxAwf51Z5Vc2n6cLxS8VcrxNCAo,6401
@@ -170,9 +170,9 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
170
170
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
171
  modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
172
172
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
173
- modal_version/_version_generated.py,sha256=K1I-OHoqF3Qf9hN8YD8e7nCzAieLwZ3R8l4ov209qJs,150
174
- modal-0.73.169.dist-info/METADATA,sha256=1QhcQPji6dqJkEd3YfibNbEuaI5JLzZAsF9LmPZ8FFQ,2475
175
- modal-0.73.169.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
- modal-0.73.169.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
- modal-0.73.169.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
- modal-0.73.169.dist-info/RECORD,,
173
+ modal_version/_version_generated.py,sha256=N0MMQdbQDFg-yfKvz_fuP7qeoaoC4EJqAQ2K7kyrYlU,150
174
+ modal-0.73.171.dist-info/METADATA,sha256=QaWuVkPGz8xGGcuVXHQLnMjSTFw8TcWwvco-P1RJzuU,2475
175
+ modal-0.73.171.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
+ modal-0.73.171.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
+ modal-0.73.171.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
+ modal-0.73.171.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 = 169 # git: 4137d51
4
+ build_number = 171 # git: 2193c1c