modal 0.72.58__py3-none-any.whl → 0.73.1__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/_runtime/container_io_manager.py +1 -1
- modal/_utils/function_utils.py +45 -16
- modal/_utils/rand_pb_testing.py +4 -4
- modal/app.py +9 -0
- modal/app.pyi +10 -0
- modal/cli/import_refs.py +34 -12
- modal/client.pyi +2 -2
- modal/file_pattern_matcher.py +0 -1
- modal/functions.py +15 -12
- modal/functions.pyi +2 -0
- modal/image.py +1 -1
- {modal-0.72.58.dist-info → modal-0.73.1.dist-info}/METADATA +1 -1
- {modal-0.72.58.dist-info → modal-0.73.1.dist-info}/RECORD +19 -19
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.72.58.dist-info → modal-0.73.1.dist-info}/LICENSE +0 -0
- {modal-0.72.58.dist-info → modal-0.73.1.dist-info}/WHEEL +0 -0
- {modal-0.72.58.dist-info → modal-0.73.1.dist-info}/entry_points.txt +0 -0
- {modal-0.72.58.dist-info → modal-0.73.1.dist-info}/top_level.txt +0 -0
@@ -1017,7 +1017,7 @@ def check_fastapi_pydantic_compatibility(exc: ImportError) -> None:
|
|
1017
1017
|
if pydantic_version >= (2, 0) and fastapi_version < (0, 100):
|
1018
1018
|
if sys.version_info < (3, 11):
|
1019
1019
|
# https://peps.python.org/pep-0678/
|
1020
|
-
exc.__notes__ = [note]
|
1020
|
+
exc.__notes__ = [note] # type: ignore
|
1021
1021
|
else:
|
1022
1022
|
exc.add_note(note)
|
1023
1023
|
except Exception:
|
modal/_utils/function_utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import asyncio
|
3
|
+
import enum
|
3
4
|
import inspect
|
4
5
|
import os
|
5
6
|
from collections.abc import AsyncGenerator
|
@@ -319,34 +320,32 @@ class FunctionInfo:
|
|
319
320
|
These are typically local modules which are imported but not part of the running package
|
320
321
|
|
321
322
|
"""
|
322
|
-
if self.
|
323
|
-
# Don't auto-mount anything for notebooks
|
323
|
+
if self.is_serialized():
|
324
|
+
# Don't auto-mount anything for serialized functions (including notebooks)
|
324
325
|
return []
|
325
326
|
|
326
327
|
# make sure the function's own entrypoint is included:
|
327
328
|
if self._type == FunctionInfoType.PACKAGE:
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
remote_path=self._remote_dir,
|
336
|
-
recursive=True,
|
337
|
-
condition=entrypoint_only_package_mount_condition(self._file),
|
338
|
-
)
|
339
|
-
]
|
340
|
-
elif not self.is_serialized():
|
329
|
+
top_level_package = self.module_name.split(".")[0]
|
330
|
+
# TODO: add deprecation warning if the following entrypoint mount
|
331
|
+
# includes non-.py files, since we'll want to migrate to .py-only
|
332
|
+
# soon to get it consistent with the `add_local_python_source()`
|
333
|
+
# defaults.
|
334
|
+
return [_Mount._from_local_python_packages(top_level_package)]
|
335
|
+
elif self._type == FunctionInfoType.FILE:
|
341
336
|
remote_path = ROOT_DIR / Path(self._file).name
|
342
337
|
if not _is_modal_path(remote_path):
|
338
|
+
# TODO: inspect if this file is already included as part of
|
339
|
+
# a package mount, and skip it + reference that package
|
340
|
+
# instead if that's the case. This avoids possible module
|
341
|
+
# duplication bugs
|
343
342
|
return [
|
344
343
|
_Mount._from_local_file(
|
345
344
|
self._file,
|
346
345
|
remote_path=remote_path,
|
347
346
|
)
|
348
347
|
]
|
349
|
-
return []
|
348
|
+
return [] # this should never be reached...
|
350
349
|
|
351
350
|
def get_tag(self):
|
352
351
|
return self.function_name
|
@@ -622,3 +621,33 @@ class FunctionCreationStatus:
|
|
622
621
|
f"Custom domain for {method_definition.function_name} => [magenta underline]"
|
623
622
|
f"{custom_domain.url}[/magenta underline]"
|
624
623
|
)
|
624
|
+
|
625
|
+
|
626
|
+
class IncludeSourceMode(enum.Enum):
|
627
|
+
INCLUDE_NOTHING = False # can only be set in source, can't be set in config
|
628
|
+
INCLUDE_MAIN_PACKAGE = True # also represented by AUTOMOUNT=0 in config
|
629
|
+
INCLUDE_FIRST_PARTY = "legacy" # mounts all "local" modules in sys.modules - represented by AUTOMOUNT=1 in config
|
630
|
+
|
631
|
+
|
632
|
+
def get_include_source_mode(function_or_app_specific) -> IncludeSourceMode:
|
633
|
+
"""Which "automount" behavior should a function use
|
634
|
+
|
635
|
+
function_or_app_specific: explicit value given in the @function or @cls decorator, in an App constructor, or None
|
636
|
+
|
637
|
+
If function_or_app_specific is specified, validate and return the IncludeSourceMode
|
638
|
+
If function_or_app_specific is None, infer it from config
|
639
|
+
"""
|
640
|
+
if function_or_app_specific is not None:
|
641
|
+
if not isinstance(function_or_app_specific, bool):
|
642
|
+
raise ValueError(
|
643
|
+
f"Invalid `include_source` value: {function_or_app_specific}. Use one of:\n"
|
644
|
+
f"True - include function's package source\n"
|
645
|
+
f"False - include no Python source (module expected to be present in Image)\n"
|
646
|
+
)
|
647
|
+
|
648
|
+
# explicitly set in app/function
|
649
|
+
return IncludeSourceMode(function_or_app_specific)
|
650
|
+
|
651
|
+
# note that the automount config boolean isn't a 1-1 mapping with include_source!
|
652
|
+
legacy_automount_mode: bool = config.get("automount")
|
653
|
+
return IncludeSourceMode.INCLUDE_FIRST_PARTY if legacy_automount_mode else IncludeSourceMode.INCLUDE_MAIN_PACKAGE
|
modal/_utils/rand_pb_testing.py
CHANGED
@@ -7,7 +7,7 @@ Modal, with random seeds, and it supports oneofs, and Protobuf v4.
|
|
7
7
|
|
8
8
|
import string
|
9
9
|
from random import Random
|
10
|
-
from typing import Any, Callable, Optional, TypeVar
|
10
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
11
11
|
|
12
12
|
from google.protobuf.descriptor import Descriptor, FieldDescriptor
|
13
13
|
|
@@ -38,9 +38,9 @@ def _fill(msg, desc: Descriptor, rand: Random) -> None:
|
|
38
38
|
field: FieldDescriptor
|
39
39
|
oneof_fields: set[str] = set()
|
40
40
|
for oneof in desc.oneofs:
|
41
|
-
|
42
|
-
if
|
43
|
-
oneof_fields.add(
|
41
|
+
oneof_field: Union[FieldDescriptor, None] = rand.choice(list(oneof.fields) + [None])
|
42
|
+
if oneof_field is not None:
|
43
|
+
oneof_fields.add(oneof_field.name)
|
44
44
|
for field in desc.fields:
|
45
45
|
if field.containing_oneof is not None and field.name not in oneof_fields:
|
46
46
|
continue
|
modal/app.py
CHANGED
@@ -170,6 +170,8 @@ class _App:
|
|
170
170
|
_running_app: Optional[RunningApp] # Various app info
|
171
171
|
_client: Optional[_Client]
|
172
172
|
|
173
|
+
_include_source_default: Optional[bool] = None
|
174
|
+
|
173
175
|
def __init__(
|
174
176
|
self,
|
175
177
|
name: Optional[str] = None,
|
@@ -178,6 +180,7 @@ class _App:
|
|
178
180
|
mounts: Sequence[_Mount] = [], # default mounts for all functions
|
179
181
|
secrets: Sequence[_Secret] = [], # default secrets for all functions
|
180
182
|
volumes: dict[Union[str, PurePosixPath], _Volume] = {}, # default volumes for all functions
|
183
|
+
include_source: Optional[bool] = None,
|
181
184
|
) -> None:
|
182
185
|
"""Construct a new app, optionally with default image, mounts, secrets, or volumes.
|
183
186
|
|
@@ -193,6 +196,7 @@ class _App:
|
|
193
196
|
|
194
197
|
self._name = name
|
195
198
|
self._description = name
|
199
|
+
self._include_source_default = include_source
|
196
200
|
|
197
201
|
check_sequence(mounts, _Mount, "`mounts=` has to be a list or tuple of `modal.Mount` objects")
|
198
202
|
check_sequence(secrets, _Secret, "`secrets=` has to be a list or tuple of `modal.Secret` objects")
|
@@ -603,6 +607,8 @@ class _App:
|
|
603
607
|
# With `max_inputs = 1`, containers will be single-use.
|
604
608
|
max_inputs: Optional[int] = None,
|
605
609
|
i6pn: Optional[bool] = None, # Whether to enable IPv6 container networking within the region.
|
610
|
+
# Whether the function's home package should be included in the image - defaults to True
|
611
|
+
include_source: Optional[bool] = None,
|
606
612
|
# Parameters below here are experimental. Use with caution!
|
607
613
|
_experimental_scheduler_placement: Optional[
|
608
614
|
SchedulerPlacement
|
@@ -751,6 +757,7 @@ class _App:
|
|
751
757
|
_experimental_proxy_ip=_experimental_proxy_ip,
|
752
758
|
i6pn_enabled=i6pn_enabled,
|
753
759
|
cluster_size=cluster_size, # Experimental: Clustered functions
|
760
|
+
include_source=include_source if include_source is not None else self._include_source_default,
|
754
761
|
)
|
755
762
|
|
756
763
|
self._add_function(function, webhook_config is not None)
|
@@ -800,6 +807,7 @@ class _App:
|
|
800
807
|
# Limits the number of inputs a container handles before shutting down.
|
801
808
|
# Use `max_inputs = 1` for single-use containers.
|
802
809
|
max_inputs: Optional[int] = None,
|
810
|
+
include_source: Optional[bool] = None,
|
803
811
|
# Parameters below here are experimental. Use with caution!
|
804
812
|
_experimental_scheduler_placement: Optional[
|
805
813
|
SchedulerPlacement
|
@@ -877,6 +885,7 @@ class _App:
|
|
877
885
|
block_network=block_network,
|
878
886
|
max_inputs=max_inputs,
|
879
887
|
scheduler_placement=scheduler_placement,
|
888
|
+
include_source=include_source if include_source is not None else self._include_source_default,
|
880
889
|
_experimental_buffer_containers=_experimental_buffer_containers,
|
881
890
|
_experimental_proxy_ip=_experimental_proxy_ip,
|
882
891
|
_experimental_custom_scaling_factor=_experimental_custom_scaling_factor,
|
modal/app.pyi
CHANGED
@@ -90,6 +90,7 @@ class _App:
|
|
90
90
|
_app_id: typing.Optional[str]
|
91
91
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
92
92
|
_client: typing.Optional[modal.client._Client]
|
93
|
+
_include_source_default: typing.Optional[bool]
|
93
94
|
|
94
95
|
def __init__(
|
95
96
|
self,
|
@@ -99,6 +100,7 @@ class _App:
|
|
99
100
|
mounts: collections.abc.Sequence[modal.mount._Mount] = [],
|
100
101
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
101
102
|
volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume] = {},
|
103
|
+
include_source: typing.Optional[bool] = None,
|
102
104
|
) -> None: ...
|
103
105
|
@property
|
104
106
|
def name(self) -> typing.Optional[str]: ...
|
@@ -189,6 +191,7 @@ class _App:
|
|
189
191
|
block_network: bool = False,
|
190
192
|
max_inputs: typing.Optional[int] = None,
|
191
193
|
i6pn: typing.Optional[bool] = None,
|
194
|
+
include_source: typing.Optional[bool] = None,
|
192
195
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
193
196
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
194
197
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
@@ -232,6 +235,7 @@ class _App:
|
|
232
235
|
enable_memory_snapshot: bool = False,
|
233
236
|
block_network: bool = False,
|
234
237
|
max_inputs: typing.Optional[int] = None,
|
238
|
+
include_source: typing.Optional[bool] = None,
|
235
239
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
236
240
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
237
241
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
@@ -288,6 +292,7 @@ class App:
|
|
288
292
|
_app_id: typing.Optional[str]
|
289
293
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
290
294
|
_client: typing.Optional[modal.client.Client]
|
295
|
+
_include_source_default: typing.Optional[bool]
|
291
296
|
|
292
297
|
def __init__(
|
293
298
|
self,
|
@@ -297,6 +302,7 @@ class App:
|
|
297
302
|
mounts: collections.abc.Sequence[modal.mount.Mount] = [],
|
298
303
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
299
304
|
volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume] = {},
|
305
|
+
include_source: typing.Optional[bool] = None,
|
300
306
|
) -> None: ...
|
301
307
|
@property
|
302
308
|
def name(self) -> typing.Optional[str]: ...
|
@@ -419,6 +425,7 @@ class App:
|
|
419
425
|
block_network: bool = False,
|
420
426
|
max_inputs: typing.Optional[int] = None,
|
421
427
|
i6pn: typing.Optional[bool] = None,
|
428
|
+
include_source: typing.Optional[bool] = None,
|
422
429
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
423
430
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
424
431
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
@@ -462,6 +469,7 @@ class App:
|
|
462
469
|
enable_memory_snapshot: bool = False,
|
463
470
|
block_network: bool = False,
|
464
471
|
max_inputs: typing.Optional[int] = None,
|
472
|
+
include_source: typing.Optional[bool] = None,
|
465
473
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
466
474
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
467
475
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
@@ -547,6 +555,7 @@ class _Stub(_App):
|
|
547
555
|
mounts: collections.abc.Sequence[modal.mount._Mount] = [],
|
548
556
|
secrets: collections.abc.Sequence[modal.secret._Secret] = [],
|
549
557
|
volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume._Volume] = {},
|
558
|
+
include_source: typing.Optional[bool] = None,
|
550
559
|
): ...
|
551
560
|
|
552
561
|
class Stub(App):
|
@@ -558,6 +567,7 @@ class Stub(App):
|
|
558
567
|
mounts: collections.abc.Sequence[modal.mount.Mount] = [],
|
559
568
|
secrets: collections.abc.Sequence[modal.secret.Secret] = [],
|
560
569
|
volumes: dict[typing.Union[str, pathlib.PurePosixPath], modal.volume.Volume] = {},
|
570
|
+
include_source: typing.Optional[bool] = None,
|
561
571
|
) -> None: ...
|
562
572
|
|
563
573
|
_default_image: modal.image._Image
|
modal/cli/import_refs.py
CHANGED
@@ -109,6 +109,14 @@ class CLICommand:
|
|
109
109
|
names: list[str]
|
110
110
|
runnable: Runnable
|
111
111
|
is_web_endpoint: bool
|
112
|
+
priority: int
|
113
|
+
|
114
|
+
|
115
|
+
class AutoRunPriority:
|
116
|
+
MODULE_LOCAL_ENTRYPOINT = 0
|
117
|
+
MODULE_FUNCTION = 1
|
118
|
+
APP_LOCAL_ENTRYPOINT = 2
|
119
|
+
APP_FUNCTION = 3
|
112
120
|
|
113
121
|
|
114
122
|
def list_cli_commands(
|
@@ -127,17 +135,21 @@ def list_cli_commands(
|
|
127
135
|
apps = cast(list[tuple[str, App]], inspect.getmembers(module, lambda x: isinstance(x, App)))
|
128
136
|
|
129
137
|
all_runnables: dict[Runnable, list[str]] = defaultdict(list)
|
138
|
+
priorities: dict[Runnable, int] = defaultdict(lambda: AutoRunPriority.APP_FUNCTION)
|
130
139
|
for app_name, app in apps:
|
131
140
|
for name, local_entrypoint in app.registered_entrypoints.items():
|
132
141
|
all_runnables[local_entrypoint].append(f"{app_name}.{name}")
|
142
|
+
priorities[local_entrypoint] = AutoRunPriority.APP_LOCAL_ENTRYPOINT
|
133
143
|
for name, function in app.registered_functions.items():
|
134
144
|
if name.endswith(".*"):
|
135
145
|
continue
|
136
146
|
all_runnables[function].append(f"{app_name}.{name}")
|
147
|
+
priorities[function] = AutoRunPriority.APP_FUNCTION
|
137
148
|
for cls_name, cls in app.registered_classes.items():
|
138
149
|
for method_name in cls._get_method_names():
|
139
150
|
method_ref = MethodReference(cls, method_name)
|
140
151
|
all_runnables[method_ref].append(f"{app_name}.{cls_name}.{method_name}")
|
152
|
+
priorities[method_ref] = AutoRunPriority.APP_FUNCTION
|
141
153
|
|
142
154
|
# If any class or function is exported as a module level object, use that
|
143
155
|
# as the preferred name by putting it first in the list
|
@@ -150,8 +162,13 @@ def list_cli_commands(
|
|
150
162
|
for method_name in entity._get_method_names():
|
151
163
|
method_ref = MethodReference(entity, method_name)
|
152
164
|
all_runnables.setdefault(method_ref, []).insert(0, f"{name}.{method_name}")
|
153
|
-
|
165
|
+
priorities[method_ref] = AutoRunPriority.MODULE_FUNCTION
|
166
|
+
elif isinstance(entity, Function) and entity._is_local():
|
154
167
|
all_runnables.setdefault(entity, []).insert(0, name)
|
168
|
+
priorities[entity] = AutoRunPriority.MODULE_FUNCTION
|
169
|
+
elif isinstance(entity, LocalEntrypoint):
|
170
|
+
all_runnables.setdefault(entity, []).insert(0, name)
|
171
|
+
priorities[entity] = AutoRunPriority.MODULE_LOCAL_ENTRYPOINT
|
155
172
|
|
156
173
|
def _is_web_endpoint(runnable: Runnable) -> bool:
|
157
174
|
if isinstance(runnable, Function) and runnable._is_web_endpoint():
|
@@ -164,7 +181,10 @@ def list_cli_commands(
|
|
164
181
|
|
165
182
|
return False
|
166
183
|
|
167
|
-
return [
|
184
|
+
return [
|
185
|
+
CLICommand(names, runnable, _is_web_endpoint(runnable), priority=priorities[runnable])
|
186
|
+
for runnable, names in all_runnables.items()
|
187
|
+
]
|
168
188
|
|
169
189
|
|
170
190
|
def filter_cli_commands(
|
@@ -226,7 +246,7 @@ def import_app(app_ref: str) -> App:
|
|
226
246
|
error_console = Console(stderr=True)
|
227
247
|
error_console.print(f"[bold red]Could not find Modal app '{object_path}' in {import_path}.[/bold red]")
|
228
248
|
|
229
|
-
if object_path
|
249
|
+
if not object_path:
|
230
250
|
guidance_msg = Markdown(
|
231
251
|
f"Expected to find an app variable named **`{DEFAULT_APP_NAME}`** (the default app name). "
|
232
252
|
"If your `modal.App` is assigned to a different variable name, "
|
@@ -308,17 +328,19 @@ def import_and_filter(
|
|
308
328
|
filtered_commands = filter_cli_commands(
|
309
329
|
cli_commands, import_ref.object_path, accept_local_entrypoint, accept_webhook
|
310
330
|
)
|
331
|
+
|
311
332
|
all_usable_commands = filter_cli_commands(cli_commands, "", accept_local_entrypoint, accept_webhook)
|
312
333
|
|
313
|
-
if
|
314
|
-
|
315
|
-
|
334
|
+
if filtered_commands:
|
335
|
+
# if there is a single command with "highest run prio" - use that
|
336
|
+
filtered_commands_by_prio = defaultdict(list)
|
337
|
+
for cmd in filtered_commands:
|
338
|
+
filtered_commands_by_prio[cmd.priority].append(cmd)
|
316
339
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
# if there is a single local entrypoint - use that
|
322
|
-
return local_entrypoint_cmds[0].runnable, all_usable_commands
|
340
|
+
_, highest_prio_commands = min(filtered_commands_by_prio.items())
|
341
|
+
if len(highest_prio_commands) == 1:
|
342
|
+
cli_command = highest_prio_commands[0]
|
343
|
+
return cli_command.runnable, all_usable_commands
|
323
344
|
|
345
|
+
# otherwise, just return the list of all commands
|
324
346
|
return None, all_usable_commands
|
modal/client.pyi
CHANGED
@@ -27,7 +27,7 @@ class _Client:
|
|
27
27
|
_snapshotted: bool
|
28
28
|
|
29
29
|
def __init__(
|
30
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.
|
30
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.1"
|
31
31
|
): ...
|
32
32
|
def is_closed(self) -> bool: ...
|
33
33
|
@property
|
@@ -85,7 +85,7 @@ class Client:
|
|
85
85
|
_snapshotted: bool
|
86
86
|
|
87
87
|
def __init__(
|
88
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.
|
88
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.1"
|
89
89
|
): ...
|
90
90
|
def is_closed(self) -> bool: ...
|
91
91
|
@property
|
modal/file_pattern_matcher.py
CHANGED
@@ -144,7 +144,6 @@ class FilePatternMatcher(_AbstractPatternMatcher):
|
|
144
144
|
library. The reason is that `Matches()` in the original library is
|
145
145
|
deprecated due to buggy behavior.
|
146
146
|
"""
|
147
|
-
|
148
147
|
matched = False
|
149
148
|
file_path = os.path.normpath(file_path)
|
150
149
|
if file_path == ".":
|
modal/functions.py
CHANGED
@@ -8,13 +8,7 @@ import warnings
|
|
8
8
|
from collections.abc import AsyncGenerator, Collection, Sequence, Sized
|
9
9
|
from dataclasses import dataclass
|
10
10
|
from pathlib import PurePosixPath
|
11
|
-
from typing import
|
12
|
-
TYPE_CHECKING,
|
13
|
-
Any,
|
14
|
-
Callable,
|
15
|
-
Optional,
|
16
|
-
Union,
|
17
|
-
)
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
18
12
|
|
19
13
|
import typing_extensions
|
20
14
|
from google.protobuf.message import Message
|
@@ -48,10 +42,12 @@ from ._utils.function_utils import (
|
|
48
42
|
OUTPUTS_TIMEOUT,
|
49
43
|
FunctionCreationStatus,
|
50
44
|
FunctionInfo,
|
45
|
+
IncludeSourceMode,
|
51
46
|
_create_input,
|
52
47
|
_process_result,
|
53
48
|
_stream_function_call_data,
|
54
49
|
get_function_type,
|
50
|
+
get_include_source_mode,
|
55
51
|
is_async,
|
56
52
|
)
|
57
53
|
from ._utils.grpc_utils import retry_transient_errors
|
@@ -474,6 +470,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
474
470
|
cluster_size: Optional[int] = None, # Experimental: Clustered functions
|
475
471
|
max_inputs: Optional[int] = None,
|
476
472
|
ephemeral_disk: Optional[int] = None,
|
473
|
+
# current default: first-party, future default: main-package
|
474
|
+
include_source: Optional[bool] = None,
|
477
475
|
_experimental_buffer_containers: Optional[int] = None,
|
478
476
|
_experimental_proxy_ip: Optional[str] = None,
|
479
477
|
_experimental_custom_scaling_factor: Optional[float] = None,
|
@@ -500,7 +498,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
500
498
|
explicit_mounts = mounts
|
501
499
|
|
502
500
|
if is_local():
|
503
|
-
|
501
|
+
include_source_mode = get_include_source_mode(include_source)
|
502
|
+
if include_source_mode != IncludeSourceMode.INCLUDE_NOTHING:
|
503
|
+
entrypoint_mounts = info.get_entrypoint_mount()
|
504
|
+
else:
|
505
|
+
entrypoint_mounts = []
|
504
506
|
|
505
507
|
all_mounts = [
|
506
508
|
_get_client_mount(),
|
@@ -508,12 +510,15 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
508
510
|
*entrypoint_mounts,
|
509
511
|
]
|
510
512
|
|
511
|
-
if
|
513
|
+
if include_source_mode is IncludeSourceMode.INCLUDE_FIRST_PARTY:
|
514
|
+
# TODO(elias): if using INCLUDE_FIRST_PARTY *and* mounts are added that haven't already been
|
515
|
+
# added to the image via add_local_python_source
|
512
516
|
all_mounts += get_auto_mounts()
|
513
517
|
else:
|
514
518
|
# skip any mount introspection/logic inside containers, since the function
|
515
519
|
# should already be hydrated
|
516
|
-
# TODO: maybe the entire
|
520
|
+
# TODO: maybe the entire from_args loader should be exited early if not local?
|
521
|
+
# since it will be hydrated
|
517
522
|
all_mounts = []
|
518
523
|
|
519
524
|
retry_policy = _parse_retries(
|
@@ -951,8 +956,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
951
956
|
parent = self
|
952
957
|
|
953
958
|
async def _load(param_bound_func: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
954
|
-
if parent is None:
|
955
|
-
raise ExecutionError("Can't find the parent class' service function")
|
956
959
|
try:
|
957
960
|
identity = f"{parent.info.function_name} class service function"
|
958
961
|
except Exception:
|
modal/functions.pyi
CHANGED
@@ -198,6 +198,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal._object
|
|
198
198
|
cluster_size: typing.Optional[int] = None,
|
199
199
|
max_inputs: typing.Optional[int] = None,
|
200
200
|
ephemeral_disk: typing.Optional[int] = None,
|
201
|
+
include_source: typing.Optional[bool] = None,
|
201
202
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
202
203
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
203
204
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
@@ -369,6 +370,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
369
370
|
cluster_size: typing.Optional[int] = None,
|
370
371
|
max_inputs: typing.Optional[int] = None,
|
371
372
|
ephemeral_disk: typing.Optional[int] = None,
|
373
|
+
include_source: typing.Optional[bool] = None,
|
372
374
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
373
375
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
374
376
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
modal/image.py
CHANGED
@@ -2054,7 +2054,7 @@ class _Image(_Object, type_prefix="im"):
|
|
2054
2054
|
yield
|
2055
2055
|
except Exception as exc:
|
2056
2056
|
if not self.is_hydrated:
|
2057
|
-
# Might be hydrated later
|
2057
|
+
# Might be hydrated later (if it's the container's used image)
|
2058
2058
|
self.inside_exceptions.append(exc)
|
2059
2059
|
elif env_image_id == self.object_id:
|
2060
2060
|
# Image is already hydrated (we can remove this case later
|
@@ -16,11 +16,11 @@ modal/_traceback.py,sha256=IZQzB3fVlUfMHOSyKUgw0H6qv4yHnpyq-XVCNZKfUdA,5023
|
|
16
16
|
modal/_tunnel.py,sha256=zTBxBiuH1O22tS1OliAJdIsSmaZS8PlnifS_6S5z-mk,6320
|
17
17
|
modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
|
18
18
|
modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
|
19
|
-
modal/app.py,sha256=
|
20
|
-
modal/app.pyi,sha256=
|
19
|
+
modal/app.py,sha256=BEl90AuVlcrVlXKhM0wDFn1E8hewvbia9Qdq6SiT-FE,44225
|
20
|
+
modal/app.pyi,sha256=NnUpnKq5y5fM45F5cXLMZK5zVJZA9T_UKawloOXCVXE,25858
|
21
21
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
22
22
|
modal/client.py,sha256=8SQawr7P1PNUCq1UmJMUQXG2jIo4Nmdcs311XqrNLRE,15276
|
23
|
-
modal/client.pyi,sha256=
|
23
|
+
modal/client.pyi,sha256=B6N3IX6_KfMiukhFzQtUJVgjMMD7wuiUHTQWphpAKaQ,7591
|
24
24
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
25
25
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
26
26
|
modal/cls.py,sha256=7k3_FwhPUzewRXRZP8bcVJA9AZstoxGJuHKQ5Db-YoY,32683
|
@@ -37,11 +37,11 @@ modal/experimental.py,sha256=H9FXT3kshbjPLovshT10DicyOkGFrPqILy-qdTX-P7s,4015
|
|
37
37
|
modal/experimental.pyi,sha256=24tIYu_w9RLwFrz1cIsgYuqmDCtV8eg6-bQNz3zjhDo,939
|
38
38
|
modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
|
39
39
|
modal/file_io.pyi,sha256=NTRft1tbPSWf9TlWVeZmTlgB5AZ_Zhu2srWIrWr7brk,9445
|
40
|
-
modal/file_pattern_matcher.py,sha256=
|
41
|
-
modal/functions.py,sha256=
|
42
|
-
modal/functions.pyi,sha256=
|
40
|
+
modal/file_pattern_matcher.py,sha256=1cZ4V2wSLiaXqAqStETSwp3bzDD6QZOt6pmmjk3Okz4,6505
|
41
|
+
modal/functions.py,sha256=bD4NbWgAfo5jsiqlT5XT0nGTaQ_6zOfyNmwYu0I-aVY,69951
|
42
|
+
modal/functions.pyi,sha256=eHwUCC7wt_DkCWDnNKuL8LBc4JIRm-rzCOtdmdUgGKY,26641
|
43
43
|
modal/gpu.py,sha256=2qZMNnoMrjU-5Bu7fx68pANUAKTtZq0EWEEeBA9OUVQ,7426
|
44
|
-
modal/image.py,sha256=
|
44
|
+
modal/image.py,sha256=c6-RsdVDCscmfOoZI26gj8GvBBw1oVC18n8WaYKBWAw,90949
|
45
45
|
modal/image.pyi,sha256=QMKS6E3CsZr5DoyNqGpcJPBYJTJZSvtAQIsAhPVd_E4,26347
|
46
46
|
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
47
47
|
modal/io_streams.pyi,sha256=bJ7ZLmSmJ0nKoa6r4FJpbqvzdUVa0lEe0Fa-MMpMezU,5071
|
@@ -82,7 +82,7 @@ modal/volume.py,sha256=JAWeDvoAG95tMBv-fYIERyHsJPS_X_xGpxRRmYtb6j0,30096
|
|
82
82
|
modal/volume.pyi,sha256=kTsXarphjZILXci84LQy7EyC84eXUs5-7D62IM5q3eE,12491
|
83
83
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
84
84
|
modal/_runtime/asgi.py,sha256=c4hmaMW1pLo-cm7ouriJjieuFm4ZF6D2LMy0638sfOs,22139
|
85
|
-
modal/_runtime/container_io_manager.py,sha256=
|
85
|
+
modal/_runtime/container_io_manager.py,sha256=2WmFnErVZyfyw3P6iiv1N6FskxZGsiUHrJe7nJ_-iTs,43161
|
86
86
|
modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
|
87
87
|
modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
|
88
88
|
modal/_runtime/user_code_imports.py,sha256=c06azC0MtYHHCGWWRGxsVY0uMAjQKe1QEj-i0oXqbWM,14699
|
@@ -93,7 +93,7 @@ modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14
|
|
93
93
|
modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
|
94
94
|
modal/_utils/deprecation.py,sha256=dycySRBxyZf3ITzEqPNM6MxXTk9-0VVLA8oCPQ5j_Os,3426
|
95
95
|
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
96
|
-
modal/_utils/function_utils.py,sha256=
|
96
|
+
modal/_utils/function_utils.py,sha256=QAQQXPhSZVPkGgbgStFp3lDCB0jnvlcwKki1N3tSSWQ,27187
|
97
97
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
98
98
|
modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
|
99
99
|
modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
|
@@ -103,7 +103,7 @@ modal/_utils/mount_utils.py,sha256=J-FRZbPQv1i__Tob-FIpbB1oXWpFLAwZiB4OCiJpFG0,3
|
|
103
103
|
modal/_utils/name_utils.py,sha256=TW1iyJedvDNPEJ5UVp93u8xuD5J2gQL_CUt1mgov_aI,1939
|
104
104
|
modal/_utils/package_utils.py,sha256=LcL2olGN4xaUzu2Tbv-C-Ft9Qp6bsLxEfETOAVd-mjU,2073
|
105
105
|
modal/_utils/pattern_utils.py,sha256=ZUffaECfe2iYBhH6cvCB-0-UWhmEBTZEl_TwG_So3ag,6714
|
106
|
-
modal/_utils/rand_pb_testing.py,sha256=
|
106
|
+
modal/_utils/rand_pb_testing.py,sha256=mmVPk1rZldHwHZx0DnHTuHQlRLAiiAYdxjwEJpxvT9c,3900
|
107
107
|
modal/_utils/shell_utils.py,sha256=hWHzv730Br2Xyj6cGPiMZ-198Z3RZuOu3pDXhFSZ22c,2157
|
108
108
|
modal/_vendor/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
109
109
|
modal/_vendor/a2wsgi_wsgi.py,sha256=Q1AsjpV_Q_vzQsz_cSqmP9jWzsGsB-ARFU6vpQYml8k,21878
|
@@ -118,7 +118,7 @@ modal/cli/container.py,sha256=FYwEgjf93j4NMorAjGbSV98i1wpebqdAeNU1wfrFp1k,3668
|
|
118
118
|
modal/cli/dict.py,sha256=8Wq3w-UDaywk8EVNdj-ECCNV9TYHqh4kzhUqhhulatM,4593
|
119
119
|
modal/cli/entry_point.py,sha256=aaNxFAqZcmtSjwzkYIA_Ba9CkL4cL4_i2gy5VjoXxkM,4228
|
120
120
|
modal/cli/environment.py,sha256=Ayddkiq9jdj3XYDJ8ZmUqFpPPH8xajYlbexRkzGtUcg,4334
|
121
|
-
modal/cli/import_refs.py,sha256
|
121
|
+
modal/cli/import_refs.py,sha256=YYseLJ6cU_wln7DjVWfKPgEhv77hxfA0klWAkTK_1HA,12672
|
122
122
|
modal/cli/launch.py,sha256=pzQt2QlcrbIUU0MVzWWPAvMQ6MCyqsHZ0X9JcV-sY04,3242
|
123
123
|
modal/cli/network_file_system.py,sha256=eq3JnwjbfFNsJodIyANHL06ByYc3BSavzdmu8C96cHA,7948
|
124
124
|
modal/cli/profile.py,sha256=rLXfjJObfPNjaZvNfHGIKqs7y9bGYyGe-K7V0w-Ni0M,3110
|
@@ -167,12 +167,12 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
167
167
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
168
168
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
169
169
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
170
|
-
modal_version/__init__.py,sha256=
|
170
|
+
modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
|
171
171
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
172
|
-
modal_version/_version_generated.py,sha256=
|
173
|
-
modal-0.
|
174
|
-
modal-0.
|
175
|
-
modal-0.
|
176
|
-
modal-0.
|
177
|
-
modal-0.
|
178
|
-
modal-0.
|
172
|
+
modal_version/_version_generated.py,sha256=b5X16t-GbrJxsrM_vcD_WXLcjcejIbm7gcrXoJd5e64,148
|
173
|
+
modal-0.73.1.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
174
|
+
modal-0.73.1.dist-info/METADATA,sha256=qsi27dGq0I0z2jCMDbs8cfkdh_kjKztW9uR00_IPuj8,2328
|
175
|
+
modal-0.73.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
176
|
+
modal-0.73.1.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
177
|
+
modal-0.73.1.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
178
|
+
modal-0.73.1.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
@@ -7,7 +7,7 @@ from ._version_generated import build_number
|
|
7
7
|
major_number = 0
|
8
8
|
|
9
9
|
# Bump this manually on breaking changes, then reset the number in _version_generated.py
|
10
|
-
minor_number =
|
10
|
+
minor_number = 73
|
11
11
|
|
12
12
|
# Right now, automatically increment the patch number in CI
|
13
13
|
__version__ = f"{major_number}.{minor_number}.{max(build_number, 0)}"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|