modal 1.2.1.dev15__py3-none-any.whl → 1.2.1.dev16__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.

@@ -442,12 +442,10 @@ class TaskCommandRouterClient:
442
442
  logger.debug(f"Cancelled JWT refresh loop for exec with task ID {self._task_id}")
443
443
  break
444
444
  except Exception as e:
445
+ # Exceptions here can stem from non-transient errors against the server sending
446
+ # the TaskGetCommandRouterAccess RPC, for instance, if the task has finished.
445
447
  logger.warning(f"Background JWT refresh failed for exec with task ID {self._task_id}: {e}")
446
- try:
447
- await asyncio.sleep(1.0)
448
- except Exception:
449
- # Ignore sleep issues; loop will re-check closed flag.
450
- pass
448
+ break
451
449
 
452
450
  async def _stream_stdio(
453
451
  self,
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.2.1.dev15",
36
+ version: str = "1.2.1.dev16",
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.2.1.dev15",
167
+ version: str = "1.2.1.dev16",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -273,6 +273,9 @@ class _ContainerProcessThroughCommandRouter(Generic[T]):
273
273
  )
274
274
  self._returncode = None
275
275
 
276
+ def __repr__(self) -> str:
277
+ return f"ContainerProcess(process_id={self._process_id!r})"
278
+
276
279
  @property
277
280
  def stdout(self) -> _StreamReader[T]:
278
281
  return self._stdout
@@ -314,7 +317,10 @@ class _ContainerProcessThroughCommandRouter(Generic[T]):
314
317
  raise InvalidError("Unexpected exit status")
315
318
  except ExecTimeoutError:
316
319
  logger.debug(f"ContainerProcess poll for {self._process_id} did not complete within deadline")
317
- return None
320
+ # TODO(saltzm): This is a weird API, but customers currently may rely on it. This
321
+ # should probably raise an ExecTimeoutError instead.
322
+ self._returncode = -1
323
+ return self._returncode
318
324
  except Exception as e:
319
325
  # Re-raise non-transient errors or errors resulting from exceeding retries on transient errors.
320
326
  logger.warning(f"ContainerProcess poll for {self._process_id} failed: {e}")
@@ -112,6 +112,10 @@ class _ContainerProcessThroughCommandRouter(typing.Generic[T]):
112
112
  """Initialize self. See help(type(self)) for accurate signature."""
113
113
  ...
114
114
 
115
+ def __repr__(self) -> str:
116
+ """Return repr(self)."""
117
+ ...
118
+
115
119
  @property
116
120
  def stdout(self) -> modal.io_streams._StreamReader[T]: ...
117
121
  @property
