modAL 1.3.6.dev9__py3-none-any.whl → 1.3.6.dev10__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.
@@ -242,6 +242,44 @@ class TaskCommandRouterClient:
242
242
  lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
243
243
  )
244
244
 
245
+ async def container_create(self, request: sr_pb2.TaskContainerCreateRequest) -> sr_pb2.TaskContainerCreateResponse:
246
+ """Create an additional container via task command router."""
247
+ with grpc_error_converter():
248
+ return await call_with_retries_on_transient_errors(
249
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerCreate, request)
250
+ )
251
+
252
+ async def container_terminate(
253
+ self,
254
+ request: sr_pb2.TaskContainerTerminateRequest,
255
+ ) -> sr_pb2.TaskContainerTerminateResponse:
256
+ """Terminate an additional container via task command router."""
257
+ with grpc_error_converter():
258
+ return await call_with_retries_on_transient_errors(
259
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerTerminate, request)
260
+ )
261
+
262
+ async def container_wait(self, request: sr_pb2.TaskContainerWaitRequest) -> sr_pb2.TaskContainerWaitResponse:
263
+ """Wait for an additional container via task command router."""
264
+ with grpc_error_converter():
265
+ return await call_with_retries_on_transient_errors(
266
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerWait, request)
267
+ )
268
+
269
+ async def container_get(self, request: sr_pb2.TaskContainerGetRequest) -> sr_pb2.TaskContainerGetResponse:
270
+ """Get the latest tracked container for a logical name via task command router."""
271
+ with grpc_error_converter():
272
+ return await call_with_retries_on_transient_errors(
273
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerGet, request)
274
+ )
275
+
276
+ async def container_list(self, request: sr_pb2.TaskContainerListRequest) -> sr_pb2.TaskContainerListResponse:
277
+ """List sandbox containers via task command router."""
278
+ with grpc_error_converter():
279
+ return await call_with_retries_on_transient_errors(
280
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerList, request)
281
+ )
282
+
245
283
  async def exec_stdio_read(
246
284
  self,
247
285
  task_id: str,
modal/cli/shell.py CHANGED
@@ -18,6 +18,7 @@ from ..mount import _Mount
18
18
  from ..runner import interactive_shell
19
19
  from ..sandbox import Sandbox
20
20
  from ..secret import Secret
21
+ from ..stream_type import StreamType
21
22
  from ..volume import Volume
22
23
  from .container import exec
23
24
  from .import_refs import (
@@ -64,26 +65,75 @@ def _is_valid_modal_id(ref: str, prefix: str) -> bool:
64
65
  return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
65
66
 
66
67
 
68
+ def _parse_sandbox_container_ref(ref: str) -> tuple[str, Optional[str]]:
69
+ """Parse a sandbox:container reference into (sandbox_id, container_name).
70
+
71
+ Supports the pattern `sb-xyz:container_name` for accessing sandbox containers.
72
+ Returns (ref, None) if no container name is specified.
73
+ """
74
+ if _is_valid_modal_id(ref, "sb-"):
75
+ return ref, None
76
+
77
+ if ":" in ref:
78
+ parts = ref.split(":", 1)
79
+ sandbox_id, container_name = parts[0], parts[1]
80
+ if _is_valid_modal_id(sandbox_id, "sb-") and container_name:
81
+ return sandbox_id, container_name
82
+
83
+ return ref, None
84
+
85
+
67
86
  def _is_running_container_ref(ref: Optional[str]) -> bool:
68
87
  if ref is None:
69
88
  return False
70
- return _is_valid_modal_id(ref, "sb-") or _is_valid_modal_id(ref, "ta-")
89
+ sandbox_id, _ = _parse_sandbox_container_ref(ref)
90
+ return _is_valid_modal_id(sandbox_id, "sb-") or _is_valid_modal_id(ref, "ta-")
91
+
92
+
93
+ def _start_shell_in_sandbox_container(sandbox_id: str, container_name: str, cmd: str, pty: bool) -> None:
94
+ """Shell into a named container within a sandbox."""
95
+ try:
96
+ sandbox = Sandbox.from_id(sandbox_id)
97
+ except NotFoundError:
98
+ raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
99
+ except Exception as e:
100
+ raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
101
+
102
+ try:
103
+ sandbox_container = sandbox._experimental_containers.get(name=container_name)
104
+ if pty:
105
+ process = sandbox_container.exec(*shlex.split(cmd), pty=pty)
106
+ process.attach()
107
+ else:
108
+ process = sandbox_container.exec(
109
+ *shlex.split(cmd), pty=pty, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
110
+ )
111
+ process.wait()
112
+ except NotFoundError:
113
+ raise ClickException(f"Container '{container_name}' not found in Sandbox '{sandbox_id}'.")
114
+ except Exception as e:
115
+ raise ClickException(f"Error exec-ing into container '{container_name}' in Sandbox '{sandbox_id}': {str(e)}")
71
116
 
72
117
 
73
118
  def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
74
- if _is_valid_modal_id(ref, "sb-"):
119
+ sandbox_id, container_name = _parse_sandbox_container_ref(ref)
120
+
121
+ if container_name is not None:
122
+ return _start_shell_in_sandbox_container(sandbox_id, container_name, cmd, pty)
123
+
124
+ if _is_valid_modal_id(sandbox_id, "sb-"):
75
125
  try:
76
- sandbox = Sandbox.from_id(ref)
126
+ sandbox = Sandbox.from_id(sandbox_id)
77
127
  ref = sandbox._get_task_id()
78
- except NotFoundError as e:
79
- raise ClickException(f"Sandbox '{ref}' not found (is it still running?)")
128
+ except NotFoundError:
129
+ raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
80
130
  except Exception as e:
81
- raise ClickException(f"Error connecting to Sandbox '{ref}': {str(e)}")
131
+ raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
82
132
 
83
133
  assert _is_valid_modal_id(ref, "ta-")
84
134
  try:
85
135
  exec(container_id=ref, command=shlex.split(cmd), pty=pty)
86
- except NotFoundError as e:
136
+ except NotFoundError:
87
137
  raise ClickException(f"Container '{ref}' not found (is it still running?)")
88
138
  except Exception as e:
89
139
  raise ClickException(f"Error connecting to container '{ref}': {str(e)}")
modal/client.pyi CHANGED
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.3.6.dev9",
38
+ version: str = "1.3.6.dev10",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -171,7 +171,7 @@ class Client:
171
171
  server_url: str,
172
172
  client_type: int,
173
173
  credentials: typing.Optional[tuple[str, str]],
174
- version: str = "1.3.6.dev9",
174
+ version: str = "1.3.6.dev10",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
modal/sandbox.py CHANGED
@@ -35,7 +35,14 @@ from ._utils.name_utils import check_object_name
35
35
  from ._utils.task_command_router_client import TaskCommandRouterClient
36
36
  from .client import _Client
37
37
  from .container_process import _ContainerProcess
38
- from .exception import ClientClosed, Error, ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
38
+ from .exception import (
39
+ ClientClosed,
40
+ Error,
41
+ ExecutionError,
42
+ InvalidError,
43
+ SandboxTerminatedError,
44
+ SandboxTimeoutError,
45
+ )
39
46
  from .file_io import FileWatchEvent, FileWatchEventType, _FileIO, ls, mkdir, rm, watch
40
47
  from .gpu import GPU_T
41
48
  from .image import _Image
@@ -68,6 +75,16 @@ if TYPE_CHECKING:
68
75
  import modal.app
69
76
 
70
77
 
78
+ def _result_returncode(result: Optional[api_pb2.GenericResult]) -> Optional[int]:
79
+ if result is None or result.status == api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
80
+ return None
81
+ if result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
82
+ return 124
83
+ if result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
84
+ return 137
85
+ return result.exitcode
86
+
87
+
71
88
  def _validate_exec_args(args: Sequence[str]) -> None:
72
89
  # Entrypoint args must be strings.
73
90
  if not all(isinstance(arg, str) for arg in args):
@@ -904,6 +921,12 @@ class _Sandbox(_Object, type_prefix="sb"):
904
921
  self._command_router_client = await TaskCommandRouterClient.try_init(self._client, task_id)
905
922
  return self._command_router_client
906
923
 
924
+ @property
925
+ def _experimental_containers(self) -> "_SandboxContainerManager":
926
+ """Manage additional containers running in this Sandbox."""
927
+ self._ensure_attached()
928
+ return _SandboxContainerManager(self)
929
+
907
930
  @overload
908
931
  async def exec(
909
932
  self,
@@ -1006,6 +1029,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1006
1029
  secrets: Optional[Collection[_Secret]] = None,
1007
1030
  text: bool = True,
1008
1031
  bufsize: Literal[-1, 1] = -1,
1032
+ container_id: Optional[str] = None,
1009
1033
  ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1010
1034
  """Private method used internally.
1011
1035
 
@@ -1035,6 +1059,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1035
1059
  "text": text,
1036
1060
  "bufsize": bufsize,
1037
1061
  "runtime_debug": config.get("function_runtime_debug"),
1062
+ "container_id": container_id,
1038
1063
  }
1039
1064
  # NB: This must come after the task ID is set, since the sandbox must be
1040
1065
  # scheduled before we can create a router client.
@@ -1057,8 +1082,11 @@ class _Sandbox(_Object, type_prefix="sb"):
1057
1082
  text: bool = True,
1058
1083
  bufsize: Literal[-1, 1] = -1,
1059
1084
  runtime_debug: bool = False,
1085
+ container_id: Optional[str] = None,
1060
1086
  ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1061
1087
  """Execute a command through the Modal server."""
1088
+ if container_id:
1089
+ raise RuntimeError("Internal error: additional container exec requires task command router support")
1062
1090
  req = api_pb2.ContainerExecRequest(
1063
1091
  task_id=task_id,
1064
1092
  command=args,
@@ -1097,6 +1125,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1097
1125
  text: bool = True,
1098
1126
  bufsize: Literal[-1, 1] = -1,
1099
1127
  runtime_debug: bool = False,
1128
+ container_id: Optional[str] = None,
1100
1129
  ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1101
1130
  """Execute a command through a task command router running on the Modal worker."""
1102
1131
 
@@ -1134,6 +1163,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1134
1163
  secret_ids=secret_ids,
1135
1164
  pty_info=pty_info,
1136
1165
  runtime_debug=runtime_debug,
1166
+ container_id=container_id or "",
1137
1167
  )
1138
1168
  _ = await command_router_client.exec_start(start_req)
1139
1169
 
@@ -1332,17 +1362,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1332
1362
  @property
1333
1363
  def returncode(self) -> Optional[int]:
1334
1364
  """Return code of the Sandbox process if it has finished running, else `None`."""
1335
- if self._result is None or self._result.status == api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
1336
- return None
1337
-
1338
- # Statuses are converted to exitcodes so we can conform to subprocess API.
1339
- # TODO: perhaps there should be a separate property that returns an enum directly?
1340
- elif self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
1341
- return 124
1342
- elif self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
1343
- return 137
1344
- else:
1345
- return self._result.exitcode
1365
+ return _result_returncode(self._result)
1346
1366
 
1347
1367
  @staticmethod
1348
1368
  async def list(
@@ -1382,4 +1402,224 @@ class _Sandbox(_Object, type_prefix="sb"):
1382
1402
  before_timestamp = resp.sandboxes[-1].created_at
1383
1403
 
1384
1404
 
1405
+ class _SandboxContainer:
1406
+ """Handle to an additional container running in a Sandbox."""
1407
+
1408
+ _result: Optional[api_pb2.GenericResult]
1409
+
1410
+ def __init__(
1411
+ self,
1412
+ sandbox: _Sandbox,
1413
+ container_id: str,
1414
+ container_name: str,
1415
+ result: Optional[api_pb2.GenericResult] = None,
1416
+ ) -> None:
1417
+ self._sandbox = sandbox
1418
+ self._container_id = container_id
1419
+ self._container_name = container_name
1420
+ self._result = result
1421
+
1422
+ @property
1423
+ def object_id(self) -> str:
1424
+ return self._container_id
1425
+
1426
+ @property
1427
+ def name(self) -> str:
1428
+ return self._container_name
1429
+
1430
+ @staticmethod
1431
+ def _from_container_info(sandbox: "_Sandbox", container_info: sr_pb2.TaskContainerInfo) -> "_SandboxContainer":
1432
+ result = container_info.result if container_info.HasField("result") else None
1433
+ return _SandboxContainer(sandbox, container_info.container_id, container_info.container_name, result)
1434
+
1435
+ async def _get_command_router(self) -> tuple[str, "TaskCommandRouterClient"]:
1436
+ """Get task ID and command router client, raising if unavailable."""
1437
+ task_id = await self._sandbox._get_task_id()
1438
+ command_router_client = await self._sandbox._get_command_router_client(task_id)
1439
+ if command_router_client is None:
1440
+ raise RuntimeError("Internal error: additional container operations require task command router support")
1441
+ return task_id, command_router_client
1442
+
1443
+ async def exec(
1444
+ self,
1445
+ *args: str,
1446
+ stdout: StreamType = StreamType.PIPE,
1447
+ stderr: StreamType = StreamType.PIPE,
1448
+ timeout: Optional[int] = None,
1449
+ workdir: Optional[str] = None,
1450
+ env: Optional[dict[str, Optional[str]]] = None,
1451
+ secrets: Optional[Collection[_Secret]] = None,
1452
+ text: bool = True,
1453
+ bufsize: Literal[-1, 1] = -1,
1454
+ pty: bool = False,
1455
+ ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1456
+ pty_info = self._sandbox._default_pty_info() if pty else None
1457
+ return await self._sandbox._exec(
1458
+ *args,
1459
+ pty_info=pty_info,
1460
+ stdout=stdout,
1461
+ stderr=stderr,
1462
+ timeout=timeout,
1463
+ workdir=workdir,
1464
+ env=env,
1465
+ secrets=secrets,
1466
+ text=text,
1467
+ bufsize=bufsize,
1468
+ container_id=self._container_id,
1469
+ )
1470
+
1471
+ async def wait(self, raise_on_termination: bool = True) -> None:
1472
+ if self._result is not None and self._result.status != api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
1473
+ if self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED and raise_on_termination:
1474
+ raise SandboxTerminatedError()
1475
+ return
1476
+
1477
+ task_id, command_router_client = await self._get_command_router()
1478
+ while True:
1479
+ resp = await command_router_client.container_wait(
1480
+ sr_pb2.TaskContainerWaitRequest(
1481
+ task_id=task_id,
1482
+ container_id=self._container_id,
1483
+ timeout=10,
1484
+ )
1485
+ )
1486
+ if resp.result.status:
1487
+ self._result = resp.result
1488
+ if resp.result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED and raise_on_termination:
1489
+ raise SandboxTerminatedError()
1490
+ return
1491
+
1492
+ async def poll(self) -> Optional[int]:
1493
+ if self._result is not None and self._result.status != api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
1494
+ return _result_returncode(self._result)
1495
+
1496
+ task_id, command_router_client = await self._get_command_router()
1497
+ resp = await command_router_client.container_wait(
1498
+ sr_pb2.TaskContainerWaitRequest(
1499
+ task_id=task_id,
1500
+ container_id=self._container_id,
1501
+ timeout=0,
1502
+ )
1503
+ )
1504
+ if resp.result.status:
1505
+ self._result = resp.result
1506
+ return _result_returncode(self._result)
1507
+
1508
+ @overload
1509
+ async def terminate(
1510
+ self,
1511
+ *,
1512
+ wait: Literal[True],
1513
+ ) -> int: ...
1514
+
1515
+ @overload
1516
+ async def terminate(
1517
+ self,
1518
+ *,
1519
+ wait: Literal[False] = False,
1520
+ ) -> None: ...
1521
+
1522
+ async def terminate(
1523
+ self,
1524
+ *,
1525
+ wait: bool = False,
1526
+ ) -> int | None:
1527
+ task_id, command_router_client = await self._get_command_router()
1528
+ await command_router_client.container_terminate(
1529
+ sr_pb2.TaskContainerTerminateRequest(
1530
+ task_id=task_id,
1531
+ container_id=self._container_id,
1532
+ )
1533
+ )
1534
+ if wait:
1535
+ await self.wait(raise_on_termination=False)
1536
+ return _result_returncode(self._result)
1537
+
1538
+
1539
+ class _SandboxContainerManager:
1540
+ """Creates and manages additional containers in a Sandbox."""
1541
+
1542
+ def __init__(self, sandbox: _Sandbox) -> None:
1543
+ self._sandbox = sandbox
1544
+
1545
+ async def _get_command_router(self) -> tuple[str, "TaskCommandRouterClient"]:
1546
+ """Get task ID and command router client, raising if unavailable."""
1547
+ task_id = await self._sandbox._get_task_id()
1548
+ command_router_client = await self._sandbox._get_command_router_client(task_id)
1549
+ if command_router_client is None:
1550
+ raise RuntimeError("Internal error: additional container operations require task command router support")
1551
+ return task_id, command_router_client
1552
+
1553
+ async def create(
1554
+ self,
1555
+ *args: str,
1556
+ name: str,
1557
+ image: _Image,
1558
+ env: Optional[dict[str, str]] = None,
1559
+ secrets: Optional[Collection[_Secret]] = None,
1560
+ workdir: Optional[str] = None,
1561
+ ) -> _SandboxContainer:
1562
+ if workdir is not None and not workdir.startswith("/"):
1563
+ raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
1564
+ _validate_exec_args(args)
1565
+
1566
+ if image._mount_layers:
1567
+ raise InvalidError(
1568
+ "Sandbox._experimental_containers.create(image=...) only supports pre-built images. "
1569
+ "When using `add_local*` methods, specify `copy=True` and call `.build()` before passing "
1570
+ "the image to `._experimental_containers.create()`:\n\nE.g.\n"
1571
+ 'img = modal.Image.debian_slim().add_local_file("foo", "/foo", copy=True).build(app)\n'
1572
+ 'sandbox._experimental_containers.create(name="worker", image=img)'
1573
+ )
1574
+ if not image._object_id:
1575
+ raise InvalidError(
1576
+ "Sandbox._experimental_containers.create(image=...) currently only supports Images that are "
1577
+ "either:\n"
1578
+ "- prebuilt using `image.build()`\n"
1579
+ "- referenced by id, e.g. `Image.from_id()`\n"
1580
+ "- filesystem/directory snapshots e.g. created by `.snapshot_directory()` "
1581
+ "or `.snapshot_filesystem()`\n"
1582
+ )
1583
+
1584
+ secrets = secrets or []
1585
+ secret_coros = [secret.hydrate(client=self._sandbox._client) for secret in secrets]
1586
+ await TaskContext.gather(*secret_coros)
1587
+
1588
+ task_id, command_router_client = await self._get_command_router()
1589
+
1590
+ create_req = sr_pb2.TaskContainerCreateRequest(
1591
+ task_id=task_id,
1592
+ container_name=name,
1593
+ image_id=image.object_id,
1594
+ args=list(args),
1595
+ env=env or {},
1596
+ workdir=workdir or "",
1597
+ secret_ids=[secret.object_id for secret in secrets],
1598
+ )
1599
+ create_resp = await command_router_client.container_create(create_req)
1600
+ container_id = create_resp.container_id
1601
+ container_name = create_resp.container_name or name
1602
+ return _SandboxContainer(self._sandbox, container_id, container_name)
1603
+
1604
+ async def get(self, *, name: str, include_terminated: bool = False) -> "_SandboxContainer":
1605
+ task_id, command_router_client = await self._get_command_router()
1606
+ resp = await command_router_client.container_get(
1607
+ sr_pb2.TaskContainerGetRequest(
1608
+ task_id=task_id,
1609
+ container_name=name,
1610
+ include_terminated=include_terminated,
1611
+ )
1612
+ )
1613
+ return _SandboxContainer._from_container_info(self._sandbox, resp.container)
1614
+
1615
+ async def list(self, include_terminated: bool = False) -> builtins.list[_SandboxContainer]:
1616
+ task_id, command_router_client = await self._get_command_router()
1617
+ resp = await command_router_client.container_list(
1618
+ sr_pb2.TaskContainerListRequest(task_id=task_id, include_terminated=include_terminated)
1619
+ )
1620
+ return [_SandboxContainer._from_container_info(self._sandbox, container) for container in resp.containers]
1621
+
1622
+
1623
+ SandboxContainer = synchronize_api(_SandboxContainer)
1624
+ SandboxContainerManager = synchronize_api(_SandboxContainerManager)
1385
1625
  Sandbox = synchronize_api(_Sandbox)
modal/sandbox.pyi CHANGED
@@ -22,11 +22,13 @@ import modal.snapshot
22
22
  import modal.stream_type
23
23
  import modal.volume
24
24
  import modal_proto.api_pb2
25
+ import modal_proto.task_command_router_pb2
25
26
  import os
26
27
  import pathlib
27
28
  import typing
28
29
  import typing_extensions
29
30
 
31
+ def _result_returncode(result: typing.Optional[modal_proto.api_pb2.GenericResult]) -> typing.Optional[int]: ...
30
32
  def _validate_exec_args(args: collections.abc.Sequence[str]) -> None: ...
31
33
 
32
34
  class DefaultSandboxNameOverride(str):
@@ -381,6 +383,11 @@ class _Sandbox(modal._object._Object):
381
383
  async def _get_command_router_client(
382
384
  self, task_id: str
383
385
  ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
386
+ @property
387
+ def _experimental_containers(self) -> _SandboxContainerManager:
388
+ """Manage additional containers running in this Sandbox."""
389
+ ...
390
+
384
391
  @typing.overload
385
392
  async def exec(
386
393
  self,
@@ -425,6 +432,7 @@ class _Sandbox(modal._object._Object):
425
432
  secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
426
433
  text: bool = True,
427
434
  bufsize: typing.Literal[-1, 1] = -1,
435
+ container_id: typing.Optional[str] = None,
428
436
  ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
429
437
  """Private method used internally.
430
438
 
@@ -445,6 +453,7 @@ class _Sandbox(modal._object._Object):
445
453
  text: bool = True,
446
454
  bufsize: typing.Literal[-1, 1] = -1,
447
455
  runtime_debug: bool = False,
456
+ container_id: typing.Optional[str] = None,
448
457
  ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
449
458
  """Execute a command through the Modal server."""
450
459
  ...
@@ -463,6 +472,7 @@ class _Sandbox(modal._object._Object):
463
472
  text: bool = True,
464
473
  bufsize: typing.Literal[-1, 1] = -1,
465
474
  runtime_debug: bool = False,
475
+ container_id: typing.Optional[str] = None,
466
476
  ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
467
477
  """Execute a command through a task command router running on the Modal worker."""
468
478
  ...
@@ -546,6 +556,220 @@ class _Sandbox(modal._object._Object):
546
556
  """
547
557
  ...
548
558
 
559
+ class _SandboxContainer:
560
+ """Handle to an additional container running in a Sandbox."""
561
+
562
+ _result: typing.Optional[modal_proto.api_pb2.GenericResult]
563
+
564
+ def __init__(
565
+ self,
566
+ sandbox: _Sandbox,
567
+ container_id: str,
568
+ container_name: str,
569
+ result: typing.Optional[modal_proto.api_pb2.GenericResult] = None,
570
+ ) -> None:
571
+ """Initialize self. See help(type(self)) for accurate signature."""
572
+ ...
573
+
574
+ @property
575
+ def object_id(self) -> str: ...
576
+ @property
577
+ def name(self) -> str: ...
578
+ @staticmethod
579
+ def _from_container_info(
580
+ sandbox: _Sandbox, container_info: modal_proto.task_command_router_pb2.TaskContainerInfo
581
+ ) -> _SandboxContainer: ...
582
+ async def _get_command_router(self) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
583
+ """Get task ID and command router client, raising if unavailable."""
584
+ ...
585
+
586
+ async def exec(
587
+ self,
588
+ *args: str,
589
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
590
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
591
+ timeout: typing.Optional[int] = None,
592
+ workdir: typing.Optional[str] = None,
593
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
594
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
595
+ text: bool = True,
596
+ bufsize: typing.Literal[-1, 1] = -1,
597
+ pty: bool = False,
598
+ ) -> typing.Union[
599
+ modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]
600
+ ]: ...
601
+ async def wait(self, raise_on_termination: bool = True) -> None: ...
602
+ async def poll(self) -> typing.Optional[int]: ...
603
+ @typing.overload
604
+ async def terminate(self, *, wait: typing.Literal[True]) -> int: ...
605
+ @typing.overload
606
+ async def terminate(self, *, wait: typing.Literal[False] = False) -> None: ...
607
+
608
+ class _SandboxContainerManager:
609
+ """Creates and manages additional containers in a Sandbox."""
610
+ def __init__(self, sandbox: _Sandbox) -> None:
611
+ """Initialize self. See help(type(self)) for accurate signature."""
612
+ ...
613
+
614
+ async def _get_command_router(self) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
615
+ """Get task ID and command router client, raising if unavailable."""
616
+ ...
617
+
618
+ async def create(
619
+ self,
620
+ *args: str,
621
+ name: str,
622
+ image: modal.image._Image,
623
+ env: typing.Optional[dict[str, str]] = None,
624
+ secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
625
+ workdir: typing.Optional[str] = None,
626
+ ) -> _SandboxContainer: ...
627
+ async def get(self, *, name: str, include_terminated: bool = False) -> _SandboxContainer: ...
628
+ async def list(self, include_terminated: bool = False) -> list[_SandboxContainer]: ...
629
+
630
+ class SandboxContainer:
631
+ """Handle to an additional container running in a Sandbox."""
632
+
633
+ _result: typing.Optional[modal_proto.api_pb2.GenericResult]
634
+
635
+ def __init__(
636
+ self,
637
+ sandbox: Sandbox,
638
+ container_id: str,
639
+ container_name: str,
640
+ result: typing.Optional[modal_proto.api_pb2.GenericResult] = None,
641
+ ) -> None: ...
642
+ @property
643
+ def object_id(self) -> str: ...
644
+ @property
645
+ def name(self) -> str: ...
646
+ @staticmethod
647
+ def _from_container_info(
648
+ sandbox: Sandbox, container_info: modal_proto.task_command_router_pb2.TaskContainerInfo
649
+ ) -> SandboxContainer: ...
650
+
651
+ class ___get_command_router_spec(typing_extensions.Protocol):
652
+ def __call__(self, /) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
653
+ """Get task ID and command router client, raising if unavailable."""
654
+ ...
655
+
656
+ async def aio(self, /) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
657
+ """Get task ID and command router client, raising if unavailable."""
658
+ ...
659
+
660
+ _get_command_router: ___get_command_router_spec
661
+
662
+ class __exec_spec(typing_extensions.Protocol):
663
+ def __call__(
664
+ self,
665
+ /,
666
+ *args: str,
667
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
668
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
669
+ timeout: typing.Optional[int] = None,
670
+ workdir: typing.Optional[str] = None,
671
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
672
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
673
+ text: bool = True,
674
+ bufsize: typing.Literal[-1, 1] = -1,
675
+ pty: bool = False,
676
+ ) -> typing.Union[
677
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
678
+ ]: ...
679
+ async def aio(
680
+ self,
681
+ /,
682
+ *args: str,
683
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
684
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
685
+ timeout: typing.Optional[int] = None,
686
+ workdir: typing.Optional[str] = None,
687
+ env: typing.Optional[dict[str, typing.Optional[str]]] = None,
688
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
689
+ text: bool = True,
690
+ bufsize: typing.Literal[-1, 1] = -1,
691
+ pty: bool = False,
692
+ ) -> typing.Union[
693
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
694
+ ]: ...
695
+
696
+ exec: __exec_spec
697
+
698
+ class __wait_spec(typing_extensions.Protocol):
699
+ def __call__(self, /, raise_on_termination: bool = True) -> None: ...
700
+ async def aio(self, /, raise_on_termination: bool = True) -> None: ...
701
+
702
+ wait: __wait_spec
703
+
704
+ class __poll_spec(typing_extensions.Protocol):
705
+ def __call__(self, /) -> typing.Optional[int]: ...
706
+ async def aio(self, /) -> typing.Optional[int]: ...
707
+
708
+ poll: __poll_spec
709
+
710
+ class __terminate_spec(typing_extensions.Protocol):
711
+ @typing.overload
712
+ def __call__(self, /, *, wait: typing.Literal[True]) -> int: ...
713
+ @typing.overload
714
+ def __call__(self, /, *, wait: typing.Literal[False] = False) -> None: ...
715
+ @typing.overload
716
+ async def aio(self, /, *, wait: typing.Literal[True]) -> int: ...
717
+ @typing.overload
718
+ async def aio(self, /, *, wait: typing.Literal[False] = False) -> None: ...
719
+
720
+ terminate: __terminate_spec
721
+
722
+ class SandboxContainerManager:
723
+ """Creates and manages additional containers in a Sandbox."""
724
+ def __init__(self, sandbox: Sandbox) -> None: ...
725
+
726
+ class ___get_command_router_spec(typing_extensions.Protocol):
727
+ def __call__(self, /) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
728
+ """Get task ID and command router client, raising if unavailable."""
729
+ ...
730
+
731
+ async def aio(self, /) -> tuple[str, modal._utils.task_command_router_client.TaskCommandRouterClient]:
732
+ """Get task ID and command router client, raising if unavailable."""
733
+ ...
734
+
735
+ _get_command_router: ___get_command_router_spec
736
+
737
+ class __create_spec(typing_extensions.Protocol):
738
+ def __call__(
739
+ self,
740
+ /,
741
+ *args: str,
742
+ name: str,
743
+ image: modal.image.Image,
744
+ env: typing.Optional[dict[str, str]] = None,
745
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
746
+ workdir: typing.Optional[str] = None,
747
+ ) -> SandboxContainer: ...
748
+ async def aio(
749
+ self,
750
+ /,
751
+ *args: str,
752
+ name: str,
753
+ image: modal.image.Image,
754
+ env: typing.Optional[dict[str, str]] = None,
755
+ secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
756
+ workdir: typing.Optional[str] = None,
757
+ ) -> SandboxContainer: ...
758
+
759
+ create: __create_spec
760
+
761
+ class __get_spec(typing_extensions.Protocol):
762
+ def __call__(self, /, *, name: str, include_terminated: bool = False) -> SandboxContainer: ...
763
+ async def aio(self, /, *, name: str, include_terminated: bool = False) -> SandboxContainer: ...
764
+
765
+ get: __get_spec
766
+
767
+ class __list_spec(typing_extensions.Protocol):
768
+ def __call__(self, /, include_terminated: bool = False) -> list[SandboxContainer]: ...
769
+ async def aio(self, /, include_terminated: bool = False) -> list[SandboxContainer]: ...
770
+
771
+ list: __list_spec
772
+
549
773
  class Sandbox(modal.object.Object):
550
774
  """A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
551
775
  [asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
@@ -1179,6 +1403,11 @@ class Sandbox(modal.object.Object):
1179
1403
 
1180
1404
  _get_command_router_client: ___get_command_router_client_spec
1181
1405
 
1406
+ @property
1407
+ def _experimental_containers(self) -> SandboxContainerManager:
1408
+ """Manage additional containers running in this Sandbox."""
1409
+ ...
1410
+
1182
1411
  class __exec_spec(typing_extensions.Protocol):
1183
1412
  @typing.overload
1184
1413
  def __call__(
@@ -1265,6 +1494,7 @@ class Sandbox(modal.object.Object):
1265
1494
  secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1266
1495
  text: bool = True,
1267
1496
  bufsize: typing.Literal[-1, 1] = -1,
1497
+ container_id: typing.Optional[str] = None,
1268
1498
  ) -> typing.Union[
1269
1499
  modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1270
1500
  ]:
@@ -1287,6 +1517,7 @@ class Sandbox(modal.object.Object):
1287
1517
  secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
1288
1518
  text: bool = True,
1289
1519
  bufsize: typing.Literal[-1, 1] = -1,
1520
+ container_id: typing.Optional[str] = None,
1290
1521
  ) -> typing.Union[
1291
1522
  modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1292
1523
  ]:
@@ -1313,6 +1544,7 @@ class Sandbox(modal.object.Object):
1313
1544
  text: bool = True,
1314
1545
  bufsize: typing.Literal[-1, 1] = -1,
1315
1546
  runtime_debug: bool = False,
1547
+ container_id: typing.Optional[str] = None,
1316
1548
  ) -> typing.Union[
1317
1549
  modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1318
1550
  ]:
@@ -1333,6 +1565,7 @@ class Sandbox(modal.object.Object):
1333
1565
  text: bool = True,
1334
1566
  bufsize: typing.Literal[-1, 1] = -1,
1335
1567
  runtime_debug: bool = False,
1568
+ container_id: typing.Optional[str] = None,
1336
1569
  ) -> typing.Union[
1337
1570
  modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1338
1571
  ]:
@@ -1357,6 +1590,7 @@ class Sandbox(modal.object.Object):
1357
1590
  text: bool = True,
1358
1591
  bufsize: typing.Literal[-1, 1] = -1,
1359
1592
  runtime_debug: bool = False,
1593
+ container_id: typing.Optional[str] = None,
1360
1594
  ) -> typing.Union[
1361
1595
  modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1362
1596
  ]:
@@ -1378,6 +1612,7 @@ class Sandbox(modal.object.Object):
1378
1612
  text: bool = True,
1379
1613
  bufsize: typing.Literal[-1, 1] = -1,
1380
1614
  runtime_debug: bool = False,
1615
+ container_id: typing.Optional[str] = None,
1381
1616
  ) -> typing.Union[
1382
1617
  modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1383
1618
  ]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.6.dev9
3
+ Version: 1.3.6.dev10
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ modal/app.pyi,sha256=8lv2eLKP-y3BLT1irRnzdULSD45OgU4_VbCIYEy_Ic0,56387
26
26
  modal/billing.py,sha256=B2zXa7KeMnpqi2AQINycEk-KSXVYaLTTTAQTtuf2Gfs,257
27
27
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
28
28
  modal/client.py,sha256=-IxazxLhdkNFAHS2AP2FpyTVpCDVR58RF6ecSVV59r0,14862
29
- modal/client.pyi,sha256=jYNvLifQve3TLlDOeU6KfuzXRxzFUDvv4mEU5nARI3w,14930
29
+ modal/client.pyi,sha256=ut6XUIpQGf73KLfo5x6eTZc7OrQkbKHQoYeR42zmdo8,14932
30
30
  modal/cloud_bucket_mount.py,sha256=KkaJVy5vpO_u2NtDYtv1JM-M3y4J0UtWBapTDKpQRcA,6034
