modal 0.66.23__py3-none-any.whl → 0.66.35__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.
@@ -0,0 +1,361 @@
1
+ # Copyright Modal Labs 2024
2
+ import importlib
3
+ import typing
4
+ from abc import ABCMeta, abstractmethod
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable, Dict, List, Optional
7
+
8
+ import modal._runtime.container_io_manager
9
+ import modal.cls
10
+ import modal.object
11
+ from modal import Function
12
+ from modal._runtime.asgi import (
13
+ LifespanManager,
14
+ asgi_app_wrapper,
15
+ get_ip_address,
16
+ wait_for_web_server,
17
+ web_server_proxy,
18
+ webhook_asgi_app,
19
+ wsgi_app_wrapper,
20
+ )
21
+ from modal._utils.async_utils import synchronizer
22
+ from modal._utils.function_utils import LocalFunctionError, is_async as get_is_async, is_global_object
23
+ from modal.exception import ExecutionError, InvalidError
24
+ from modal.functions import _Function
25
+ from modal.partial_function import _find_partial_methods_for_user_cls, _PartialFunctionFlags
26
+ from modal_proto import api_pb2
27
+
28
+ if typing.TYPE_CHECKING:
29
+ import modal.app
30
+ import modal.partial_function
31
+
32
+
33
+ @dataclass
34
+ class FinalizedFunction:
35
+ callable: Callable[..., Any]
36
+ is_async: bool
37
+ is_generator: bool
38
+ data_format: int # api_pb2.DataFormat
39
+ lifespan_manager: Optional[LifespanManager] = None
40
+
41
+
42
+ class Service(metaclass=ABCMeta):
43
+ """Common interface for singular functions and class-based "services"
44
+
45
+ There are differences in the importing/finalization logic, and this
46
+ "protocol"/abc basically defines a common interface for the two types
47
+ of "Services" after the point of import.
48
+ """
49
+
50
+ user_cls_instance: Any
51
+ app: Optional["modal.app._App"]
52
+ code_deps: Optional[List["modal.object._Object"]]
53
+
54
+ @abstractmethod
55
+ def get_finalized_functions(
56
+ self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
57
+ ) -> Dict[str, "FinalizedFunction"]:
58
+ ...
59
+
60
+
61
+ def construct_webhook_callable(
62
+ user_defined_callable: Callable,
63
+ webhook_config: api_pb2.WebhookConfig,
64
+ container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
65
+ ):
66
+ # For webhooks, the user function is used to construct an asgi app:
67
+ if webhook_config.type == api_pb2.WEBHOOK_TYPE_ASGI_APP:
68
+ # Function returns an asgi_app, which we can use as a callable.
69
+ return asgi_app_wrapper(user_defined_callable(), container_io_manager)
70
+
71
+ elif webhook_config.type == api_pb2.WEBHOOK_TYPE_WSGI_APP:
72
+ # Function returns an wsgi_app, which we can use as a callable.
73
+ return wsgi_app_wrapper(user_defined_callable(), container_io_manager)
74
+
75
+ elif webhook_config.type == api_pb2.WEBHOOK_TYPE_FUNCTION:
76
+ # Function is a webhook without an ASGI app. Create one for it.
77
+ return asgi_app_wrapper(
78
+ webhook_asgi_app(user_defined_callable, webhook_config.method, webhook_config.web_endpoint_docs),
79
+ container_io_manager,
80
+ )
81
+
82
+ elif webhook_config.type == api_pb2.WEBHOOK_TYPE_WEB_SERVER:
83
+ # Function spawns an HTTP web server listening at a port.
84
+ user_defined_callable()
85
+
86
+ # We intentionally try to connect to the external interface instead of the loopback
87
+ # interface here so users are forced to expose the server. This allows us to potentially
88
+ # change the implementation to use an external bridge in the future.
89
+ host = get_ip_address(b"eth0")
90
+ port = webhook_config.web_server_port
91
+ startup_timeout = webhook_config.web_server_startup_timeout
92
+ wait_for_web_server(host, port, timeout=startup_timeout)
93
+ return asgi_app_wrapper(web_server_proxy(host, port), container_io_manager)
94
+ else:
95
+ raise InvalidError(f"Unrecognized web endpoint type {webhook_config.type}")
96
+
97
+
98
+ @dataclass
99
+ class ImportedFunction(Service):
100
+ user_cls_instance: Any
101
+ app: Optional["modal.app._App"]
102
+ code_deps: Optional[List["modal.object._Object"]]
103
+
104
+ _user_defined_callable: Callable[..., Any]
105
+
106
+ def get_finalized_functions(
107
+ self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
108
+ ) -> Dict[str, "FinalizedFunction"]:
109
+ # Check this property before we turn it into a method (overriden by webhooks)
110
+ is_async = get_is_async(self._user_defined_callable)
111
+ # Use the function definition for whether this is a generator (overriden by webhooks)
112
+ is_generator = fun_def.function_type == api_pb2.Function.FUNCTION_TYPE_GENERATOR
113
+
114
+ webhook_config = fun_def.webhook_config
115
+ if not webhook_config.type:
116
+ # for non-webhooks, the runnable is straight forward:
117
+ return {
118
+ "": FinalizedFunction(
119
+ callable=self._user_defined_callable,
120
+ is_async=is_async,
121
+ is_generator=is_generator,
122
+ data_format=api_pb2.DATA_FORMAT_PICKLE,
123
+ )
124
+ }
125
+
126
+ web_callable, lifespan_manager = construct_webhook_callable(
127
+ self._user_defined_callable, fun_def.webhook_config, container_io_manager
128
+ )
129
+
130
+ return {
131
+ "": FinalizedFunction(
132
+ callable=web_callable,
133
+ lifespan_manager=lifespan_manager,
134
+ is_async=True,
135
+ is_generator=True,
136
+ data_format=api_pb2.DATA_FORMAT_ASGI,
137
+ )
138
+ }
139
+
140
+
141
+ @dataclass
142
+ class ImportedClass(Service):
143
+ user_cls_instance: Any
144
+ app: Optional["modal.app._App"]
145
+ code_deps: Optional[List["modal.object._Object"]]
146
+
147
+ _partial_functions: Dict[str, "modal.partial_function._PartialFunction"]
148
+
149
+ def get_finalized_functions(
150
+ self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
151
+ ) -> Dict[str, "FinalizedFunction"]:
152
+ finalized_functions = {}
153
+ for method_name, partial in self._partial_functions.items():
154
+ partial = synchronizer._translate_in(partial) # ugly
155
+ user_func = partial.raw_f
156
+ # Check this property before we turn it into a method (overriden by webhooks)
157
+ is_async = get_is_async(user_func)
158
+ # Use the function definition for whether this is a generator (overriden by webhooks)
159
+ is_generator = partial.is_generator
160
+ webhook_config = partial.webhook_config
161
+
162
+ bound_func = user_func.__get__(self.user_cls_instance)
163
+
164
+ if not webhook_config or webhook_config.type == api_pb2.WEBHOOK_TYPE_UNSPECIFIED:
165
+ # for non-webhooks, the runnable is straight forward:
166
+ finalized_function = FinalizedFunction(
167
+ callable=bound_func,
168
+ is_async=is_async,
169
+ is_generator=is_generator,
170
+ data_format=api_pb2.DATA_FORMAT_PICKLE,
171
+ )
172
+ else:
173
+ web_callable, lifespan_manager = construct_webhook_callable(
174
+ bound_func, webhook_config, container_io_manager
175
+ )
176
+ finalized_function = FinalizedFunction(
177
+ callable=web_callable,
178
+ lifespan_manager=lifespan_manager,
179
+ is_async=True,
180
+ is_generator=True,
181
+ data_format=api_pb2.DATA_FORMAT_ASGI,
182
+ )
183
+ finalized_functions[method_name] = finalized_function
184
+ return finalized_functions
185
+
186
+
187
+ def get_user_class_instance(
188
+ cls: typing.Union[type, modal.cls.Cls], args: typing.Tuple, kwargs: Dict[str, Any]
189
+ ) -> typing.Any:
190
+ """Returns instance of the underlying class to be used as the `self`
191
+
192
+ The input `cls` can either be the raw Python class the user has declared ("user class"),
193
+ or an @app.cls-decorated version of it which is a modal.Cls-instance wrapping the user class.
194
+ """
195
+ if isinstance(cls, modal.cls.Cls):
196
+ # globally @app.cls-decorated class
197
+ modal_obj: modal.cls.Obj = cls(*args, **kwargs)
198
+ modal_obj.entered = True # ugly but prevents .local() from triggering additional enter-logic
199
+ # TODO: unify lifecycle logic between .local() and container_entrypoint
200
+ user_cls_instance = modal_obj._get_user_cls_instance()
201
+ else:
202
+ # undecorated class (non-global decoration or serialized)
203
+ user_cls_instance = cls(*args, **kwargs)
204
+
205
+ return user_cls_instance
206
+
207
+
208
+ def import_single_function_service(
209
+ function_def: api_pb2.Function,
210
+ ser_cls, # used only for @build functions
211
+ ser_fun,
212
+ cls_args, # used only for @build functions
213
+ cls_kwargs, # used only for @build functions
214
+ ) -> Service:
215
+ """Imports a function dynamically, and locates the app.
216
+
217
+ This is somewhat complex because we're dealing with 3 quite different type of functions:
218
+ 1. Functions defined in global scope and decorated in global scope (Function objects)
219
+ 2. Functions defined in global scope but decorated elsewhere (these will be raw callables)
220
+ 3. Serialized functions
221
+
222
+ In addition, we also need to handle
223
+ * Normal functions
224
+ * Methods on classes (in which case we need to instantiate the object)
225
+
226
+ This helper also handles web endpoints, ASGI/WSGI servers, and HTTP servers.
227
+
228
+ In order to locate the app, we try two things:
229
+ * If the function is a Function, we can get the app directly from it
230
+ * Otherwise, use the app name and look it up from a global list of apps: this
231
+ typically only happens in case 2 above, or in sometimes for case 3
232
+
233
+ Note that `import_function` is *not* synchronized, because we need it to run on the main
234
+ thread. This is so that any user code running in global scope (which executes as a part of
235
+ the import) runs on the right thread.
236
+ """
237
+ user_defined_callable: Callable
238
+ function: Optional[_Function] = None
239
+ code_deps: Optional[List["modal.object._Object"]] = None
240
+ active_app: Optional[modal.app._App] = None
241
+
242
+ if ser_fun is not None:
243
+ # This is a serialized function we already fetched from the server
244
+ cls, user_defined_callable = ser_cls, ser_fun
245
+ else:
246
+ # Load the module dynamically
247
+ module = importlib.import_module(function_def.module_name)
248
+ qual_name: str = function_def.function_name
249
+
250
+ if not is_global_object(qual_name):
251
+ raise LocalFunctionError("Attempted to load a function defined in a function scope")
252
+
253
+ parts = qual_name.split(".")
254
+ if len(parts) == 1:
255
+ # This is a function
256
+ cls = None
257
+ f = getattr(module, qual_name)
258
+ if isinstance(f, Function):
259
+ function = synchronizer._translate_in(f)
260
+ user_defined_callable = function.get_raw_f()
261
+ active_app = function._app
262
+ else:
263
+ user_defined_callable = f
264
+ elif len(parts) == 2:
265
+ # As of v0.63 - this path should only be triggered by @build class builder methods
266
+ assert not function_def.use_method_name # new "placeholder methods" should not be invoked directly!
267
+ assert function_def.is_builder_function
268
+ cls_name, fun_name = parts
269
+ cls = getattr(module, cls_name)
270
+ if isinstance(cls, modal.cls.Cls):
271
+ # The cls decorator is in global scope
272
+ _cls = synchronizer._translate_in(cls)
273
+ user_defined_callable = _cls._callables[fun_name]
274
+ function = _cls._method_functions.get(fun_name)
275
+ active_app = _cls._app
276
+ else:
277
+ # This is a raw class
278
+ user_defined_callable = getattr(cls, fun_name)
279
+ else:
280
+ raise InvalidError(f"Invalid function qualname {qual_name}")
281
+
282
+ # Instantiate the class if it's defined
283
+ if cls:
284
+ # This code is only used for @build methods on classes
285
+ user_cls_instance = get_user_class_instance(cls, cls_args, cls_kwargs)
286
+ # Bind the function to the instance as self (using the descriptor protocol!)
287
+ user_defined_callable = user_defined_callable.__get__(user_cls_instance)
288
+ else:
289
+ user_cls_instance = None
290
+
291
+ if function:
292
+ code_deps = function.deps(only_explicit_mounts=True)
293
+
294
+ return ImportedFunction(
295
+ user_cls_instance,
296
+ active_app,
297
+ code_deps,
298
+ user_defined_callable,
299
+ )
300
+
301
+
302
+ def import_class_service(
303
+ function_def: api_pb2.Function,
304
+ ser_cls,
305
+ cls_args,
306
+ cls_kwargs,
307
+ ) -> Service:
308
+ """
309
+ This imports a full class to be able to execute any @method or webhook decorated methods.
310
+
311
+ See import_function.
312
+ """
313
+ active_app: Optional["modal.app._App"]
314
+ code_deps: Optional[List["modal.object._Object"]]
315
+ cls: typing.Union[type, modal.cls.Cls]
316
+
317
+ if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
318
+ assert ser_cls is not None
319
+ cls = ser_cls
320
+ else:
321
+ # Load the module dynamically
322
+ module = importlib.import_module(function_def.module_name)
323
+ qual_name: str = function_def.function_name
324
+
325
+ if not is_global_object(qual_name):
326
+ raise LocalFunctionError("Attempted to load a class defined in a function scope")
327
+
328
+ parts = qual_name.split(".")
329
+ if not (
330
+ len(parts) == 2 and parts[1] == "*"
331
+ ): # the "function name" of a class service "function placeholder" is expected to be "ClassName.*"
332
+ raise ExecutionError(
333
+ f"Internal error: Invalid 'service function' identifier {qual_name}. Please contact Modal support"
334
+ )
335
+
336
+ assert not function_def.use_method_name # new "placeholder methods" should not be invoked directly!
337
+ cls_name = parts[0]
338
+ cls = getattr(module, cls_name)
339
+
340
+ if isinstance(cls, modal.cls.Cls):
341
+ # The cls decorator is in global scope
342
+ _cls = synchronizer._translate_in(cls)
343
+ method_partials = _cls._get_partial_functions()
344
+ service_function: _Function = _cls._class_service_function
345
+ code_deps = service_function.deps(only_explicit_mounts=True)
346
+ active_app = service_function.app
347
+ else:
348
+ # Undecorated user class - find all methods
349
+ method_partials = _find_partial_methods_for_user_cls(cls, _PartialFunctionFlags.all())
350
+ code_deps = None
351
+ active_app = None
352
+
353
+ user_cls_instance = get_user_class_instance(cls, cls_args, cls_kwargs)
354
+
355
+ return ImportedClass(
356
+ user_cls_instance,
357
+ active_app,
358
+ code_deps,
359
+ # TODO (elias/deven): instead of using method_partials here we should use a set of api_pb2.MethodDefinition
360
+ method_partials,
361
+ )
modal/app.py CHANGED
@@ -1,6 +1,5 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import inspect
3
- import os
4
3
  import typing
