modal 1.0.6.dev58__py3-none-any.whl → 1.2.3.dev7__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 (147) hide show
  1. modal/__main__.py +3 -4
  2. modal/_billing.py +80 -0
  3. modal/_clustered_functions.py +7 -3
  4. modal/_clustered_functions.pyi +4 -2
  5. modal/_container_entrypoint.py +41 -49
  6. modal/_functions.py +424 -195
  7. modal/_grpc_client.py +171 -0
  8. modal/_load_context.py +105 -0
  9. modal/_object.py +68 -20
  10. modal/_output.py +58 -45
  11. modal/_partial_function.py +36 -11
  12. modal/_pty.py +7 -3
  13. modal/_resolver.py +21 -35
  14. modal/_runtime/asgi.py +4 -3
  15. modal/_runtime/container_io_manager.py +301 -186
  16. modal/_runtime/container_io_manager.pyi +70 -61
  17. modal/_runtime/execution_context.py +18 -2
  18. modal/_runtime/execution_context.pyi +4 -1
  19. modal/_runtime/gpu_memory_snapshot.py +170 -63
  20. modal/_runtime/user_code_imports.py +28 -58
  21. modal/_serialization.py +57 -1
  22. modal/_utils/async_utils.py +33 -12
  23. modal/_utils/auth_token_manager.py +2 -5
  24. modal/_utils/blob_utils.py +110 -53
  25. modal/_utils/function_utils.py +49 -42
  26. modal/_utils/grpc_utils.py +80 -50
  27. modal/_utils/mount_utils.py +26 -1
  28. modal/_utils/name_utils.py +17 -3
  29. modal/_utils/task_command_router_client.py +536 -0
  30. modal/_utils/time_utils.py +34 -6
  31. modal/app.py +219 -83
  32. modal/app.pyi +229 -56
  33. modal/billing.py +5 -0
  34. modal/{requirements → builder}/2025.06.txt +1 -0
  35. modal/{requirements → builder}/PREVIEW.txt +1 -0
  36. modal/cli/_download.py +19 -3
  37. modal/cli/_traceback.py +3 -2
  38. modal/cli/app.py +4 -4
  39. modal/cli/cluster.py +15 -7
  40. modal/cli/config.py +5 -3
  41. modal/cli/container.py +7 -6
  42. modal/cli/dict.py +22 -16
  43. modal/cli/entry_point.py +12 -5
  44. modal/cli/environment.py +5 -4
  45. modal/cli/import_refs.py +3 -3
  46. modal/cli/launch.py +102 -5
  47. modal/cli/network_file_system.py +9 -13
  48. modal/cli/profile.py +3 -2
  49. modal/cli/programs/launch_instance_ssh.py +94 -0
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/run_marimo.py +95 -0
  52. modal/cli/programs/vscode.py +1 -1
  53. modal/cli/queues.py +57 -26
  54. modal/cli/run.py +58 -16
  55. modal/cli/secret.py +48 -22
  56. modal/cli/utils.py +3 -4
  57. modal/cli/volume.py +28 -25
  58. modal/client.py +13 -116
  59. modal/client.pyi +9 -91
  60. modal/cloud_bucket_mount.py +5 -3
  61. modal/cloud_bucket_mount.pyi +5 -1
  62. modal/cls.py +130 -102
  63. modal/cls.pyi +45 -85
  64. modal/config.py +29 -10
  65. modal/container_process.py +291 -13
  66. modal/container_process.pyi +95 -32
  67. modal/dict.py +282 -63
  68. modal/dict.pyi +423 -73
  69. modal/environments.py +15 -27
  70. modal/environments.pyi +5 -15
  71. modal/exception.py +8 -0
  72. modal/experimental/__init__.py +143 -38
  73. modal/experimental/flash.py +247 -78
  74. modal/experimental/flash.pyi +137 -9
  75. modal/file_io.py +14 -28
  76. modal/file_io.pyi +2 -2
  77. modal/file_pattern_matcher.py +25 -16
  78. modal/functions.pyi +134 -61
  79. modal/image.py +255 -86
  80. modal/image.pyi +300 -62
  81. modal/io_streams.py +436 -126
  82. modal/io_streams.pyi +236 -171
  83. modal/mount.py +62 -157
  84. modal/mount.pyi +45 -172
  85. modal/network_file_system.py +30 -53
  86. modal/network_file_system.pyi +16 -76
  87. modal/object.pyi +42 -8
  88. modal/parallel_map.py +821 -113
  89. modal/parallel_map.pyi +134 -0
  90. modal/partial_function.pyi +4 -1
  91. modal/proxy.py +16 -7
  92. modal/proxy.pyi +10 -2
  93. modal/queue.py +263 -61
  94. modal/queue.pyi +409 -66
  95. modal/runner.py +112 -92
  96. modal/runner.pyi +45 -27
  97. modal/sandbox.py +451 -124
  98. modal/sandbox.pyi +513 -67
  99. modal/secret.py +291 -67
  100. modal/secret.pyi +425 -19
  101. modal/serving.py +7 -11
  102. modal/serving.pyi +7 -8
  103. modal/snapshot.py +11 -8
  104. modal/token_flow.py +4 -4
  105. modal/volume.py +344 -98
  106. modal/volume.pyi +464 -68
  107. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +9 -8
  108. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  109. modal_docs/mdmd/mdmd.py +11 -1
  110. modal_proto/api.proto +399 -67
  111. modal_proto/api_grpc.py +241 -1
  112. modal_proto/api_pb2.py +1395 -1000
  113. modal_proto/api_pb2.pyi +1239 -79
  114. modal_proto/api_pb2_grpc.py +499 -4
  115. modal_proto/api_pb2_grpc.pyi +162 -14
  116. modal_proto/modal_api_grpc.py +175 -160
  117. modal_proto/sandbox_router.proto +145 -0
  118. modal_proto/sandbox_router_grpc.py +105 -0
  119. modal_proto/sandbox_router_pb2.py +149 -0
  120. modal_proto/sandbox_router_pb2.pyi +333 -0
  121. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  122. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  123. modal_proto/task_command_router.proto +144 -0
  124. modal_proto/task_command_router_grpc.py +105 -0
  125. modal_proto/task_command_router_pb2.py +149 -0
  126. modal_proto/task_command_router_pb2.pyi +333 -0
  127. modal_proto/task_command_router_pb2_grpc.py +203 -0
  128. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  129. modal_version/__init__.py +1 -1
  130. modal-1.0.6.dev58.dist-info/RECORD +0 -183
  131. modal_proto/modal_options_grpc.py +0 -3
  132. modal_proto/options.proto +0 -19
  133. modal_proto/options_grpc.py +0 -3
  134. modal_proto/options_pb2.py +0 -35
  135. modal_proto/options_pb2.pyi +0 -20
  136. modal_proto/options_pb2_grpc.py +0 -4
  137. modal_proto/options_pb2_grpc.pyi +0 -7
  138. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  139. /modal/{requirements → builder}/2023.12.txt +0 -0
  140. /modal/{requirements → builder}/2024.04.txt +0 -0
  141. /modal/{requirements → builder}/2024.10.txt +0 -0
  142. /modal/{requirements → builder}/README.md +0 -0
  143. /modal/{requirements → builder}/base-images.json +0 -0
  144. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  145. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  146. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  147. {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/cli/volume.py CHANGED
@@ -7,18 +7,15 @@ from typing import Optional
7
7
  import typer
8
8
  from click import UsageError
9
9
  from grpclib import GRPCError, Status
10
- from rich.console import Console
11
10
  from rich.syntax import Syntax
12
11
  from typer import Argument, Option, Typer
13
12
 
14
13
  import modal
15
- from modal._output import OutputManager, ProgressHandler
14
+ from modal._output import OutputManager, ProgressHandler, make_console
16
15
  from modal._utils.async_utils import synchronizer
17
- from modal._utils.grpc_utils import retry_transient_errors
18
- from modal._utils.time_utils import timestamp_to_local
16
+ from modal._utils.time_utils import timestamp_to_localized_str
19
17
  from modal.cli._download import _volume_download
20
18
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
21
- from modal.client import _Client
22
19
  from modal.environments import ensure_env
23
20
  from modal.volume import _AbstractVolumeUploadContextManager, _Volume
24
21
  from modal_proto import api_pb2
@@ -57,14 +54,14 @@ def create(
57
54
  version: Optional[int] = Option(default=None, help="VolumeFS version. (Experimental)"),
58
55
  ):
59
56
  env_name = ensure_env(env)
60
- modal.Volume.create_deployed(name, environment_name=env, version=version)
57
+ modal.Volume.objects.create(name, environment_name=env, version=version)
61
58
  usage_code = f"""
62
59
  @app.function(volumes={{"/my_vol": modal.Volume.from_name("{name}")}})
63
60
  def some_func():
64
61
  os.listdir("/my_vol")
65
62
  """
66
63
 
67
- console = Console()
64
+ console = make_console()
68
65
  console.print(f"Created Volume '{name}' in environment '{env_name}'. \n\nCode example:\n")
69
66
  usage = Syntax(usage_code, "python")
70
67
  console.print(usage)
@@ -96,10 +93,16 @@ async def get(
96
93
  ensure_env(env)
97
94
  destination = Path(local_destination)
98
95
  volume = _Volume.from_name(volume_name, environment_name=env)
99
- console = Console()
96
+ console = make_console()
100
97
  progress_handler = ProgressHandler(type="download", console=console)
101
98
  with progress_handler.live:
102
- await _volume_download(volume, remote_path, destination, force, progress_cb=progress_handler.progress)
99
+ await _volume_download(
100
+ volume=volume,
101
+ remote_path=remote_path,
102
+ local_destination=destination,
103
+ overwrite=force,
104
+ progress_cb=progress_handler.progress,
105
+ )
103
106
  console.print(OutputManager.step_completed("Finished downloading files to local!"))
104
107
 
105
108
 
@@ -111,14 +114,13 @@ async def get(
111
114
  @synchronizer.create_blocking
112
115
  async def list_(env: Optional[str] = ENV_OPTION, json: Optional[bool] = False):
113
116
  env = ensure_env(env)
114
- client = await _Client.from_env()
115
- response = await retry_transient_errors(client.stub.VolumeList, api_pb2.VolumeListRequest(environment_name=env))
116
- env_part = f" in environment '{env}'" if env else ""
117
- column_names = ["Name", "Created at"]
117
+ volumes = await _Volume.objects.list(environment_name=env)
118
118
  rows = []
119
- for item in response.items:
120
- rows.append([item.label, timestamp_to_local(item.created_at, json)])
121
- display_table(column_names, rows, json, title=f"Volumes{env_part}")
119
+ for obj in volumes:
120
+ info = await obj.info()
121
+ rows.append((info.name, timestamp_to_localized_str(info.created_at.timestamp(), json), info.created_by))
122
+
123
+ display_table(["Name", "Created at", "Created by"], rows, json)
122
124
 
123
125
 
124
126
  @volume_cli.command(
@@ -164,7 +166,7 @@ async def ls(
164
166
  (
165
167
  entry.path.encode("unicode_escape").decode("utf-8"),
166
168
  filetype,
167
- timestamp_to_local(entry.mtime, False),
169
+ timestamp_to_localized_str(entry.mtime, False),
168
170
  humanize_filesize(entry.size),
169
171
  )
170
172
  )
@@ -197,7 +199,7 @@ async def put(
197
199
 
198
200
  if remote_path.endswith("/"):
199
201
  remote_path = remote_path + os.path.basename(local_path)
200
- console = Console()
202
+ console = make_console()
201
203
  progress_handler = ProgressHandler(type="upload", console=console)
202
204
 
203
205
  if Path(local_path).is_dir():
@@ -245,7 +247,7 @@ async def rm(
245
247
  ):
246
248
  ensure_env(env)
247
249
  volume = _Volume.from_name(volume_name, environment_name=env)
248
- console = Console()
250
+ console = make_console()
249
251
  try:
250
252
  await volume.remove_file(remote_path, recursive=recursive)
251
253
  console.print(OutputManager.step_completed(f"{remote_path} was deleted successfully!"))
@@ -278,25 +280,26 @@ async def cp(
278
280
 
279
281
  @volume_cli.command(
280
282
  name="delete",
281
- help="Delete a named, persistent modal.Volume.",
283
+ help="Delete a named Volume and all of its data.",
282
284
  rich_help_panel="Management",
283
285
  )
284
286
  @synchronizer.create_blocking
285
287
  async def delete(
286
- volume_name: str = Argument(help="Name of the modal.Volume to be deleted. Case sensitive"),
288
+ name: str = Argument(help="Name of the modal.Volume to be deleted. Case sensitive"),
289
+ *,
290
+ allow_missing: bool = Option(False, "--allow-missing", help="Don't error if the Volume doesn't exist."),
287
291
  yes: bool = YES_OPTION,
288
292
  env: Optional[str] = ENV_OPTION,
289
293
  ):
290
- # Lookup first to validate the name, even though delete is a staticmethod
291
- await _Volume.from_name(volume_name, environment_name=env).hydrate()
294
+ env = ensure_env(env)
292
295
  if not yes:
293
296
  typer.confirm(
294
- f"Are you sure you want to irrevocably delete the modal.Volume '{volume_name}'?",
297
+ f"Are you sure you want to irrevocably delete the modal.Volume '{name}'?",
295
298
  default=False,
296
299
  abort=True,
297
300
  )
298
301
 
299
- await _Volume.delete(volume_name, environment_name=env)
302
+ await _Volume.objects.delete(name, environment_name=env, allow_missing=allow_missing)
300
303
 
301
304
 
302
305
  @volume_cli.command(
modal/client.py CHANGED
@@ -6,32 +6,24 @@ import sys
6
6
  import urllib.parse
7
7
  import warnings
8
8
  from collections.abc import AsyncGenerator, AsyncIterator, Collection, Mapping
9
- from typing import (
10
- Any,
11
- ClassVar,
12
- Generic,
13
- Optional,
14
- TypeVar,
15
- Union,
16
- )
9
+ from typing import Any, ClassVar, Optional, TypeVar, Union
17
10
 
18
11
  import grpclib.client
19
12
  from google.protobuf import empty_pb2
20
13
  from google.protobuf.message import Message
21
- from grpclib import GRPCError, Status
22
14
  from synchronicity.async_wrap import asynccontextmanager
23
15
 
24
16
  from modal._utils.async_utils import synchronizer
25
17
  from modal_proto import api_grpc, api_pb2, modal_api_grpc
26
18
  from modal_version import __version__
27
19
 
28
- from ._traceback import print_server_warnings, suppress_tb_frames
20
+ from ._traceback import print_server_warnings
29
21
  from ._utils import async_utils
30
22
  from ._utils.async_utils import TaskContext, synchronize_api
31
23
  from ._utils.auth_token_manager import _AuthTokenManager
32
- from ._utils.grpc_utils import ConnectionManager, retry_transient_errors
24
+ from ._utils.grpc_utils import ConnectionManager
33
25
  from .config import _check_config, _is_remote, config, logger
34
- from .exception import AuthError, ClientClosed, NotFoundError
26
+ from .exception import AuthError, ClientClosed
35
27
 
36
28
  HEARTBEAT_INTERVAL: float = config.get("heartbeat_interval")
37
29
  HEARTBEAT_TIMEOUT: float = HEARTBEAT_INTERVAL + 0.1
@@ -159,7 +151,7 @@ class _Client:
159
151
  async def hello(self):
160
152
  """Connect to server and retrieve version information; raise appropriate error for various failures."""
161
153
  logger.debug(f"Client ({id(self)}): Starting")
162
- resp = await retry_transient_errors(self.stub.ClientHello, empty_pb2.Empty())
154
+ resp = await self.stub.ClientHello(empty_pb2.Empty())
163
155
  print_server_warnings(resp.server_warnings)
164
156
 
165
157
  async def __aenter__(self):
@@ -268,6 +260,14 @@ class _Client:
268
260
  # Just used from tests.
269
261
  cls._client_from_env = client
270
262
 
263
+ async def get_input_plane_metadata(self, input_plane_region: str) -> list[tuple[str, str]]:
264
+ assert self._auth_token_manager, "Client must have an instance of auth token manager."
265
+ token = await self._auth_token_manager.get_token()
266
+ return [
267
+ ("x-modal-input-plane-region", input_plane_region),
268
+ ("x-modal-auth-token", token),
269
+ ]
270
+
271
271
  async def _call_safely(self, coro, readable_method: str):
272
272
  """Runs coroutine wrapped in a task that's part of the client's task context
273
273
 
@@ -354,106 +354,3 @@ class _Client:
354
354
 
355
355
 
356
356
  Client = synchronize_api(_Client)
357
-
358
-
359
- class grpc_error_converter:
360
- def __enter__(self):
361
- pass
362
-
363
- def __exit__(self, exc_type, exc, traceback) -> bool:
364
- # skip all internal frames from grpclib
365
- use_full_traceback = config.get("traceback")
366
- with suppress_tb_frames(1):
367
- if isinstance(exc, GRPCError):
368
- if exc.status == Status.NOT_FOUND:
369
- if use_full_traceback:
370
- raise NotFoundError(exc.message)
371
- else:
372
- raise NotFoundError(exc.message) from None # from None to skip the grpc-internal cause
373
-
374
- if not use_full_traceback:
375
- # just include the frame in grpclib that actually raises the GRPCError
376
- tb = exc.__traceback__
377
- while tb.tb_next:
378
- tb = tb.tb_next
379
- exc.with_traceback(tb)
380
- raise exc from None # from None to skip the grpc-internal cause
381
- raise exc
382
-
383
- return False
384
-
385
-
386
- class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
387
- # Calls a grpclib.UnaryUnaryMethod using a specific Client instance, respecting
388
- # if that client is closed etc. and possibly introducing Modal-specific retry logic
389
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
390
- client: _Client
391
-
392
- def __init__(
393
- self,
394
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
395
- client: _Client,
396
- server_url: str,
397
- ):
398
- self.wrapped_method = wrapped_method
399
- self.client = client
400
- self.server_url = server_url
401
-
402
- @property
403
- def name(self) -> str:
404
- return self.wrapped_method.name
405
-
406
- async def __call__(
407
- self,
408
- req: RequestType,
409
- *,
410
- timeout: Optional[float] = None,
411
- metadata: Optional[_MetadataLike] = None,
412
- ) -> ResponseType:
413
- if self.client._snapshotted:
414
- logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
415
- self.client = await _Client.from_env()
416
-
417
- # Note: We override the grpclib method's channel (see grpclib's code [1]). I think this is fine
418
- # since grpclib's code doesn't seem to change very much, but we could also recreate the
419
- # grpclib stub if we aren't comfortable with this. The downside is then we need to cache
420
- # the grpclib stub so the rest of our code becomes a bit more complicated.
421
- #
422
- # We need to override the channel because after the process is forked or the client is
423
- # snapshotted, the existing channel may be stale / unusable.
424
- #
425
- # [1]: https://github.com/vmagamedov/grpclib/blob/62f968a4c84e3f64e6966097574ff0a59969ea9b/grpclib/client.py#L844
426
- self.wrapped_method.channel = await self.client._get_channel(self.server_url)
427
- with suppress_tb_frames(1), grpc_error_converter():
428
- return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
429
-
430
-
431
- class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
432
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
433
-
434
- def __init__(
435
- self,
436
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
437
- client: _Client,
438
- server_url: str,
439
- ):
440
- self.wrapped_method = wrapped_method
441
- self.client = client
442
- self.server_url = server_url
443
-
444
- @property
445
- def name(self) -> str:
446
- return self.wrapped_method.name
447
-
448
- async def unary_stream(
449
- self,
450
- request,
451
- metadata: Optional[Any] = None,
452
- ):
453
- if self.client._snapshotted:
454
- logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
455
- self.client = await _Client.from_env()
456
- self.wrapped_method.channel = await self.client._get_channel(self.server_url)
457
- async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
458
- yield response
459
-
modal/client.pyi CHANGED
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.0.6.dev58",
36
+ version: str = "1.2.3.dev7",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -112,6 +112,7 @@ class _Client:
112
112
  """mdmd:hidden"""
113
113
  ...
114
114
 
115
+ async def get_input_plane_metadata(self, input_plane_region: str) -> list[tuple[str, str]]: ...
115
116
  async def _call_safely(self, coro, readable_method: str):
116
117
  """Runs coroutine wrapped in a task that's part of the client's task context
117
118
 
@@ -163,7 +164,7 @@ class Client:
163
164
  server_url: str,
164
165
  client_type: int,
165
166
  credentials: typing.Optional[tuple[str, str]],
166
- version: str = "1.0.6.dev58",
167
+ version: str = "1.2.3.dev7",
167
168
  ):
168
169
  """mdmd:hidden
169
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -275,6 +276,12 @@ class Client:
275
276
  """mdmd:hidden"""
276
277
  ...
277
278
 
279
+ class __get_input_plane_metadata_spec(typing_extensions.Protocol[SUPERSELF]):
280
+ def __call__(self, /, input_plane_region: str) -> list[tuple[str, str]]: ...
281
+ async def aio(self, /, input_plane_region: str) -> list[tuple[str, str]]: ...
282
+
283
+ get_input_plane_metadata: __get_input_plane_metadata_spec[typing_extensions.Self]
284
+
278
285
  class ___call_safely_spec(typing_extensions.Protocol[SUPERSELF]):
279
286
  def __call__(self, /, coro, readable_method: str):
280
287
  """Runs coroutine wrapped in a task that's part of the client's task context
@@ -332,95 +339,6 @@ class Client:
332
339
  ],
333
340
  ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
334
341
 
335
- class grpc_error_converter:
336
- def __enter__(self): ...
337
- def __exit__(self, exc_type, exc, traceback) -> bool: ...
338
-
339
- class UnaryUnaryWrapper(typing.Generic[RequestType, ResponseType]):
340
- """Abstract base class for generic types.
341
-
342
- A generic type is typically declared by inheriting from
343
- this class parameterized with one or more type variables.
344
- For example, a generic mapping type might be defined as::
345
-
346
- class Mapping(Generic[KT, VT]):
347
- def __getitem__(self, key: KT) -> VT:
348
- ...
349
- # Etc.
350
-
351
- This class can then be used as follows::
352
-
353
- def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
354
- try:
355
- return mapping[key]
356
- except KeyError:
357
- return default
358
- """
359
-
360
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
361
- client: _Client
362
-
363
- def __init__(
364
- self,
365
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
366
- client: _Client,
367
- server_url: str,
368
- ):
369
- """Initialize self. See help(type(self)) for accurate signature."""
370
- ...
371
-
372
- @property
373
- def name(self) -> str: ...
374
- async def __call__(
375
- self,
376
- req: RequestType,
377
- *,
378
- timeout: typing.Optional[float] = None,
379
- metadata: typing.Union[
380
- collections.abc.Mapping[str, typing.Union[str, bytes]],
381
- collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
382
- None,
383
- ] = None,
384
- ) -> ResponseType:
385
- """Call self as a function."""
386
- ...
387
-
388
- class UnaryStreamWrapper(typing.Generic[RequestType, ResponseType]):
389
- """Abstract base class for generic types.
390
-
391
- A generic type is typically declared by inheriting from
392
- this class parameterized with one or more type variables.
393
- For example, a generic mapping type might be defined as::
394
-
395
- class Mapping(Generic[KT, VT]):
396
- def __getitem__(self, key: KT) -> VT:
397
- ...
398
- # Etc.
399
-
400
- This class can then be used as follows::
401
-
402
- def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
403
- try:
404
- return mapping[key]
405
- except KeyError:
406
- return default
407
- """
408
-
409
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
410
-
411
- def __init__(
412
- self,
413
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
414
- client: _Client,
415
- server_url: str,
416
- ):
417
- """Initialize self. See help(type(self)) for accurate signature."""
418
- ...
419
-
420
- @property
421
- def name(self) -> str: ...
422
- def unary_stream(self, request, metadata: typing.Optional[typing.Any] = None): ...
423
-
424
342
  HEARTBEAT_INTERVAL: float
425
343
 
426
344
  HEARTBEAT_TIMEOUT: float
@@ -1,6 +1,6 @@
1
1
  # Copyright Modal Labs 2022
2
2
  from dataclasses import dataclass
3
- from typing import Optional
3
+ from typing import Optional, Sequence
4
4
  from urllib.parse import urlparse
5
5
 
6
6
  from modal_proto import api_pb2
@@ -117,9 +117,10 @@ class _CloudBucketMount:
117
117
 
118
118
  read_only: bool = False
119
119
  requester_pays: bool = False
120
+ force_path_style: bool = False
120
121
 
121
122
 
122
- def cloud_bucket_mounts_to_proto(mounts: list[tuple[str, _CloudBucketMount]]) -> list[api_pb2.CloudBucketMount]:
123
+ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]) -> list[api_pb2.CloudBucketMount]:
123
124
  """Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server."""
124
125
  cloud_bucket_mounts: list[api_pb2.CloudBucketMount] = []
125
126
 
@@ -132,7 +133,7 @@ def cloud_bucket_mounts_to_proto(mounts: list[tuple[str, _CloudBucketMount]]) ->
132
133
  elif parse_result.hostname.endswith("storage.googleapis.com"):
133
134
  bucket_type = api_pb2.CloudBucketMount.BucketType.GCP
134
135
  else:
135
- logger.warning(
136
+ logger.info(
136
137
  "CloudBucketMount received unrecognized bucket endpoint URL. "
137
138
  "Assuming AWS S3 configuration as fallback."
138
139
  )
@@ -159,6 +160,7 @@ def cloud_bucket_mounts_to_proto(mounts: list[tuple[str, _CloudBucketMount]]) ->
159
160
  requester_pays=mount.requester_pays,
160
161
  key_prefix=key_prefix,
161
162
  oidc_auth_role_arn=mount.oidc_auth_role_arn,
163
+ force_path_style=mount.force_path_style,
162
164
  )
163
165
  cloud_bucket_mounts.append(cloud_bucket_mount)
164
166
 
@@ -99,6 +99,7 @@ class _CloudBucketMount:
99
99
  oidc_auth_role_arn: typing.Optional[str]
100
100
  read_only: bool
101
101
  requester_pays: bool
102
+ force_path_style: bool
102
103
 
103
104
  def __init__(
104
105
  self,
@@ -109,6 +110,7 @@ class _CloudBucketMount:
109
110
  oidc_auth_role_arn: typing.Optional[str] = None,
110
111
  read_only: bool = False,
111
112
  requester_pays: bool = False,
113
+ force_path_style: bool = False,
112
114
  ) -> None:
113
115
  """Initialize self. See help(type(self)) for accurate signature."""
114
116
  ...
@@ -122,7 +124,7 @@ class _CloudBucketMount:
122
124
  ...
123
125
 
124
126
  def cloud_bucket_mounts_to_proto(
125
- mounts: list[tuple[str, _CloudBucketMount]],
127
+ mounts: typing.Sequence[tuple[str, _CloudBucketMount]],
126
128
  ) -> list[modal_proto.api_pb2.CloudBucketMount]:
127
129
  """Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server."""
128
130
  ...
@@ -224,6 +226,7 @@ class CloudBucketMount:
224
226
  oidc_auth_role_arn: typing.Optional[str]
225
227
  read_only: bool
226
228
  requester_pays: bool
229
+ force_path_style: bool
227
230
 
228
231
  def __init__(
229
232
  self,
@@ -234,6 +237,7 @@ class CloudBucketMount:
234
237
  oidc_auth_role_arn: typing.Optional[str] = None,
235
238
  read_only: bool = False,
236
239
  requester_pays: bool = False,
240
+ force_path_style: bool = False,
237
241
  ) -> None: ...
238
242
  def __repr__(self): ...
239
243
  def __eq__(self, other): ...