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
@@ -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(sys.stdin.fileno())
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(self, obj, existing_object_id: Optional[str]):
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
- await obj._preload(obj, self, existing_object_id)
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(self, obj: "modal._object._Object", existing_object_id: Optional[str] = None):
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
- obj._hydrate(hydrated_object.object_id, self._client, hydrated_object._get_metadata())
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
- # Wait for all its dependencies
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": 502})
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: