modal 0.68.27__py3-none-any.whl → 0.68.29__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.
@@ -0,0 +1,44 @@
1
+ # Copyright Modal Labs 2024
2
+ import sys
3
+ import warnings
4
+ from datetime import date
5
+
6
+ from ..exception import DeprecationError, PendingDeprecationError
7
+
8
+ _INTERNAL_MODULES = ["modal", "synchronicity"]
9
+
10
+
11
+ def _is_internal_frame(frame):
12
+ module = frame.f_globals["__name__"].split(".")[0]
13
+ return module in _INTERNAL_MODULES
14
+
15
+
16
+ def deprecation_error(deprecated_on: tuple[int, int, int], msg: str):
17
+ raise DeprecationError(f"Deprecated on {date(*deprecated_on)}: {msg}")
18
+
19
+
20
+ def deprecation_warning(
21
+ deprecated_on: tuple[int, int, int], msg: str, *, pending: bool = False, show_source: bool = True
22
+ ) -> None:
23
+ """Issue a Modal deprecation warning with source optionally attributed to user code.
24
+
25
+ See the implementation of the built-in [warnings.warn](https://docs.python.org/3/library/warnings.html#available-functions).
26
+ """
27
+ filename, lineno = "<unknown>", 0
28
+ if show_source:
29
+ # Find the last non-Modal line that triggered the warning
30
+ try:
31
+ frame = sys._getframe()
32
+ while frame is not None and _is_internal_frame(frame):
33
+ frame = frame.f_back
34
+ if frame is not None:
35
+ filename = frame.f_code.co_filename
36
+ lineno = frame.f_lineno
37
+ except ValueError:
38
+ # Use the defaults from above
39
+ pass
40
+
41
+ warning_cls = PendingDeprecationError if pending else DeprecationError
42
+
43
+ # This is a lower-level function that warnings.warn uses
44
+ warnings.warn_explicit(f"{date(*deprecated_on)}: {msg}", warning_cls, filename, lineno)
modal/app.py CHANGED
@@ -22,6 +22,7 @@ from modal_proto import api_pb2
22
22
 
23
23
  from ._ipython import is_notebook
24
24
  from ._utils.async_utils import synchronize_api
25
+ from ._utils.deprecation import deprecation_error, deprecation_warning
25
26
  from ._utils.function_utils import FunctionInfo, is_global_object, is_method_fn
26
27
  from ._utils.grpc_utils import retry_transient_errors
27
28
  from ._utils.mount_utils import validate_volumes
@@ -29,7 +30,7 @@ from .client import _Client
29
30
  from .cloud_bucket_mount import _CloudBucketMount
30
31
  from .cls import _Cls, parameter
31
32
  from .config import logger
32
- from .exception import ExecutionError, InvalidError, deprecation_error, deprecation_warning
33
+ from .exception import ExecutionError, InvalidError
33
34
  from .functions import Function, _Function
34
35
  from .gpu import GPU_T
35
36
  from .image import _Image
modal/cli/app.py CHANGED
@@ -10,9 +10,9 @@ from rich.text import Text
10
10
  from typer import Argument
11
11
 
12
12
  from modal._utils.async_utils import synchronizer
13
+ from modal._utils.deprecation import deprecation_warning
13
14
  from modal.client import _Client
14
15
  from modal.environments import ensure_env
15
- from modal.exception import deprecation_warning
16
16
  from modal.object import _get_environment_name
17
17
  from modal_proto import api_pb2
18
18
 
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.27"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.29"
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.27"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.29"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/config.py CHANGED
@@ -86,8 +86,9 @@ from google.protobuf.empty_pb2 import Empty
86
86
 
87
87
  from modal_proto import api_pb2
88
88
 
89
+ from ._utils.deprecation import deprecation_error
89
90
  from ._utils.logger import configure_logger
90
- from .exception import InvalidError, deprecation_error
91
+ from .exception import InvalidError
91
92
 