31
31
  modal/cloud_bucket_mount.pyi,sha256=mGDeEd1XvxGpCE6O5jRBSfIvMXvZ5AtIybea5k-0w9w,7560
32
32
  modal/cls.py,sha256=ozx5ZVGyG6w5SQ1ouj9Y6M3yPtnpL9PfblnmqFzf694,42338
@@ -69,8 +69,8 @@ modal/retries.py,sha256=vPWWKP-FRolJwTBuLPXdWqHV7aJfSQFgEW7vhlBBXRQ,5067
69
69
  modal/runner.py,sha256=rz3th7ZtN-UlUe7RO38J89gg4XtFlbBrSq4cOGIl1Eg,29594
70
70
  modal/runner.pyi,sha256=UgV8jEjoRgnIowZHnOWrbGjZFsLCDm6D-4gpf77H4so,9517
71
71
  modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
72
- modal/sandbox.py,sha256=uFU5zT2lOgGeuZRqL8CEpzO6VVGQIS0zpibfc1FUxYA,58218
73
- modal/sandbox.pyi,sha256=LvF04EgeFCywCigcWZEEzgBRosEv5WrkPtCD4ggy--Q,66905
72
+ modal/sandbox.py,sha256=lJlx5FHait0BYB2anXc0ciiy78NwAWwKYszQdA6jXhA,67572
73
+ modal/sandbox.pyi,sha256=MLPfBwHxg6EpplMjky44lhNPDb0HDFxBuImJleTJvqU,76610
74
74
  modal/sandbox_fs.py,sha256=mEgfFKGgLFO7Ncvw_iQc54htHZ66E1i56ADNnXj9NHQ,13080
