modal 0.68.26__py3-none-any.whl → 0.68.28__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/app.py CHANGED
@@ -45,7 +45,6 @@ from .partial_function import (
45
45
  from .proxy import _Proxy
46
46
  from .retries import Retries
47
47
  from .running_app import RunningApp
48
- from .sandbox import _Sandbox
49
48
  from .schedule import Schedule
50
49
  from .scheduler_placement import SchedulerPlacement
51
50
  from .secret import _Secret
@@ -964,36 +963,16 @@ class _App:
964
963
  _experimental_scheduler_placement: Optional[
965
964
  SchedulerPlacement
966
965
  ] = None, # Experimental controls over fine-grained scheduling (alpha).
967
- ) -> _Sandbox:
968
- """`App.spawn_sandbox` is deprecated in favor of `Sandbox.create(app=...)`.
969
-
970
- See https://modal.com/docs/guide/sandbox for more info on working with sandboxes.
971
- """
972
- deprecation_warning((2024, 7, 5), _App.spawn_sandbox.__doc__)
973
- if not self._running_app:
974
- raise InvalidError("`app.spawn_sandbox` requires a running app.")
975
-
976
- return await _Sandbox.create(
977
- *entrypoint_args,
978
- app=self,
979
- environment_name=self._running_app.environment_name,
980
- image=image or _default_image,
981
- mounts=mounts,
982
- secrets=secrets,
983
- timeout=timeout,
984
- workdir=workdir,
985
- gpu=gpu,
986
- cloud=cloud,
987
- region=region,
988
- cpu=cpu,
989
- memory=memory,
990
- network_file_systems=network_file_systems,
991
- block_network=block_network,
992
- volumes=volumes,
993
- pty_info=pty_info,
994
- _experimental_scheduler_placement=_experimental_scheduler_placement,
995
- client=self._client,
966
+ ) -> None:
967
+ """mdmd:hidden"""
968
+ arglist = ", ".join(repr(s) for s in entrypoint_args)
969
+ message = (
970
+ "`App.spawn_sandbox` is deprecated.\n\n"
971
+ "Sandboxes can be created using the `Sandbox` object:\n\n"
972
+ f"```\nsb = Sandbox.create({arglist}, app=app)\n```\n\n"
973
+ "See https://modal.com/docs/guide/sandbox for more info on working with sandboxes."
996
974
  )
975
+ deprecation_error((2024, 7, 5), message)
997
976
 
998
977
  def include(self, /, other_app: "_App"):
999
978
  """Include another App's objects in this one.
modal/app.pyi CHANGED
@@ -13,7 +13,6 @@ import modal.partial_function
13
13
  import modal.proxy
14
14
  import modal.retries
15
15
  import modal.running_app
16
- import modal.sandbox
17
16
  import modal.schedule
18
17
  import modal.scheduler_placement
19
18
  import modal.secret
@@ -261,7 +260,7 @@ class _App:
261
260
  ] = {},
262
261
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
263
262
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
264
- ) -> modal.sandbox._Sandbox: ...
263
+ ) -> None: ...
265
264
  def include(self, /, other_app: _App): ...
266
265
  def _logs(
267
266
  self, client: typing.Optional[modal.client._Client] = None
@@ -491,7 +490,7 @@ class App:
491
490
  ] = {},
492
491
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
493
492
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
494
- ) -> modal.sandbox.Sandbox: ...
493
+ ) -> None: ...
495
494
  async def aio(
496
495
  self,
497
496
  *entrypoint_args: str,
@@ -515,7 +514,7 @@ class App:
515
514
  ] = {},
516
515
  pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
517
516
  _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
518
- ) -> modal.sandbox.Sandbox: ...
517
+ ) -> None: ...
519
518
 
520
519
  spawn_sandbox: __spawn_sandbox_spec
521
520
 
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.26"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.28"
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.26"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.28"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/file_io.py CHANGED
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, AsyncIterator, Generic, Optional, Sequence, Ty
6
6
  if TYPE_CHECKING:
7
7
  import _typeshed
8
8
 
9
+ import json
10
+
9
11
  from grpclib.exceptions import GRPCError, StreamTerminatedError
10
12
 
11
13
  from modal._utils.grpc_utils import retry_transient_errors
@@ -267,12 +269,12 @@ class _FileIO(Generic[T]):
267
269
  output = await self._make_read_request(None)
268
270
  if self._binary:
269
271
  lines_bytes = output.split(b"\n")
270
- output = [line + b"\n" for line in lines_bytes[:-1]] + ([lines_bytes[-1]] if lines_bytes[-1] else [])
271
- return cast(Sequence[T], output)
272
+ return_bytes = [line + b"\n" for line in lines_bytes[:-1]] + ([lines_bytes[-1]] if lines_bytes[-1] else [])
273
+ return cast(Sequence[T], return_bytes)
272
274
  else:
273
275
  lines = output.decode("utf-8").split("\n")
274
- output = [line + "\n" for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
275
- return cast(Sequence[T], output)
276
+ return_strs = [line + "\n" for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
277
+ return cast(Sequence[T], return_strs)
276
278
 
277
279
  async def write(self, data: Union[bytes, str]) -> None:
278
280
  """Write data to the current position.