92
93
  # Locate config file and read it
93
94
 
@@ -6,10 +6,11 @@ from typing import Generic, Optional, TypeVar
6
6
  from modal_proto import api_pb2
7
7
 
8
8
  from ._utils.async_utils import TaskContext, synchronize_api
9
+ from ._utils.deprecation import deprecation_error
9
10
  from ._utils.grpc_utils import retry_transient_errors
10
11
  from ._utils.shell_utils import stream_from_stdin, write_to_fd
11
12
  from .client import _Client
12
- from .exception import InteractiveTimeoutError, InvalidError, deprecation_error
13
+ from .exception import InteractiveTimeoutError, InvalidError
13
14
  from .io_streams import _StreamReader, _StreamWriter
14
15
  from .stream_type import StreamType
15
16
 
modal/dict.py CHANGED
@@ -10,11 +10,12 @@ from modal_proto import api_pb2
10
10
  from ._resolver import Resolver
11
11
  from ._serialization import deserialize, serialize
12
12
  from ._utils.async_utils import TaskContext, synchronize_api
13
+ from ._utils.deprecation import deprecation_error
13
14
  from ._utils.grpc_utils import retry_transient_errors
14
15
  from ._utils.name_utils import check_object_name
15
16
  from .client import _Client
16
17
  from .config import logger
17
- from .exception import RequestSizeError, deprecation_error
18
+ from .exception import RequestSizeError
18
19
  from .object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
19
20
 
20
21
 
modal/exception.py CHANGED
@@ -1,12 +1,6 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import random
3
3
  import signal
4
- import sys
5
- import warnings
6
- from datetime import date
7
- from typing import Iterable
8
-
9
- from modal_proto import api_pb2
10
4
 
11
5
 
12
6
  class Error(Exception):
@@ -129,45 +123,6 @@ class _CliUserExecutionError(Exception):
129
123
  self.user_source = user_source
130
124
 
131
125
 
132
- # TODO(erikbern): we have something similready in function_utils.py
133
- _INTERNAL_MODULES = ["modal", "synchronicity"]
134
-
135
-
136
- def _is_internal_frame(frame):
137
- module = frame.f_globals["__name__"].split(".")[0]
138
- return module in _INTERNAL_MODULES
139
-
140
-
141
- def deprecation_error(deprecated_on: tuple[int, int, int], msg: str):
142
- raise DeprecationError(f"Deprecated on {date(*deprecated_on)}: {msg}")
143
-
144
-
145
- def deprecation_warning(
146
- deprecated_on: tuple[int, int, int], msg: str, *, pending: bool = False, show_source: bool = True
147
- ) -> None:
148
- """Utility for getting the proper stack entry.
149
-
150
- See the implementation of the built-in [warnings.warn](https://docs.python.org/3/library/warnings.html#available-functions).
151
- """
152
- filename, lineno = "<unknown>", 0
153
- if show_source:
154
- # Find the last non-Modal line that triggered the warning
155
- try:
156
- frame = sys._getframe()
157
- while frame is not None and _is_internal_frame(frame):
158
- frame = frame.f_back
159
- filename = frame.f_code.co_filename
160
- lineno = frame.f_lineno
161
- except ValueError:
162
- # Use the defaults from above
163
- pass
164
-
165
- warning_cls: type = PendingDeprecationError if pending else DeprecationError
166
-
167
- # This is a lower-level function that warnings.warn uses
168
- warnings.warn_explicit(f"{date(*deprecated_on)}: {msg}", warning_cls, filename, lineno)
169
-
170
-
171
126
  def _simulate_preemption_interrupt(signum, frame):
172
127
  signal.alarm(30) # simulate a SIGKILL after 30s
173
128
  raise KeyboardInterrupt("Simulated preemption interrupt from modal-client!")
@@ -224,12 +179,3 @@ class ClientClosed(Error):
224
179
 
225
180
  class FilesystemExecutionError(Error):
