modal 1.2.1.dev8__py3-none-any.whl → 1.2.2.dev19__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.
Files changed (70) hide show
  1. modal/_clustered_functions.py +1 -3
  2. modal/_container_entrypoint.py +4 -1
  3. modal/_functions.py +33 -49
  4. modal/_grpc_client.py +148 -0
  5. modal/_output.py +3 -4
  6. modal/_partial_function.py +22 -2
  7. modal/_runtime/container_io_manager.py +21 -22
  8. modal/_utils/async_utils.py +12 -3
  9. modal/_utils/auth_token_manager.py +1 -4
  10. modal/_utils/blob_utils.py +3 -4
  11. modal/_utils/function_utils.py +4 -0
  12. modal/_utils/grpc_utils.py +80 -51
  13. modal/_utils/mount_utils.py +26 -1
  14. modal/_utils/task_command_router_client.py +536 -0
  15. modal/app.py +7 -5
  16. modal/cli/cluster.py +4 -2
  17. modal/cli/config.py +3 -1
  18. modal/cli/container.py +5 -4
  19. modal/cli/entry_point.py +1 -0
  20. modal/cli/launch.py +1 -2
  21. modal/cli/network_file_system.py +1 -4
  22. modal/cli/queues.py +1 -2
  23. modal/cli/secret.py +1 -2
  24. modal/client.py +5 -115
  25. modal/client.pyi +2 -91
  26. modal/cls.py +1 -2
  27. modal/config.py +3 -1
  28. modal/container_process.py +287 -11
  29. modal/container_process.pyi +95 -32
  30. modal/dict.py +12 -12
  31. modal/environments.py +1 -2
  32. modal/exception.py +4 -0
  33. modal/experimental/__init__.py +2 -3
  34. modal/experimental/flash.py +27 -57
  35. modal/experimental/flash.pyi +6 -20
  36. modal/file_io.py +13 -27
  37. modal/functions.pyi +6 -6
  38. modal/image.py +24 -3
  39. modal/image.pyi +4 -0
  40. modal/io_streams.py +433 -127
  41. modal/io_streams.pyi +236 -171
  42. modal/mount.py +4 -4
  43. modal/network_file_system.py +5 -6
  44. modal/parallel_map.py +29 -31
  45. modal/parallel_map.pyi +3 -9
  46. modal/partial_function.pyi +4 -1
  47. modal/queue.py +17 -18
  48. modal/runner.py +12 -11
  49. modal/sandbox.py +148 -42
  50. modal/sandbox.pyi +139 -0
  51. modal/secret.py +4 -5
  52. modal/snapshot.py +1 -4
  53. modal/token_flow.py +1 -1
  54. modal/volume.py +22 -22
  55. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/METADATA +1 -1
  56. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/RECORD +70 -68
  57. modal_proto/api.proto +2 -24
  58. modal_proto/api_grpc.py +0 -32
  59. modal_proto/api_pb2.py +838 -878
  60. modal_proto/api_pb2.pyi +8 -70
  61. modal_proto/api_pb2_grpc.py +0 -67
  62. modal_proto/api_pb2_grpc.pyi +0 -22
  63. modal_proto/modal_api_grpc.py +175 -177
  64. modal_proto/sandbox_router.proto +0 -4
  65. modal_proto/sandbox_router_pb2.pyi +0 -4
  66. modal_version/__init__.py +1 -1
  67. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/WHEEL +0 -0
  68. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/entry_points.txt +0 -0
  69. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/licenses/LICENSE +0 -0
  70. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,6 @@ import modal
15
15
  from modal._location import display_location
16
16
  from modal._output import OutputManager, ProgressHandler, 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._download import _volume_download
21
20
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
@@ -33,9 +32,7 @@ async def list_(env: Optional[str] = ENV_OPTION, json: Optional[bool] = False):
33
32
  env = ensure_env(env)
34
33
 
35
34
  client = await _Client.from_env()
36
- response = await retry_transient_errors(
37
- client.stub.SharedVolumeList, api_pb2.SharedVolumeListRequest(environment_name=env)
38
- )
35
+ response = await client.stub.SharedVolumeList(api_pb2.SharedVolumeListRequest(environment_name=env))
39
36
  env_part = f" in environment '{env}'" if env else ""
40
37
  column_names = ["Name", "Location", "Created at"]
41
38
  rows = []
modal/cli/queues.py CHANGED
@@ -8,7 +8,6 @@ from typer import Argument, Option, Typer
8
8
  from modal._output import make_console