5
4
  import warnings
6
5
  from pathlib import PurePosixPath
@@ -42,7 +41,6 @@ from .image import _Image
42
41
  from .mount import _Mount
43
42
  from .network_file_system import _NetworkFileSystem
44
43
  from .object import _get_environment_name, _Object
45
- from .output import _get_output_manager, enable_output
46
44
  from .partial_function import (
47
45
  PartialFunction,
48
46
  _find_partial_methods_for_user_cls,
@@ -141,25 +139,9 @@ def f(x, y):
141
139
  ```
142
140
  """
143
141
 
144
- _enable_output_warning = """\
145
- Note that output will soon not be be printed with `app.run`.
146
-
147
- If you want to print output, use `modal.enable_output()`:
148
-
149
- ```python
150
- with modal.enable_output():
151
- with app.run():
152
- ...
153
- ```
154
-
155
- If you don't want output, and you want to to suppress this warning,
156
- use `app.run(..., show_progress=False)`.
157
- """
158
-
159
142
 
160
143
  class _App:
161
- """A Modal app (prior to April 2024 a "stub") is a group of functions and classes
162
- deployed together.
144
+ """A Modal App is a group of functions and classes that are deployed together.
163
145
 
164
146
  The app serves at least three purposes:
165
147
 
@@ -447,32 +429,16 @@ class _App:
447
429
 
448
430
  # See Github discussion here: https://github.com/modal-labs/modal-client/pull/2030#issuecomment-2237266186
449
431
 
450
- auto_enable_output = False
451
-
452
- if "MODAL_DISABLE_APP_RUN_OUTPUT_WARNING" not in os.environ:
453
- if show_progress is None:
454
- if _get_output_manager() is None:
455
- deprecation_warning((2024, 7, 18), _enable_output_warning)
456
- auto_enable_output = True
457
- elif show_progress is True:
458
- if _get_output_manager() is None:
459
- deprecation_warning((2024, 7, 18), _enable_output_warning)
460
- auto_enable_output = True
461
- else:
462
- deprecation_warning((2024, 7, 18), "`show_progress=True` is deprecated and no longer needed.")
463
- elif show_progress is False:
464
- if _get_output_manager() is not None:
465
- deprecation_warning(
466
- (2024, 7, 18), "`show_progress=False` will have no effect since output is enabled."
467
- )
432
+ if show_progress is True:
433
+ deprecation_error(
434
+ (2024, 11, 20),
435
+ "`show_progress=True` is no longer supported. Use `with modal.enable_output():` instead.",
436
+ )
437
+ elif show_progress is False:
438
+ deprecation_warning((2024, 11, 20), "`show_progress=False` is deprecated (and has no effect)")
468
439
 
469
- if auto_enable_output:
470
- with enable_output():
471
- async with _run_app(self, client=client, detach=detach, interactive=interactive):
472
- yield self
473
- else:
474
- async with _run_app(self, client=client, detach=detach, interactive=interactive):
475
- yield self
440
+ async with _run_app(self, client=client, detach=detach, interactive=interactive):
441
+ yield self
476
442
 
477
443
  def _get_default_image(self):
478
444
  if self._image:
@@ -488,7 +454,7 @@ class _App:
488
454
  *self._mounts,
489
455
  ]
490
456
  for function in self.registered_functions.values():
491
- all_mounts.extend(function._used_local_mounts)
457
+ all_mounts.extend(function._serve_mounts)
492
458
 
493
459
  return [m for m in all_mounts if m.is_local()]
494
460
 
@@ -1083,7 +1049,8 @@ App = synchronize_api(_App)
1083
1049
 
1084
1050
 
1085
1051
  class _Stub(_App):
1086
- """This enables using an "Stub" class instead of "App".
1052
+ """mdmd:hidden
1053
+ This enables using a "Stub" class instead of "App".
1087
1054
 
1088
1055
  For most of Modal's history, the app class was called "Stub", so this exists for
1089
1056
  backwards compatibility, in order to facilitate moving from "Stub" to "App".
modal/cli/import_refs.py CHANGED
@@ -19,7 +19,7 @@ from rich.console import Console
19
19
  from rich.markdown import Markdown
20
20
 
21
21
  from modal.app import App, LocalEntrypoint
22
- from modal.exception import InvalidError, _CliUserExecutionError, deprecation_warning
22
+ from modal.exception import InvalidError, _CliUserExecutionError
23
23
  from modal.functions import Function
24
24
 
25
25
 
@@ -79,7 +79,7 @@ def import_file_or_module(file_or_module: str):
79
79
  return module
80
80
 
81
81
 
82
- def get_by_object_path(obj: Any, obj_path: Optional[str]) -> Optional[Any]:
82
+ def get_by_object_path(obj: Any, obj_path: str) -> Optional[Any]:
83
83
  # Try to evaluate a `.`-delimited object path in a Modal context
84
84
  # With the caveat that some object names can actually have `.` in their name (lifecycled methods' tags)
85
85
 
@@ -107,35 +107,6 @@ def get_by_object_path(obj: Any, obj_path: Optional[str]) -> Optional[Any]:
107
107
  return obj
108
108
 
109
109
 
110
- def get_by_object_path_try_possible_app_names(obj: Any, obj_path: Optional[str]) -> Optional[Any]:
111
- """This just exists as a dumb workaround to support both "stub" and "app" """
112
-
113
- if obj_path:
114
- return get_by_object_path(obj, obj_path)
115
- else:
116
- app = get_by_object_path(obj, DEFAULT_APP_NAME)
117
- stub = get_by_object_path(obj, "stub")
118
- if isinstance(app, App):
119
- return app
120
- elif app is not None and isinstance(stub, App):
121
- deprecation_warning(
122
- (2024, 4, 20),
123
- "The symbol `app` is present at the module level but it's not a Modal app."
124
- " We will use `stub` instead, but this will not work in future Modal versions."
125
- " Suggestion: change the name of `app` to something else.",
126
- )
127
- return stub
128
- elif isinstance(stub, App):
129
- deprecation_warning(
130
- (2024, 5, 1),
131
- "The symbol `app` is not present but `stub` is. This will not work in future"
132
- " Modal versions. Suggestion: change the name of `stub` to `app`.",
133
- )
134
- return stub
135
- else:
136
- return None
137
-
138
-
139
110
  def _infer_function_or_help(
140
111
  app: App, module, accept_local_entrypoint: bool, accept_webhook: bool
141
112
  ) -> Union[Function, LocalEntrypoint]:
@@ -210,7 +181,7 @@ def import_app(app_ref: str) -> App:
210
181
  import_ref = parse_import_ref(app_ref)
211
182
 
212
183
  module = import_file_or_module(import_ref.file_or_module)
213
- app = get_by_object_path_try_possible_app_names(module, import_ref.object_path)
184
+ app = get_by_object_path(module, import_ref.object_path or DEFAULT_APP_NAME)
214
185
 
215
186
  if app is None:
216
187
  _show_no_auto_detectable_app(import_ref)
@@ -258,7 +229,7 @@ def import_function(
258
229
  import_ref = parse_import_ref(func_ref)
259
230
 
260
231
  module = import_file_or_module(import_ref.file_or_module)
261
- app_or_function = get_by_object_path_try_possible_app_names(module, import_ref.object_path)
232
+ app_or_function = get_by_object_path(module, import_ref.object_path or DEFAULT_APP_NAME)
262
233
 
263
234
  if app_or_function is None:
264
235
  _show_function_ref_help(import_ref, base_cmd)
@@ -279,8 +250,3 @@ def import_function(
279
250
  return app_or_function
280
251
  else:
281
252
  raise click.UsageError(f"{app_or_function} is not a Modal entity (should be an App or Function)")
282
-
283
-
284
- # For backwards compatibility - delete soon
285
- # We use it in our internal intergration tests
286
- import_stub = import_app
modal/client.pyi CHANGED
@@ -31,7 +31,7 @@ class _Client:
31
31
  server_url: str,
32
32
  client_type: int,
33
33
  credentials: typing.Optional[typing.Tuple[str, str]],
34
- version: str = "0.66.23",
34
+ version: str = "0.66.35",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -90,7 +90,7 @@ class Client:
90
90
  server_url: str,
91
91
  client_type: int,
92
92
  credentials: typing.Optional[typing.Tuple[str, str]],
93
- version: str = "0.66.23",
93
+ version: str = "0.66.35",
94
94
  ): ...
95
95
  def is_closed(self) -> bool: ...
96
96
  @property
modal/experimental.py CHANGED
@@ -17,7 +17,6 @@ from .partial_function import _PartialFunction, _PartialFunctionFlags
17
17
  def stop_fetching_inputs():
18
18
  """Don't fetch any more inputs from the server, after the current one.
19
19
  The container will exit gracefully after the current input is processed."""
20
-
21
20
  _ContainerIOManager.stop_fetching_inputs()
22
21
 
23
22
 
@@ -25,7 +24,6 @@ def get_local_input_concurrency():
25
24
  """Get the container's local input concurrency.
26
25
  If recently reduced to particular value, it can return a larger number than
27
26
  set due to in-progress inputs."""
28
-
29
27
  return _ContainerIOManager.get_input_concurrency()
30
28
 
31
29
 
@@ -33,7 +31,6 @@ def set_local_input_concurrency(concurrency: int):
33
31
  """Set the container's local input concurrency. Dynamic concurrency will be disabled.
34
32
  When setting to a smaller value, this method will not interrupt in-progress inputs.
35
33
  """
36
-
37
34
  _ContainerIOManager.set_input_concurrency(concurrency)
38
35
 
39
36
 
modal/functions.py CHANGED
@@ -299,13 +299,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
299
299
  """Functions are the basic units of serverless execution on Modal.
300
300
 
301
301
  Generally, you will not construct a `Function` directly. Instead, use the
302
- `@app.function()` decorator on the `App` object (formerly called "Stub")
303
- for your application.
302
+ `App.function()` decorator to register your Python functions with your App.
304
303
  """
305
304
 
306
305
  # TODO: more type annotations
307
306
  _info: Optional[FunctionInfo]
308
- _used_local_mounts: typing.FrozenSet[_Mount] # set at load time, only by loader
307
+ _serve_mounts: typing.FrozenSet[_Mount] # set at load time, only by loader
309
308
  _app: Optional["modal.app._App"] = None
310
309
  _obj: Optional["modal.cls._Obj"] = None # only set for InstanceServiceFunctions and bound instance methods
311
310
  _web_url: Optional[str]
@@ -580,6 +579,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
580
579
 
581
580
  if is_local():
582
581
  entrypoint_mounts = info.get_entrypoint_mount()
582
+
583
583
  all_mounts = [
584
584
  _get_client_mount(),
585
585
  *explicit_mounts,
@@ -612,6 +612,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
612
612
  if proxy:
613
613
  # HACK: remove this once we stop using ssh tunnels for this.
614
614
  if image:
615
+ # TODO(elias): this will cause an error if users use prior `.add_local_*` commands without copy=True
615
616
  image = image.apt_install("autossh")
616
617
 
617
618
  function_spec = _FunctionSpec(
@@ -828,7 +829,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
828
829
  )
829
830
  for path, volume in validated_volumes
830
831
  ]
831
- loaded_mount_ids = {m.object_id for m in all_mounts}
832
+ loaded_mount_ids = {m.object_id for m in all_mounts} | {m.object_id for m in image._mount_layers}
832
833
 
833
834
  # Get object dependencies
834
835
  object_dependencies = []
@@ -970,9 +971,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
970
971
  raise InvalidError(f"Function {info.function_name} is too large to deploy.")
971
972
  raise
972
973
  function_creation_status.set_response(response)
973
- local_mounts = set(m for m in all_mounts if m.is_local()) # needed for modal.serve file watching
974
- local_mounts |= image._used_local_mounts
975
- obj._used_local_mounts = frozenset(local_mounts)
974
+ serve_mounts = set(m for m in all_mounts if m.is_local()) # needed for modal.serve file watching
975
+ serve_mounts |= image._serve_mounts
976
+ obj._serve_mounts = frozenset(serve_mounts)
976
977
  self._hydrate(response.function_id, resolver.client, response.handle_metadata)
977
978
 
978
979
  rep = f"Function({tag})"
@@ -1223,7 +1224,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1223
1224
  self._function_name = None
1224
1225
  self._info = None
1225
1226
  self._use_function_id = ""
1226
- self._used_local_mounts = frozenset()
1227
+ self._serve_mounts = frozenset()
1227
1228
 
1228
1229
  def _hydrate_metadata(self, metadata: Optional[Message]):
1229
1230
  # Overridden concrete implementation of base class method