75
75
  modal/sandbox_fs.pyi,sha256=qr3PWlWwHqKRa_fPe9aI2ecj-iNF-NKY52GaL_MXc1U,21245
76
76
  modal/schedule.py,sha256=ng0g0AqNY5GQI9KhkXZQ5Wam5G42glbkqVQsNpBtbDE,3078
@@ -127,7 +127,7 @@ modal/_utils/pattern_utils.py,sha256=ZUffaECfe2iYBhH6cvCB-0-UWhmEBTZEl_TwG_So3ag
127
127
  modal/_utils/rand_pb_testing.py,sha256=oNtQB1BY9CDh7vDXEkJitCuCu9a2ufk0KRcEuZ15d0k,4229
128
128
  modal/_utils/sandbox_fs_utils.py,sha256=BZQXwfWMQHXTw_5u1ttbb2Ouxxbe3W7DaJDTJZPbMAs,6536
129
129
  modal/_utils/shell_utils.py,sha256=GjMrO1YxnBr4_UlpA2ZYQKCHxV78QTg0mqxAMHWGTE4,3427
130
- modal/_utils/task_command_router_client.py,sha256=BnlOtEtFTwJFDaCDMlu21PSj0Y4AloboyJObmnRCwqM,23347
130
+ modal/_utils/task_command_router_client.py,sha256=VLzjgwdSFZvub6MxsjsWtx2a4UwI2SCbK5-K4GGpHz0,25363
131
131
  modal/_utils/time_utils.py,sha256=VVrIwm7YMv2vMznNGknkGzyATDp5iJ29F_7NZPfm5b0,7062
