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.
Files changed (51) hide show
  1. modal/__init__.py +1 -1
  2. modal/_container_entrypoint.py +27 -358
  3. modal/_runtime/__init__.py +1 -0
  4. modal/{_asgi.py → _runtime/asgi.py} +8 -7
  5. modal/{_container_io_manager.py → _runtime/container_io_manager.py} +18 -27
  6. modal/{execution_context.py → _runtime/execution_context.py} +2 -1
  7. modal/{_telemetry.py → _runtime/telemetry.py} +1 -1
  8. modal/_runtime/user_code_imports.py +361 -0
  9. modal/_serialization.py +1 -1
  10. modal/_utils/function_utils.py +28 -8
  11. modal/app.py +13 -46
  12. modal/cli/import_refs.py +4 -38
  13. modal/client.pyi +2 -2
  14. modal/dict.py +0 -6
  15. modal/dict.pyi +0 -4
  16. modal/experimental.py +1 -4
  17. modal/functions.py +11 -10
  18. modal/functions.pyi +8 -8
  19. modal/gpu.py +8 -6
  20. modal/image.py +93 -6
  21. modal/image.pyi +20 -2
  22. modal/io_streams.py +32 -12
  23. modal/io_streams.pyi +8 -4
  24. modal/mount.py +3 -2
  25. modal/network_file_system.py +0 -28
  26. modal/network_file_system.pyi +0 -14
  27. modal/parallel_map.py +1 -1
  28. modal/partial_function.py +11 -1
  29. modal/queue.py +0 -6
  30. modal/queue.pyi +0 -4
  31. modal/runner.py +1 -1
  32. modal/sandbox.py +1 -1
  33. modal/secret.py +1 -1
  34. modal/volume.py +0 -22
  35. modal/volume.pyi +0 -9
  36. {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/METADATA +1 -2
  37. {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/RECORD +49 -49
  38. modal_proto/api.proto +2 -21
  39. modal_proto/api_grpc.py +0 -16
  40. modal_proto/api_pb2.py +702 -726
  41. modal_proto/api_pb2.pyi +6 -60
  42. modal_proto/api_pb2_grpc.py +0 -33
  43. modal_proto/api_pb2_grpc.pyi +0 -10
  44. modal_proto/modal_api_grpc.py +0 -1
  45. modal_version/_version_generated.py +1 -1
  46. modal/_container_io_manager.pyi +0 -414
  47. modal/execution_context.pyi +0 -22
  48. {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/LICENSE +0 -0
  49. {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/WHEEL +0 -0
  50. {modal-0.66.14.dist-info → modal-0.66.39.dist-info}/entry_points.txt +0 -0
  51. {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
@@ -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 typing
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 modal_proto import api_pb2
29
-
30
- from ._asgi import (
31
- asgi_app_wrapper,
32
- get_ip_address,
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 .cls import Cls, Obj
52
- from .config import logger
53
- from .exception import ExecutionError, InputCancellation, InvalidError
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._container_io_manager
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._container_io_manager.ContainerIOManager",
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._asgi
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
@@ -12,7 +12,7 @@ import uuid
12
12
  from importlib.util import find_spec, module_from_spec
13
13
  from struct import pack
14
14
 
15
- from .config import logger
15
+ from modal.config import logger
16
16
 
17
17
  MODULE_LOAD_START = "module_load_start"
18
18
  MODULE_LOAD_END = "module_load_end"