9
9
  from modal._resolver import Resolver
10
10
  from modal._utils.async_utils import synchronizer
11
- from modal._utils.grpc_utils import retry_transient_errors
12
11
  from modal._utils.time_utils import timestamp_to_localized_str
13
12
  from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
14
13
  from modal.client import _Client
@@ -83,7 +82,7 @@ async def list_(*, json: bool = False, env: Optional[str] = ENV_OPTION):
83
82
  max_page_size = 100
84
83
  pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
85
84
  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)
85
+ resp = await client.stub.QueueList(req)
87
86
  items.extend(resp.queues)
88
87
  return len(resp.queues) < max_page_size
89
88
 
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
 
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):
@@ -362,105 +354,3 @@ class _Client:
362
354
 
363
355
 
364
356
  Client = synchronize_api(_Client)
365
-
366
-
367
- class grpc_error_converter:
368
- def __enter__(self):
369
- pass
370
-
371
- def __exit__(self, exc_type, exc, traceback) -> bool:
372
- # skip all internal frames from grpclib
373
- use_full_traceback = config.get("traceback")
374
- with suppress_tb_frames(1):
375
- if isinstance(exc, GRPCError):
376
- if exc.status == Status.NOT_FOUND:
377
- if use_full_traceback:
378
- raise NotFoundError(exc.message)
379
- else:
380
- raise NotFoundError(exc.message) from None # from None to skip the grpc-internal cause
381
-
382
- if not use_full_traceback:
383
- # just include the frame in grpclib that actually raises the GRPCError
384
- tb = exc.__traceback__
385
- while tb.tb_next:
386
- tb = tb.tb_next
387
- exc.with_traceback(tb)
388
- raise exc from None # from None to skip the grpc-internal cause
389
- raise exc
390
-
391
- return False
392
-
393
-
394
- class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
395
- # Calls a grpclib.UnaryUnaryMethod using a specific Client instance, respecting
396
- # if that client is closed etc. and possibly introducing Modal-specific retry logic
397
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
398
- client: _Client
399
-
400
- def __init__(
401
- self,
402
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
403
- client: _Client,
404
- server_url: str,
405
- ):
406
- self.wrapped_method = wrapped_method
407
- self.client = client
408
- self.server_url = server_url
409
-
410
- @property
411
- def name(self) -> str:
412
- return self.wrapped_method.name
413
-
414
- async def __call__(
415
- self,
416
- req: RequestType,
417
- *,
418
- timeout: Optional[float] = None,
419
- metadata: Optional[_MetadataLike] = None,
420
- ) -> ResponseType:
421
- if self.client._snapshotted:
422
- logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
423
- self.client = await _Client.from_env()
424
-
425
- # Note: We override the grpclib method's channel (see grpclib's code [1]). I think this is fine
426
- # since grpclib's code doesn't seem to change very much, but we could also recreate the
427
- # grpclib stub if we aren't comfortable with this. The downside is then we need to cache
428
- # the grpclib stub so the rest of our code becomes a bit more complicated.
429
- #
430
- # We need to override the channel because after the process is forked or the client is
431
- # snapshotted, the existing channel may be stale / unusable.
432
- #
433
- # [1]: https://github.com/vmagamedov/grpclib/blob/62f968a4c84e3f64e6966097574ff0a59969ea9b/grpclib/client.py#L844
434
- self.wrapped_method.channel = await self.client._get_channel(self.server_url)
435
- with suppress_tb_frames(1), grpc_error_converter():
436
- return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
437
-
438
-
439
- class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
440
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
441
-
442
- def __init__(
443
- self,
444
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
445
- client: _Client,
446
- server_url: str,
447
- ):
448
- self.wrapped_method = wrapped_method
449
- self.client = client
450
- self.server_url = server_url
451
-
452
- @property
453
- def name(self) -> str:
454
- return self.wrapped_method.name
455
-
456
- async def unary_stream(
457
- self,
458
- request,
459
- metadata: Optional[Any] = None,
460
- ):
461
- if self.client._snapshotted:
462
- logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
463
- self.client = await _Client.from_env()
464
- self.wrapped_method.channel = await self.client._get_channel(self.server_url)
465
- async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
466
- yield response
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.2.1.dev8",
36
+ version: str = "1.2.2.dev19",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.1.dev8",
167
+ version: str = "1.2.2.dev19",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -339,95 +339,6 @@ class Client:
339
339
  ],
340
340
  ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
341
341
 