132
132
  modal/_vendor/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
133
133
  modal/_vendor/a2wsgi_wsgi.py,sha256=Q1AsjpV_Q_vzQsz_cSqmP9jWzsGsB-ARFU6vpQYml8k,21878
@@ -163,7 +163,7 @@ modal/cli/queues.py,sha256=0sl1aydtudoH9RYLyUAK7FJSJ8oCtgYkkENQJ_7ifXc,6230
163
163
  modal/cli/run.py,sha256=kpdTvbNa3jvxJGYmIt6Eh5MJjLPctFusfK2Tq8jOWbM,21389
164
164
  modal/cli/secret.py,sha256=yBpjzzkbA2dNb_Q_2qqltMvCfhzUxDH7-xehLiqrvhI,8061
165
165
  modal/cli/selector.py,sha256=2P4fAgvntNLBJaMjZdDHHopCSoefHKpCMfKGzXzk9jc,4178
166
- modal/cli/shell.py,sha256=L5Dw8yeuu9-f6vNlBGbUT4oh9AVSK4GuB5J6m6vYLBk,12613
166
+ modal/cli/shell.py,sha256=oJT5U7ZTW7KJZ2cFl44su_zrbsvaPJKWAWvwuCEX3CQ,14663
167
167
  modal/cli/token.py,sha256=KIEOgHJ-k1t2h6tolBmQJSSOJlpU3kvmH_IKQzaHhig,4098
