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.
- modal/__main__.py +3 -4
- modal/_billing.py +80 -0
- modal/_clustered_functions.py +7 -3
- modal/_clustered_functions.pyi +4 -2
- modal/_container_entrypoint.py +41 -49
- modal/_functions.py +424 -195
- modal/_grpc_client.py +171 -0
- modal/_load_context.py +105 -0
- modal/_object.py +68 -20
- modal/_output.py +58 -45
- modal/_partial_function.py +36 -11
- modal/_pty.py +7 -3
- modal/_resolver.py +21 -35
- modal/_runtime/asgi.py +4 -3
- modal/_runtime/container_io_manager.py +301 -186
- modal/_runtime/container_io_manager.pyi +70 -61
- modal/_runtime/execution_context.py +18 -2
- modal/_runtime/execution_context.pyi +4 -1
- modal/_runtime/gpu_memory_snapshot.py +170 -63
- modal/_runtime/user_code_imports.py +28 -58
- modal/_serialization.py +57 -1
- modal/_utils/async_utils.py +33 -12
- modal/_utils/auth_token_manager.py +2 -5
- modal/_utils/blob_utils.py +110 -53
- modal/_utils/function_utils.py +49 -42
- modal/_utils/grpc_utils.py +80 -50
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +17 -3
- modal/_utils/task_command_router_client.py +536 -0
- modal/_utils/time_utils.py +34 -6
- modal/app.py +219 -83
- modal/app.pyi +229 -56
- modal/billing.py +5 -0
- modal/{requirements → builder}/2025.06.txt +1 -0
- modal/{requirements → builder}/PREVIEW.txt +1 -0
- modal/cli/_download.py +19 -3
- modal/cli/_traceback.py +3 -2
- modal/cli/app.py +4 -4
- modal/cli/cluster.py +15 -7
- modal/cli/config.py +5 -3
- modal/cli/container.py +7 -6
- modal/cli/dict.py +22 -16
- modal/cli/entry_point.py +12 -5
- modal/cli/environment.py +5 -4
- modal/cli/import_refs.py +3 -3
- modal/cli/launch.py +102 -5
- modal/cli/network_file_system.py +9 -13
- modal/cli/profile.py +3 -2
- modal/cli/programs/launch_instance_ssh.py +94 -0
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/run_marimo.py +95 -0
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +57 -26
- modal/cli/run.py +58 -16
- modal/cli/secret.py +48 -22
- modal/cli/utils.py +3 -4
- modal/cli/volume.py +28 -25
- modal/client.py +13 -116
- modal/client.pyi +9 -91
- modal/cloud_bucket_mount.py +5 -3
- modal/cloud_bucket_mount.pyi +5 -1
- modal/cls.py +130 -102
- modal/cls.pyi +45 -85
- modal/config.py +29 -10
- modal/container_process.py +291 -13
- modal/container_process.pyi +95 -32
- modal/dict.py +282 -63
- modal/dict.pyi +423 -73
- modal/environments.py +15 -27
- modal/environments.pyi +5 -15
- modal/exception.py +8 -0
- modal/experimental/__init__.py +143 -38
- modal/experimental/flash.py +247 -78
- modal/experimental/flash.pyi +137 -9
- modal/file_io.py +14 -28
- modal/file_io.pyi +2 -2
- modal/file_pattern_matcher.py +25 -16
- modal/functions.pyi +134 -61
- modal/image.py +255 -86
- modal/image.pyi +300 -62
- modal/io_streams.py +436 -126
- modal/io_streams.pyi +236 -171
- modal/mount.py +62 -157
- modal/mount.pyi +45 -172
- modal/network_file_system.py +30 -53
- modal/network_file_system.pyi +16 -76
- modal/object.pyi +42 -8
- modal/parallel_map.py +821 -113
- modal/parallel_map.pyi +134 -0
- modal/partial_function.pyi +4 -1
- modal/proxy.py +16 -7
- modal/proxy.pyi +10 -2
- modal/queue.py +263 -61
- modal/queue.pyi +409 -66
- modal/runner.py +112 -92
- modal/runner.pyi +45 -27
- modal/sandbox.py +451 -124
- modal/sandbox.pyi +513 -67
- modal/secret.py +291 -67
- modal/secret.pyi +425 -19
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +11 -8
- modal/token_flow.py +4 -4
- modal/volume.py +344 -98
- modal/volume.pyi +464 -68
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +9 -8
- modal-1.2.3.dev7.dist-info/RECORD +195 -0
- modal_docs/mdmd/mdmd.py +11 -1
- modal_proto/api.proto +399 -67
- modal_proto/api_grpc.py +241 -1
- modal_proto/api_pb2.py +1395 -1000
- modal_proto/api_pb2.pyi +1239 -79
- modal_proto/api_pb2_grpc.py +499 -4
- modal_proto/api_pb2_grpc.pyi +162 -14
- modal_proto/modal_api_grpc.py +175 -160
- modal_proto/sandbox_router.proto +145 -0
- modal_proto/sandbox_router_grpc.py +105 -0
- modal_proto/sandbox_router_pb2.py +149 -0
- modal_proto/sandbox_router_pb2.pyi +333 -0
- modal_proto/sandbox_router_pb2_grpc.py +203 -0
- modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
- modal_proto/task_command_router.proto +144 -0
- modal_proto/task_command_router_grpc.py +105 -0
- modal_proto/task_command_router_pb2.py +149 -0
- modal_proto/task_command_router_pb2.pyi +333 -0
- modal_proto/task_command_router_pb2_grpc.py +203 -0
- modal_proto/task_command_router_pb2_grpc.pyi +75 -0
- modal_version/__init__.py +1 -1
- modal-1.0.6.dev58.dist-info/RECORD +0 -183
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- /modal/{requirements → builder}/2023.12.312.txt +0 -0
- /modal/{requirements → builder}/2023.12.txt +0 -0
- /modal/{requirements → builder}/2024.04.txt +0 -0
- /modal/{requirements → builder}/2024.10.txt +0 -0
- /modal/{requirements → builder}/README.md +0 -0
- /modal/{requirements → builder}/base-images.json +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/_partial_function.py
CHANGED
|
@@ -19,7 +19,7 @@ from ._functions import _Function
|
|
|
19
19
|
from ._utils.async_utils import synchronizer
|
|
20
20
|
from ._utils.deprecation import deprecation_warning
|
|
21
21
|
from ._utils.function_utils import callable_has_non_self_params
|
|
22
|
-
from .config import logger
|
|
22
|
+
from .config import config, logger
|
|
23
23
|
from .exception import InvalidError
|
|
24
24
|
|
|
25
25
|
MAX_MAX_BATCH_SIZE = 1000
|
|
@@ -93,6 +93,26 @@ NullaryFuncOrMethod = Union[Callable[[], Any], Callable[[Any], Any]]
|
|
|
93
93
|
NullaryMethod = Callable[[Any], Any]
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def verify_concurrent_params(params: _PartialFunctionParams, is_flash: bool = False) -> None:
|
|
97
|
+
def _verify_concurrent_params_with_flash_settings(params: _PartialFunctionParams) -> None:
|
|
98
|
+
if params.max_concurrent_inputs is not None:
|
|
99
|
+
raise TypeError(
|
|
100
|
+
"@modal.concurrent(max_inputs=...) is not yet supported for Flash functions. "
|
|
101
|
+
"Use `@modal.concurrent(target_inputs=...)` instead."
|
|
102
|
+
)
|
|
103
|
+
if params.target_concurrent_inputs is None:
|
|
104
|
+
raise TypeError("`@modal.concurrent()` missing required argument: `target_inputs`.")
|
|
105
|
+
|
|
106
|
+
def _verify_concurrent_params(params: _PartialFunctionParams) -> None:
|
|
107
|
+
if params.max_concurrent_inputs is None:
|
|
108
|
+
raise TypeError("`@modal.concurrent()` missing required argument: `max_inputs`.")
|
|
109
|
+
|
|
110
|
+
if is_flash:
|
|
111
|
+
_verify_concurrent_params_with_flash_settings(params)
|
|
112
|
+
else:
|
|
113
|
+
_verify_concurrent_params(params)
|
|
114
|
+
|
|
115
|
+
|
|
96
116
|
class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
97
117
|
"""Object produced by a decorator in the `modal` namespace
|
|
98
118
|
|
|
@@ -282,7 +302,7 @@ class _MethodDecoratorType:
|
|
|
282
302
|
|
|
283
303
|
# TODO(elias): fix support for coroutine type unwrapping for methods (static typing)
|
|
284
304
|
def _method(
|
|
285
|
-
_warn_parentheses_missing=None,
|
|
305
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
286
306
|
*,
|
|
287
307
|
# Set this to True if it's a non-generator function returning
|
|
288
308
|
# a [sync/async] generator object
|
|
@@ -337,7 +357,7 @@ def _parse_custom_domains(custom_domains: Optional[Iterable[str]] = None) -> lis
|
|
|
337
357
|
|
|
338
358
|
|
|
339
359
|
def _fastapi_endpoint(
|
|
340
|
-
_warn_parentheses_missing=None,
|
|
360
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
341
361
|
*,
|
|
342
362
|
method: str = "GET", # REST method for the created endpoint.
|
|
343
363
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
@@ -378,6 +398,7 @@ def _fastapi_endpoint(
|
|
|
378
398
|
method=method,
|
|
379
399
|
web_endpoint_docs=docs,
|
|
380
400
|
requested_suffix=label or "",
|
|
401
|
+
ephemeral_suffix=config.get("dev_suffix"),
|
|
381
402
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
382
403
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
383
404
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -400,7 +421,7 @@ def _fastapi_endpoint(
|
|
|
400
421
|
|
|
401
422
|
|
|
402
423
|
def _web_endpoint(
|
|
403
|
-
_warn_parentheses_missing=None,
|
|
424
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
404
425
|
*,
|
|
405
426
|
method: str = "GET", # REST method for the created endpoint.
|
|
406
427
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
@@ -446,6 +467,7 @@ def _web_endpoint(
|
|
|
446
467
|
method=method,
|
|
447
468
|
web_endpoint_docs=docs,
|
|
448
469
|
requested_suffix=label or "",
|
|
470
|
+
ephemeral_suffix=config.get("dev_suffix"),
|
|
449
471
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
450
472
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
451
473
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -468,7 +490,7 @@ def _web_endpoint(
|
|
|
468
490
|
|
|
469
491
|
|
|
470
492
|
def _asgi_app(
|
|
471
|
-
_warn_parentheses_missing=None,
|
|
493
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
472
494
|
*,
|
|
473
495
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
474
496
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
|
@@ -505,6 +527,7 @@ def _asgi_app(
|
|
|
505
527
|
webhook_config = api_pb2.WebhookConfig(
|
|
506
528
|
type=api_pb2.WEBHOOK_TYPE_ASGI_APP,
|
|
507
529
|
requested_suffix=label or "",
|
|
530
|
+
ephemeral_suffix=config.get("dev_suffix"),
|
|
508
531
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
509
532
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
510
533
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -525,7 +548,7 @@ def _asgi_app(
|
|
|
525
548
|
|
|
526
549
|
|
|
527
550
|
def _wsgi_app(
|
|
528
|
-
_warn_parentheses_missing=None,
|
|
551
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
529
552
|
*,
|
|
530
553
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
531
554
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
|
@@ -562,6 +585,7 @@ def _wsgi_app(
|
|
|
562
585
|
webhook_config = api_pb2.WebhookConfig(
|
|
563
586
|
type=api_pb2.WEBHOOK_TYPE_WSGI_APP,
|
|
564
587
|
requested_suffix=label or "",
|
|
588
|
+
ephemeral_suffix=config.get("dev_suffix"),
|
|
565
589
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
566
590
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
567
591
|
requires_proxy_auth=requires_proxy_auth,
|
|
@@ -623,6 +647,7 @@ def _web_server(
|
|
|
623
647
|
webhook_config = api_pb2.WebhookConfig(
|
|
624
648
|
type=api_pb2.WEBHOOK_TYPE_WEB_SERVER,
|
|
625
649
|
requested_suffix=label or "",
|
|
650
|
+
ephemeral_suffix=config.get("dev_suffix"),
|
|
626
651
|
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
627
652
|
custom_domains=_parse_custom_domains(custom_domains),
|
|
628
653
|
web_server_port=port,
|
|
@@ -645,7 +670,7 @@ def _web_server(
|
|
|
645
670
|
|
|
646
671
|
|
|
647
672
|
def _enter(
|
|
648
|
-
_warn_parentheses_missing=None,
|
|
673
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
649
674
|
*,
|
|
650
675
|
snap: bool = False,
|
|
651
676
|
) -> Callable[[Union[_PartialFunction, NullaryMethod]], _PartialFunction]:
|
|
@@ -696,7 +721,7 @@ def _exit(_warn_parentheses_missing=None) -> Callable[[NullaryMethod], _PartialF
|
|
|
696
721
|
|
|
697
722
|
|
|
698
723
|
def _batched(
|
|
699
|
-
_warn_parentheses_missing=None,
|
|
724
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
700
725
|
*,
|
|
701
726
|
max_batch_size: int,
|
|
702
727
|
wait_ms: int,
|
|
@@ -758,9 +783,9 @@ def _batched(
|
|
|
758
783
|
|
|
759
784
|
|
|
760
785
|
def _concurrent(
|
|
761
|
-
_warn_parentheses_missing=None,
|
|
786
|
+
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
762
787
|
*,
|
|
763
|
-
max_inputs: int, # Hard limit on each container's input concurrency
|
|
788
|
+
max_inputs: Optional[int] = None, # Hard limit on each container's input concurrency
|
|
764
789
|
target_inputs: Optional[int] = None, # Input concurrency that Modal's autoscaler should target
|
|
765
790
|
) -> Callable[
|
|
766
791
|
[Union[Callable[P, ReturnType], _PartialFunction[P, ReturnType, ReturnType]]],
|
|
@@ -812,7 +837,7 @@ def _concurrent(
|
|
|
812
837
|
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.concurrent()`."
|
|
813
838
|
)
|
|
814
839
|
|
|
815
|
-
if target_inputs and target_inputs > max_inputs:
|
|
840
|
+
if max_inputs is not None and target_inputs is not None and target_inputs > max_inputs:
|
|
816
841
|
raise InvalidError("`target_inputs` parameter cannot be greater than `max_inputs`.")
|
|
817
842
|
|
|
818
843
|
flags = _PartialFunctionFlags.CONCURRENT
|
modal/_pty.py
CHANGED
|
@@ -7,8 +7,11 @@ from typing import Optional
|
|
|
7
7
|
from modal_proto import api_pb2
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def get_winsz(fd) -> tuple[Optional[int], Optional[int]]:
|
|
10
|
+
def get_winsz(fd=None) -> tuple[Optional[int], Optional[int]]:
|
|
11
11
|
try:
|
|
12
|
+
if fd is None:
|
|
13
|
+
fd = sys.stdin.fileno()
|
|
14
|
+
|
|
12
15
|
import fcntl
|
|
13
16
|
import struct
|
|
14
17
|
import termios
|
|
@@ -40,8 +43,8 @@ def raw_terminal():
|
|
|
40
43
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
def get_pty_info(shell: bool) -> api_pb2.PTYInfo:
|
|
44
|
-
rows, cols = get_winsz(
|
|
46
|
+
def get_pty_info(shell: bool, no_terminate_on_idle_stdin: bool = False) -> api_pb2.PTYInfo:
|
|
47
|
+
rows, cols = get_winsz()
|
|
45
48
|
return api_pb2.PTYInfo(
|
|
46
49
|
enabled=True, # TODO(erikbern): deprecated
|
|
47
50
|
winsz_rows=rows,
|
|
@@ -50,4 +53,5 @@ def get_pty_info(shell: bool) -> api_pb2.PTYInfo:
|
|
|
50
53
|
env_colorterm=os.environ.get("COLORTERM"),
|
|
51
54
|
env_term_program=os.environ.get("TERM_PROGRAM"),
|
|
52
55
|
pty_type=api_pb2.PTYInfo.PTY_TYPE_SHELL if shell else api_pb2.PTYInfo.PTY_TYPE_FUNCTION,
|
|
56
|
+
no_terminate_on_idle_stdin=no_terminate_on_idle_stdin,
|
|
53
57
|
)
|
modal/_resolver.py
CHANGED
|
@@ -8,17 +8,16 @@ from asyncio import Future
|
|
|
8
8
|
from collections.abc import Hashable
|
|
9
9
|
from typing import TYPE_CHECKING, Optional
|
|
10
10
|
|
|
11
|
+
import modal._object
|
|
11
12
|
from modal._traceback import suppress_tb_frames
|
|
12
13
|
from modal_proto import api_pb2
|
|
13
14
|
|
|
15
|
+
from ._load_context import LoadContext
|
|
14
16
|
from ._utils.async_utils import TaskContext
|
|
15
|
-
from .client import _Client
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from rich.tree import Tree
|
|
19
20
|
|
|
20
|
-
import modal._object
|
|
21
|
-
|
|
22
21
|
|
|
23
22
|
class StatusRow:
|
|
24
23
|
def __init__(self, progress: "typing.Optional[Tree]"):
|
|
@@ -48,19 +47,10 @@ class StatusRow:
|
|
|
48
47
|
|
|
49
48
|
class Resolver:
|
|
50
49
|
_local_uuid_to_future: dict[str, Future]
|
|
51
|
-
_environment_name: Optional[str]
|
|
52
|
-
_app_id: Optional[str]
|
|
53
50
|
_deduplication_cache: dict[Hashable, Future]
|
|
54
|
-
_client: _Client
|
|
55
51
|
_build_start: float
|
|
56
52
|
|
|
57
|
-
def __init__(
|
|
58
|
-
self,
|
|
59
|
-
client: _Client,
|
|
60
|
-
*,
|
|
61
|
-
environment_name: Optional[str] = None,
|
|
62
|
-
app_id: Optional[str] = None,
|
|
63
|
-
):
|
|
53
|
+
def __init__(self):
|
|
64
54
|
try:
|
|
65
55
|
# TODO(michael) If we don't clean this up more thoroughly, it would probably
|
|
66
56
|
# be good to have a single source of truth for "rich is installed" rather than
|
|
@@ -75,9 +65,6 @@ class Resolver:
|
|
|
75
65
|
|
|
76
66
|
self._local_uuid_to_future = {}
|
|
77
67
|
self._tree = tree
|
|
78
|
-
self._client = client
|
|
79
|
-
self._app_id = app_id
|
|
80
|
-
self._environment_name = environment_name
|
|
81
68
|
self._deduplication_cache = {}
|
|
82
69
|
|
|
83
70
|
with tempfile.TemporaryFile() as temp_file:
|
|
@@ -85,27 +72,24 @@ class Resolver:
|
|
|
85
72
|
# to the mtime on mounted files, and want those measurements to have the same resolution.
|
|
86
73
|
self._build_start = os.fstat(temp_file.fileno()).st_mtime
|
|
87
74
|
|
|
88
|
-
@property
|
|
89
|
-
def app_id(self) -> Optional[str]:
|
|
90
|
-
return self._app_id
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def client(self):
|
|
94
|
-
return self._client
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
def environment_name(self):
|
|
98
|
-
return self._environment_name
|
|
99
|
-
|
|
100
75
|
@property
|
|
101
76
|
def build_start(self) -> float:
|
|
102
77
|
return self._build_start
|
|
103
78
|
|
|
104
|
-
async def preload(
|
|
79
|
+
async def preload(
|
|
80
|
+
self, obj: "modal._object._Object", parent_load_context: "LoadContext", existing_object_id: Optional[str]
|
|
81
|
+
):
|
|
105
82
|
if obj._preload is not None:
|
|
106
|
-
|
|
83
|
+
load_context = obj._load_context_overrides.merged_with(parent_load_context)
|
|
84
|
+
await obj._preload(obj, self, load_context, existing_object_id)
|
|
107
85
|
|
|
108
|
-
async def load(
|
|
86
|
+
async def load(
|
|
87
|
+
self,
|
|
88
|
+
obj: "modal._object._Object",
|
|
89
|
+
parent_load_context: "LoadContext",
|
|
90
|
+
*,
|
|
91
|
+
existing_object_id: Optional[str] = None,
|
|
92
|
+
):
|
|
109
93
|
if obj._is_hydrated and obj._is_another_app:
|
|
110
94
|
# No need to reload this, it won't typically change
|
|
111
95
|
if obj.local_uuid not in self._local_uuid_to_future:
|
|
@@ -129,21 +113,23 @@ class Resolver:
|
|
|
129
113
|
cached_future = self._deduplication_cache.get(deduplication_key)
|
|
130
114
|
if cached_future:
|
|
131
115
|
hydrated_object = await cached_future
|
|
132
|
-
|
|
116
|
+
# Use the client from the already-hydrated object
|
|
117
|
+
obj._hydrate(hydrated_object.object_id, hydrated_object.client, hydrated_object._get_metadata())
|
|
133
118
|
return obj
|
|
134
119
|
|
|
135
120
|
if not cached_future:
|
|
136
121
|
# don't run any awaits within this if-block to prevent race conditions
|
|
137
122
|
async def loader():
|
|
138
|
-
|
|
123
|
+
load_context = await obj._load_context_overrides.merged_with(parent_load_context).apply_defaults()
|
|
124
|
+
|
|
139
125
|
# TODO(erikbern): do we need existing_object_id for those?
|
|
140
|
-
await TaskContext.gather(*[self.load(dep) for dep in obj.deps()])
|
|
126
|
+
await TaskContext.gather(*[self.load(dep, load_context) for dep in obj.deps()])
|
|
141
127
|
|
|
142
128
|
# Load the object itself
|
|
143
129
|
if not obj._load:
|
|
144
130
|
raise Exception(f"Object {obj} has no loader function")
|
|
145
131
|
|
|
146
|
-
await obj._load(obj, self, existing_object_id)
|
|
132
|
+
await obj._load(obj, self, load_context, existing_object_id)
|
|
147
133
|
|
|
148
134
|
# Check that the id of functions didn't change
|
|
149
135
|
# Persisted refs are ignored because their life cycle is managed independently.
|
modal/_runtime/asgi.py
CHANGED
|
@@ -16,7 +16,7 @@ from modal.config import logger
|
|
|
16
16
|
from modal.exception import ExecutionError, InvalidError
|
|
17
17
|
from modal.experimental import stop_fetching_inputs
|
|
18
18
|
|
|
19
|
-
from .execution_context import current_function_call_id
|
|
19
|
+
from .execution_context import current_attempt_token, current_function_call_id
|
|
20
20
|
|
|
21
21
|
FIRST_MESSAGE_TIMEOUT_SECONDS = 5.0
|
|
22
22
|
|
|
@@ -106,6 +106,7 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., Asyn
|
|
|
106
106
|
raise ExecutionError("Unpexected state in ASGI scope")
|
|
107
107
|
scope["state"] = state
|
|
108
108
|
function_call_id = current_function_call_id()
|
|
109
|
+
attempt_token = current_attempt_token()
|
|
109
110
|
assert function_call_id, "internal error: function_call_id not set in asgi_app() scope"
|
|
110
111
|
|
|
111
112
|
messages_from_app: asyncio.Queue[dict[str, Any]] = asyncio.Queue(1)
|
|
@@ -119,7 +120,7 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., Asyn
|
|
|
119
120
|
|
|
120
121
|
async def handle_first_input_timeout():
|
|
121
122
|
if scope["type"] == "http":
|
|
122
|
-
await messages_from_app.put({"type": "http.response.start", "status":
|
|
123
|
+
await messages_from_app.put({"type": "http.response.start", "status": 408})
|
|
123
124
|
await messages_from_app.put(
|
|
124
125
|
{
|
|
125
126
|
"type": "http.response.body",
|
|
@@ -142,7 +143,7 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., Asyn
|
|
|
142
143
|
# This initial message, "http.request" or "websocket.connect", should be sent
|
|
143
144
|
# immediately after starting the ASGI app's function call. If it is not received, that
|
|
144
145
|
# indicates a request cancellation or other abnormal circumstance.
|
|
145
|
-
message_gen = container_io_manager.get_data_in.aio(function_call_id)
|
|
146
|
+
message_gen = container_io_manager.get_data_in.aio(function_call_id, attempt_token)
|
|
146
147
|
first_message_task = asyncio.create_task(message_gen.__anext__())
|
|
147
148
|
|
|
148
149
|
try:
|