342
- class grpc_error_converter:
343
- def __enter__(self): ...
344
- def __exit__(self, exc_type, exc, traceback) -> bool: ...
345
-
346
- class UnaryUnaryWrapper(typing.Generic[RequestType, ResponseType]):
347
- """Abstract base class for generic types.
348
-
349
- A generic type is typically declared by inheriting from
350
- this class parameterized with one or more type variables.
351
- For example, a generic mapping type might be defined as::
352
-
353
- class Mapping(Generic[KT, VT]):
354
- def __getitem__(self, key: KT) -> VT:
355
- ...
356
- # Etc.
357
-
358
- This class can then be used as follows::
359
-
360
- def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
361
- try:
362
- return mapping[key]
363
- except KeyError:
364
- return default
365
- """
366
-
367
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
368
- client: _Client
369
-
370
- def __init__(
371
- self,
372
- wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
373
- client: _Client,
374
- server_url: str,
375
- ):
376
- """Initialize self. See help(type(self)) for accurate signature."""
377
- ...
378
-
379
- @property
380
- def name(self) -> str: ...
381
- async def __call__(
382
- self,
383
- req: RequestType,
384
- *,
385
- timeout: typing.Optional[float] = None,
386
- metadata: typing.Union[
387
- collections.abc.Mapping[str, typing.Union[str, bytes]],
388
- collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
389
- None,
390
- ] = None,
391
- ) -> ResponseType:
392
- """Call self as a function."""
393
- ...
394
-
395
- class UnaryStreamWrapper(typing.Generic[RequestType, ResponseType]):
396
- """Abstract base class for generic types.
397
-
398
- A generic type is typically declared by inheriting from
399
- this class parameterized with one or more type variables.
400
- For example, a generic mapping type might be defined as::
401
-
402
- class Mapping(Generic[KT, VT]):
403
- def __getitem__(self, key: KT) -> VT:
404
- ...
405
- # Etc.
406
-
407
- This class can then be used as follows::
408
-
409
- def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
410
- try:
411
- return mapping[key]
412
- except KeyError:
413
- return default
414
- """
415
-
416
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
417
-
418
- def __init__(
419
- self,
420
- wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
421
- client: _Client,
422
- server_url: str,
423
- ):
424
- """Initialize self. See help(type(self)) for accurate signature."""
425
- ...
426
-
427
- @property
428
- def name(self) -> str: ...
429
- def unary_stream(self, request, metadata: typing.Optional[typing.Any] = None): ...
430
-
431
342
  HEARTBEAT_INTERVAL: float
432
343
 
433
344
  HEARTBEAT_TIMEOUT: float
modal/cls.py CHANGED
@@ -30,7 +30,6 @@ from ._utils.deprecation import (
30
30
  warn_if_passing_namespace,
31
31
  warn_on_renamed_autoscaler_settings,
32
32
  )
33
- from ._utils.grpc_utils import retry_transient_errors
34
33
  from ._utils.mount_utils import validate_volumes
35
34
  from .cloud_bucket_mount import _CloudBucketMount
36
35
  from .config import config
@@ -643,7 +642,7 @@ More information on class parameterization can be found here: https://modal.com/
643
642
  only_class_function=True,
644
643
  )
645
644
  try:
646
- response = await retry_transient_errors(resolver.client.stub.ClassGet, request)
645
+ response = await resolver.client.stub.ClassGet(request)
647
646
  except NotFoundError as exc:
648
647
  env_context = f" (in the '{environment_name}' environment)" if environment_name else ""
649
648
  raise NotFoundError(
modal/config.py CHANGED
@@ -147,7 +147,7 @@ async def _lookup_workspace(server_url: str, token_id: str, token_secret: str) -
147
147
 
148
148
  credentials = (token_id, token_secret)
149
149
  async with _Client(server_url, api_pb2.CLIENT_TYPE_CLIENT, credentials) as client:
150
- return await client.stub.WorkspaceNameLookup(Empty(), timeout=3)
150
+ return await client.stub.WorkspaceNameLookup(Empty(), retry=None, timeout=3)
151
151
 
152
152
 
153
153
  def config_profiles():
@@ -247,6 +247,8 @@ _SETTINGS = {
247
247
  "traceback": _Setting(False, transform=_to_boolean),
248
248
  "image_builder_version": _Setting(),
249
249
  "strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
250
+ # Allow insecure TLS for the task command router when running locally (testing/dev only)
251
+ "task_command_router_insecure": _Setting(False, transform=_to_boolean),
250
252
  "snapshot_debug": _Setting(False, transform=_to_boolean),
251
253
  "cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
252
254
  "build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),