modal 0.68.22__py3-none-any.whl → 0.68.25__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
@@ -133,6 +133,18 @@ def _get_clean_app_description(func_ref: str) -> str:
133
133
  return " ".join(sys.argv)
134
134
 
135
135
 
136
+ def _write_local_result(result_path: str, res: Any):
137
+ if isinstance(res, str):
138
+ mode = "wt"
139
+ elif isinstance(res, bytes):
140
+ mode = "wb"
141
+ else:
142
+ res_type = type(res).__name__
143
+ raise InvalidError(f"Function must return str or bytes when using `--write-result`; got {res_type}.")
144
+ with open(result_path, mode) as fid:
145
+ fid.write(res)
146
+
147
+
136
148
  def _get_click_command_for_function(app: App, function_tag):
137
149
  function = app.registered_functions.get(function_tag)
138
150
  if not function or (isinstance(function, Function) and function.info.user_cls is not None):
@@ -177,7 +189,7 @@ def _get_click_command_for_function(app: App, function_tag):
177
189
  interactive=ctx.obj["interactive"],
178
190
  ):
179
191
  if cls is None:
180
- function.remote(**kwargs)
192
+ res = function.remote(**kwargs)
181
193
  else:
182
194
  # unpool class and method arguments
183
195
  # TODO(erikbern): this code is a bit hacky
@@ -186,7 +198,10 @@ def _get_click_command_for_function(app: App, function_tag):
186
198
 
187
199
  instance = cls(**cls_kwargs)
188
200
  method: Function = getattr(instance, method_name)
189
- method.remote(**fun_kwargs)
201
+ res = method.remote(**fun_kwargs)
202
+
203
+ if result_path := ctx.obj["result_path"]:
204
+ _write_local_result(result_path, res)
190
205
 
191
206
  with_click_options = _add_click_options(f, signature)
192
207
  return click.command(with_click_options)
@@ -214,12 +229,15 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
214
229
  ):
215
230
  try:
216
231
  if isasync:
217
- asyncio.run(func(*args, **kwargs))
232
+ res = asyncio.run(func(*args, **kwargs))
218
233
  else:
219
- func(*args, **kwargs)
234
+ res = func(*args, **kwargs)
220
235
  except Exception as exc:
221
236
  raise _CliUserExecutionError(inspect.getsourcefile(func)) from exc
222
237
 
238
+ if result_path := ctx.obj["result_path"]:
239
+ _write_local_result(result_path, res)
240
+
223
241
  with_click_options = _add_click_options(f, _get_signature(func))
224
242
  return click.command(with_click_options)
225
243
 
@@ -248,12 +266,13 @@ class RunGroup(click.Group):
248
266
  cls=RunGroup,
249
267
  subcommand_metavar="FUNC_REF",
250
268
  )
269
+ @click.option("-w", "--write-result", help="Write return value (which must be str or bytes) to this local path.")
251
270
  @click.option("-q", "--quiet", is_flag=True, help="Don't show Modal progress indicators.")
252
271
  @click.option("-d", "--detach", is_flag=True, help="Don't stop the app if the local process dies or disconnects.")
253
272
  @click.option("-i", "--interactive", is_flag=True, help="Run the app in interactive mode.")
254
273
  @click.option("-e", "--env", help=ENV_OPTION_HELP, default=None)
255
274
  @click.pass_context
256
- def run(ctx, detach, quiet, interactive, env):
275
+ def run(ctx, write_result, detach, quiet, interactive, env):
257
276
  """Run a Modal function or local entrypoint.
258
277
 
259
278
  `FUNC_REF` should be of the format `{file or module}::{function name}`.
@@ -284,6 +303,7 @@ def run(ctx, detach, quiet, interactive, env):
284
303
  ```
285
304
  """
286
305
  ctx.ensure_object(dict)
306
+ ctx.obj["result_path"] = write_result
287
307
  ctx.obj["detach"] = detach # if subcommand would be a click command...
288
308
  ctx.obj["show_progress"] = False if quiet else True
289
309
  ctx.obj["interactive"] = interactive
modal/client.pyi CHANGED
@@ -26,7 +26,7 @@ class _Client:
26
26
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
27
27
 
28
28
  def __init__(
29
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.22"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.25"
30
30
  ): ...
31
31
  def is_closed(self) -> bool: ...
32
32
  @property
@@ -81,7 +81,7 @@ class Client:
81
81
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
82
82
 
83
83
  def __init__(
84
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.22"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.25"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/file_io.py CHANGED
@@ -367,11 +367,10 @@ class _FileIO(Generic[T]):
367
367
  if self._closed:
368
368
  raise ValueError("I/O operation on closed file")
369
369
 
370
- def __enter__(self) -> "_FileIO":
371
- self._check_closed()
370
+ async def __aenter__(self) -> "_FileIO":
372
371
  return self
373
372
 
374
- async def __exit__(self, exc_type, exc_value, traceback) -> None:
373
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None:
375
374
  await self._close()
376
375
 
377
376
 
modal/file_io.pyi CHANGED
@@ -48,8 +48,8 @@ class _FileIO(typing.Generic[T]):
48
48
  def _check_writable(self) -> None: ...
49
49
  def _check_readable(self) -> None: ...
50
50
  def _check_closed(self) -> None: ...
51
- def __enter__(self) -> _FileIO: ...
52
- async def __exit__(self, exc_type, exc_value, traceback) -> None: ...
51
+ async def __aenter__(self) -> _FileIO: ...
52
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None: ...
53
53
 
54
54
  class __delete_bytes_spec(typing_extensions.Protocol):
55
55
  def __call__(self, file: FileIO, start: typing.Optional[int] = None, end: typing.Optional[int] = None) -> None: ...
@@ -177,9 +177,6 @@ class FileIO(typing.Generic[T]):
177
177
  def _check_readable(self) -> None: ...
178
178
  def _check_closed(self) -> None: ...
179
179
  def __enter__(self) -> FileIO: ...
180
-
181
- class ____exit___spec(typing_extensions.Protocol):
182
- def __call__(self, exc_type, exc_value, traceback) -> None: ...
183
- async def aio(self, exc_type, exc_value, traceback) -> None: ...
184
-
185
- __exit__: ____exit___spec
180
+ async def __aenter__(self) -> FileIO: ...
181
+ def __exit__(self, exc_type, exc_value, traceback) -> None: ...
182
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None: ...
modal/functions.pyi CHANGED
@@ -456,11 +456,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
456
456
 
457
457
  _call_generator_nowait: ___call_generator_nowait_spec
458
458
 
459
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
459
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
460
460
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
461
461
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
462
462
 
463
- remote: __remote_spec[P, ReturnType]
463
+ remote: __remote_spec[ReturnType, P]
464
464
 
465
465
  class __remote_gen_spec(typing_extensions.Protocol):
466
466
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -473,17 +473,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
473
473
  def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
474
474
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
475
475
 
476
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
476
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
477
477
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
478
478
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
479
479
 
480
- _experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
480
+ _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
481
481
 
482
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
482
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
483
483
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
484
484
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
485
485
 
486
- spawn: __spawn_spec[P, ReturnType]
486
+ spawn: __spawn_spec[ReturnType, P]
487
487
 
488
488
  def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
489
489
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.22
3
+ Version: 0.68.25
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -19,7 +19,7 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
19
19
  modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
20
20
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
21
21
  modal/client.py,sha256=JAnd4-GCN093BwkvOFAK5a6iy5ycxofjpUncMxlrIMw,15253
22
- modal/client.pyi,sha256=dfEnFo8-UHPgN1obEZZNljgyXnRbGEjXdZld0uZtxL0,7280
22
+ modal/client.pyi,sha256=bTCfVc8EDreYA_wdgSRtgtQ-amgHCynC0qCNyZbBZVo,7280
23
23
  modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
24
24
  modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
25
25
  modal/cls.py,sha256=ONnrfZ2vPcaY2JuKypPiBA9eTiyg8Qfg-Ull40nn9zs,30956
@@ -33,11 +33,11 @@ modal/environments.py,sha256=5cgA-zbm6ngKLsRA19zSOgtgo9-BarJK3FJK0BiF2Lo,6505
33
33
  modal/environments.pyi,sha256=XalNpiPkAtHWAvOU2Cotq0ozmtl-Jv0FDsR8h9mr27Q,3521
34
34
  modal/exception.py,sha256=dRK789TD1HaB63kHhu1yZuvS2vP_Vua3iLMBtA6dgqk,7128
35
35
  modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
36
- modal/file_io.py,sha256=q8s872qf6Ntdw7dPogDlpYbixxGkwCA0BlQn2UUoVhY,14637
37
- modal/file_io.pyi,sha256=pfkmJiaBpMCZReE6-KCjYOzB1dVtyYDYokJoYX8ARK4,6932
36
+ modal/file_io.py,sha256=2eoSYpMMYs-FRCQbU7joFvaObuYz6HtEtLBik1hz5Xw,14616
37
+ modal/file_io.pyi,sha256=QSWGm35no2ApXiy3olLwUiT7jaNKVxIOz0rXQauCg4M,6897
38
38
  modal/file_pattern_matcher.py,sha256=vX6MjWRGdonE4I8QPdjFUnz6moBjSzvgD6417BNQrW4,4021
39
39
  modal/functions.py,sha256=IIdHw0FNOdoMksG1b2zvkn8f-xskhJu07ZvHMey9iq4,67667
40
- modal/functions.pyi,sha256=EYH4w4VgQtdbEWLGarnU5QtYVfuM2_tnovKFEbYyg2c,25068
40
+ modal/functions.pyi,sha256=bHbJiWW5TbFKKjDn7bSCFvOcUcAjPFqTStS-NAHPSeM,25068
41
41
  modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
42
42
  modal/image.py,sha256=xQAC1gWOG0L77QfVfAbHfLwfVkvMYi2sy0V_Ah7GWPg,82253
43
43
  modal/image.pyi,sha256=8R8Ac9eZ83ocM_1qrFUvH3rCbI5zRnq-Eq0xaAQq4nI,25105
@@ -117,7 +117,7 @@ modal/cli/launch.py,sha256=uyI-ouGvYRjHLGxGQ2lYBZq32BiRT1i0L8ksz5iy7K8,2935
117
117
  modal/cli/network_file_system.py,sha256=3QbAxKEoRc6RCMsYE3OS-GcuiI4GMkz_wAKsIBbN1qg,8186
118
118
  modal/cli/profile.py,sha256=rLXfjJObfPNjaZvNfHGIKqs7y9bGYyGe-K7V0w-Ni0M,3110
119
119
  modal/cli/queues.py,sha256=MIh2OsliNE2QeL1erubfsRsNuG4fxqcqWA2vgIfQ4Mg,4494
120
- modal/cli/run.py,sha256=rv2t7WURME6fCUYo4aA48azVIXLZqo4NjYjTg2I8vzk,17040
120
+ modal/cli/run.py,sha256=W9isFpIQTNCxKafVfI_PUjxNrDMl5V8N9y15F3wtPXk,17831
121
121
  modal/cli/secret.py,sha256=uQpwYrMY98iMCWeZOQTcktOYhPTZ8IHnyealDc2CZqo,4206
122
122
  modal/cli/token.py,sha256=mxSgOWakXG6N71hQb1ko61XAR9ZGkTMZD-Txn7gmTac,1924
123
123
  modal/cli/utils.py,sha256=hZmjyzcPjDnQSkLvycZD2LhGdcsfdZshs_rOU78EpvI,3717
@@ -146,10 +146,10 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
146
146
  modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
147
147
  modal_global_objects/mounts/python_standalone.py,sha256=SL_riIxpd8mP4i4CLDCWiFFNj0Ltknm9c_UIGfX5d60,1836
148
148
  modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
149
- modal_proto/api.proto,sha256=8lkerFXbR4V9ehuMchuJN0qkWDnQhR2hqZVIkqL4IpQ,79350
149
+ modal_proto/api.proto,sha256=K1QnKJ3UuBkOJ4bCsrKqn-kDeitAiaoBK8Lq2Wzs28Y,79552
150
150
  modal_proto/api_grpc.py,sha256=DveC4ejFYEhCLiWbQShnmY31_FWGYU675Bmr7nHhsgs,101342
151
- modal_proto/api_pb2.py,sha256=iesj4DdMPBSsURqpMHsZvJD6xLfWAzVH8RndkN5hcbw,290156
152
- modal_proto/api_pb2.pyi,sha256=FqZjV1yc9-gbhDgQkh8V52pKBNbobTm7AzATJ1FtCn8,388320
151
+ modal_proto/api_pb2.py,sha256=HoQkd_G9iIPPcXyBQ_50NJFNXxcckZ6VyzGWdaurkgE,292334
152
+ modal_proto/api_pb2.pyi,sha256=EqUBkMJr0CKILSvS9AwAAOxlyo26ftZBnMOECfOAzTA,390736
153
153
  modal_proto/api_pb2_grpc.py,sha256=2PEP6JPOoTw2rDC5qYjLNuumP68ZwAouRhCoayisAhY,219162
154
154
  modal_proto/api_pb2_grpc.pyi,sha256=uWtCxVEd0cFpOZ1oOGfZNO7Dv45OP4kp09jMnNyx9D4,51098
155
155
  modal_proto/modal_api_grpc.py,sha256=-8mLby_om5MYo6yu1zA_hqhz0yLsQW7k2YWBVZW1iVs,13546
@@ -163,10 +163,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
163
163
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
164
  modal_version/__init__.py,sha256=RT6zPoOdFO99u5Wcxxaoir4ZCuPTbQ22cvzFAXl3vUY,470
165
165
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
166
- modal_version/_version_generated.py,sha256=wLHIwO28sQtEk0ciwX4jSNkWaBSvrjwFXwTKHNJnrk8,149
167
- modal-0.68.22.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
168
- modal-0.68.22.dist-info/METADATA,sha256=lxET0EL_6taaugt-THklYA5DDoV-yEiaulTb6-hv33I,2329
169
- modal-0.68.22.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
170
- modal-0.68.22.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
171
- modal-0.68.22.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
172
- modal-0.68.22.dist-info/RECORD,,
166
+ modal_version/_version_generated.py,sha256=DerEU6zVIc7dqruAOfER6IUbfS1shalQMeQgLAXiaN8,149
167
+ modal-0.68.25.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
168
+ modal-0.68.25.dist-info/METADATA,sha256=xBi2AEEVfybma-KaRqmkx_Kb_ZtwrCLZWCvRnogVckc,2329
169
+ modal-0.68.25.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
170
+ modal-0.68.25.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
171
+ modal-0.68.25.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
172
+ modal-0.68.25.dist-info/RECORD,,
modal_proto/api.proto CHANGED
@@ -360,6 +360,12 @@ message AppHeartbeatRequest {
360
360
  string app_id = 1;
361
361
  }
362
362
 
363
+ message AppLayout {
364
+ repeated Object object = 1;
365
+ map<string, string> function_ids = 2; // tag -> function id
366
+ map<string, string> class_ids = 3; // tag -> class id
367
+ }
368
+
363
369
  message AppListRequest {
364
370
  string environment_name = 1;
365
371
  }
@@ -710,6 +716,7 @@ message ContainerArguments { // This is used to pass data from the worker to th
710
716
  string runtime = 11;
711
717
  string environment_name = 13;
712
718
  optional string checkpoint_id = 14;
719
+ AppLayout app_layout = 15;
713
720
  }
714
721
 
715
722
  message ContainerCheckpointRequest {