168
168
  modal/cli/utils.py,sha256=aLTpttgH2lkOAC6HRtaAMCIKgo-zluvC_d-yQ3gsrz4,6462
169
169
  modal/cli/volume.py,sha256=Tp97ekMTEHT0NXwgyEudO8yIZ75OGOfjIfH44WS5r9Y,11026
@@ -174,7 +174,7 @@ modal/experimental/__init__.py,sha256=4UyAPlExXYM0tJH7s6WSDt2dIfSVXvzARkRLsLMyuq
174
174
  modal/experimental/flash.py,sha256=ATGhVO8AleJ-TOe2PGolI5T7zfCEtj0kl_ivMPp7Rqg,33362
175
175
  modal/experimental/flash.pyi,sha256=R4h2nQl7-oiwqq_twCp0YqID_LnPjPGqxywXvxbiLIk,17011
176
176
  modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
177
- modal-1.3.6.dev9.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
177
+ modal-1.3.6.dev10.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
178
178
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
179
179
  modal_docs/gen_cli_docs.py,sha256=5sVABw4uGUqPXjcWT0Nqjkpe5KLs89C3tFnla58-D0M,3756
180
180
  modal_docs/gen_cli_docs_main.py,sha256=UPGr7O1DYTIRLwfINe2rbdh89J3cPVTU1OWL-SjxnOQ,198
