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/__main__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
+
from ._output import make_console
|
|
4
5
|
from ._traceback import reduce_traceback_to_user_code
|
|
5
6
|
from .cli._traceback import highlight_modal_warnings, setup_rich_traceback
|
|
6
7
|
from .cli.entry_point import entrypoint_cli
|
|
@@ -35,9 +36,7 @@ def main():
|
|
|
35
36
|
raise
|
|
36
37
|
|
|
37
38
|
from grpclib import GRPCError, Status
|
|
38
|
-
from rich.console import Console
|
|
39
39
|
from rich.panel import Panel
|
|
40
|
-
from rich.text import Text
|
|
41
40
|
|
|
42
41
|
if isinstance(exc, GRPCError):
|
|
43
42
|
status_map = {
|
|
@@ -68,8 +67,8 @@ def main():
|
|
|
68
67
|
if notes := getattr(exc, "__notes__", []):
|
|
69
68
|
content = f"{content}\n\nNote: {' '.join(notes)}"
|
|
70
69
|
|
|
71
|
-
console =
|
|
72
|
-
panel = Panel(
|
|
70
|
+
console = make_console(stderr=True)
|
|
71
|
+
panel = Panel(content, title=title, title_align="left", border_style="red")
|
|
73
72
|
console.print(panel, highlight=False)
|
|
74
73
|
sys.exit(1)
|
|
75
74
|
|
modal/_billing.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Any, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
from modal_proto import api_pb2
|
|
7
|
+
|
|
8
|
+
from .client import _Client
|
|
9
|
+
from .exception import InvalidError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkspaceBillingReportItem(TypedDict):
|
|
13
|
+
object_id: str
|
|
14
|
+
description: str
|
|
15
|
+
environment_name: str
|
|
16
|
+
interval_start: datetime
|
|
17
|
+
cost: Decimal
|
|
18
|
+
tags: dict[str, str]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def _workspace_billing_report(
|
|
22
|
+
*,
|
|
23
|
+
start: datetime, # Start of the report, inclusive
|
|
24
|
+
end: Optional[datetime] = None, # End of the report, exclusive
|
|
25
|
+
resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
|
|
26
|
+
tag_names: Optional[list[str]] = None, # Optional additional metadata to include
|
|
27
|
+
client: Optional[_Client] = None,
|
|
28
|
+
) -> list[dict[str, Any]]:
|
|
29
|
+
"""Generate a tabular report of workspace usage by object and time.
|
|
30
|
+
|
|
31
|
+
The result will be a list of dictionaries for each interval (determined by `resolution`)
|
|
32
|
+
between the `start` and `end` limits. The dictionary represents a single Modal object
|
|
33
|
+
that billing can be attributed to (e.g., an App) along with metadata (including user-defined
|
|
34
|
+
tags) for identifying that object.
|
|
35
|
+
|
|
36
|
+
The `start` and `end` parameters are required to either have a UTC timezone or to be
|
|
37
|
+
timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
|
|
38
|
+
be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
|
|
39
|
+
parameters are partial: `start` will be rounded to the beginning of its interval, while
|
|
40
|
+
partial `end` intervals will be excluded.
|
|
41
|
+
|
|
42
|
+
Additional user-provided metadata can be included in the report if the objects have tags
|
|
43
|
+
and `tag_names` (i.e., keys) are specified in the request. Note that tags will be attributed
|
|
44
|
+
to the entire interval even if they were added or removed at some point within it.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
if client is None:
|
|
48
|
+
client = await _Client.from_env()
|
|
49
|
+
|
|
50
|
+
tag_names = tag_names or []
|
|
51
|
+
|
|
52
|
+
if end is None:
|
|
53
|
+
end = datetime.now(timezone.utc)
|
|
54
|
+
|
|
55
|
+
for dt in (start, end):
|
|
56
|
+
if dt.tzinfo is None:
|
|
57
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
58
|
+
elif dt.tzinfo != timezone.utc:
|
|
59
|
+
raise InvalidError("Timezone-aware start/end limits must be in UTC.")
|
|
60
|
+
|
|
61
|
+
request = api_pb2.WorkspaceBillingReportRequest(
|
|
62
|
+
resolution=resolution,
|
|
63
|
+
tag_names=tag_names,
|
|
64
|
+
)
|
|
65
|
+
request.start_timestamp.FromDatetime(start)
|
|
66
|
+
request.end_timestamp.FromDatetime(end)
|
|
67
|
+
|
|
68
|
+
rows = []
|
|
69
|
+
async for pb_item in client.stub.WorkspaceBillingReport.unary_stream(request):
|
|
70
|
+
item = {
|
|
71
|
+
"object_id": pb_item.object_id,
|
|
72
|
+
"description": pb_item.description,
|
|
73
|
+
"environment_name": pb_item.environment_name,
|
|
74
|
+
"interval_start": pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
|
|
75
|
+
"cost": Decimal(pb_item.cost),
|
|
76
|
+
"tags": dict(pb_item.tags),
|
|
77
|
+
}
|
|
78
|
+
rows.append(item)
|
|
79
|
+
|
|
80
|
+
return rows
|
modal/_clustered_functions.py
CHANGED
|
@@ -5,7 +5,6 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from modal._utils.async_utils import synchronize_api
|
|
8
|
-
from modal._utils.grpc_utils import retry_transient_errors
|
|
9
8
|
from modal.client import _Client
|
|
10
9
|
from modal.exception import InvalidError
|
|
11
10
|
from modal_proto import api_pb2
|
|
@@ -14,7 +13,9 @@ from modal_proto import api_pb2
|
|
|
14
13
|
@dataclass
|
|
15
14
|
class ClusterInfo:
|
|
16
15
|
rank: int
|
|
16
|
+
cluster_id: str
|
|
17
17
|
container_ips: list[str]
|
|
18
|
+
container_ipv4_ips: list[str]
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
cluster_info: Optional[ClusterInfo] = None
|
|
@@ -59,8 +60,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
59
60
|
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
|
60
61
|
|
|
61
62
|
if world_size > 1:
|
|
62
|
-
resp
|
|
63
|
-
client.stub.TaskClusterHello,
|
|
63
|
+
resp = await client.stub.TaskClusterHello(
|
|
64
64
|
api_pb2.TaskClusterHelloRequest(
|
|
65
65
|
task_id=task_id,
|
|
66
66
|
container_ip=container_ip,
|
|
@@ -68,12 +68,16 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
68
68
|
)
|
|
69
69
|
cluster_info = ClusterInfo(
|
|
70
70
|
rank=resp.cluster_rank,
|
|
71
|
+
cluster_id=resp.cluster_id,
|
|
71
72
|
container_ips=resp.container_ips,
|
|
73
|
+
container_ipv4_ips=resp.container_ipv4_ips,
|
|
72
74
|
)
|
|
73
75
|
else:
|
|
74
76
|
cluster_info = ClusterInfo(
|
|
75
77
|
rank=0,
|
|
78
|
+
cluster_id="", # No cluster ID for single-node # TODO(irfansharif): Is this right?
|
|
76
79
|
container_ips=[container_ip],
|
|
80
|
+
container_ipv4_ips=[], # No IPv4 IPs for single-node
|
|
77
81
|
)
|
|
78
82
|
|
|
79
83
|
|
modal/_clustered_functions.pyi
CHANGED
|
@@ -3,12 +3,14 @@ import typing
|
|
|
3
3
|
import typing_extensions
|
|
4
4
|
|
|
5
5
|
class ClusterInfo:
|
|
6
|
-
"""ClusterInfo(rank: int, container_ips: list[str])"""
|
|
6
|
+
"""ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str])"""
|
|
7
7
|
|
|
8
8
|
rank: int
|
|
9
|
+
cluster_id: str
|
|
9
10
|
container_ips: list[str]
|
|
11
|
+
container_ipv4_ips: list[str]
|
|
10
12
|
|
|
11
|
-
def __init__(self, rank: int, container_ips: list[str]) -> None:
|
|
13
|
+
def __init__(self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str]) -> None:
|
|
12
14
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
13
15
|
...
|
|
14
16
|
|
modal/_container_entrypoint.py
CHANGED
|
@@ -32,14 +32,14 @@ from modal._partial_function import (
|
|
|
32
32
|
_PartialFunctionFlags,
|
|
33
33
|
)
|
|
34
34
|
from modal._serialization import deserialize, deserialize_params
|
|
35
|
-
from modal._utils.async_utils import TaskContext, synchronizer
|
|
35
|
+
from modal._utils.async_utils import TaskContext, aclosing, synchronizer
|
|
36
36
|
from modal._utils.function_utils import (
|
|
37
37
|
callable_has_non_self_params,
|
|
38
38
|
)
|
|
39
39
|
from modal.app import App, _App
|
|
40
40
|
from modal.client import Client, _Client
|
|
41
41
|
from modal.config import logger
|
|
42
|
-
from modal.exception import ExecutionError, InputCancellation
|
|
42
|
+
from modal.exception import ExecutionError, InputCancellation
|
|
43
43
|
from modal.running_app import RunningApp, running_app_from_layout
|
|
44
44
|
from modal_proto import api_pb2
|
|
45
45
|
|
|
@@ -184,86 +184,75 @@ def call_function(
|
|
|
184
184
|
batch_wait_ms: int,
|
|
185
185
|
):
|
|
186
186
|
async def run_input_async(io_context: IOContext) -> None:
|
|
187
|
+
reset_context = execution_context._set_current_context_ids(
|
|
188
|
+
io_context.input_ids, io_context.function_call_ids, io_context.attempt_tokens
|
|
189
|
+
)
|
|
187
190
|
started_at = time.time()
|
|
188
|
-
input_ids, function_call_ids = io_context.input_ids, io_context.function_call_ids
|
|
189
|
-
reset_context = execution_context._set_current_context_ids(input_ids, function_call_ids)
|
|
190
191
|
async with container_io_manager.handle_input_exception.aio(io_context, started_at):
|
|
191
|
-
res = io_context.call_finalized_function()
|
|
192
192
|
# TODO(erikbern): any exception below shouldn't be considered a user exception
|
|
193
193
|
if io_context.finalized_function.is_generator:
|
|
194
|
-
if not inspect.isasyncgen(res):
|
|
195
|
-
raise InvalidError(f"Async generator function returned value of type {type(res)}")
|
|
196
|
-
|
|
197
194
|
# Send up to this many outputs at a time.
|
|
195
|
+
current_function_call_id = execution_context.current_function_call_id()
|
|
196
|
+
assert current_function_call_id is not None # Set above.
|
|
197
|
+
current_attempt_token = execution_context.current_attempt_token()
|
|
198
|
+
assert current_attempt_token is not None # Set above, but can be empty string.
|
|
198
199
|
generator_queue: asyncio.Queue[Any] = await container_io_manager._queue_create.aio(1024)
|
|
199
200
|
async with container_io_manager.generator_output_sender(
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
current_function_call_id,
|
|
202
|
+
current_attempt_token,
|
|
203
|
+
io_context._generator_output_format(),
|
|
202
204
|
generator_queue,
|
|
203
205
|
):
|
|
204
206
|
item_count = 0
|
|
205
|
-
async
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
async with aclosing(io_context.call_generator_async()) as gen:
|
|
208
|
+
async for value in gen:
|
|
209
|
+
await container_io_manager._queue_put.aio(generator_queue, value)
|
|
210
|
+
item_count += 1
|
|
208
211
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
io_context,
|
|
212
|
-
started_at,
|
|
213
|
-
message,
|
|
214
|
-
api_pb2.DATA_FORMAT_GENERATOR_DONE,
|
|
212
|
+
await container_io_manager._send_outputs.aio(
|
|
213
|
+
started_at, io_context.output_items_generator_done(started_at, item_count)
|
|
215
214
|
)
|
|
216
215
|
else:
|
|
217
|
-
|
|
218
|
-
raise InvalidError(
|
|
219
|
-
f"Async (non-generator) function returned value of type {type(res)}"
|
|
220
|
-
" You might need to use @app.function(..., is_generator=True)."
|
|
221
|
-
)
|
|
222
|
-
value = await res
|
|
216
|
+
value = await io_context.call_function_async()
|
|
223
217
|
await container_io_manager.push_outputs.aio(
|
|
224
218
|
io_context,
|
|
225
219
|
started_at,
|
|
226
220
|
value,
|
|
227
|
-
io_context.finalized_function.data_format,
|
|
228
221
|
)
|
|
229
222
|
reset_context()
|
|
230
223
|
|
|
231
224
|
def run_input_sync(io_context: IOContext) -> None:
|
|
232
225
|
started_at = time.time()
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
reset_context = execution_context._set_current_context_ids(
|
|
227
|
+
io_context.input_ids, io_context.function_call_ids, io_context.attempt_tokens
|
|
228
|
+
)
|
|
235
229
|
with container_io_manager.handle_input_exception(io_context, started_at):
|
|
236
|
-
res = io_context.call_finalized_function()
|
|
237
|
-
|
|
238
230
|
# TODO(erikbern): any exception below shouldn't be considered a user exception
|
|
239
231
|
if io_context.finalized_function.is_generator:
|
|
240
|
-
|
|
241
|
-
raise InvalidError(f"Generator function returned value of type {type(res)}")
|
|
242
|
-
|
|
232
|
+
gen = io_context.call_generator_sync()
|
|
243
233
|
# Send up to this many outputs at a time.
|
|
234
|
+
current_function_call_id = execution_context.current_function_call_id()
|
|
235
|
+
assert current_function_call_id is not None # Set above.
|
|
236
|
+
current_attempt_token = execution_context.current_attempt_token()
|
|
237
|
+
assert current_attempt_token is not None # Set above, but can be empty string.
|
|
244
238
|
generator_queue: asyncio.Queue[Any] = container_io_manager._queue_create(1024)
|
|
245
|
-
|
|
246
239
|
with container_io_manager.generator_output_sender(
|
|
247
|
-
|
|
248
|
-
|
|
240
|
+
current_function_call_id,
|
|
241
|
+
current_attempt_token,
|
|
242
|
+
io_context._generator_output_format(),
|
|
249
243
|
generator_queue,
|
|
250
244
|
):
|
|
251
245
|
item_count = 0
|
|
252
|
-
for value in
|
|
246
|
+
for value in gen:
|
|
253
247
|
container_io_manager._queue_put(generator_queue, value)
|
|
254
248
|
item_count += 1
|
|
255
249
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
else:
|
|
259
|
-
if inspect.iscoroutine(res) or inspect.isgenerator(res) or inspect.isasyncgen(res):
|
|
260
|
-
raise InvalidError(
|
|
261
|
-
f"Sync (non-generator) function return value of type {type(res)}."
|
|
262
|
-
" You might need to use @app.function(..., is_generator=True)."
|
|
263
|
-
)
|
|
264
|
-
container_io_manager.push_outputs(
|
|
265
|
-
io_context, started_at, res, io_context.finalized_function.data_format
|
|
250
|
+
container_io_manager._send_outputs(
|
|
251
|
+
started_at, io_context.output_items_generator_done(started_at, item_count)
|
|
266
252
|
)
|
|
253
|
+
else:
|
|
254
|
+
values = io_context.call_function_sync()
|
|
255
|
+
container_io_manager.push_outputs(io_context, started_at, values)
|
|
267
256
|
reset_context()
|
|
268
257
|
|
|
269
258
|
if container_io_manager.input_concurrency_enabled:
|
|
@@ -426,9 +415,9 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
426
415
|
param_kwargs,
|
|
427
416
|
)
|
|
428
417
|
else:
|
|
418
|
+
assert ser_usr_cls is None
|
|
429
419
|
service = import_single_function_service(
|
|
430
420
|
function_def,
|
|
431
|
-
ser_usr_cls,
|
|
432
421
|
ser_fun,
|
|
433
422
|
)
|
|
434
423
|
|
|
@@ -461,7 +450,10 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
461
450
|
f"Function has {len(service.service_deps)} dependencies"
|
|
462
451
|
f" but container got {len(dep_object_ids)} object ids.\n"
|
|
463
452
|
f"Code deps: {service.service_deps}\n"
|
|
464
|
-
f"Object ids: {dep_object_ids}"
|
|
453
|
+
f"Object ids: {dep_object_ids}\n"
|
|
454
|
+
"\n"
|
|
455
|
+
"This can happen if you are defining Modal objects under a conditional statement "
|
|
456
|
+
"that evaluates differently in the local and remote environments."
|
|
465
457
|
)
|
|
466
458
|
for object_id, obj in zip(dep_object_ids, service.service_deps):
|
|
467
459
|
metadata: Message = container_app.object_handle_metadata[object_id]
|