modal 0.66.17__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.
@@ -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
+ )
@@ -519,6 +519,16 @@ async def _create_input(
519
519
  )
520
520
 
521
521
 
522
+ def _get_suffix_from_web_url_info(url_info: api_pb2.WebUrlInfo) -> str:
523
+ if url_info.truncated:
524
+ suffix = " [grey70](label truncated)[/grey70]"
525
+ elif url_info.label_stolen:
526
+ suffix = " [grey70](label stolen)[/grey70]"
527
+ else:
528
+ suffix = ""
529
+ return suffix
530
+
531
+
522
532
  class FunctionCreationStatus:
523
533
  # TODO(michael) this really belongs with other output-related code
524
534
  # but moving it here so we can use it when loading a function with output disabled
@@ -547,12 +557,7 @@ class FunctionCreationStatus:
547
557
  elif self.response.function.web_url:
548
558
  url_info = self.response.function.web_url_info
549
559
  # Ensure terms used here match terms used in modal.com/docs/guide/webhook-urls doc.
550
- if url_info.truncated:
551
- suffix = " [grey70](label truncated)[/grey70]"
552
- elif url_info.label_stolen:
553
- suffix = " [grey70](label stolen)[/grey70]"
554
- else:
555
- suffix = ""
560
+ suffix = _get_suffix_from_web_url_info(url_info)
556
561
  # TODO: this is only printed when we're showing progress. Maybe move this somewhere else.
557
562
  web_url = self.response.handle_metadata.web_url
558
563
  self.status_row.finish(
@@ -563,8 +568,23 @@ class FunctionCreationStatus:
563
568
  for custom_domain in self.response.function.custom_domain_info:
564
569
  custom_domain_status_row = self.resolver.add_status_row()
565
570
  custom_domain_status_row.finish(
566
- f"Custom domain for {self.tag} => [magenta underline]"
567
- f"{custom_domain.url}[/magenta underline]{suffix}"
571
+ f"Custom domain for {self.tag} => [magenta underline]" f"{custom_domain.url}[/magenta underline]"
568
572
  )
569
573
  else:
570
574
  self.status_row.finish(f"Created function {self.tag}.")
575
+ if self.response.function.method_definitions_set:
576
+ for method_definition in self.response.function.method_definitions.values():
577
+ if method_definition.web_url:
578
+ url_info = method_definition.web_url_info
579
+ suffix = _get_suffix_from_web_url_info(url_info)
580
+ class_web_endpoint_method_status_row = self.resolver.add_status_row()
581
+ class_web_endpoint_method_status_row.finish(
582
+ f"Created web endpoint for {method_definition.function_name} => [magenta underline]"
583
+ f"{method_definition.web_url}[/magenta underline]{suffix}"
584
+ )
585
+ for custom_domain in method_definition.custom_domain_info:
586
+ custom_domain_status_row = self.resolver.add_status_row()
587
+ custom_domain_status_row.finish(
588
+ f"Custom domain for {method_definition.function_name} => [magenta underline]"
589
+ f"{custom_domain.url}[/magenta underline]"
590
+ )
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.17",
34
+ version: str = "0.66.39",
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.17",
93
+ version: str = "0.66.39",
94
94
  ): ...
95
95
  def is_closed(self) -> bool: ...
96
96
  @property
modal/dict.py CHANGED
@@ -143,12 +143,6 @@ class _Dict(_Object, type_prefix="di"):
143
143
 
144
144
  return _Dict._from_loader(_load, "Dict()", is_another_app=True, hydrate_lazily=True)
145
145
 
146
- @staticmethod
147
- def persisted(label: str, namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE, environment_name: Optional[str] = None):
148
- """mdmd:hidden"""
149
- message = "`Dict.persisted` is deprecated. Please use `Dict.from_name(name, create_if_missing=True)` instead."
150
- deprecation_error((2024, 3, 1), message)
151
-
152
146
  @staticmethod
153
147
  async def lookup(
154
148
  label: str,
modal/dict.pyi CHANGED
@@ -27,8 +27,6 @@ class _Dict(modal.object._Object):
27
27
  create_if_missing: bool = False,
28
28
  ) -> _Dict: ...
29
29
  @staticmethod
30
- def persisted(label: str, namespace=1, environment_name: typing.Optional[str] = None): ...
31
- @staticmethod
32
30
  async def lookup(
33
31
  label: str,
34
32
  data: typing.Optional[dict] = None,
@@ -79,8 +77,6 @@ class Dict(modal.object.Object):
79
77
  environment_name: typing.Optional[str] = None,
80
78
  create_if_missing: bool = False,
81
79
  ) -> Dict: ...
82
- @staticmethod
83
- def persisted(label: str, namespace=1, environment_name: typing.Optional[str] = None): ...
84
80
 
85
81
  class __lookup_spec(typing_extensions.Protocol):
86
82
  def __call__(
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