modal/functions.pyi CHANGED
@@ -401,7 +401,7 @@ class Function(
401
401
 
402
402
  _call_generator: ___call_generator_spec[typing_extensions.Self]
403
403
 
404
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
404
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
405
405
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
406
406
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
407
407
  ...
@@ -410,7 +410,7 @@ class Function(
410
410
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
411
411
  ...
412
412
 
413
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
413
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
414
414
 
415
415
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
416
416
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -437,7 +437,7 @@ class Function(
437
437
  """
438
438
  ...
439
439
 
440
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
440
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
441
441
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
442
442
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
443
443
 
@@ -461,7 +461,7 @@ class Function(
461
461
  ...
462
462
 
463
463
  _experimental_spawn: ___experimental_spawn_spec[
464
- modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
464
+ modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
465
465
  ]
466
466
 
467
467
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -470,7 +470,7 @@ class Function(
470
470
 
471
471
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
472
472
 
473
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
473
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
474
474
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
475
475
  """Calls the function with the given arguments, without waiting for the results.
476
476
 
@@ -491,7 +491,7 @@ class Function(
491
491
  """
492
492
  ...
493
493
 
494
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
494
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
495
495
 
496
496
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
497
497
  """Return the inner Python object wrapped by this Modal Function."""
modal/io_streams.py CHANGED
@@ -553,7 +553,7 @@ class _StreamReader(Generic[T]):
553
553
  # unimplemented for now.
554
554
  if stream_type == StreamType.STDOUT:
555
555
  raise NotImplementedError(
556
- "Currently only the PIPE stream type is supported when using exec "
556
+ "Currently the STDOUT stream type is not supported when using exec "
557
557
  "through a task command router, which is currently in beta."
558
558
  )
559
559
  params = _StreamReaderThroughCommandRouterParams(
modal/sandbox.py CHANGED
@@ -3,6 +3,7 @@ import asyncio
3
3
  import json
4
4
  import os
5
5
  import time
6
+ import uuid
6
7
  from collections.abc import AsyncGenerator, Collection, Sequence
7
8
  from dataclasses import dataclass
8
9
  from typing import TYPE_CHECKING, Any, AsyncIterator, Literal, Optional, Union, overload
@@ -20,7 +21,7 @@ from modal._tunnel import Tunnel
20
21
  from modal.cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
21
22
  from modal.mount import _Mount
22
23
  from modal.volume import _Volume
23
- from modal_proto import api_pb2
24
+ from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
24
25
 
25
26
  from ._object import _get_environment_name, _Object
26
27
  from ._resolver import Resolver
@@ -30,6 +31,7 @@ from ._utils.deprecation import deprecation_warning
30
31
  from ._utils.grpc_utils import retry_transient_errors
31
32
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
32
33
  from ._utils.name_utils import is_valid_object_name
34
+ from ._utils.task_command_router_client import TaskCommandRouterClient
33
35
  from .client import _Client
34
36
  from .container_process import _ContainerProcess
35
37
  from .exception import AlreadyExistsError, ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
@@ -121,9 +123,10 @@ class _Sandbox(_Object, type_prefix="sb"):
121
123
  _stdout: _StreamReader[str]
122
124
  _stderr: _StreamReader[str]
123
125
  _stdin: _StreamWriter
124
- _task_id: Optional[str] = None
125
- _tunnels: Optional[dict[int, Tunnel]] = None
126
- _enable_snapshot: bool = False
126
+ _task_id: Optional[str]
127
+ _tunnels: Optional[dict[int, Tunnel]]
128
+ _enable_snapshot: bool
129
+ _command_router_client: Optional[TaskCommandRouterClient]
127
130
 
128
131
  @staticmethod
129
132
  def _default_pty_info() -> api_pb2.PTYInfo:
@@ -521,6 +524,10 @@ class _Sandbox(_Object, type_prefix="sb"):
521
524
  )
522
525
  self._stdin = StreamWriter(self.object_id, "sandbox", self._client)
523
526
  self._result = None
527
+ self._task_id = None
528
+ self._tunnels = None
529
+ self._enable_snapshot = False
530
+ self._command_router_client = None
524
531
 
525
532
  @staticmethod
526
533
  async def from_name(
@@ -730,6 +737,13 @@ class _Sandbox(_Object, type_prefix="sb"):
730
737
  await asyncio.sleep(0.5)
731
738
  return self._task_id
732
739
 
740
+ async def _get_command_router_client(self, task_id: str) -> Optional[TaskCommandRouterClient]:
741
+ if self._command_router_client is None:
742
+ # Attempt to initialize a router client. Returns None if the new exec path not enabled
743
+ # for this sandbox.
744
+ self._command_router_client = await TaskCommandRouterClient.try_init(self._client, task_id)
745
+ return self._command_router_client
746
+
733
747
  @overload
734
748
  async def exec(
735
749
  self,
@@ -855,14 +869,49 @@ class _Sandbox(_Object, type_prefix="sb"):
855
869
  await TaskContext.gather(*secret_coros)
856
870
 
857
871
  task_id = await self._get_task_id()
872
+ kwargs = {
873
+ "task_id": task_id,
874
+ "pty_info": pty_info,
875
+ "stdout": stdout,
876
+ "stderr": stderr,
877
+ "timeout": timeout,
878
+ "workdir": workdir,
879
+ "secret_ids": [secret.object_id for secret in secrets],
880
+ "text": text,
881
+ "bufsize": bufsize,
882
+ "runtime_debug": config.get("function_runtime_debug"),
883
+ }
884
+ # NB: This must come after the task ID is set, since the sandbox must be
885
+ # scheduled before we can create a router client.
886
+ if (command_router_client := await self._get_command_router_client(task_id)) is not None:
887
+ kwargs["command_router_client"] = command_router_client
888
+ return await self._exec_through_command_router(*args, **kwargs)
889
+ else:
890
+ return await self._exec_through_server(*args, **kwargs)
891
+
892
+ async def _exec_through_server(
893
+ self,
894
+ *args: str,
895
+ task_id: str,
896
+ pty_info: Optional[api_pb2.PTYInfo] = None,
897
+ stdout: StreamType = StreamType.PIPE,
898
+ stderr: StreamType = StreamType.PIPE,
899
+ timeout: Optional[int] = None,
900
+ workdir: Optional[str] = None,
901
+ secret_ids: Optional[Collection[str]] = None,
902
+ text: bool = True,
903
+ bufsize: Literal[-1, 1] = -1,
904
+ runtime_debug: bool = False,
905
+ ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
906
+ """Execute a command through the Modal server."""
858
907
  req = api_pb2.ContainerExecRequest(
859
908
  task_id=task_id,
860
909
  command=args,
861
910
  pty_info=pty_info,
862
- runtime_debug=config.get("function_runtime_debug"),
911
+ runtime_debug=runtime_debug,
863
912
  timeout_secs=timeout or 0,
864
913
  workdir=workdir,
865
- secret_ids=[secret.object_id for secret in secrets],
914
+ secret_ids=secret_ids,
866
915
  )
867
916
  resp = await retry_transient_errors(self._client.stub.ContainerExec, req)
868
917
  by_line = bufsize == 1
@@ -879,6 +928,75 @@ class _Sandbox(_Object, type_prefix="sb"):
879
928
  by_line=by_line,
880
929
  )
881
930
 
931
+ async def _exec_through_command_router(
932
+ self,
933
+ *args: str,
934
+ task_id: str,
935
+ command_router_client: TaskCommandRouterClient,
936
+ pty_info: Optional[api_pb2.PTYInfo] = None,
937
+ stdout: StreamType = StreamType.PIPE,
938
+ stderr: StreamType = StreamType.PIPE,
939
+ timeout: Optional[int] = None,
940
+ workdir: Optional[str] = None,
941
+ secret_ids: Optional[Collection[str]] = None,
942
+ text: bool = True,
943
+ bufsize: Literal[-1, 1] = -1,
944
+ runtime_debug: bool = False,
945
+ ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
946
+ """Execute a command through a task command router running on the Modal worker."""
947
+
948
+ # Generate a random process ID to use as a combination of idempotency key/process identifier.
949
+ process_id = str(uuid.uuid4())
950
+ if stdout == StreamType.PIPE:
951
+ stdout_config = sr_pb2.TaskExecStdoutConfig.TASK_EXEC_STDOUT_CONFIG_PIPE
952
+ elif stdout == StreamType.DEVNULL:
953
+ stdout_config = sr_pb2.TaskExecStdoutConfig.TASK_EXEC_STDOUT_CONFIG_DEVNULL
954
+ elif stdout == StreamType.STDOUT:
955
+ # TODO(saltzm): This is a behavior change from the old implementation. We should
956
+ # probably implement the old behavior of printing to stdout before moving out of beta.
957
+ raise NotImplementedError(
958
+ "Currently the STDOUT stream type is not supported when using exec "
959
+ "through a task command router, which is currently in beta."
960
+ )
961
+ else:
962
+ raise ValueError("Unsupported StreamType for stdout")
963
+
964
+ if stderr == StreamType.PIPE:
965
+ stderr_config = sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_PIPE
966
+ elif stderr == StreamType.DEVNULL:
967
+ stderr_config = sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_DEVNULL
968
+ elif stderr == StreamType.STDOUT:
969
+ stderr_config = sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_STDOUT
970
+ else:
971
+ raise ValueError("Unsupported StreamType for stderr")
972
+
973
+ # Start the process.
974
+ start_req = sr_pb2.TaskExecStartRequest(
975
+ task_id=task_id,
976
+ exec_id=process_id,
977
+ command_args=args,
978
+ stdout_config=stdout_config,
979
+ stderr_config=stderr_config,
980
+ timeout_secs=timeout,
981
+ workdir=workdir,
982
+ secret_ids=secret_ids,
983
+ pty_info=pty_info,
984
+ runtime_debug=runtime_debug,
985
+ )
986
+ _ = await command_router_client.exec_start(start_req)
987
+
988
+ return _ContainerProcess(
989
+ process_id,
990
+ task_id,
991
+ self._client,
992
+ command_router_client=command_router_client,
993
+ stdout=stdout,
994
+ stderr=stderr,
995
+ text=text,
996
+ by_line=bufsize == 1,
997
+ exec_deadline=time.monotonic() + int(timeout) if timeout else None,
998
+ )
999
+
882
1000
  async def _experimental_snapshot(self) -> _SandboxSnapshot:
883
1001
  await self._get_task_id()
884
1002
  snap_req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id)
modal/sandbox.pyi CHANGED
@@ -3,6 +3,7 @@ import collections.abc
3
3
  import google.protobuf.message
4
4
  import modal._object
5
5
  import modal._tunnel
6
+ import modal._utils.task_command_router_client
6
7
  import modal.app
7
8
  import modal.client
8
9
  import modal.cloud_bucket_mount
@@ -83,6 +84,7 @@ class _Sandbox(modal._object._Object):
83
84
  _task_id: typing.Optional[str]
84
85
  _tunnels: typing.Optional[dict[int, modal._tunnel.Tunnel]]
85
86
  _enable_snapshot: bool
87
+ _command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]
86
88
 
87
89
  @staticmethod
88
90
  def _default_pty_info() -> modal_proto.api_pb2.PTYInfo: ...
@@ -305,6 +307,9 @@ class _Sandbox(modal._object._Object):
305
307
  ...
306
308
 
307
309
  async def _get_task_id(self) -> str: ...
310
+ async def _get_command_router_client(
311
+ self, task_id: str
312
+ ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
308
313
  @typing.overload
309
314
  async def exec(
310
315
  self,
@@ -356,6 +361,41 @@ class _Sandbox(modal._object._Object):
356
361
  """
357
362
  ...
358
363
 
364
+ async def _exec_through_server(
365
+ self,
366
+ *args: str,
367
+ task_id: str,
368
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
369
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
370
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
371
+ timeout: typing.Optional[int] = None,
372
+ workdir: typing.Optional[str] = None,
373
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
374
+ text: bool = True,
375
+ bufsize: typing.Literal[-1, 1] = -1,
376
+ runtime_debug: bool = False,
377
+ ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
378
+ """Execute a command through the Modal server."""
379
+ ...
380
+
381
+ async def _exec_through_command_router(
382
+ self,
383
+ *args: str,
384
+ task_id: str,
385
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
386
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
387
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
388
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
389
+ timeout: typing.Optional[int] = None,
390
+ workdir: typing.Optional[str] = None,
391
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
392
+ text: bool = True,
393
+ bufsize: typing.Literal[-1, 1] = -1,
394
+ runtime_debug: bool = False,
395
+ ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
396
+ """Execute a command through a task command router running on the Modal worker."""
397
+ ...
398
+
359
399
  async def _experimental_snapshot(self) -> modal.snapshot._SandboxSnapshot: ...
360
400
  @staticmethod
361
401
  async def _experimental_from_snapshot(
@@ -444,6 +484,7 @@ class Sandbox(modal.object.Object):
444
484
  _task_id: typing.Optional[str]
445
485
  _tunnels: typing.Optional[dict[int, modal._tunnel.Tunnel]]
446
486
  _enable_snapshot: bool
487
+ _command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]
447
488
 
448
489
  def __init__(self, *args, **kwargs):
449
490
  """mdmd:hidden"""
@@ -907,6 +948,16 @@ class Sandbox(modal.object.Object):
907
948
 
908
949
  _get_task_id: ___get_task_id_spec[typing_extensions.Self]
909
950
 
951
+ class ___get_command_router_client_spec(typing_extensions.Protocol[SUPERSELF]):
952
+ def __call__(
953
+ self, /, task_id: str
954
+ ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
955
+ async def aio(
956
+ self, /, task_id: str
957
+ ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
958
+
959
+ _get_command_router_client: ___get_command_router_client_spec[typing_extensions.Self]
960
+
910
961
  class __exec_spec(typing_extensions.Protocol[SUPERSELF]):
911
962
  @typing.overload
912
963
  def __call__(
@@ -1026,6 +1077,94 @@ class Sandbox(modal.object.Object):
1026
1077
 
1027
1078
  _exec: ___exec_spec[typing_extensions.Self]
1028
1079
 
1080
+ class ___exec_through_server_spec(typing_extensions.Protocol[SUPERSELF]):
1081
+ def __call__(
1082
+ self,
1083
+ /,
1084
+ *args: str,
1085
+ task_id: str,
1086
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1087
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1088
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1089
+ timeout: typing.Optional[int] = None,
1090
+ workdir: typing.Optional[str] = None,
1091
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1092
+ text: bool = True,
1093
+ bufsize: typing.Literal[-1, 1] = -1,
1094
+ runtime_debug: bool = False,
1095
+ ) -> typing.Union[
1096
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1097
+ ]:
1098
+ """Execute a command through the Modal server."""
1099
+ ...
1100
+
1101
+ async def aio(
1102
+ self,
1103
+ /,
1104
+ *args: str,
1105
+ task_id: str,
1106
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1107
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1108
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1109
+ timeout: typing.Optional[int] = None,
1110
+ workdir: typing.Optional[str] = None,
1111
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1112
+ text: bool = True,
1113
+ bufsize: typing.Literal[-1, 1] = -1,
1114
+ runtime_debug: bool = False,
1115
+ ) -> typing.Union[
1116
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1117
+ ]:
1118
+ """Execute a command through the Modal server."""
1119
+ ...
1120
+
1121
+ _exec_through_server: ___exec_through_server_spec[typing_extensions.Self]
1122
+
1123
+ class ___exec_through_command_router_spec(typing_extensions.Protocol[SUPERSELF]):
1124
+ def __call__(
1125
+ self,
1126
+ /,
1127
+ *args: str,
1128
+ task_id: str,
1129
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
1130
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1131
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1132
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1133
+ timeout: typing.Optional[int] = None,
1134
+ workdir: typing.Optional[str] = None,
1135
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1136
+ text: bool = True,
1137
+ bufsize: typing.Literal[-1, 1] = -1,
1138
+ runtime_debug: bool = False,
1139
+ ) -> typing.Union[
1140
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1141
+ ]:
1142
+ """Execute a command through a task command router running on the Modal worker."""
1143
+ ...
1144
+
1145
+ async def aio(
1146
+ self,
1147
+ /,
1148
+ *args: str,
1149
+ task_id: str,
1150
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
1151
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1152
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1153
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1154
+ timeout: typing.Optional[int] = None,
1155
+ workdir: typing.Optional[str] = None,
1156
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1157
+ text: bool = True,
1158
+ bufsize: typing.Literal[-1, 1] = -1,
1159
+ runtime_debug: bool = False,
1160
+ ) -> typing.Union[
1161
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1162
+ ]:
1163
+ """Execute a command through a task command router running on the Modal worker."""
1164
+ ...
1165
+
1166
+ _exec_through_command_router: ___exec_through_command_router_spec[typing_extensions.Self]
1167
+
1029
1168
  class ___experimental_snapshot_spec(typing_extensions.Protocol[SUPERSELF]):
1030
1169
  def __call__(self, /) -> modal.snapshot.SandboxSnapshot: ...
1031
1170
  async def aio(self, /) -> modal.snapshot.SandboxSnapshot: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev15
3
+ Version: 1.2.1.dev16
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -24,14 +24,14 @@ modal/app.pyi,sha256=AUV5Rp8qQrZJTP2waoKHFY7rYgsXNMYibMcCAQKuSeo,50544
24
24
  modal/billing.py,sha256=zmQ3bcCJlwa4KD1IA_QgdWpm1pn13c-7qfy79iEauYI,195
25
25
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
26
26
  modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
27
- modal/client.pyi,sha256=UGtqk5bx_bnjPg7uudfmO0QmKws4uCbox7LUe-OzS98,15831
27
+ modal/client.pyi,sha256=w4D59FS-waDxl9dip8AM6LuabB6haDqnmGykntaCSmI,15831
28
28
  modal/cloud_bucket_mount.py,sha256=I2GRXYhOWLIz2kJZjXu75jAm9EJkBNcutGc6jR2ReUw,5928
29
29
  modal/cloud_bucket_mount.pyi,sha256=VuUOipMIHqFXMkD-3g2bsoqpSxV5qswlFHDOqPQzYAo,7405
30
30
  modal/cls.py,sha256=ZxzivE3fNci4-A5uyBYNAzXMXtdqDg3gnYvgbdy5fhg,40384
31
31
  modal/cls.pyi,sha256=jJsDPFoqzM4ht-V-e-xEJKJ5TINLF0fYtoBm_UeAW5Y,27281
32
32
  modal/config.py,sha256=hpgkgQKbjzo6gVbRzXQrky72_KpdSEm65RNi1M2iNjc,13038
33
- modal/container_process.py,sha256=X5gkIpXeoeaRemrOFHIpI2XYSVq5NmP7iP54cTqvMFE,16643
34
- modal/container_process.pyi,sha256=raXBD-ImyYUum4UtgEXCgTGuZoWQ7SriDkRuD91Nfuc,8296
33
+ modal/container_process.py,sha256=S-JgzAqEBlYWw7f5gwbb_RVKA2APn-VAnInrntIWBL0,16948
34
+ modal/container_process.pyi,sha256=xMKr-VbQsydS8AbhAys9UTpHHnH2QRyINpPtPG7NwmI,8373
35
35
  modal/dict.py,sha256=XkaxuojMVtcc4bZvCjJcd6DedU5xxfF8H4w-mDzFPCo,21580
36
36
  modal/dict.pyi,sha256=deOiwuwZtwXqedC3h19SwoQIWc4mUnDTBM5XkONt48Y,31712
37
37
  modal/environments.py,sha256=xXYDfgzd20CuFdww_zQ53OB0qANQG-j_ls_fT7mGdoQ,6028
@@ -41,11 +41,11 @@ modal/file_io.py,sha256=OSKr77TujcXGJW1iikzYiHckLSmv07QBgBHcxxYEkoI,21456
41
41
  modal/file_io.pyi,sha256=xtO6Glf_BFwDE7QiQQo24QqcMf_Vv-iz7WojcGVlLBU,15932
42
42
  modal/file_pattern_matcher.py,sha256=A_Kdkej6q7YQyhM_2-BvpFmPqJ0oHb54B6yf9VqvPVE,8116
43
43
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
44
- modal/functions.pyi,sha256=Z6VuukLrjASAgf0kV9I6c09WvP_b2gCujX6f9j2bBaw,37988
44
+ modal/functions.pyi,sha256=CMwApS396tdElFrjnV6RuL2DTCz4C3jYzYoq1y_LPUQ,37988
45
45
  modal/gpu.py,sha256=Fe5ORvVPDIstSq1xjmM6OoNgLYFWvogP9r5BgmD3hYg,6769
46
46
  modal/image.py,sha256=HDkOnhIAN8g63a8LTN4J5SjC9ciReFQQJIxTS2z5KFM,107216
47
47
  modal/image.pyi,sha256=dMvMwAuvWkNN2BRYJFijkEy2m_xtEXgCKK0T7FVldsc,77514
48
- modal/io_streams.py,sha256=Kv-No6WcNBouwdoogwHafOsmPOKqxTpvVGLU0mM6xMc,29564
48
+ modal/io_streams.py,sha256=6fTMyPt8wCdoWFH5EuEBoW1Ye0dHITaxxMmzDPA-sdM,29565
49
49
  modal/io_streams.pyi,sha256=h7qtAbj8LsN-eJKAGjBhnMBegvWprc_0AmwVFi6rj2Y,18084
50
50
  modal/mount.py,sha256=G7_xhQMZqokgfsaFLMch0YR3fs-OUNqYUm3f4jHTSMQ,33161
51
51
  modal/mount.pyi,sha256=MD_zV2M7eCWxbOpQRjU60aHevN-bmbiywaCX82QoFlw,15380
@@ -67,8 +67,8 @@ modal/retries.py,sha256=IvNLDM0f_GLUDD5VgEDoN09C88yoxSrCquinAuxT1Sc,5205
67
67
  modal/runner.py,sha256=Ni54hwa42SEBxLPpqFwKMsUPYY8Dv-I-Kz3_jL1StCI,25220
68
68
  modal/runner.pyi,sha256=DV3Z7h0owgRyOu9W5KU5O3UbRftX99KGrZQId91fpsU,8671
69
69
  modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
70
- modal/sandbox.py,sha256=TVENhuPVJXu3RiOKkqSKVWMnHTP7zMUO61StZ2-Ldb8,45991
71
- modal/sandbox.pyi,sha256=elVE1xEy_ZhD009oNPnCwlZi4tK-RUb1qAoxkVteG9E,50713
70
+ modal/sandbox.py,sha256=QHpnp7ifmlVSzJcJyRCEHYmhvu5SrLBVjIx6gaTnlXg,51071
71
+ modal/sandbox.pyi,sha256=VqGO59NZX5fSU1tnA_g0pAd7eq6GvV6lNtC8TH9Xlo8,57478
72
72
  modal/schedule.py,sha256=ng0g0AqNY5GQI9KhkXZQ5Wam5G42glbkqVQsNpBtbDE,3078
73
73
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
74
74
  modal/secret.py,sha256=ThwP-PkwUZwiYkygqumh15n8P_77-N5ZyRWLc6I3r28,18323
@@ -113,7 +113,7 @@ modal/_utils/package_utils.py,sha256=LcL2olGN4xaUzu2Tbv-C-Ft9Qp6bsLxEfETOAVd-mjU
113
113
  modal/_utils/pattern_utils.py,sha256=ZUffaECfe2iYBhH6cvCB-0-UWhmEBTZEl_TwG_So3ag,6714
114
114
  modal/_utils/rand_pb_testing.py,sha256=mmVPk1rZldHwHZx0DnHTuHQlRLAiiAYdxjwEJpxvT9c,3900
115
115
  modal/_utils/shell_utils.py,sha256=hWHzv730Br2Xyj6cGPiMZ-198Z3RZuOu3pDXhFSZ22c,2157
116
- modal/_utils/task_command_router_client.py,sha256=c8WD8DHkQ91lcMb9M_8eBj-NFm6cxeZSv_hgJjRfpJo,23577
116
+ modal/_utils/task_command_router_client.py,sha256=ugWyExVncfs_9SbcPaWjMi3gmXPaQWI-8LuL-xHah7M,23589
117
117
  modal/_utils/time_utils.py,sha256=43tpFVwT7ykOjlETIFLVt9auMsRZqYYRYBEKxGCrRSA,1212
118
118
  modal/_vendor/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
119
119
  modal/_vendor/a2wsgi_wsgi.py,sha256=Q1AsjpV_Q_vzQsz_cSqmP9jWzsGsB-ARFU6vpQYml8k,21878
@@ -156,7 +156,7 @@ modal/experimental/__init__.py,sha256=9gkVuDmu3m4TlKoU3MzEtTOemUSs8EEOWba40s7Aa0
156
156
  modal/experimental/flash.py,sha256=-lSyFBbeT6UT-uB29L955SNh6L6ISg_uBDy5gF4ZpLo,26919
157
157
  modal/experimental/flash.pyi,sha256=uwinKAYxpunNNfBj58FP88DXb535Qik4F6tnJKPAIwQ,14696
158
158
  modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
159
- modal-1.2.1.dev15.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
159
+ modal-1.2.1.dev16.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
160
160
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
161
161
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
162
162
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -184,10 +184,10 @@ modal_proto/task_command_router_pb2.py,sha256=_pD2ZpU0bNzhwBdzmLoLyLtAtftI_Agxwn
184
184
  modal_proto/task_command_router_pb2.pyi,sha256=EyDgXPLr7alqjXYERV8w_MPuO404x0uCppmSkrfE9IE,14589
185
185
  modal_proto/task_command_router_pb2_grpc.py,sha256=uEQ0HdrCp8v-9bB5yIic9muA8spCShLHY6Bz9cCgOUE,10114
186
186
  modal_proto/task_command_router_pb2_grpc.pyi,sha256=s3Yxsrawdj4nr8vqQqsAxyX6ilWaGbdECy425KKbLIA,3301
187
- modal_version/__init__.py,sha256=vkq2oCnafaqr6nqiR7nHb8JclOJLXYMcCph81nNMr3o,121
187
+ modal_version/__init__.py,sha256=3gamILnSS1yoiqvQH-M3ton5KUFBv4m7Kc5tIOOJTsU,121
188
188
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
189
- modal-1.2.1.dev15.dist-info/METADATA,sha256=er7pk05y0NA4kL_o8Jd0mYSzjqNc54yCFsz_vXY60u4,2484
190
- modal-1.2.1.dev15.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
191
- modal-1.2.1.dev15.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
192
- modal-1.2.1.dev15.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
193
- modal-1.2.1.dev15.dist-info/RECORD,,
189
+ modal-1.2.1.dev16.dist-info/METADATA,sha256=JezoR7gj50q88E7bk4WMlmG8CFOGmgzVjfD6BviUXiE,2484
190
+ modal-1.2.1.dev16.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
191
+ modal-1.2.1.dev16.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
192
+ modal-1.2.1.dev16.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
193
+ modal-1.2.1.dev16.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.2.1.dev15"
4
+ __version__ = "1.2.1.dev16"