226
181
  """Raised when an unknown error is thrown during a container filesystem operation."""
227
-
228
-
229
- def print_server_warnings(server_warnings: Iterable[api_pb2.Warning]):
230
- # TODO(erikbern): move this to modal._utils.deprecation
231
- for warning in server_warnings:
232
- if warning.type == api_pb2.Warning.WARNING_TYPE_CLIENT_DEPRECATION:
233
- warnings.warn_explicit(warning.message, DeprecationError, "<unknown>", 0)
234
- else:
235
- warnings.warn_explicit(warning.message, UserWarning, "<unknown>", 0)
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/functions.py CHANGED
@@ -41,6 +41,7 @@ from ._utils.async_utils import (
41
41
  synchronizer,
42
42
  warn_if_generator_is_not_consumed,
43
43
  )
44
+ from ._utils.deprecation import deprecation_warning
44
45
  from ._utils.function_utils import (
45
46
  ATTEMPT_TIMEOUT_GRACE_PERIOD,
46
47
  OUTPUTS_TIMEOUT,
@@ -58,14 +59,7 @@ from .call_graph import InputInfo, _reconstruct_call_graph
58
59
  from .client import _Client
59
60
  from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
60
61
  from .config import config
61
- from .exception import (
62
- ExecutionError,
63
- FunctionTimeoutError,
64
- InvalidError,
65
- NotFoundError,
66
- OutputExpiredError,
67
- deprecation_warning,
68
- )
62
+ from .exception import ExecutionError, FunctionTimeoutError, InvalidError, NotFoundError, OutputExpiredError
69
63
  from .gpu import GPU_T, parse_gpu_config
70
64
  from .image import _Image
71
65
  from .mount import _get_client_mount, _Mount, get_auto_mounts
modal/functions.pyi CHANGED
@@ -448,7 +448,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
448
448
 
449
449
  _call_function_nowait: ___call_function_nowait_spec
450
450
 
451
- def _call_generator(self, args, kwargs): ...
451
+ class ___call_generator_spec(typing_extensions.Protocol):
452
+ def __call__(self, args, kwargs): ...
453
+ def aio(self, args, kwargs): ...
454
+
455
+ _call_generator: ___call_generator_spec
452
456
 
453
457
  class ___call_generator_nowait_spec(typing_extensions.Protocol):
454
458
  def __call__(self, args, kwargs): ...
@@ -456,11 +460,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
456
460
 
457
461
  _call_generator_nowait: ___call_generator_nowait_spec
458
462
 
459
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
463
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
460
464
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
461
465
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
462
466
 
463
- remote: __remote_spec[ReturnType, P]
467
+ remote: __remote_spec[P, ReturnType]
464
468
 
465
469
  class __remote_gen_spec(typing_extensions.Protocol):
466
470
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -473,17 +477,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
473
477
  def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
474
478
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
475
479
 
476
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
480
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
477
481
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
478
482
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
479
483
 
480
- _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
484
+ _experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
481
485
 
482
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
486
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
483
487
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
484
488
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
485
489
 
486
- spawn: __spawn_spec[ReturnType, P]
490
+ spawn: __spawn_spec[P, ReturnType]
487
491
 
488
492
  def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
489
493
 
modal/image.py CHANGED
@@ -30,13 +30,14 @@ from ._resolver import Resolver
30
30
  from ._serialization import serialize
31
31
  from ._utils.async_utils import synchronize_api
32
32
  from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
33
+ from ._utils.deprecation import deprecation_error, deprecation_warning
33
34
  from ._utils.function_utils import FunctionInfo
34
35
  from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
35
36
  from .client import _Client
36
37
  from .cloud_bucket_mount import _CloudBucketMount
37
38
  from .config import config, logger, user_config_path
38
39
  from .environments import _get_environment_cached
39
- from .exception import InvalidError, NotFoundError, RemoteError, VersionError, deprecation_error, deprecation_warning
40
+ from .exception import InvalidError, NotFoundError, RemoteError, VersionError
40
41
  from .file_pattern_matcher import FilePatternMatcher
41
42
  from .gpu import GPU_T, parse_gpu_config
42
43
  from .mount import _Mount, python_standalone_mount_name
@@ -15,11 +15,12 @@ from modal_proto import api_pb2
15
15
  from ._resolver import Resolver
16
16
  from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
17
17
  from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
18
+ from ._utils.deprecation import deprecation_error
18
19
  from ._utils.grpc_utils import retry_transient_errors
19
20
  from ._utils.hash_utils import get_sha256_hex
20
21
  from ._utils.name_utils import check_object_name
21
22
  from .client import _Client
22
- from .exception import InvalidError, deprecation_error
23
+ from .exception import InvalidError
23
24
  from .object import (
24
25
  EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
25
26
  _get_environment_name,
modal/partial_function.py CHANGED
@@ -15,9 +15,10 @@ import typing_extensions
15
15
  from modal_proto import api_pb2
16
16
 
17
17
  from ._utils.async_utils import synchronize_api, synchronizer
18
+ from ._utils.deprecation import deprecation_error, deprecation_warning
18
19
  from ._utils.function_utils import callable_has_non_self_non_default_params, callable_has_non_self_params
19
20
  from .config import logger
20
- from .exception import InvalidError, deprecation_error, deprecation_warning
21
+ from .exception import InvalidError
21
22
  from .functions import _Function
22
23
 
23
24
  MAX_MAX_BATCH_SIZE = 1000
modal/queue.py CHANGED
@@ -13,10 +13,11 @@ from modal_proto import api_pb2
13
13
  from ._resolver import Resolver
14
14
  from ._serialization import deserialize, serialize
15
15
  from ._utils.async_utils import TaskContext, synchronize_api, warn_if_generator_is_not_consumed
16
+ from ._utils.deprecation import deprecation_error
16
17
  from ._utils.grpc_utils import retry_transient_errors
17
18
  from ._utils.name_utils import check_object_name
18
19
  from .client import _Client
19
- from .exception import InvalidError, RequestSizeError, deprecation_error
20
+ from .exception import InvalidError, RequestSizeError
20
21
  from .object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
21
22
 
22
23
 
modal/runner.py CHANGED
@@ -19,19 +19,14 @@ from ._resolver import Resolver
19
19
  from ._runtime.execution_context import is_local
20
20
  from ._traceback import print_server_warnings, traceback_contains_remote_call
21
21
  from ._utils.async_utils import TaskContext, gather_cancel_on_exc, synchronize_api
22
+ from ._utils.deprecation import deprecation_error
22
23
  from ._utils.grpc_utils import retry_transient_errors
23
24
  from ._utils.name_utils import check_object_name, is_valid_tag
24
25
  from .client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
25
26
  from .cls import _Cls
26
27
  from .config import config, logger
27
28
  from .environments import _get_environment_cached
28
- from .exception import (
29
- InteractiveTimeoutError,
30
- InvalidError,
31
- RemoteError,
32
- _CliUserExecutionError,
33
- deprecation_error,
34
- )
29
+ from .exception import InteractiveTimeoutError, InvalidError, RemoteError, _CliUserExecutionError
35
30
  from .functions import _Function
36
31
  from .object import _get_environment_name, _Object
37
32
  from .output import _get_output_manager, enable_output
modal/sandbox.py CHANGED
@@ -19,18 +19,13 @@ from ._location import parse_cloud_provider
19
19
  from ._resolver import Resolver
20
20
  from ._resources import convert_fn_config_to_resources_config
21
21
  from ._utils.async_utils import synchronize_api
22
+ from ._utils.deprecation import deprecation_error
22
23
  from ._utils.grpc_utils import retry_transient_errors
23
24
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
24
25
  from .client import _Client
25
26
  from .config import config
26
27
  from .container_process import _ContainerProcess
27
- from .exception import (
28
- ExecutionError,
29
- InvalidError,
30
- SandboxTerminatedError,
31
- SandboxTimeoutError,
32
- deprecation_error,
33
- )
28
+ from .exception import ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
34
29
  from .file_io import _FileIO
35
30
  from .gpu import GPU_T
36
31
  from .image import _Image
@@ -567,6 +562,21 @@ class _Sandbox(_Object, type_prefix="sb"):
567
562
  task_id = await self._get_task_id()
568
563
  return await _FileIO.create(path, mode, self._client, task_id)
569
564
 
565
+ async def ls(self, path: str) -> list[str]:
566
+ """List the contents of a directory in the Sandbox."""
567
+ task_id = await self._get_task_id()
568
+ return await _FileIO.ls(path, self._client, task_id)
569
+
570
+ async def mkdir(self, path: str, parents: bool = False) -> None:
571
+ """Create a new directory in the Sandbox."""
572
+ task_id = await self._get_task_id()
573
+ return await _FileIO.mkdir(path, self._client, task_id, parents)
574
+
575
+ async def rm(self, path: str, recursive: bool = False) -> None:
576
+ """Remove a file or directory in the Sandbox."""
577
+ task_id = await self._get_task_id()
578
+ return await _FileIO.rm(path, self._client, task_id, recursive)
579
+
570
580
  @property
571
581
  def stdout(self) -> _StreamReader[str]:
572
582
  """
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
modal/serving.py CHANGED
@@ -11,12 +11,12 @@ from synchronicity.async_wrap import asynccontextmanager
11
11
  from modal._output import OutputManager
12
12
 
13
13
  from ._utils.async_utils import TaskContext, asyncify, synchronize_api, synchronizer
14
+ from ._utils.deprecation import deprecation_error
14
15
  from ._utils.logger import logger
15
16
  from ._watcher import watch
16
17
  from .cli.import_refs import import_app
17
18
  from .client import _Client
18
19
  from .config import config
19
- from .exception import deprecation_error
20
20
  from .output import _get_output_manager, enable_output
21
21
  from .runner import _run_app, serve_update
22
22
 
modal/volume.py CHANGED
@@ -24,7 +24,7 @@ from grpclib import GRPCError, Status
24
24
  from synchronicity.async_wrap import asynccontextmanager
25
25
 
26
26
  import modal_proto.api_pb2
27
- from modal.exception import VolumeUploadTimeoutError, deprecation_error, deprecation_warning
27
+ from modal.exception import VolumeUploadTimeoutError
28
28
  from modal_proto import api_pb2
29
29
 
30
30
  from ._resolver import Resolver
@@ -36,6 +36,7 @@ from ._utils.blob_utils import (
36
36
  get_file_upload_spec_from_fileobj,
37
37
  get_file_upload_spec_from_path,
38
38
  )
39
+ from ._utils.deprecation import deprecation_error, deprecation_warning
39
40
  from ._utils.grpc_utils import retry_transient_errors
40
41
  from ._utils.name_utils import check_object_name
41
42
  from .client import _Client
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.27
3
+ Version: 0.68.29
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -15,66 +15,66 @@ 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=e-zwazkqvbtv0eXnIWQnfBW_v9g_6S9a8CGh1Bzr5HU,45423
18
+ modal/app.py,sha256=nt25_TXlzG4Kw_Ev4cgGaQnJaMnXN9-23tUh8dtR5xk,45454
19
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=9YTYpc1W_gagxXekcHSXylx-k8T2kRj4dkH1mSU2fwM,7280
22
+ modal/client.pyi,sha256=uk2qJcL5zrBtv_Q8oh8szilrVQTylCvusxHoa5s1A6s,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
26
26
  modal/cls.pyi,sha256=uoOEANXgCFT9Au3e-_bU98M6ZfAgQWF5ngj8f4c6qpY,8225
27
- modal/config.py,sha256=1KhNJkjYsJkX1V8RPPdRYPlM2HE-ZZs0JVSxbiXjmrw,11010
28
- modal/container_process.py,sha256=zDxCLk6KfJT1G9FfNtjom6gekBQ46op3TWepT7-Hkbg,6077
27
+ modal/config.py,sha256=BzhZYUUwOmvVwf6x5kf0ywMC257s648dmuhsnB6g3gk,11041
28
+ modal/container_process.py,sha256=WTqLn01dJPVkPpwR_0w_JH96ceN5mV4TGtiu1ZR2RRA,6108
29
29
  modal/container_process.pyi,sha256=dqtqBmyRpXXpRrDooESL6WBVU_1Rh6OG-66P2Hk9E5U,2666
30
- modal/dict.py,sha256=RmJlEwFJOdSfAYcVa50hbbFccV8e7BvC5tc5g1HXF-c,12622
30
+ modal/dict.py,sha256=put8iNVOoifRa7vOvMThZ2_NeHiDc474xBCsGCOZ8I4,12653
31
31
  modal/dict.pyi,sha256=2cYgOqBxYZih4BYgMV0c3rNPuxYR6-cB1GBXzFkHA5c,7265
32
32
  modal/environments.py,sha256=5cgA-zbm6ngKLsRA19zSOgtgo9-BarJK3FJK0BiF2Lo,6505
33
33
  modal/environments.pyi,sha256=XalNpiPkAtHWAvOU2Cotq0ozmtl-Jv0FDsR8h9mr27Q,3521
34
- modal/exception.py,sha256=dRK789TD1HaB63kHhu1yZuvS2vP_Vua3iLMBtA6dgqk,7128
34
+ modal/exception.py,sha256=GEV6xMnVnkle0gsFZVLB4B7cUMyw8HzVDvAvPr34ZV4,5185
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
- modal/functions.py,sha256=IIdHw0FNOdoMksG1b2zvkn8f-xskhJu07ZvHMey9iq4,67667
40
- modal/functions.pyi,sha256=bHbJiWW5TbFKKjDn7bSCFvOcUcAjPFqTStS-NAHPSeM,25068
39
+ modal/functions.py,sha256=ig7FjNHrzsYzIgpagLGphQYLybvtx_4m0Exp0ribO1c,67669
40
+ modal/functions.pyi,sha256=dsA-f4Jc-VvXWaKcPyzUSgDCBQ5ujKv55AL7rtkSCT4,25213
41
41
  modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
42
- modal/image.py,sha256=xQAC1gWOG0L77QfVfAbHfLwfVkvMYi2sy0V_Ah7GWPg,82253
42
+ modal/image.py,sha256=S4AjJf6yAL8I5rxDll2yOoNJI-2PJk6oUV-x14J9zck,82284
43
43
  modal/image.pyi,sha256=8R8Ac9eZ83ocM_1qrFUvH3rCbI5zRnq-Eq0xaAQq4nI,25105
44
44
  modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
45
45
  modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
46
46
  modal/mount.py,sha256=QKvrgpS_FMqrGdoyVZWeWnkNpQeDSLpuiwZFSGgRp_Y,29017
47
47
  modal/mount.pyi,sha256=a0WAFmT7kZvoq_ZAu6R6fwxiEUR6QSmeC_avUpJKGWM,10495
48
- modal/network_file_system.py,sha256=kwwQLCJVO086FTiAWSF_jz9BkqijZLpSbEYXpFvS0Ik,14600
48
+ modal/network_file_system.py,sha256=6H-TMvU5pet448aSnqzmOOFOgv-IVjjEiVFeYT68Qc4,14631
49
49
  modal/network_file_system.pyi,sha256=8mHKXuRkxHPazF6ljIW7g4M5aVqLSl6eKUPLgDCug5c,7901
50
50
  modal/object.py,sha256=HZs3N59C6JxlMuPQWJYvrWV1FEEkH9txUovVDorVUbs,9763
51
51
  modal/object.pyi,sha256=MO78H9yFSE5i1gExPEwyyQzLdlshkcGHN1aQ0ylyvq0,8802
52
52
  modal/output.py,sha256=N0xf4qeudEaYrslzdAl35VKV8rapstgIM2e9wO8_iy0,1967
53
53
  modal/parallel_map.py,sha256=4aoMXIrlG3wl5Ifk2YDNOQkXsGRsm6Xbfm6WtJ2t3WY,16002
54
54
  modal/parallel_map.pyi,sha256=pOhT0P3DDYlwLx0fR3PTsecA7DI8uOdXC1N8i-ZkyOY,2328
55
- modal/partial_function.py,sha256=zr_tFmTdoFxpTDw8cSwo_IjiWwZAyROCmQyl2z-Z6MY,29201
55
+ modal/partial_function.py,sha256=s87ILMvIfQY0gwGzRcdfLog-nqeyrl04kaQpj-w1gmg,29232
56
56
  modal/partial_function.pyi,sha256=pO6kf8i5HVsZ7CF0z_KkzLk4Aeq7NJhFJ_VNIycRXaU,9260
57
57
  modal/proxy.py,sha256=ZrOsuQP7dSZFq1OrIxalNnt0Zvsnp1h86Th679sSL40,1417
58
58
  modal/proxy.pyi,sha256=UvygdOYneLTuoDY6hVaMNCyZ947Tmx93IdLjErUqkvM,368
59
59
  modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- modal/queue.py,sha256=fJYFfpdrlVj_doc3QxgvJvv7c8BGHjjja5q_9HCtSqs,18658
60
+ modal/queue.py,sha256=KJmY2kakfZZWwkYqTJ5maUwCty-Ijo3co1P5gHuzFDg,18689
61
61
  modal/queue.pyi,sha256=di3ownBw4jc6d4X7ygXtbpjlUMOK69qyaD3lVsJbpoM,9900
62
62
  modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
63
- modal/runner.py,sha256=Q02VdfLCO7YKpnOSqqh58XL3hR2XHaDeiJVYW3MKz_8,24580
63
+ modal/runner.py,sha256=qfkB0OM97kb_-oP-D5KPj_jUwfd8ePUA3R_zLkjSTBQ,24586
64
64
  modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
65
65
  modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
66
- modal/sandbox.py,sha256=pPHRLcShPoexUaMJUJecsH6YJJVwQtAWc8ouxH3i4y4,27344
67
- modal/sandbox.pyi,sha256=QPNuiTLNoKwYf8JK_fmfUBXpdGYlukyaksFV1DpCd2g,18987
66
+ modal/sandbox.py,sha256=YJaAeeBiyf3k1loHpLyJ3kwobEhS0Jnr7voC9venNJA,28049
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
71
71
  modal/secret.pyi,sha256=B_9QWiLvTwSopLsAHre-qSB2VS74cqfhtbA4GS8faQY,2957
72
- modal/serving.py,sha256=5JMDpcbf0ETse884R-hgXG3kCbhpQb5gqH0oQpqad34,4455
72
+ modal/serving.py,sha256=MnVuTsimN05LfNPxuJZ4sr5s1_BPUkIsOP_VC-bkp78,4464
73
73
  modal/serving.pyi,sha256=ncV-9jY_vZYFnGs5ZnMb3ffrX8LmcLdIMHBC56xRbtE,1711
74
74
  modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
75
75
  modal/token_flow.py,sha256=LcgSce_MSQ2p7j55DPwpVRpiAtCDe8GRSEwzO7muNR8,6774
76
76
  modal/token_flow.pyi,sha256=gOYtYujrWt_JFZeiI8EmfahXPx5GCR5Na-VaPQcWgEY,1937
77
- modal/volume.py,sha256=PGzbninvRU-IhSwJgM2jZKzD8llRhZhadsOxZ-YNwaM,29316
77
+ modal/volume.py,sha256=m25hZctl3zV5EMRM5BTd8mVDe66Scysbt__z6IXXa5g,29347
78
78
  modal/volume.pyi,sha256=St0mDiaojfep6Bs4sBbkRJmeacYHF6lh6FKOWGmheHA,11182
79
79
  modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
80
80
  modal/_runtime/asgi.py,sha256=H68KAN8bz8Zp7EcRl2c_ite1Y3kP1MHvjQAf-uUpCx8,21691
@@ -87,6 +87,7 @@ modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
87
87
  modal/_utils/async_utils.py,sha256=9ubwMkwiDB4gzOYG2jL9j7Fs-5dxHjcifZe3r7JRg-k,25091
88
88
  modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14509
89
89
  modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
90
+ modal/_utils/deprecation.py,sha256=9yHc2_bIuvivx-oYOzg-kj6JIolPdxXBlEkOVgnZcFA,1556
90
91
  modal/_utils/function_utils.py,sha256=LgcveUUb4XU_dWxtqgK_3ujZBvS3cGVzcDOkljyFZ2w,25066
91
92
  modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
92
93
  modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
@@ -106,7 +107,7 @@ modal/_vendor/tblib.py,sha256=g1O7QUDd3sDoLd8YPFltkXkih7r_fyZOjgmGuligv3s,9722
106
107
  modal/cli/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
107
108
  modal/cli/_download.py,sha256=t6BXZwjTd9MgznDvbsV8rp0FZWggdzC-lUAGZU4xx1g,3984
108
109
  modal/cli/_traceback.py,sha256=QlLa_iw3fAOA-mqCqjS8qAxvNT48J3YY3errtVVc2cw,7316
109
- modal/cli/app.py,sha256=KOU3tKdcw50612rmN2LmO-N8cT1M1-UgLs7tw68Kgds,7717
110
+ modal/cli/app.py,sha256=HkwI38FZxx66jxiur4o_DRN3uwyO3L8hqgyo8oXKZxc,7726
110
111
  modal/cli/config.py,sha256=pXPLmX0bIoV57rQNqIPK7V-yllj-GPRY4jiBO_EklGg,1667
111
112
  modal/cli/container.py,sha256=nCySVD10VJPzmX3ghTsGmpxdYeVYYMW6ofjsyt2gQcM,3667
112
113
  modal/cli/dict.py,sha256=gwX4ZBsrr0dpWf_B5_5GN_ealcVzpcGyvY24dEY4y3Y,4455
@@ -163,10 +164,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
163
164
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
165
  modal_version/__init__.py,sha256=RT6zPoOdFO99u5Wcxxaoir4ZCuPTbQ22cvzFAXl3vUY,470
165
166
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
166
- modal_version/_version_generated.py,sha256=-fthnpx3dlNaui7c3f7RAZuXLNwCnL-H9fI359j-_3Y,149
167
- modal-0.68.27.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
168
- modal-0.68.27.dist-info/METADATA,sha256=jSpBvGP2tKY45IX_6MISrLQxLm1QI8OT8DRPVY5vzlQ,2329
169
- modal-0.68.27.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
170
- modal-0.68.27.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
171
- modal-0.68.27.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
172
- modal-0.68.27.dist-info/RECORD,,
167
+ modal_version/_version_generated.py,sha256=UQ2M1CvKKAS9AQwfABAriMylUuZnYUEjvNsyc9Pj-AI,149
168
+ modal-0.68.29.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
169
+ modal-0.68.29.dist-info/METADATA,sha256=Htn3XrX5yV5OSlQzh51K9Z9IF5rKMkgaElRR9OnpmgQ,2329
170
+ modal-0.68.29.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
171
+ modal-0.68.29.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
172
+ modal-0.68.29.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
173
+ modal-0.68.29.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 = 27 # git: 8342982
4
+ build_number = 29 # git: 0af7fd9