@@ -196,10 +196,10 @@ modal_proto/task_command_router_pb2.py,sha256=uSSR8Y9Yp07cIL60ds7__wUr5rj1uL2CQY
196
196
  modal_proto/task_command_router_pb2.pyi,sha256=utNwipYgMZUEUuWBE7sJ5cNjRctozHKPKDRpLy6cPLM,25667
197
197
  modal_proto/task_command_router_pb2_grpc.py,sha256=YoinPeLc-uwFShig8gE0MluOi6n-qqJZQ3YvnKwKUz8,23194
198
198
  modal_proto/task_command_router_pb2_grpc.pyi,sha256=VUSE5l1O6ATPT8Wbj4M3faMiIy5vFxSoS5ZlKfTEpHc,7449
199
- modal_version/__init__.py,sha256=mxS-hT7DtmdHGbRDYQRsbpyTBhA7g0YMOsezQo15t2A,120
199
+ modal_version/__init__.py,sha256=1Wfo-F7lJ0XaY1v8peKaecTUR3Pn0XJfD1xUVedGfLc,121
200
200
  modal_version/__main__.py,sha256=nLUbubxKx7GXNzJIkTB7M7_u8080LyLM0IS0rzFkC_c,119
201
- modal-1.3.6.dev9.dist-info/METADATA,sha256=cb0Idkuh_5XTvizbMIqfUckFqXYgYhQFwIvAimKCuIk,2364
202
- modal-1.3.6.dev9.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
203
- modal-1.3.6.dev9.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
204
- modal-1.3.6.dev9.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
205
- modal-1.3.6.dev9.dist-info/RECORD,,
201
+ modal-1.3.6.dev10.dist-info/METADATA,sha256=ESSWZgUHODQ12XLEpz04vhV9AFdMd98V_UZ466knejI,2365
202
+ modal-1.3.6.dev10.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
203
+ modal-1.3.6.dev10.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
204
+ modal-1.3.6.dev10.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
205
+ modal-1.3.6.dev10.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.3.6.dev9"
4
+ __version__ = "1.3.6.dev10"