@@ -337,6 +339,52 @@ class _FileIO(Generic[T]):
337
339
  )
338
340
  await self._wait(resp.exec_id)
339
341
 
342
+ @classmethod
343
+ async def ls(cls, path: str, client: _Client, task_id: str) -> list[str]:
344
+ """List the contents of the provided directory."""
345
+ self = cls.__new__(cls)
346
+ self._client = client
347
+ self._task_id = task_id
348
+ resp = await self._make_request(
349
+ api_pb2.ContainerFilesystemExecRequest(
350
+ file_ls_request=api_pb2.ContainerFileLsRequest(path=path),
351
+ task_id=task_id,
352
+ )
353
+ )
354
+ output = await self._wait(resp.exec_id)
355
+ try:
356
+ return json.loads(output.decode("utf-8"))["paths"]
357
+ except json.JSONDecodeError:
358
+ raise FilesystemExecutionError("failed to parse list output")
359
+
360
+ @classmethod
361
+ async def mkdir(cls, path: str, client: _Client, task_id: str, parents: bool = False) -> None:
362
+ """Create a new directory."""
363
+ self = cls.__new__(cls)
364
+ self._client = client
365
+ self._task_id = task_id
366
+ resp = await self._make_request(
367
+ api_pb2.ContainerFilesystemExecRequest(
368
+ file_mkdir_request=api_pb2.ContainerFileMkdirRequest(path=path, make_parents=parents),
369
+ task_id=self._task_id,
370
+ )
371
+ )
372
+ await self._wait(resp.exec_id)
373
+
374
+ @classmethod
375
+ async def rm(cls, path: str, client: _Client, task_id: str, recursive: bool = False) -> None:
376
+ """Remove a file or directory in the Sandbox."""
377
+ self = cls.__new__(cls)
378
+ self._client = client
379
+ self._task_id = task_id
380
+ resp = await self._make_request(
381
+ api_pb2.ContainerFilesystemExecRequest(
382
+ file_rm_request=api_pb2.ContainerFileRmRequest(path=path, recursive=recursive),
383
+ task_id=self._task_id,
384
+ )
385
+ )
386
+ await self._wait(resp.exec_id)
387
+
340
388
  async def _close(self) -> None:
341
389
  # Buffer is flushed by the runner on close
