modal 0.66.14__py3-none-any.whl → 0.66.39__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.
- modal/__init__.py +1 -1
- modal/_container_entrypoint.py +27 -358
- modal/_runtime/__init__.py +1 -0
- modal/{_asgi.py → _runtime/asgi.py} +8 -7
- modal/{_container_io_manager.py → _runtime/container_io_manager.py} +18 -27
- modal/{execution_context.py → _runtime/execution_context.py} +2 -1
- modal/{_telemetry.py → _runtime/telemetry.py} +1 -1
- modal/_runtime/user_code_imports.py +361 -0
- modal/_serialization.py +1 -1
- modal/_utils/function_utils.py +28 -8
- modal/app.py +13 -46
- modal/cli/import_refs.py +4 -38
- modal/client.pyi +2 -2
- modal/dict.py +0 -6
- modal/dict.pyi +0 -4
- modal/experimental.py +1 -4
- modal/functions.py +11 -10
- modal/functions.pyi +8 -8
- modal/gpu.py +8 -6
- modal/image.py +93 -6
- modal/image.pyi +20 -2
- modal/io_streams.py +32 -12
- modal/io_streams.pyi +8 -4
- modal/mount.py +3 -2
- modal/network_file_system.py +0 -28
- modal/network_file_system.pyi +0 -14
- modal/parallel_map.py +1 -1
- modal/partial_function.py +11 -1
- modal/queue.py +0 -6
- modal/queue.pyi +0 -4
- modal/runner.py +1 -1
- modal/sandbox.py +1 -1
- modal/secret.py +1 -1
- modal/volume.py +0 -22
- modal/volume.pyi +0 -9
- {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/METADATA +1 -2
- {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/RECORD +49 -49
- modal_proto/api.proto +2 -21
- modal_proto/api_grpc.py +0 -16
- modal_proto/api_pb2.py +702 -726
- modal_proto/api_pb2.pyi +6 -60
- modal_proto/api_pb2_grpc.py +0 -33
- modal_proto/api_pb2_grpc.pyi +0 -10
- modal_proto/modal_api_grpc.py +0 -1
- modal_version/_version_generated.py +1 -1
- modal/_container_io_manager.pyi +0 -414
- modal/execution_context.pyi +0 -22
- {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/LICENSE +0 -0
- {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/WHEEL +0 -0
- {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/entry_points.txt +0 -0
- {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/top_level.txt +0 -0
modal/__init__.py
CHANGED
@@ -9,6 +9,7 @@ if sys.version_info[:2] >= (3, 14):
|
|
9
9
|
from modal_version import __version__
|
10
10
|
|
11
11
|
try:
|
12
|
+
from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
|
12
13
|
from ._tunnel import Tunnel, forward
|
13
14
|
from .app import App, Stub
|
14
15
|
from .client import Client
|
@@ -16,7 +17,6 @@ try:
|
|
16
17
|
from .cls import Cls, parameter
|
17
18
|
from .dict import Dict
|
18
19
|
from .exception import Error
|
19
|
-
from .execution_context import current_function_call_id, current_input_id, interact, is_local
|
20
20
|
from .functions import Function
|
21
21
|
from .image import Image
|
22
22
|
from .mount import Mount
|
modal/_container_entrypoint.py
CHANGED
@@ -2,215 +2,58 @@
|
|
2
2
|
# ruff: noqa: E402
|
3
3
|
import os
|
4
4
|
|
5
|
+
from modal._runtime.user_code_imports import Service, import_class_service, import_single_function_service
|
6
|
+
|
5
7
|
telemetry_socket = os.environ.get("MODAL_TELEMETRY_SOCKET")
|
6
8
|
if telemetry_socket:
|
7
|
-
from ._telemetry import instrument_imports
|
9
|
+
from runtime._telemetry import instrument_imports
|
8
10
|
|
9
11
|
instrument_imports(telemetry_socket)
|
10
12
|
|
11
13
|
import asyncio
|
12
14
|
import base64
|
13
15
|
import concurrent.futures
|
14
|
-
import importlib
|
15
16
|
import inspect
|
16
17
|
import queue
|
17
18
|
import signal
|
18
19
|
import sys
|
19
20
|
import threading
|
20
21
|
import time
|
21
|
-
import
|
22
|
-
from abc import ABCMeta, abstractmethod
|
23
|
-
from dataclasses import dataclass
|
24
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Tuple
|
22
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence
|
25
23
|
|
26
24
|
from google.protobuf.message import Message
|
27
25
|
|
28
|
-
from
|
29
|
-
|
30
|
-
from .
|
31
|
-
|
32
|
-
|
33
|
-
wait_for_web_server,
|
34
|
-
web_server_proxy,
|
35
|
-
webhook_asgi_app,
|
36
|
-
wsgi_app_wrapper,
|
37
|
-
)
|
38
|
-
from ._clustered_functions import initialize_clustered_function
|
39
|
-
from ._container_io_manager import ContainerIOManager, FinalizedFunction, IOContext, UserException, _ContainerIOManager
|
40
|
-
from ._proxy_tunnel import proxy_tunnel
|
41
|
-
from ._serialization import deserialize, deserialize_proto_params
|
42
|
-
from ._utils.async_utils import TaskContext, synchronizer
|
43
|
-
from ._utils.function_utils import (
|
44
|
-
LocalFunctionError,
|
26
|
+
from modal._clustered_functions import initialize_clustered_function
|
27
|
+
from modal._proxy_tunnel import proxy_tunnel
|
28
|
+
from modal._serialization import deserialize, deserialize_proto_params
|
29
|
+
from modal._utils.async_utils import TaskContext, synchronizer
|
30
|
+
from modal._utils.function_utils import (
|
45
31
|
callable_has_non_self_params,
|
46
|
-
is_async as get_is_async,
|
47
|
-
is_global_object,
|
48
32
|
)
|
49
|
-
from .app import App, _App
|
50
|
-
from .client import Client, _Client
|
51
|
-
from .
|
52
|
-
from .
|
53
|
-
from .
|
54
|
-
from .execution_context import _set_current_context_ids
|
55
|
-
from .functions import Function, _Function
|
56
|
-
from .partial_function import (
|
33
|
+
from modal.app import App, _App
|
34
|
+
from modal.client import Client, _Client
|
35
|
+
from modal.config import logger
|
36
|
+
from modal.exception import ExecutionError, InputCancellation, InvalidError
|
37
|
+
from modal.partial_function import (
|
57
38
|
_find_callables_for_obj,
|
58
|
-
_find_partial_methods_for_user_cls,
|
59
|
-
_PartialFunction,
|
60
39
|
_PartialFunctionFlags,
|
61
40
|
)
|
62
|
-
from .running_app import RunningApp
|
41
|
+
from modal.running_app import RunningApp
|
42
|
+
from modal_proto import api_pb2
|
43
|
+
|
44
|
+
from ._runtime.container_io_manager import (
|
45
|
+
ContainerIOManager,
|
46
|
+
IOContext,
|
47
|
+
UserException,
|
48
|
+
_ContainerIOManager,
|
49
|
+
)
|
50
|
+
from ._runtime.execution_context import _set_current_context_ids
|
63
51
|
|
64
52
|
if TYPE_CHECKING:
|
65
|
-
import modal.
|
53
|
+
import modal._runtime.container_io_manager
|
66
54
|
import modal.object
|
67
55
|
|
68
56
|
|
69
|
-
def construct_webhook_callable(
|
70
|
-
user_defined_callable: Callable,
|
71
|
-
webhook_config: api_pb2.WebhookConfig,
|
72
|
-
container_io_manager: "modal._container_io_manager.ContainerIOManager",
|
73
|
-
):
|
74
|
-
# For webhooks, the user function is used to construct an asgi app:
|
75
|
-
if webhook_config.type == api_pb2.WEBHOOK_TYPE_ASGI_APP:
|
76
|
-
# Function returns an asgi_app, which we can use as a callable.
|
77
|
-
return asgi_app_wrapper(user_defined_callable(), container_io_manager)
|
78
|
-
|
79
|
-
elif webhook_config.type == api_pb2.WEBHOOK_TYPE_WSGI_APP:
|
80
|
-
# Function returns an wsgi_app, which we can use as a callable.
|
81
|
-
return wsgi_app_wrapper(user_defined_callable(), container_io_manager)
|
82
|
-
|
83
|
-
elif webhook_config.type == api_pb2.WEBHOOK_TYPE_FUNCTION:
|
84
|
-
# Function is a webhook without an ASGI app. Create one for it.
|
85
|
-
return asgi_app_wrapper(
|
86
|
-
webhook_asgi_app(user_defined_callable, webhook_config.method, webhook_config.web_endpoint_docs),
|
87
|
-
container_io_manager,
|
88
|
-
)
|
89
|
-
|
90
|
-
elif webhook_config.type == api_pb2.WEBHOOK_TYPE_WEB_SERVER:
|
91
|
-
# Function spawns an HTTP web server listening at a port.
|
92
|
-
user_defined_callable()
|
93
|
-
|
94
|
-
# We intentionally try to connect to the external interface instead of the loopback
|
95
|
-
# interface here so users are forced to expose the server. This allows us to potentially
|
96
|
-
# change the implementation to use an external bridge in the future.
|
97
|
-
host = get_ip_address(b"eth0")
|
98
|
-
port = webhook_config.web_server_port
|
99
|
-
startup_timeout = webhook_config.web_server_startup_timeout
|
100
|
-
wait_for_web_server(host, port, timeout=startup_timeout)
|
101
|
-
return asgi_app_wrapper(web_server_proxy(host, port), container_io_manager)
|
102
|
-
else:
|
103
|
-
raise InvalidError(f"Unrecognized web endpoint type {webhook_config.type}")
|
104
|
-
|
105
|
-
|
106
|
-
class Service(metaclass=ABCMeta):
|
107
|
-
"""Common interface for singular functions and class-based "services"
|
108
|
-
|
109
|
-
There are differences in the importing/finalization logic, and this
|
110
|
-
"protocol"/abc basically defines a common interface for the two types
|
111
|
-
of "Services" after the point of import.
|
112
|
-
"""
|
113
|
-
|
114
|
-
user_cls_instance: Any
|
115
|
-
app: Optional[_App]
|
116
|
-
code_deps: Optional[List["modal.object._Object"]]
|
117
|
-
|
118
|
-
@abstractmethod
|
119
|
-
def get_finalized_functions(
|
120
|
-
self, fun_def: api_pb2.Function, container_io_manager: "modal._container_io_manager.ContainerIOManager"
|
121
|
-
) -> Dict[str, "FinalizedFunction"]:
|
122
|
-
...
|
123
|
-
|
124
|
-
|
125
|
-
@dataclass
|
126
|
-
class ImportedFunction(Service):
|
127
|
-
user_cls_instance: Any
|
128
|
-
app: Optional[_App]
|
129
|
-
code_deps: Optional[List["modal.object._Object"]]
|
130
|
-
|
131
|
-
_user_defined_callable: Callable[..., Any]
|
132
|
-
|
133
|
-
def get_finalized_functions(
|
134
|
-
self, fun_def: api_pb2.Function, container_io_manager: "modal._container_io_manager.ContainerIOManager"
|
135
|
-
) -> Dict[str, "FinalizedFunction"]:
|
136
|
-
# Check this property before we turn it into a method (overriden by webhooks)
|
137
|
-
is_async = get_is_async(self._user_defined_callable)
|
138
|
-
# Use the function definition for whether this is a generator (overriden by webhooks)
|
139
|
-
is_generator = fun_def.function_type == api_pb2.Function.FUNCTION_TYPE_GENERATOR
|
140
|
-
|
141
|
-
webhook_config = fun_def.webhook_config
|
142
|
-
if not webhook_config.type:
|
143
|
-
# for non-webhooks, the runnable is straight forward:
|
144
|
-
return {
|
145
|
-
"": FinalizedFunction(
|
146
|
-
callable=self._user_defined_callable,
|
147
|
-
is_async=is_async,
|
148
|
-
is_generator=is_generator,
|
149
|
-
data_format=api_pb2.DATA_FORMAT_PICKLE,
|
150
|
-
)
|
151
|
-
}
|
152
|
-
|
153
|
-
web_callable, lifespan_manager = construct_webhook_callable(
|
154
|
-
self._user_defined_callable, fun_def.webhook_config, container_io_manager
|
155
|
-
)
|
156
|
-
|
157
|
-
return {
|
158
|
-
"": FinalizedFunction(
|
159
|
-
callable=web_callable,
|
160
|
-
lifespan_manager=lifespan_manager,
|
161
|
-
is_async=True,
|
162
|
-
is_generator=True,
|
163
|
-
data_format=api_pb2.DATA_FORMAT_ASGI,
|
164
|
-
)
|
165
|
-
}
|
166
|
-
|
167
|
-
|
168
|
-
@dataclass
|
169
|
-
class ImportedClass(Service):
|
170
|
-
user_cls_instance: Any
|
171
|
-
app: Optional[_App]
|
172
|
-
code_deps: Optional[List["modal.object._Object"]]
|
173
|
-
|
174
|
-
_partial_functions: Dict[str, _PartialFunction]
|
175
|
-
|
176
|
-
def get_finalized_functions(
|
177
|
-
self, fun_def: api_pb2.Function, container_io_manager: "modal._container_io_manager.ContainerIOManager"
|
178
|
-
) -> Dict[str, "FinalizedFunction"]:
|
179
|
-
finalized_functions = {}
|
180
|
-
for method_name, partial in self._partial_functions.items():
|
181
|
-
partial = synchronizer._translate_in(partial) # ugly
|
182
|
-
user_func = partial.raw_f
|
183
|
-
# Check this property before we turn it into a method (overriden by webhooks)
|
184
|
-
is_async = get_is_async(user_func)
|
185
|
-
# Use the function definition for whether this is a generator (overriden by webhooks)
|
186
|
-
is_generator = partial.is_generator
|
187
|
-
webhook_config = partial.webhook_config
|
188
|
-
|
189
|
-
bound_func = user_func.__get__(self.user_cls_instance)
|
190
|
-
|
191
|
-
if not webhook_config or webhook_config.type == api_pb2.WEBHOOK_TYPE_UNSPECIFIED:
|
192
|
-
# for non-webhooks, the runnable is straight forward:
|
193
|
-
finalized_function = FinalizedFunction(
|
194
|
-
callable=bound_func,
|
195
|
-
is_async=is_async,
|
196
|
-
is_generator=is_generator,
|
197
|
-
data_format=api_pb2.DATA_FORMAT_PICKLE,
|
198
|
-
)
|
199
|
-
else:
|
200
|
-
web_callable, lifespan_manager = construct_webhook_callable(
|
201
|
-
bound_func, webhook_config, container_io_manager
|
202
|
-
)
|
203
|
-
finalized_function = FinalizedFunction(
|
204
|
-
callable=web_callable,
|
205
|
-
lifespan_manager=lifespan_manager,
|
206
|
-
is_async=True,
|
207
|
-
is_generator=True,
|
208
|
-
data_format=api_pb2.DATA_FORMAT_ASGI,
|
209
|
-
)
|
210
|
-
finalized_functions[method_name] = finalized_function
|
211
|
-
return finalized_functions
|
212
|
-
|
213
|
-
|
214
57
|
class DaemonizedThreadPool:
|
215
58
|
# Used instead of ThreadPoolExecutor, since the latter won't allow
|
216
59
|
# the interpreter to shut down before the currently running tasks
|
@@ -331,8 +174,8 @@ class UserCodeEventLoop:
|
|
331
174
|
|
332
175
|
def call_function(
|
333
176
|
user_code_event_loop: UserCodeEventLoop,
|
334
|
-
container_io_manager: "modal.
|
335
|
-
finalized_functions: Dict[str, FinalizedFunction],
|
177
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
178
|
+
finalized_functions: Dict[str, "modal._runtime.user_code_imports.FinalizedFunction"],
|
336
179
|
batch_max_size: int,
|
337
180
|
batch_wait_ms: int,
|
338
181
|
):
|
@@ -493,180 +336,6 @@ def call_function(
|
|
493
336
|
signal.signal(signal.SIGUSR1, usr1_handler) # reset signal handler
|
494
337
|
|
495
338
|
|
496
|
-
def import_single_function_service(
|
497
|
-
function_def: api_pb2.Function,
|
498
|
-
ser_cls, # used only for @build functions
|
499
|
-
ser_fun,
|
500
|
-
cls_args, # used only for @build functions
|
501
|
-
cls_kwargs, # used only for @build functions
|
502
|
-
) -> Service:
|
503
|
-
"""Imports a function dynamically, and locates the app.
|
504
|
-
|
505
|
-
This is somewhat complex because we're dealing with 3 quite different type of functions:
|
506
|
-
1. Functions defined in global scope and decorated in global scope (Function objects)
|
507
|
-
2. Functions defined in global scope but decorated elsewhere (these will be raw callables)
|
508
|
-
3. Serialized functions
|
509
|
-
|
510
|
-
In addition, we also need to handle
|
511
|
-
* Normal functions
|
512
|
-
* Methods on classes (in which case we need to instantiate the object)
|
513
|
-
|
514
|
-
This helper also handles web endpoints, ASGI/WSGI servers, and HTTP servers.
|
515
|
-
|
516
|
-
In order to locate the app, we try two things:
|
517
|
-
* If the function is a Function, we can get the app directly from it
|
518
|
-
* Otherwise, use the app name and look it up from a global list of apps: this
|
519
|
-
typically only happens in case 2 above, or in sometimes for case 3
|
520
|
-
|
521
|
-
Note that `import_function` is *not* synchronized, because we need it to run on the main
|
522
|
-
thread. This is so that any user code running in global scope (which executes as a part of
|
523
|
-
the import) runs on the right thread.
|
524
|
-
"""
|
525
|
-
user_defined_callable: Callable
|
526
|
-
function: Optional[_Function] = None
|
527
|
-
code_deps: Optional[List["modal.object._Object"]] = None
|
528
|
-
active_app: Optional[_App] = None
|
529
|
-
|
530
|
-
if ser_fun is not None:
|
531
|
-
# This is a serialized function we already fetched from the server
|
532
|
-
cls, user_defined_callable = ser_cls, ser_fun
|
533
|
-
else:
|
534
|
-
# Load the module dynamically
|
535
|
-
module = importlib.import_module(function_def.module_name)
|
536
|
-
qual_name: str = function_def.function_name
|
537
|
-
|
538
|
-
if not is_global_object(qual_name):
|
539
|
-
raise LocalFunctionError("Attempted to load a function defined in a function scope")
|
540
|
-
|
541
|
-
parts = qual_name.split(".")
|
542
|
-
if len(parts) == 1:
|
543
|
-
# This is a function
|
544
|
-
cls = None
|
545
|
-
f = getattr(module, qual_name)
|
546
|
-
if isinstance(f, Function):
|
547
|
-
function = synchronizer._translate_in(f)
|
548
|
-
user_defined_callable = function.get_raw_f()
|
549
|
-
active_app = function._app
|
550
|
-
else:
|
551
|
-
user_defined_callable = f
|
552
|
-
elif len(parts) == 2:
|
553
|
-
# As of v0.63 - this path should only be triggered by @build class builder methods
|
554
|
-
assert not function_def.use_method_name # new "placeholder methods" should not be invoked directly!
|
555
|
-
assert function_def.is_builder_function
|
556
|
-
cls_name, fun_name = parts
|
557
|
-
cls = getattr(module, cls_name)
|
558
|
-
if isinstance(cls, Cls):
|
559
|
-
# The cls decorator is in global scope
|
560
|
-
_cls = synchronizer._translate_in(cls)
|
561
|
-
user_defined_callable = _cls._callables[fun_name]
|
562
|
-
function = _cls._method_functions.get(fun_name)
|
563
|
-
active_app = _cls._app
|
564
|
-
else:
|
565
|
-
# This is a raw class
|
566
|
-
user_defined_callable = getattr(cls, fun_name)
|
567
|
-
else:
|
568
|
-
raise InvalidError(f"Invalid function qualname {qual_name}")
|
569
|
-
|
570
|
-
# Instantiate the class if it's defined
|
571
|
-
if cls:
|
572
|
-
# This code is only used for @build methods on classes
|
573
|
-
user_cls_instance = get_user_class_instance(cls, cls_args, cls_kwargs)
|
574
|
-
# Bind the function to the instance as self (using the descriptor protocol!)
|
575
|
-
user_defined_callable = user_defined_callable.__get__(user_cls_instance)
|
576
|
-
else:
|
577
|
-
user_cls_instance = None
|
578
|
-
|
579
|
-
if function:
|
580
|
-
code_deps = function.deps(only_explicit_mounts=True)
|
581
|
-
|
582
|
-
return ImportedFunction(
|
583
|
-
user_cls_instance,
|
584
|
-
active_app,
|
585
|
-
code_deps,
|
586
|
-
user_defined_callable,
|
587
|
-
)
|
588
|
-
|
589
|
-
|
590
|
-
def import_class_service(
|
591
|
-
function_def: api_pb2.Function,
|
592
|
-
ser_cls,
|
593
|
-
cls_args,
|
594
|
-
cls_kwargs,
|
595
|
-
) -> Service:
|
596
|
-
"""
|
597
|
-
This imports a full class to be able to execute any @method or webhook decorated methods.
|
598
|
-
|
599
|
-
See import_function.
|
600
|
-
"""
|
601
|
-
active_app: Optional[_App] = None
|
602
|
-
code_deps: Optional[List["modal.object._Object"]] = None
|
603
|
-
cls: typing.Union[type, Cls]
|
604
|
-
|
605
|
-
if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
|
606
|
-
assert ser_cls is not None
|
607
|
-
cls = ser_cls
|
608
|
-
else:
|
609
|
-
# Load the module dynamically
|
610
|
-
module = importlib.import_module(function_def.module_name)
|
611
|
-
qual_name: str = function_def.function_name
|
612
|
-
|
613
|
-
if not is_global_object(qual_name):
|
614
|
-
raise LocalFunctionError("Attempted to load a class defined in a function scope")
|
615
|
-
|
616
|
-
parts = qual_name.split(".")
|
617
|
-
if not (
|
618
|
-
len(parts) == 2 and parts[1] == "*"
|
619
|
-
): # the "function name" of a class service "function placeholder" is expected to be "ClassName.*"
|
620
|
-
raise ExecutionError(
|
621
|
-
f"Internal error: Invalid 'service function' identifier {qual_name}. Please contact Modal support"
|
622
|
-
)
|
623
|
-
|
624
|
-
assert not function_def.use_method_name # new "placeholder methods" should not be invoked directly!
|
625
|
-
cls_name = parts[0]
|
626
|
-
cls = getattr(module, cls_name)
|
627
|
-
|
628
|
-
if isinstance(cls, Cls):
|
629
|
-
# The cls decorator is in global scope
|
630
|
-
_cls = synchronizer._translate_in(cls)
|
631
|
-
method_partials = _cls._get_partial_functions()
|
632
|
-
function = _cls._class_service_function
|
633
|
-
else:
|
634
|
-
# Undecorated user class - find all methods
|
635
|
-
method_partials = _find_partial_methods_for_user_cls(cls, _PartialFunctionFlags.all())
|
636
|
-
function = None
|
637
|
-
|
638
|
-
if function:
|
639
|
-
code_deps = function.deps(only_explicit_mounts=True)
|
640
|
-
|
641
|
-
user_cls_instance = get_user_class_instance(cls, cls_args, cls_kwargs)
|
642
|
-
|
643
|
-
return ImportedClass(
|
644
|
-
user_cls_instance,
|
645
|
-
active_app,
|
646
|
-
code_deps,
|
647
|
-
method_partials,
|
648
|
-
)
|
649
|
-
|
650
|
-
|
651
|
-
def get_user_class_instance(cls: typing.Union[type, Cls], args: Tuple, kwargs: Dict[str, Any]) -> typing.Any:
|
652
|
-
"""Returns instance of the underlying class to be used as the `self`
|
653
|
-
|
654
|
-
The input `cls` can either be the raw Python class the user has declared ("user class"),
|
655
|
-
or an @app.cls-decorated version of it which is a modal.Cls-instance wrapping the user class.
|
656
|
-
"""
|
657
|
-
if isinstance(cls, Cls):
|
658
|
-
# globally @app.cls-decorated class
|
659
|
-
modal_obj: Obj = cls(*args, **kwargs)
|
660
|
-
modal_obj.entered = True # ugly but prevents .local() from triggering additional enter-logic
|
661
|
-
# TODO: unify lifecycle logic between .local() and container_entrypoint
|
662
|
-
user_cls_instance = modal_obj._get_user_cls_instance()
|
663
|
-
else:
|
664
|
-
# undecorated class (non-global decoration or serialized)
|
665
|
-
user_cls_instance = cls(*args, **kwargs)
|
666
|
-
|
667
|
-
return user_cls_instance
|
668
|
-
|
669
|
-
|
670
339
|
def get_active_app_fallback(function_def: api_pb2.Function) -> Optional[_App]:
|
671
340
|
# This branch is reached in the special case that the imported function/class is:
|
672
341
|
# 1) not serialized, and
|
@@ -0,0 +1 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
@@ -4,13 +4,14 @@ from typing import Any, AsyncGenerator, Callable, Dict, NoReturn, Optional, Tupl
|
|
4
4
|
|
5
5
|
import aiohttp
|
6
6
|
|
7
|
-
from ._utils.async_utils import TaskContext
|
8
|
-
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
9
|
-
from ._utils.package_utils import parse_major_minor_version
|
10
|
-
from .config import logger
|
11
|
-
from .exception import ExecutionError, InvalidError
|
7
|
+
from modal._utils.async_utils import TaskContext
|
8
|
+
from modal._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
9
|
+
from modal._utils.package_utils import parse_major_minor_version
|
10
|
+
from modal.config import logger
|
11
|
+
from modal.exception import ExecutionError, InvalidError
|
12
|
+
from modal.experimental import stop_fetching_inputs
|
13
|
+
|
12
14
|
from .execution_context import current_function_call_id
|
13
|
-
from .experimental import stop_fetching_inputs
|
14
15
|
|
15
16
|
FIRST_MESSAGE_TIMEOUT_SECONDS = 5.0
|
16
17
|
|
@@ -213,7 +214,7 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> Tuple[Callable[..., Asyn
|
|
213
214
|
|
214
215
|
|
215
216
|
def wsgi_app_wrapper(wsgi_app, container_io_manager):
|
216
|
-
from ._vendor.a2wsgi_wsgi import WSGIMiddleware
|
217
|
+
from modal._vendor.a2wsgi_wsgi import WSGIMiddleware
|
217
218
|
|
218
219
|
asgi_app = WSGIMiddleware(wsgi_app, workers=10000, send_queue_size=1) # unlimited workers
|
219
220
|
return asgi_app_wrapper(asgi_app, container_io_manager)
|
@@ -10,7 +10,6 @@ import sys
|
|
10
10
|
import time
|
11
11
|
import traceback
|
12
12
|
from contextlib import AsyncExitStack
|
13
|
-
from dataclasses import dataclass
|
14
13
|
from pathlib import Path
|
15
14
|
from typing import (
|
16
15
|
TYPE_CHECKING,
|
@@ -31,22 +30,23 @@ from grpclib import Status
|
|
31
30
|
from synchronicity.async_wrap import asynccontextmanager
|
32
31
|
|
33
32
|
import modal_proto.api_pb2
|
33
|
+
from modal._serialization import deserialize, serialize, serialize_data_format
|
34
|
+
from modal._traceback import extract_traceback, print_exception
|
35
|
+
from modal._utils.async_utils import TaskContext, asyncify, synchronize_api, synchronizer
|
36
|
+
from modal._utils.blob_utils import MAX_OBJECT_SIZE_BYTES, blob_download, blob_upload
|
37
|
+
from modal._utils.function_utils import _stream_function_call_data
|
38
|
+
from modal._utils.grpc_utils import get_proto_oneof, retry_transient_errors
|
39
|
+
from modal._utils.package_utils import parse_major_minor_version
|
40
|
+
from modal.client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
|
41
|
+
from modal.config import config, logger
|
42
|
+
from modal.exception import ClientClosed, InputCancellation, InvalidError, SerializationError
|
43
|
+
from modal.running_app import RunningApp
|
34
44
|
from modal_proto import api_pb2
|
35
45
|
|
36
|
-
from ._serialization import deserialize, serialize, serialize_data_format
|
37
|
-
from ._traceback import extract_traceback, print_exception
|
38
|
-
from ._utils.async_utils import TaskContext, asyncify, synchronize_api, synchronizer
|
39
|
-
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES, blob_download, blob_upload
|
40
|
-
from ._utils.function_utils import _stream_function_call_data
|
41
|
-
from ._utils.grpc_utils import get_proto_oneof, retry_transient_errors
|
42
|
-
from ._utils.package_utils import parse_major_minor_version
|
43
|
-
from .client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
|
44
|
-
from .config import config, logger
|
45
|
-
from .exception import ClientClosed, InputCancellation, InvalidError, SerializationError
|
46
|
-
from .running_app import RunningApp
|
47
|
-
|
48
46
|
if TYPE_CHECKING:
|
49
|
-
import modal.
|
47
|
+
import modal._runtime.asgi
|
48
|
+
import modal._runtime.user_code_imports
|
49
|
+
|
50
50
|
|
51
51
|
DYNAMIC_CONCURRENCY_INTERVAL_SECS = 3
|
52
52
|
DYNAMIC_CONCURRENCY_TIMEOUT_SECS = 10
|
@@ -63,15 +63,6 @@ class Sentinel:
|
|
63
63
|
"""Used to get type-stubs to work with this object."""
|
64
64
|
|
65
65
|
|
66
|
-
@dataclass
|
67
|
-
class FinalizedFunction:
|
68
|
-
callable: Callable[..., Any]
|
69
|
-
is_async: bool
|
70
|
-
is_generator: bool
|
71
|
-
data_format: int # api_pb2.DataFormat
|
72
|
-
lifespan_manager: Optional["modal._asgi.LifespanManager"] = None
|
73
|
-
|
74
|
-
|
75
66
|
class IOContext:
|
76
67
|
"""Context object for managing input, function calls, and function executions
|
77
68
|
in a batched or single input context.
|
@@ -79,7 +70,7 @@ class IOContext:
|
|
79
70
|
|
80
71
|
input_ids: List[str]
|
81
72
|
function_call_ids: List[str]
|
82
|
-
finalized_function: FinalizedFunction
|
73
|
+
finalized_function: "modal._runtime.user_code_imports.FinalizedFunction"
|
83
74
|
|
84
75
|
_cancel_issued: bool = False
|
85
76
|
_cancel_callback: Optional[Callable[[], None]] = None
|
@@ -88,7 +79,7 @@ class IOContext:
|
|
88
79
|
self,
|
89
80
|
input_ids: List[str],
|
90
81
|
function_call_ids: List[str],
|
91
|
-
finalized_function: FinalizedFunction,
|
82
|
+
finalized_function: "modal._runtime.user_code_imports.FinalizedFunction",
|
92
83
|
function_inputs: List[api_pb2.FunctionInput],
|
93
84
|
is_batched: bool,
|
94
85
|
client: _Client,
|
@@ -104,7 +95,7 @@ class IOContext:
|
|
104
95
|
async def create(
|
105
96
|
cls,
|
106
97
|
client: _Client,
|
107
|
-
finalized_functions: Dict[str, FinalizedFunction],
|
98
|
+
finalized_functions: Dict[str, "modal._runtime.user_code_imports.FinalizedFunction"],
|
108
99
|
inputs: List[Tuple[str, str, api_pb2.FunctionInput]],
|
109
100
|
is_batched: bool,
|
110
101
|
) -> "IOContext":
|
@@ -654,7 +645,7 @@ class _ContainerIOManager:
|
|
654
645
|
@synchronizer.no_io_translation
|
655
646
|
async def run_inputs_outputs(
|
656
647
|
self,
|
657
|
-
finalized_functions: Dict[str, FinalizedFunction],
|
648
|
+
finalized_functions: Dict[str, "modal._runtime.user_code_imports.FinalizedFunction"],
|
658
649
|
batch_max_size: int = 0,
|
659
650
|
batch_wait_ms: int = 0,
|
660
651
|
) -> AsyncIterator[IOContext]:
|
@@ -2,10 +2,11 @@
|
|
2
2
|
from contextvars import ContextVar
|
3
3
|
from typing import Callable, List, Optional
|
4
4
|
|
5
|
-
from modal._container_io_manager import _ContainerIOManager
|
6
5
|
from modal._utils.async_utils import synchronize_api
|
7
6
|
from modal.exception import InvalidError
|
8
7
|
|
8
|
+
from .container_io_manager import _ContainerIOManager
|
9
|
+
|
9
10
|
|
10
11
|
def is_local() -> bool:
|
11
12
|
"""Returns if we are currently on the machine launching/deploying a Modal app
|