modal 0.73.168__py3-none-any.whl → 0.73.170__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.168",
34
+ version: str = "0.73.170",
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.168",
96
+ version: str = "0.73.170",
97
97
  ): ...
98
98
  def is_closed(self) -> bool: ...
99
99
  @property
modal/parallel_map.py CHANGED
@@ -41,7 +41,8 @@ if typing.TYPE_CHECKING:
41
41
  # pump_inputs should retry if it receives any of the standard retryable codes plus RESOURCE_EXHAUSTED.
42
42
  PUMP_INPUTS_RETRYABLE_GRPC_STATUS_CODES = RETRYABLE_GRPC_STATUS_CODES + [Status.RESOURCE_EXHAUSTED]
43
43
  PUMP_INPUTS_MAX_RETRIES = 8
44
- PUMP_INPUTS_MAX_RETRY_DELAY=15
44
+ PUMP_INPUTS_MAX_RETRY_DELAY = 15
45
+
45
46
 
46
47
  class _SynchronizedQueue:
47
48
  """mdmd:hidden"""
@@ -357,6 +358,7 @@ async def _map_invocation(
357
358
  f"retried_outputs={retried_outputs} input_queue_size={input_queue.qsize()} "
358
359
  f"retry_queue_size={retry_queue.qsize()} map_items_manager={len(map_items_manager)}"
359
360
  )
361
+
360
362
  while True:
361
363
  log_stats()
362
364
  try:
@@ -401,7 +403,7 @@ def _map_sync(
401
403
  assert list(my_func.map([1, 2, 3, 4])) == [1, 4, 9, 16]
402
404
  ```
403
405
 
404
- If applied to a `stub.function`, `map()` returns one result per input and the output order
406
+ If applied to a `app.function`, `map()` returns one result per input and the output order
405
407
  is guaranteed to be the same as the input order. Set `order_outputs=False` to return results
406
408
  in the order that they are completed instead.
407
409
 
@@ -570,6 +572,7 @@ class _MapItemState(enum.Enum):
570
572
  # The output has been received and was either successful, or failed with no more retries remaining.
571
573
  COMPLETE = 5
572
574
 
575
+
573
576
  class _OutputType(enum.Enum):
574
577
  SUCCESSFUL_COMPLETION = 1
575
578
  FAILED_COMPLETION = 2
@@ -578,11 +581,12 @@ class _OutputType(enum.Enum):
578
581
  STALE_RETRY_DUPLICATE = 5
579
582
  NO_CONTEXT_DUPLICATE = 6
580
583
 
584
+
581
585
  class _MapItemContext:
582
586
  state: _MapItemState
583
587
  input: api_pb2.FunctionInput
584
588
  retry_manager: RetryManager
585
- sync_client_retries_enabled:bool
589
+ sync_client_retries_enabled: bool
586
590
  # Both these futures are strings. Omitting generic type because
587
591
  # it causes an error when running `inv protoc type-stubs`.
588
592
  input_id: asyncio.Future
@@ -701,8 +705,7 @@ class _MapItemsManager:
701
705
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
702
706
  retry_queue: TimestampPriorityQueue,
703
707
  sync_client_retries_enabled: bool,
704
- max_inputs_outstanding: int
705
-
708
+ max_inputs_outstanding: int,
706
709
  ):
707
710
  self._retry_policy = retry_policy
708
711
  self.function_call_invocation_type = function_call_invocation_type
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.73.168
3
+ Version: 0.73.170
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=EVuMDnBwDN6KjVggka2jbjfk49kNwIqk-ymDq07yF4Y,7661
25
+ modal/client.pyi,sha256=KBxA9Giwan5u4NmHsSuD_dQ8XlWfKL5syIR70BuLbOA,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
@@ -52,7 +52,7 @@ modal/network_file_system.pyi,sha256=4N3eqMbTSlqmS8VV_aJK-uvrgJC8xnf_YtW5FHfRfc8
52
52
  modal/object.py,sha256=bTeskuY8JFrESjU4_UL_nTwYlBQdOLmVaOX3X6EMxsg,164
53
53
  modal/object.pyi,sha256=kyJkRQcVv3ct7zSAxvvXcuhBVeH914v80uSlqeS7cA4,5632
54
54
  modal/output.py,sha256=q4T9uHduunj4NwY-YSwkHGgjZlCXMuJbfQ5UFaAGRAc,1968
55
- modal/parallel_map.py,sha256=p9dklKZTxYCFb7szYWqTGjPZ-Yikvm6OFhU3JNoiTB4,33850
55
+ modal/parallel_map.py,sha256=NN48Xg_qzbL4lbw781-t1HDV19w8lib8-9HofoAKxh4,33856
56
56
  modal/parallel_map.pyi,sha256=mEenHruPiZDq3ucV_6RM8ctc0c_Qpqra5MBagXeHiiQ,5708
57
57
  modal/partial_function.py,sha256=SwuAAj2wj4SO6F6nkSnwNZrczEmm9w9YdlQTHh6hr04,1195
58
58
  modal/partial_function.pyi,sha256=NFWz1aCAs2B3-GnPf1cTatWRZOLnYpFKCnjP_X9iNRs,6411
@@ -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.168.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
148
+ modal-0.73.170.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=-aZvps2fh2rCp0UNwje5SJ9IP_jUoaH-CNdrA4Q-0mo,150
174
- modal-0.73.168.dist-info/METADATA,sha256=RogiuavaiLYUjRPCDOeC74__s09i-PFiFsUExNzeoAo,2475
175
- modal-0.73.168.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
- modal-0.73.168.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
- modal-0.73.168.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
- modal-0.73.168.dist-info/RECORD,,
173
+ modal_version/_version_generated.py,sha256=uAuYxu7DBPrzw4Kuc3Hd44Hu_fSVZZC0e5kkTME2oz0,150
174
+ modal-0.73.170.dist-info/METADATA,sha256=2ImlbrU1H3oX54VF9Ocmrbe90XCzqZezDpcHbPWPdEo,2475
175
+ modal-0.73.170.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
+ modal-0.73.170.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
+ modal-0.73.170.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
+ modal-0.73.170.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 = 168 # git: cafe25f
4
+ build_number = 170 # git: b0e5f86