342
390
  resp = await self._make_request(
modal/file_io.pyi CHANGED
@@ -43,6 +43,12 @@ class _FileIO(typing.Generic[T]):
43
43
  async def flush(self) -> None: ...
44
44
  def _get_whence(self, whence: int): ...
45
45
  async def seek(self, offset: int, whence: int = 0) -> None: ...
46
+ @classmethod
47
+ async def ls(cls, path: str, client: modal.client._Client, task_id: str) -> list[str]: ...
48
+ @classmethod
49
+ async def mkdir(cls, path: str, client: modal.client._Client, task_id: str, parents: bool = False) -> None: ...
50
+ @classmethod
51
+ async def rm(cls, path: str, client: modal.client._Client, task_id: str, recursive: bool = False) -> None: ...
46
52
  async def _close(self) -> None: ...
47
53
  async def close(self) -> None: ...
48
54
  def _check_writable(self) -> None: ...
@@ -161,6 +167,13 @@ class FileIO(typing.Generic[T]):
161
167
 
162
168
  seek: __seek_spec
163
169
 
170
+ @classmethod
171
+ def ls(cls, path: str, client: modal.client.Client, task_id: str) -> list[str]: ...
172
+ @classmethod
173
+ def mkdir(cls, path: str, client: modal.client.Client, task_id: str, parents: bool = False) -> None: ...
174
+ @classmethod
175
+ def rm(cls, path: str, client: modal.client.Client, task_id: str, recursive: bool = False) -> None: ...
176
+
164
177
  class ___close_spec(typing_extensions.Protocol):
165
178
  def __call__(self) -> None: ...
166
179
  async def aio(self) -> None: ...
modal/sandbox.py CHANGED
@@ -30,7 +30,6 @@ from .exception import (
30
30
  SandboxTerminatedError,
31
31
  SandboxTimeoutError,
32
32
  deprecation_error,
33
- deprecation_warning,
34
33
  )
35
34
  from .file_io import _FileIO
36
35
  from .gpu import GPU_T
@@ -284,13 +283,14 @@ class _Sandbox(_Object, type_prefix="sb"):
284
283
  app_id = _App._container_app.app_id
285
284
  app_client = _App._container_app.client
286
285
  else:
287
- deprecation_warning(
286
+ arglist = ", ".join(repr(s) for s in entrypoint_args)
287
+ deprecation_error(
288
288
  (2024, 9, 14),
289
- "Creating a `Sandbox` without an `App` is deprecated.\n"
290
- "You may pass in an `App` object, or reference one by name with `App.lookup`:\n"
289
+ "Creating a `Sandbox` without an `App` is deprecated.\n\n"
290
+ "You may pass in an `App` object, or reference one by name with `App.lookup`:\n\n"
291
291
  "```\n"
292
- "app = modal.App.lookup('my-app', create_if_missing=True)\n"
293
- "modal.Sandbox.create('echo', 'hi', app=app)\n"
292
+ "app = modal.App.lookup('sandbox-app', create_if_missing=True)\n"
293
+ f"sb = modal.Sandbox.create({arglist}, app=app)\n"
294
294
  "```",
295
295
  )
296
296
 
@@ -567,6 +567,21 @@ class _Sandbox(_Object, type_prefix="sb"):
567
567
  task_id = await self._get_task_id()
568
568
  return await _FileIO.create(path, mode, self._client, task_id)
569
569
 
570
+ async def ls(self, path: str) -> list[str]:
571
+ """List the contents of a directory in the Sandbox."""
572
+ task_id = await self._get_task_id()
573
+ return await _FileIO.ls(path, self._client, task_id)
574
+
575
+ async def mkdir(self, path: str, parents: bool = False) -> None:
576
+ """Create a new directory in the Sandbox."""
577
+ task_id = await self._get_task_id()
578
+ return await _FileIO.mkdir(path, self._client, task_id, parents)
579
+
580
+ async def rm(self, path: str, recursive: bool = False) -> None:
581
+ """Remove a file or directory in the Sandbox."""
582
+ task_id = await self._get_task_id()
583
+ return await _FileIO.rm(path, self._client, task_id, recursive)
584
+
570
585
  @property
571
586
  def stdout(self) -> _StreamReader[str]:
572
587
  """
modal/sandbox.pyi CHANGED
@@ -128,6 +128,9 @@ class _Sandbox(modal.object._Object):
128
128
  async def open(self, path: str, mode: _typeshed.OpenTextMode) -> modal.file_io._FileIO[str]: ...
129
129
  @typing.overload
130
130
  async def open(self, path: str, mode: _typeshed.OpenBinaryMode) -> modal.file_io._FileIO[bytes]: ...
131
+ async def ls(self, path: str) -> list[str]: ...
132
+ async def mkdir(self, path: str, parents: bool = False) -> None: ...
133
+ async def rm(self, path: str, recursive: bool = False) -> None: ...
131
134
  @property
132
135
  def stdout(self) -> modal.io_streams._StreamReader[str]: ...
133
136
  @property
@@ -367,6 +370,24 @@ class Sandbox(modal.object.Object):
367
370
 
368
371
  open: __open_spec
369
372
 
373
+ class __ls_spec(typing_extensions.Protocol):
374
+ def __call__(self, path: str) -> list[str]: ...
375
+ async def aio(self, path: str) -> list[str]: ...
376
+
377
+ ls: __ls_spec
378
+
379
+ class __mkdir_spec(typing_extensions.Protocol):
380
+ def __call__(self, path: str, parents: bool = False) -> None: ...
381
+ async def aio(self, path: str, parents: bool = False) -> None: ...
382
+
383
+ mkdir: __mkdir_spec
384
+
385
+ class __rm_spec(typing_extensions.Protocol):
386
+ def __call__(self, path: str, recursive: bool = False) -> None: ...
387
+ async def aio(self, path: str, recursive: bool = False) -> None: ...
388
+
389
+ rm: __rm_spec
390
+
370
391
  @property
371
392
  def stdout(self) -> modal.io_streams.StreamReader[str]: ...
372
393
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.26
3
+ Version: 0.68.28
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -15,11 +15,11 @@ modal/_traceback.py,sha256=orZ7rsCk9ekV7ZoFjZTH_H00azCypwRKaLh0MZb1dR8,4898
15
15
  modal/_tunnel.py,sha256=o-jJhS4vQ6-XswDhHcJWGMZZmD03SC0e9i8fEu1JTjo,6310
16
16
  modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
17
17
  modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
18
- modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
19
- modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
18
+ modal/app.py,sha256=e-zwazkqvbtv0eXnIWQnfBW_v9g_6S9a8CGh1Bzr5HU,45423
19
+ modal/app.pyi,sha256=3h538rJ0Z2opldsKLuQhDnvop05TfzNG-Uw_n9rEHa4,25197
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=kcuMWRN7DwmSRaL5mOadbO8W5QRWBnIKcukxDBD96j4,7280
22
+ modal/client.pyi,sha256=TcmFzBzBi5CDGun6ZTmON8TFf7rBIS2_QCai9JTbUIA,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,8 +33,8 @@ 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=2eoSYpMMYs-FRCQbU7joFvaObuYz6HtEtLBik1hz5Xw,14616
37
- modal/file_io.pyi,sha256=QSWGm35no2ApXiy3olLwUiT7jaNKVxIOz0rXQauCg4M,6897
36
+ modal/file_io.py,sha256=pDOFNQU5m-x-k3oJauck4fOp3bZ55Vc-_LvSaN5_Bow,16465
37
+ modal/file_io.pyi,sha256=GMhCCRyMftXYI3HqI9EdGPOx70CbCNi-VC5Sfy5TYnc,7631
38
38
  modal/file_pattern_matcher.py,sha256=vX6MjWRGdonE4I8QPdjFUnz6moBjSzvgD6417BNQrW4,4021
39
39
  modal/functions.py,sha256=IIdHw0FNOdoMksG1b2zvkn8f-xskhJu07ZvHMey9iq4,67667
40
40
  modal/functions.pyi,sha256=bHbJiWW5TbFKKjDn7bSCFvOcUcAjPFqTStS-NAHPSeM,25068
@@ -63,8 +63,8 @@ modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
63
63
  modal/runner.py,sha256=Q02VdfLCO7YKpnOSqqh58XL3hR2XHaDeiJVYW3MKz_8,24580
64
64
  modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
65
65
  modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
66
- modal/sandbox.py,sha256=abmprnPtA-WVsHKu4J1deoltpAUvXtIHQHX-ZpYjYPE,27293
67
- modal/sandbox.pyi,sha256=QPNuiTLNoKwYf8JK_fmfUBXpdGYlukyaksFV1DpCd2g,18987
66
+ modal/sandbox.py,sha256=yoiBbUzUGnCnDauoMkHLNjzsyHenhM1NFk0poXq_Wv4,28043
67
+ modal/sandbox.pyi,sha256=k8_vHjN3oigxSCF13Cm2HfcSHuliGuSb8ryd3CGqwoA,19815
68
68
  modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
69
69
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
70
70
  modal/secret.py,sha256=Y1WgybQIkfkxdzH9CQ1h-Wd1DJJpzipigMhyyvSxTww,10007
@@ -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=JvXpnr2-hH8ddr5vsrzjO2GUjPxopcf2PJMi6xl0pgg,149
167
- modal-0.68.26.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
168
- modal-0.68.26.dist-info/METADATA,sha256=kM-cg3iM_me6zn1ZwbNpfnYvD_UgSKdgOvsFQ1qU8L8,2329
169
- modal-0.68.26.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
170
- modal-0.68.26.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
171
- modal-0.68.26.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
172
- modal-0.68.26.dist-info/RECORD,,
166
+ modal_version/_version_generated.py,sha256=rCrKUIX9RlDkBXIhxZbfOSQ7GhlTnQx5fyFV4xz8l2M,149
167
+ modal-0.68.28.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
168
+ modal-0.68.28.dist-info/METADATA,sha256=4XYesHFd9NjVz8TZumQ1NyHdDGeO2Wphy-G_cWttZcI,2329
169
+ modal-0.68.28.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
170
+ modal-0.68.28.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
171
+ modal-0.68.28.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
172
+ modal-0.68.28.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2024
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 26 # git: 439f3b7
4
+ build_number = 28 # git: 3c875f8