modal 1.1.5.dev83__py3-none-any.whl → 1.3.1.dev8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (139) hide show
  1. modal/__init__.py +4 -4
  2. modal/__main__.py +4 -29
  3. modal/_billing.py +84 -0
  4. modal/_clustered_functions.py +1 -3
  5. modal/_container_entrypoint.py +33 -208
  6. modal/_functions.py +146 -121
  7. modal/_grpc_client.py +191 -0
  8. modal/_ipython.py +16 -6
  9. modal/_load_context.py +106 -0
  10. modal/_object.py +72 -21
  11. modal/_output.py +12 -14
  12. modal/_partial_function.py +31 -4
  13. modal/_resolver.py +44 -57
  14. modal/_runtime/container_io_manager.py +26 -28
  15. modal/_runtime/container_io_manager.pyi +42 -44
  16. modal/_runtime/gpu_memory_snapshot.py +9 -7
  17. modal/_runtime/user_code_event_loop.py +80 -0
  18. modal/_runtime/user_code_imports.py +236 -10
  19. modal/_serialization.py +2 -1
  20. modal/_traceback.py +4 -13
  21. modal/_tunnel.py +16 -11
  22. modal/_tunnel.pyi +25 -3
  23. modal/_utils/async_utils.py +337 -10
  24. modal/_utils/auth_token_manager.py +1 -4
  25. modal/_utils/blob_utils.py +29 -22
  26. modal/_utils/function_utils.py +20 -21
  27. modal/_utils/grpc_testing.py +6 -3
  28. modal/_utils/grpc_utils.py +223 -64
  29. modal/_utils/mount_utils.py +26 -1
  30. modal/_utils/package_utils.py +0 -1
  31. modal/_utils/rand_pb_testing.py +8 -1
  32. modal/_utils/task_command_router_client.py +524 -0
  33. modal/_vendor/cloudpickle.py +144 -48
  34. modal/app.py +215 -96
  35. modal/app.pyi +78 -37
  36. modal/billing.py +5 -0
  37. modal/builder/2025.06.txt +6 -3
  38. modal/builder/PREVIEW.txt +2 -1
  39. modal/builder/base-images.json +4 -2
  40. modal/cli/_download.py +19 -3
  41. modal/cli/cluster.py +4 -2
  42. modal/cli/config.py +3 -1
  43. modal/cli/container.py +5 -4
  44. modal/cli/dict.py +5 -2
  45. modal/cli/entry_point.py +26 -2
  46. modal/cli/environment.py +2 -16
  47. modal/cli/launch.py +1 -76
  48. modal/cli/network_file_system.py +5 -20
  49. modal/cli/queues.py +5 -4
  50. modal/cli/run.py +24 -204
  51. modal/cli/secret.py +1 -2
  52. modal/cli/shell.py +375 -0
  53. modal/cli/utils.py +1 -13
  54. modal/cli/volume.py +11 -17
  55. modal/client.py +16 -125
  56. modal/client.pyi +94 -144
  57. modal/cloud_bucket_mount.py +3 -1
  58. modal/cloud_bucket_mount.pyi +4 -0
  59. modal/cls.py +101 -64
  60. modal/cls.pyi +9 -8
  61. modal/config.py +21 -1
  62. modal/container_process.py +288 -12
  63. modal/container_process.pyi +99 -38
  64. modal/dict.py +72 -33
  65. modal/dict.pyi +88 -57
  66. modal/environments.py +16 -8
  67. modal/environments.pyi +6 -2
  68. modal/exception.py +154 -16
  69. modal/experimental/__init__.py +23 -5
  70. modal/experimental/flash.py +161 -74
  71. modal/experimental/flash.pyi +97 -49
  72. modal/file_io.py +50 -92
  73. modal/file_io.pyi +117 -89
  74. modal/functions.pyi +70 -87
  75. modal/image.py +73 -47
  76. modal/image.pyi +33 -30
  77. modal/io_streams.py +500 -149
  78. modal/io_streams.pyi +279 -189
  79. modal/mount.py +60 -45
  80. modal/mount.pyi +41 -17
  81. modal/network_file_system.py +19 -11
  82. modal/network_file_system.pyi +72 -39
  83. modal/object.pyi +114 -22
  84. modal/parallel_map.py +42 -44
  85. modal/parallel_map.pyi +9 -17
  86. modal/partial_function.pyi +4 -2
  87. modal/proxy.py +14 -6
  88. modal/proxy.pyi +10 -2
  89. modal/queue.py +45 -38
  90. modal/queue.pyi +88 -52
  91. modal/runner.py +96 -96
  92. modal/runner.pyi +44 -27
  93. modal/sandbox.py +225 -108
  94. modal/sandbox.pyi +226 -63
  95. modal/secret.py +58 -56
  96. modal/secret.pyi +28 -13
  97. modal/serving.py +7 -11
  98. modal/serving.pyi +7 -8
  99. modal/snapshot.py +29 -15
  100. modal/snapshot.pyi +18 -10
  101. modal/token_flow.py +1 -1
  102. modal/token_flow.pyi +4 -6
  103. modal/volume.py +102 -55
  104. modal/volume.pyi +125 -66
  105. {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
  106. modal-1.3.1.dev8.dist-info/RECORD +189 -0
  107. modal_proto/api.proto +86 -30
  108. modal_proto/api_grpc.py +10 -25
  109. modal_proto/api_pb2.py +1080 -1047
  110. modal_proto/api_pb2.pyi +253 -79
  111. modal_proto/api_pb2_grpc.py +14 -48
  112. modal_proto/api_pb2_grpc.pyi +6 -18
  113. modal_proto/modal_api_grpc.py +175 -176
  114. modal_proto/{sandbox_router.proto → task_command_router.proto} +62 -45
  115. modal_proto/task_command_router_grpc.py +138 -0
  116. modal_proto/task_command_router_pb2.py +180 -0
  117. modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +110 -63
  118. modal_proto/task_command_router_pb2_grpc.py +272 -0
  119. modal_proto/task_command_router_pb2_grpc.pyi +100 -0
  120. modal_version/__init__.py +1 -1
  121. modal_version/__main__.py +1 -1
  122. modal/cli/programs/launch_instance_ssh.py +0 -94
  123. modal/cli/programs/run_marimo.py +0 -95
  124. modal-1.1.5.dev83.dist-info/RECORD +0 -191
  125. modal_proto/modal_options_grpc.py +0 -3
  126. modal_proto/options.proto +0 -19
  127. modal_proto/options_grpc.py +0 -3
  128. modal_proto/options_pb2.py +0 -35
  129. modal_proto/options_pb2.pyi +0 -20
  130. modal_proto/options_pb2_grpc.py +0 -4
  131. modal_proto/options_pb2_grpc.pyi +0 -7
  132. modal_proto/sandbox_router_grpc.py +0 -105
  133. modal_proto/sandbox_router_pb2.py +0 -148
  134. modal_proto/sandbox_router_pb2_grpc.py +0 -203
  135. modal_proto/sandbox_router_pb2_grpc.pyi +0 -75
  136. {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
  137. {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
  138. {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
  139. {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ from typing import Optional
6
6
 
7
7
  import typer
8
8
  from click import UsageError
9
- from grpclib import GRPCError, Status
10
9
  from rich.syntax import Syntax
11
10
  from rich.table import Table
12
11
  from typer import Argument, Typer
@@ -15,7 +14,6 @@ import modal
15
14
  from modal._location import display_location
16
15
  from modal._output import OutputManager, ProgressHandler, make_console
17
16
  from modal._utils.async_utils import synchronizer
18
- from modal._utils.grpc_utils import retry_transient_errors
19
17
  from modal._utils.time_utils import timestamp_to_localized_str
20
18
  from modal.cli._download import _volume_download
21
19
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
@@ -33,9 +31,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: Optional[bool] = False):
33
31
  env = ensure_env(env)
34
32
 
35
33
  client = await _Client.from_env()
36
- response = await retry_transient_errors(
37
- client.stub.SharedVolumeList, api_pb2.SharedVolumeListRequest(environment_name=env)
38
- )
34
+ response = await client.stub.SharedVolumeList(api_pb2.SharedVolumeListRequest(environment_name=env))
39
35
  env_part = f" in environment '{env}'" if env else ""
40
36
  column_names = ["Name", "Location", "Created at"]
41
37
  rows = []
@@ -84,12 +80,7 @@ async def ls(
84
80
  ):
85
81
  ensure_env(env)
86
82
  volume = _NetworkFileSystem.from_name(volume_name)
87
- try:
88
- entries = await volume.listdir(path)
89
- except GRPCError as exc:
90
- if exc.status in (Status.INVALID_ARGUMENT, Status.NOT_FOUND):
91
- raise UsageError(exc.message)
92
- raise
83
+ entries = await volume.listdir(path)
93
84
 
94
85
  if sys.stdout.isatty():
95
86
  console = make_console()
@@ -105,7 +96,7 @@ async def ls(
105
96
  console.print(table)
106
97
  else:
107
98
  for entry in entries:
108
- print(entry.path)
99
+ print(entry.path) # noqa: T201
109
100
 
110
101
 
111
102
  @nfs_cli.command(
@@ -203,14 +194,8 @@ async def rm(
203
194
  ensure_env(env)
204
195
  volume = _NetworkFileSystem.from_name(volume_name)
205
196
  console = make_console()
206
- try:
207
- await volume.remove_file(remote_path, recursive=recursive)
208
- console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
209
-
210
- except GRPCError as exc:
211
- if exc.status in (Status.NOT_FOUND, Status.INVALID_ARGUMENT):
212
- raise UsageError(exc.message)
213
- raise
197
+ await volume.remove_file(remote_path, recursive=recursive)
198
+ console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
214
199
 
215
200
 
216
201
  @nfs_cli.command(
modal/cli/queues.py CHANGED
@@ -5,10 +5,10 @@ from typing import Optional
5
5
  import typer
6
6
  from typer import Argument, Option, Typer
7
7
 
8
+ from modal._load_context import LoadContext
8
9
  from modal._output import make_console
9
10
  from modal._resolver import Resolver
10
11
  from modal._utils.async_utils import synchronizer
11
- from modal._utils.grpc_utils import retry_transient_errors
12
12
  from modal._utils.time_utils import timestamp_to_localized_str
13
13
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
14
14
  from modal.client import _Client
@@ -39,8 +39,9 @@ async def create(name: str, *, env: Optional[str] = ENV_OPTION):
39
39
  """
40
40
  q = _Queue.from_name(name, environment_name=env, create_if_missing=True)
41
41
  client = await _Client.from_env()
42
- resolver = Resolver(client=client)
43
- await resolver.load(q)
42
+ resolver = Resolver()
43
+ load_context = LoadContext(client=client, environment_name=env)
44
+ await resolver.load(q, load_context)
44
45
 
45
46
 
46
47
  @queue_cli.command(name="delete", rich_help_panel="Management")
@@ -83,7 +84,7 @@ async def list_(*, json: bool = False, env: Optional[str] = ENV_OPTION):
83
84
  max_page_size = 100
84
85
  pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
85
86
  req = api_pb2.QueueListRequest(environment_name=env, pagination=pagination, total_size_limit=max_total_size)
86
- resp = await retry_transient_errors(client.stub.QueueList, req)
87
+ resp = await client.stub.QueueList(req)
87
88
  items.extend(resp.queues)
88
89
  return len(resp.queues) < max_page_size
89
90
 
modal/cli/run.py CHANGED
@@ -2,14 +2,11 @@
2
2
  import asyncio
3
3
  import functools
4
4
  import inspect
5
- import platform
6
5
  import re
7
- import shlex
8
6
  import sys
9
7
  import time
10
8
  import typing
11
9
  from dataclasses import dataclass
12
- from functools import partial
13
10
  from typing import Any, Callable, Optional
14
11
 
15
12
  import click
@@ -17,19 +14,15 @@ import typer
17
14
  from click import ClickException
18
15
  from typing_extensions import TypedDict
19
16
 
20
- from .._functions import _FunctionSpec
21
17
  from ..app import App, LocalEntrypoint
22
18
  from ..cls import _get_class_constructor_signature
23
19
  from ..config import config
24
20
  from ..environments import ensure_env
25
- from ..exception import ExecutionError, InvalidError, NotFoundError, _CliUserExecutionError
21
+ from ..exception import ExecutionError, InvalidError, _CliUserExecutionError
26
22
  from ..functions import Function
27
- from ..image import Image
28
23
  from ..output import enable_output
29
- from ..runner import deploy_app, interactive_shell, run_app
30
- from ..secret import Secret
24
+ from ..runner import deploy_app, run_app
31
25
  from ..serving import serve_app
32
- from ..volume import Volume
33
26
  from .import_refs import (
34
27
  CLICommand,
35
28
  MethodReference,
@@ -38,7 +31,7 @@ from .import_refs import (
38
31
  import_app_from_ref,
39
32
  parse_import_ref,
40
33
  )
41
- from .utils import ENV_OPTION, ENV_OPTION_HELP, is_tty, stream_app_logs
34
+ from .utils import ENV_OPTION, ENV_OPTION_HELP, stream_app_logs
42
35
 
43
36
 
44
37
  class ParameterMetadata(TypedDict):
@@ -136,7 +129,7 @@ def _add_click_options(func, parameters: dict[str, ParameterMetadata]):
136
129
 
137
130
  parser = option_parsers.get(param_type_str)
138
131
  if parser is None:
139
- msg = f"Parameter `{param_name}` has unparseable annotation: {param['annotation']!r}"
132
+ msg = f"Parameter `{param_name}` has unparseable annotation: {param['annotation']}"
140
133
  raise NoParserAvailable(msg)
141
134
  kwargs: Any = {
142
135
  "type": parser,
@@ -207,11 +200,21 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
207
200
  return f
208
201
 
209
202
 
203
+ def _get_signature(func: typing.Any) -> inspect.Signature:
204
+ """Returns signature with the original source annotations."""
205
+ kwargs: dict[str, typing.Any] = {}
206
+ if sys.version_info[:2] >= (3, 14):
207
+ import annotationlib
208
+
209
+ kwargs["annotation_format"] = annotationlib.Format.STRING
210
+ return inspect.signature(func, **kwargs)
211
+
212
+
210
213
  def _get_click_command_for_function(app: App, function: Function, ctx: click.Context):
211
214
  if function.is_generator:
212
215
  raise InvalidError("`modal run` is not supported for generator functions")
213
216
 
214
- sig: inspect.Signature = inspect.signature(function.info.raw_f)
217
+ sig: inspect.Signature = _get_signature(function.info.raw_f)
215
218
  type_hints = safe_get_type_hints(function.info.raw_f)
216
219
  signature: CliRunnableSignature = _get_cli_runnable_signature(sig, type_hints)
217
220
 
@@ -260,7 +263,7 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference, ctx: click
260
263
 
261
264
  partial_function = partial_functions[method_name]
262
265
  raw_f = partial_function._get_raw_f()
263
- sig_without_self = inspect.signature(functools.partial(raw_f, None))
266
+ sig_without_self = _get_signature(functools.partial(raw_f, None))
264
267
  fun_signature = _get_cli_runnable_signature(sig_without_self, safe_get_type_hints(raw_f))
265
268
 
266
269
  # TODO(erikbern): assert there's no overlap?
@@ -294,12 +297,12 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
294
297
  func = entrypoint.info.raw_f
295
298
  isasync = inspect.iscoroutinefunction(func)
296
299
 
297
- signature = _get_cli_runnable_signature(inspect.signature(func), safe_get_type_hints(func))
300
+ signature = _get_cli_runnable_signature(_get_signature(func), safe_get_type_hints(func))
298
301
 
299
302
  @click.pass_context
300
303
  def f(ctx, *args, **kwargs):
301
304
  if ctx.obj["detach"]:
302
- print(
305
+ print( # noqa: T201
303
306
  "Note that running a local entrypoint in detached mode only keeps the last "
304
307
  "triggered Modal function alive after the parent process has been killed or disconnected."
305
308
  )
@@ -496,6 +499,12 @@ def serve(
496
499
  ```
497
500
  modal serve hello_world.py
498
501
  ```
502
+
503
+ Modal-generated URLs will have a `-dev` suffix appended to them when running with `modal serve`.
504
+ To customize this suffix (i.e., to avoid collisions with other users in your workspace who are
505
+ concurrently serving the App), you can set the `dev_suffix` in your `.modal.toml` file or the
506
+ `MODAL_DEV_SUFFIX` environment variable.
507
+
499
508
  """
500
509
  env = ensure_env(env)
501
510
  import_ref = parse_import_ref(app_ref, use_module_mode=use_module_mode)
@@ -513,192 +522,3 @@ def serve(
513
522
  t = min(timeout, 3600)
514
523
  time.sleep(t)
515
524
  timeout -= t
516
-
517
-
518
- def shell(
519
- ref: Optional[str] = typer.Argument(
520
- default=None,
521
- help=(
522
- "ID of running container or Sandbox, or path to a Python file containing an App."
523
- " Can also include a Function specifier, like `module.py::func`, if the file defines multiple Functions."
524
- ),
525
- ),
526
- cmd: str = typer.Option("/bin/bash", "-c", "--cmd", help="Command to run inside the Modal image."),
527
- env: str = ENV_OPTION,
528
- image: Optional[str] = typer.Option(
529
- default=None, help="Container image tag for inside the shell (if not using REF)."
530
- ),
531
- add_python: Optional[str] = typer.Option(default=None, help="Add Python to the image (if not using REF)."),
532
- volume: Optional[list[str]] = typer.Option(
533
- default=None,
534
- help=(
535
- "Name of a `modal.Volume` to mount inside the shell at `/mnt/{name}` (if not using REF)."
536
- " Can be used multiple times."
537
- ),
538
- ),
539
- secret: Optional[list[str]] = typer.Option(
540
- default=None,
541
- help=("Name of a `modal.Secret` to mount inside the shell (if not using REF). Can be used multiple times."),
542
- ),
543
- cpu: Optional[int] = typer.Option(default=None, help="Number of CPUs to allocate to the shell (if not using REF)."),
544
- memory: Optional[int] = typer.Option(
545
- default=None, help="Memory to allocate for the shell, in MiB (if not using REF)."
546
- ),
547
- gpu: Optional[str] = typer.Option(
548
- default=None,
549
- help="GPUs to request for the shell, if any. Examples are `any`, `a10g`, `a100:4` (if not using REF).",
550
- ),
551
- cloud: Optional[str] = typer.Option(
552
- default=None,
553
- help=(
554
- "Cloud provider to run the shell on. Possible values are `aws`, `gcp`, `oci`, `auto` (if not using REF)."
555
- ),
556
- ),
557
- region: Optional[str] = typer.Option(
558
- default=None,
559
- help=(
560
- "Region(s) to run the container on. "
561
- "Can be a single region or a comma-separated list to choose from (if not using REF)."
562
- ),
563
- ),
564
- pty: Optional[bool] = typer.Option(default=None, help="Run the command using a PTY."),
565
- use_module_mode: bool = typer.Option(
566
- False, "-m", help="Interpret argument as a Python module path instead of a file/script path"
567
- ),
568
- ):
569
- """Run a command or interactive shell inside a Modal container.
570
-
571
- **Examples:**
572
-
573
- Start an interactive shell inside the default Debian-based image:
574
-
575
- ```
576
- modal shell
577
- ```
578
-
579
- Start an interactive shell with the spec for `my_function` in your App
580
- (uses the same image, volumes, mounts, etc.):
581
-
582
- ```
583
- modal shell hello_world.py::my_function
584
- ```
585
-
586
- Or, if you're using a [modal.Cls](https://modal.com/docs/reference/modal.Cls)
587
- you can refer to a `@modal.method` directly:
588
-
589
- ```
590
- modal shell hello_world.py::MyClass.my_method
591
- ```
592
-
593
- Start a `python` shell:
594
-
595
- ```
596
- modal shell hello_world.py --cmd=python
597
- ```
598
-
599
- Run a command with your function's spec and pipe the output to a file:
600
-
601
- ```
602
- modal shell hello_world.py -c 'uv pip list' > env.txt
603
- ```
604
-
605
- Connect to a running Sandbox by ID:
606
-
607
- ```
608
- modal shell sb-abc123xyz
609
- ```
610
- """
611
- env = ensure_env(env)
612
-
613
- if pty is None:
614
- pty = is_tty()
615
-
616
- if platform.system() == "Windows":
617
- raise InvalidError("`modal shell` is currently not supported on Windows")
618
-
619
- app = App("modal shell")
620
-
621
- if ref is not None:
622
- # `modal shell` with a sandbox ID gets the task_id, that's then handled by the `ta-*` flow below.
623
- if ref.startswith("sb-") and len(ref[3:]) > 0 and ref[3:].isalnum():
624
- from ..sandbox import Sandbox
625
-
626
- try:
627
- sandbox = Sandbox.from_id(ref)
628
- task_id = sandbox._get_task_id()
629
- ref = task_id
630
- except NotFoundError as e:
631
- raise ClickException(f"Sandbox '{ref}' not found")
632
- except Exception as e:
633
- raise ClickException(f"Error connecting to sandbox '{ref}': {str(e)}")
634
-
635
- # `modal shell` with a container ID is a special case, alias for `modal container exec`.
636
- if ref.startswith("ta-") and len(ref[3:]) > 0 and ref[3:].isalnum():
637
- from .container import exec
638
-
639
- exec(container_id=ref, command=shlex.split(cmd), pty=pty)
640
- return
641
-
642
- import_ref = parse_import_ref(ref, use_module_mode=use_module_mode)
643
- runnable, all_usable_commands = import_and_filter(
644
- import_ref, base_cmd="modal shell", accept_local_entrypoint=False, accept_webhook=True
645
- )
646
- if not runnable:
647
- help_header = (
648
- "Specify a Modal function to start a shell session for. E.g.\n"
649
- f"> modal shell {import_ref.file_or_module}::my_function"
650
- )
651
-
652
- if all_usable_commands:
653
- help_footer = f"The selected module '{import_ref.file_or_module}' has the following choices:\n\n"
654
- help_footer += _get_runnable_list(all_usable_commands)
655
- else:
656
- help_footer = f"The selected module '{import_ref.file_or_module}' has no Modal functions or classes."
657
-
658
- raise ClickException(f"{help_header}\n\n{help_footer}")
659
-
660
- function_spec: _FunctionSpec
661
- if isinstance(runnable, MethodReference):
662
- # TODO: let users specify a class instead of a method, since they use the same environment
663
- class_service_function = runnable.cls._get_class_service_function()
664
- function_spec = class_service_function.spec
665
- elif isinstance(runnable, Function):
666
- function_spec = runnable.spec
667
- else:
668
- raise ValueError("Referenced entity is not a Modal function or class")
669
-
670
- start_shell = partial(
671
- interactive_shell,
672
- image=function_spec.image,
673
- mounts=function_spec.mounts,
674
- secrets=function_spec.secrets,
675
- network_file_systems=function_spec.network_file_systems,
676
- gpu=function_spec.gpus,
677
- cloud=function_spec.cloud,
678
- cpu=function_spec.cpu,
679
- memory=function_spec.memory,
680
- volumes=function_spec.volumes,
681
- region=function_spec.scheduler_placement.proto.regions if function_spec.scheduler_placement else None,
682
- pty=pty,
683
- proxy=function_spec.proxy,
684
- )
685
- else:
686
- modal_image = Image.from_registry(image, add_python=add_python) if image else None
687
- volumes = {} if volume is None else {f"/mnt/{vol}": Volume.from_name(vol) for vol in volume}
688
- secrets = [] if secret is None else [Secret.from_name(s) for s in secret]
689
- start_shell = partial(
690
- interactive_shell,
691
- image=modal_image,
692
- cpu=cpu,
693
- memory=memory,
694
- gpu=gpu,
695
- cloud=cloud,
696
- volumes=volumes,
697
- secrets=secrets,
698
- region=region.split(",") if region else [],
699
- pty=pty,
700
- )
701
-
702
- # NB: invoking under bash makes --cmd a lot more flexible.
703
- cmds = shlex.split(f'/bin/bash -c "{cmd}"')
704
- start_shell(app, cmds=cmds, environment_name=env, timeout=3600)
modal/cli/secret.py CHANGED
@@ -15,7 +15,6 @@ from typer import Argument, Option
15
15
 
16
16
  from modal._output import make_console
17
17
  from modal._utils.async_utils import synchronizer
18
- from modal._utils.grpc_utils import retry_transient_errors
19
18
  from modal._utils.time_utils import timestamp_to_localized_str
20
19
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
21
20
  from modal.client import _Client
@@ -44,7 +43,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
44
43
  max_page_size = 100
45
44
  pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
46
45
  req = api_pb2.SecretListRequest(environment_name=env, pagination=pagination)
47
- resp = await retry_transient_errors(client.stub.SecretList, req)
46
+ resp = await client.stub.SecretList(req)
48
47
  items.extend(resp.items)
49
48
  return len(resp.items) < max_page_size
50
49