modal 1.2.2.dev30__py3-none-any.whl → 1.2.2.dev36__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/_functions.py +77 -52
- modal/_load_context.py +105 -0
- modal/_object.py +47 -18
- modal/_resolver.py +21 -35
- modal/app.py +7 -0
- modal/app.pyi +3 -0
- modal/cli/dict.py +5 -2
- modal/cli/queues.py +4 -2
- modal/client.pyi +2 -2
- modal/cls.py +71 -32
- modal/cls.pyi +3 -0
- modal/dict.py +14 -5
- modal/dict.pyi +2 -0
- modal/environments.py +16 -7
- modal/environments.pyi +6 -2
- modal/functions.pyi +10 -4
- modal/image.py +22 -22
- modal/mount.py +35 -25
- modal/mount.pyi +33 -7
- modal/network_file_system.py +14 -5
- modal/network_file_system.pyi +12 -2
- modal/object.pyi +35 -8
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +14 -5
- modal/queue.pyi +12 -2
- modal/runner.py +43 -47
- modal/runner.pyi +2 -2
- modal/sandbox.py +21 -12
- modal/secret.py +57 -39
- modal/secret.pyi +21 -4
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +11 -5
- modal/volume.py +25 -7
- modal/volume.pyi +2 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/METADATA +1 -1
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/RECORD +46 -45
- modal_proto/api.proto +4 -0
- modal_proto/api_pb2.py +684 -684
- modal_proto/api_pb2.pyi +24 -3
- modal_version/__init__.py +1 -1
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/WHEEL +0 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/entry_points.txt +0 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/licenses/LICENSE +0 -0
- {modal-1.2.2.dev30.dist-info → modal-1.2.2.dev36.dist-info}/top_level.txt +0 -0
modal/app.py
CHANGED
|
@@ -22,6 +22,7 @@ from modal_proto import api_pb2
|
|
|
22
22
|
|
|
23
23
|
from ._functions import _Function
|
|
24
24
|
from ._ipython import is_notebook
|
|
25
|
+
from ._load_context import LoadContext
|
|
25
26
|
from ._object import _get_environment_name, _Object
|
|
26
27
|
from ._partial_function import (
|
|
27
28
|
_find_partial_methods_for_user_cls,
|
|
@@ -176,6 +177,10 @@ class _App:
|
|
|
176
177
|
_running_app: Optional[RunningApp] # Various app info
|
|
177
178
|
_client: Optional[_Client]
|
|
178
179
|
|
|
180
|
+
# Metadata for loading objects within this app
|
|
181
|
+
# passed by reference to functions and classes so it can be updated by run()/deploy()
|
|
182
|
+
_root_load_context: LoadContext
|
|
183
|
+
|
|
179
184
|
@property
|
|
180
185
|
def _local_state(self) -> _LocalAppState:
|
|
181
186
|
"""For internal use only. Do not use this property directly."""
|
|
@@ -237,6 +242,7 @@ class _App:
|
|
|
237
242
|
# Client is special - needed to be set just before the app is "hydrated" or running at the latest
|
|
238
243
|
# Guaranteed to be set for running apps, but also needed to actually *hydrate* the app and make it running
|
|
239
244
|
self._client = None
|
|
245
|
+
self._root_load_context = LoadContext.empty()
|
|
240
246
|
|
|
241
247
|
# Register this app. This is used to look up the app in the container, when we can't get it from the function
|
|
242
248
|
_App._all_apps.setdefault(self._name, []).append(self)
|
|
@@ -308,6 +314,7 @@ class _App:
|
|
|
308
314
|
app._local_state_attr = None # this is not a locally defined App, so no local state
|
|
309
315
|
app._app_id = response.app_id
|
|
310
316
|
app._client = client
|
|
317
|
+
app._root_load_context = LoadContext(client=client, environment_name=environment_name, app_id=response.app_id)
|
|
311
318
|
app._running_app = RunningApp(response.app_id, interactive=False)
|
|
312
319
|
return app
|
|
313
320
|
|
modal/app.pyi
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import collections.abc
|
|
2
2
|
import modal._functions
|
|
3
|
+
import modal._load_context
|
|
3
4
|
import modal._partial_function
|
|
4
5
|
import modal._utils.function_utils
|
|
5
6
|
import modal.client
|
|
@@ -150,6 +151,7 @@ class _App:
|
|
|
150
151
|
_app_id: typing.Optional[str]
|
|
151
152
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
|
152
153
|
_client: typing.Optional[modal.client._Client]
|
|
154
|
+
_root_load_context: modal._load_context.LoadContext
|
|
153
155
|
|
|
154
156
|
@property
|
|
155
157
|
def _local_state(self) -> _LocalAppState:
|
|
@@ -667,6 +669,7 @@ class App:
|
|
|
667
669
|
_app_id: typing.Optional[str]
|
|
668
670
|
_running_app: typing.Optional[modal.running_app.RunningApp]
|
|
669
671
|
_client: typing.Optional[modal.client.Client]
|
|
672
|
+
_root_load_context: modal._load_context.LoadContext
|
|
670
673
|
|
|
671
674
|
def __init__(
|
|
672
675
|
self,
|
modal/cli/dict.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Optional
|
|
|
4
4
|
import typer
|
|
5
5
|
from typer import Argument, Option, Typer
|
|
6
6
|
|
|
7
|
+
from modal._load_context import LoadContext
|
|
7
8
|
from modal._output import make_console
|
|
8
9
|
from modal._resolver import Resolver
|
|
9
10
|
from modal._utils.async_utils import synchronizer
|
|
@@ -29,8 +30,10 @@ async def create(name: str, *, env: Optional[str] = ENV_OPTION):
|
|
|
29
30
|
"""
|
|
30
31
|
d = _Dict.from_name(name, environment_name=env, create_if_missing=True)
|
|
31
32
|
client = await _Client.from_env()
|
|
32
|
-
resolver = Resolver(
|
|
33
|
-
|
|
33
|
+
resolver = Resolver()
|
|
34
|
+
|
|
35
|
+
load_context = LoadContext(client=client, environment_name=env)
|
|
36
|
+
await resolver.load(d, load_context)
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
@dict_cli.command(name="list", rich_help_panel="Management")
|
modal/cli/queues.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
|
5
5
|
import typer
|
|
6
6
|
from typer import Argument, Option, Typer
|
|
7
7
|
|
|
8
|
+
from modal._load_context import LoadContext
|
|
8
9
|
from modal._output import make_console
|
|
9
10
|
from modal._resolver import Resolver
|
|
10
11
|
from modal._utils.async_utils import synchronizer
|
|
@@ -38,8 +39,9 @@ async def create(name: str, *, env: Optional[str] = ENV_OPTION):
|
|
|
38
39
|
"""
|
|
39
40
|
q = _Queue.from_name(name, environment_name=env, create_if_missing=True)
|
|
40
41
|
client = await _Client.from_env()
|
|
41
|
-
resolver = Resolver(
|
|
42
|
-
|
|
42
|
+
resolver = Resolver()
|
|
43
|
+
load_context = LoadContext(client=client, environment_name=env)
|
|
44
|
+
await resolver.load(q, load_context)
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
@queue_cli.command(name="delete", rich_help_panel="Management")
|
modal/client.pyi
CHANGED
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.2.2.
|
|
36
|
+
version: str = "1.2.2.dev36",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -164,7 +164,7 @@ class Client:
|
|
|
164
164
|
server_url: str,
|
|
165
165
|
client_type: int,
|
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
-
version: str = "1.2.2.
|
|
167
|
+
version: str = "1.2.2.dev36",
|
|
168
168
|
):
|
|
169
169
|
"""mdmd:hidden
|
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
modal/cls.py
CHANGED
|
@@ -12,6 +12,7 @@ from grpclib import GRPCError, Status
|
|
|
12
12
|
from modal_proto import api_pb2
|
|
13
13
|
|
|
14
14
|
from ._functions import _Function, _parse_retries
|
|
15
|
+
from ._load_context import LoadContext
|
|
15
16
|
from ._object import _Object, live_method
|
|
16
17
|
from ._partial_function import (
|
|
17
18
|
_find_callables_for_obj,
|
|
@@ -31,8 +32,8 @@ from ._utils.deprecation import (
|
|
|
31
32
|
warn_on_renamed_autoscaler_settings,
|
|
32
33
|
)
|
|
33
34
|
from ._utils.mount_utils import validate_volumes
|
|
35
|
+
from .client import _Client
|
|
34
36
|
from .cloud_bucket_mount import _CloudBucketMount
|
|
35
|
-
from .config import config
|
|
36
37
|
from .exception import ExecutionError, InvalidError, NotFoundError
|
|
37
38
|
from .gpu import GPU_T
|
|
38
39
|
from .retries import Retries
|
|
@@ -134,7 +135,7 @@ def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name:
|
|
|
134
135
|
method_metadata = cls._method_metadata[method_name]
|
|
135
136
|
new_function._hydrate(service_function.object_id, service_function.client, method_metadata)
|
|
136
137
|
|
|
137
|
-
async def _load(fun: "_Function", resolver: Resolver, existing_object_id: Optional[str]):
|
|
138
|
+
async def _load(fun: "_Function", resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
|
|
138
139
|
# there is currently no actual loading logic executed to create each method on
|
|
139
140
|
# the *parametrized* instance of a class - it uses the parameter-bound service-function
|
|
140
141
|
# for the instance. This load method just makes sure to set all attributes after the
|
|
@@ -153,11 +154,14 @@ def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name:
|
|
|
153
154
|
|
|
154
155
|
rep = f"Method({cls._name}.{method_name})"
|
|
155
156
|
|
|
157
|
+
# Bound methods should *reference* their parent Cls's LoadContext
|
|
158
|
+
# so that it can be modified in place on the parent and be reflected in the method
|
|
156
159
|
fun = _Function._from_loader(
|
|
157
160
|
_load,
|
|
158
161
|
rep,
|
|
159
162
|
deps=_deps,
|
|
160
163
|
hydrate_lazily=True,
|
|
164
|
+
load_context_overrides=cls._load_context_overrides,
|
|
161
165
|
)
|
|
162
166
|
if service_function.is_hydrated:
|
|
163
167
|
# Eager hydration (skip load) if the instance service function is already loaded
|
|
@@ -421,14 +425,13 @@ class _Obj:
|
|
|
421
425
|
|
|
422
426
|
# Not hydrated Cls, and we don't have the class - typically a Cls.from_name that
|
|
423
427
|
# has not yet been loaded. So use a special loader that loads it lazily:
|
|
424
|
-
async def method_loader(fun, resolver: Resolver, existing_object_id):
|
|
425
|
-
await resolver.load(self._cls) # load class so we get info about methods
|
|
428
|
+
async def method_loader(fun, resolver: Resolver, load_context: LoadContext, existing_object_id):
|
|
426
429
|
method_function = _get_maybe_method()
|
|
427
430
|
if method_function is None:
|
|
428
431
|
raise NotFoundError(
|
|
429
432
|
f"Class has no method {k}, and attributes can't be accessed for `Cls.from_name` instances"
|
|
430
433
|
)
|
|
431
|
-
await resolver.load(method_function) # get the appropriate method handle (lazy)
|
|
434
|
+
await resolver.load(method_function, load_context) # get the appropriate method handle (lazy)
|
|
432
435
|
fun._hydrate_from_other(method_function)
|
|
433
436
|
|
|
434
437
|
# The reason we don't *always* use this lazy loader is because it precludes attribute access
|
|
@@ -436,8 +439,9 @@ class _Obj:
|
|
|
436
439
|
return _Function._from_loader(
|
|
437
440
|
method_loader,
|
|
438
441
|
rep=f"Method({self._cls._name}.{k})",
|
|
439
|
-
deps=lambda: [],
|
|
442
|
+
deps=lambda: [self._cls],
|
|
440
443
|
hydrate_lazily=True,
|
|
444
|
+
load_context_overrides=self._cls._load_context_overrides,
|
|
441
445
|
)
|
|
442
446
|
|
|
443
447
|
|
|
@@ -484,6 +488,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
|
484
488
|
self._callables = other._callables
|
|
485
489
|
self._name = other._name
|
|
486
490
|
self._method_metadata = other._method_metadata
|
|
491
|
+
self._load_context_overrides = other._load_context_overrides
|
|
487
492
|
|
|
488
493
|
def _get_partial_functions(self) -> dict[str, _PartialFunction]:
|
|
489
494
|
if not self._user_cls:
|
|
@@ -595,15 +600,18 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
595
600
|
def _deps() -> list[_Function]:
|
|
596
601
|
return [class_service_function]
|
|
597
602
|
|
|
598
|
-
async def _load(self: "_Cls", resolver: Resolver, existing_object_id: Optional[str]):
|
|
603
|
+
async def _load(self: "_Cls", resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
|
|
599
604
|
req = api_pb2.ClassCreateRequest(
|
|
600
|
-
app_id=
|
|
605
|
+
app_id=load_context.app_id, existing_class_id=existing_object_id, only_class_function=True
|
|
601
606
|
)
|
|
602
|
-
resp = await
|
|
603
|
-
self._hydrate(resp.class_id,
|
|
607
|
+
resp = await load_context.client.stub.ClassCreate(req)
|
|
608
|
+
self._hydrate(resp.class_id, load_context.client, resp.handle_metadata)
|
|
604
609
|
|
|
605
610
|
rep = f"Cls({user_cls.__name__})"
|
|
606
|
-
|
|
611
|
+
# Pass a *reference* to the App's LoadContext - this is important since the App is
|
|
612
|
+
# the only way to infer a LoadContext for an `@app.cls`, and the App doesn't
|
|
613
|
+
# get its client until *after* the Cls is created.
|
|
614
|
+
cls: _Cls = _Cls._from_loader(_load, rep, deps=_deps, load_context_overrides=app._root_load_context)
|
|
607
615
|
cls._app = app
|
|
608
616
|
cls._user_cls = user_cls
|
|
609
617
|
cls._class_service_function = class_service_function
|
|
@@ -620,6 +628,7 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
620
628
|
*,
|
|
621
629
|
namespace: Any = None, # mdmd:line-hidden
|
|
622
630
|
environment_name: Optional[str] = None,
|
|
631
|
+
client: Optional["_Client"] = None,
|
|
623
632
|
) -> "_Cls":
|
|
624
633
|
"""Reference a Cls from a deployed App by its name.
|
|
625
634
|
|
|
@@ -632,19 +641,22 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
632
641
|
```
|
|
633
642
|
"""
|
|
634
643
|
warn_if_passing_namespace(namespace, "modal.Cls.from_name")
|
|
635
|
-
_environment_name = environment_name or config.get("environment")
|
|
636
644
|
|
|
637
|
-
async def _load_remote(
|
|
645
|
+
async def _load_remote(
|
|
646
|
+
self: _Cls, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
647
|
+
):
|
|
638
648
|
request = api_pb2.ClassGetRequest(
|
|
639
649
|
app_name=app_name,
|
|
640
650
|
object_tag=name,
|
|
641
|
-
environment_name=
|
|
651
|
+
environment_name=load_context.environment_name,
|
|
642
652
|
only_class_function=True,
|
|
643
653
|
)
|
|
644
654
|
try:
|
|
645
|
-
response = await
|
|
655
|
+
response = await load_context.client.stub.ClassGet(request)
|
|
646
656
|
except NotFoundError as exc:
|
|
647
|
-
env_context =
|
|
657
|
+
env_context = (
|
|
658
|
+
f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
|
|
659
|
+
)
|
|
648
660
|
raise NotFoundError(
|
|
649
661
|
f"Lookup failed for Cls '{name}' from the '{app_name}' app{env_context}: {exc}."
|
|
650
662
|
) from None
|
|
@@ -655,19 +667,26 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
655
667
|
raise
|
|
656
668
|
|
|
657
669
|
print_server_warnings(response.server_warnings)
|
|
658
|
-
await resolver.load(self._class_service_function)
|
|
659
|
-
self._hydrate(response.class_id,
|
|
670
|
+
await resolver.load(self._class_service_function, load_context)
|
|
671
|
+
self._hydrate(response.class_id, load_context.client, response.handle_metadata)
|
|
660
672
|
|
|
661
673
|
environment_rep = f", environment_name={environment_name!r}" if environment_name else ""
|
|
662
674
|
rep = f"Cls.from_name({app_name!r}, {name!r}{environment_rep})"
|
|
663
|
-
|
|
675
|
+
|
|
676
|
+
load_context_overrides = LoadContext(client=client, environment_name=environment_name)
|
|
677
|
+
cls = cls._from_loader(
|
|
678
|
+
_load_remote,
|
|
679
|
+
rep,
|
|
680
|
+
is_another_app=True,
|
|
681
|
+
hydrate_lazily=True,
|
|
682
|
+
load_context_overrides=load_context_overrides,
|
|
683
|
+
)
|
|
664
684
|
|
|
665
685
|
class_service_name = f"{name}.*" # special name of the base service function for the class
|
|
666
686
|
cls._class_service_function = _Function._from_name(
|
|
667
687
|
app_name,
|
|
668
688
|
class_service_name,
|
|
669
|
-
|
|
670
|
-
environment_name=_environment_name,
|
|
689
|
+
load_context_overrides=load_context_overrides,
|
|
671
690
|
)
|
|
672
691
|
cls._name = name
|
|
673
692
|
return cls
|
|
@@ -736,7 +755,7 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
736
755
|
" please use the `.with_concurrency` method instead.",
|
|
737
756
|
)
|
|
738
757
|
|
|
739
|
-
async def _load_from_base(new_cls, resolver, existing_object_id):
|
|
758
|
+
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
740
759
|
# this is a bit confusing, the cls will always have the same metadata
|
|
741
760
|
# since it has the same *class* service function (i.e. "template")
|
|
742
761
|
# But the (instance) service function for each Obj will be different
|
|
@@ -745,14 +764,21 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
745
764
|
if not self.is_hydrated:
|
|
746
765
|
# this should only happen for Cls.from_name instances
|
|
747
766
|
# other classes should already be hydrated!
|
|
748
|
-
await resolver.load(self)
|
|
767
|
+
await resolver.load(self, load_context)
|
|
749
768
|
|
|
750
769
|
new_cls._initialize_from_other(self)
|
|
751
770
|
|
|
752
771
|
def _deps():
|
|
753
772
|
return []
|
|
754
773
|
|
|
755
|
-
cls = _Cls._from_loader(
|
|
774
|
+
cls = _Cls._from_loader(
|
|
775
|
+
_load_from_base,
|
|
776
|
+
rep=f"{self._name}.with_options(...)",
|
|
777
|
+
is_another_app=True,
|
|
778
|
+
deps=_deps,
|
|
779
|
+
load_context_overrides=self._load_context_overrides,
|
|
780
|
+
hydrate_lazily=True,
|
|
781
|
+
)
|
|
756
782
|
cls._initialize_from_other(self)
|
|
757
783
|
|
|
758
784
|
# Validate volumes
|
|
@@ -797,16 +823,21 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
797
823
|
```
|
|
798
824
|
"""
|
|
799
825
|
|
|
800
|
-
async def _load_from_base(new_cls, resolver, existing_object_id):
|
|
826
|
+
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
801
827
|
if not self.is_hydrated:
|
|
802
|
-
await resolver.load(self)
|
|
828
|
+
await resolver.load(self, load_context)
|
|
803
829
|
new_cls._initialize_from_other(self)
|
|
804
830
|
|
|
805
831
|
def _deps():
|
|
806
832
|
return []
|
|
807
833
|
|
|
808
834
|
cls = _Cls._from_loader(
|
|
809
|
-
_load_from_base,
|
|
835
|
+
_load_from_base,
|
|
836
|
+
rep=f"{self._name}.with_concurrency(...)",
|
|
837
|
+
is_another_app=True,
|
|
838
|
+
deps=_deps,
|
|
839
|
+
load_context_overrides=self._load_context_overrides,
|
|
840
|
+
hydrate_lazily=True,
|
|
810
841
|
)
|
|
811
842
|
cls._initialize_from_other(self)
|
|
812
843
|
|
|
@@ -826,16 +857,21 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
826
857
|
```
|
|
827
858
|
"""
|
|
828
859
|
|
|
829
|
-
async def _load_from_base(new_cls, resolver, existing_object_id):
|
|
860
|
+
async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
|
|
830
861
|
if not self.is_hydrated:
|
|
831
|
-
await resolver.load(self)
|
|
862
|
+
await resolver.load(self, load_context)
|
|
832
863
|
new_cls._initialize_from_other(self)
|
|
833
864
|
|
|
834
865
|
def _deps():
|
|
835
866
|
return []
|
|
836
867
|
|
|
837
868
|
cls = _Cls._from_loader(
|
|
838
|
-
_load_from_base,
|
|
869
|
+
_load_from_base,
|
|
870
|
+
rep=f"{self._name}.with_concurrency(...)",
|
|
871
|
+
is_another_app=True,
|
|
872
|
+
deps=_deps,
|
|
873
|
+
load_context_overrides=self._load_context_overrides,
|
|
874
|
+
hydrate_lazily=True,
|
|
839
875
|
)
|
|
840
876
|
cls._initialize_from_other(self)
|
|
841
877
|
|
|
@@ -865,17 +901,20 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
865
901
|
# We create a synthetic dummy Function that is guaranteed to raise an AttributeError when
|
|
866
902
|
# a user tries to use any of its "live methods" - this lets us raise exceptions for users
|
|
867
903
|
# only if they try to access methods on a Cls as if they were methods on the instance.
|
|
868
|
-
async def
|
|
904
|
+
async def error_loader(
|
|
905
|
+
fun: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
906
|
+
):
|
|
869
907
|
raise AttributeError(
|
|
870
908
|
"You can't access methods on a Cls directly - Did you forget to instantiate the class first?\n"
|
|
871
909
|
"e.g. instead of MyClass.method.remote(), do MyClass().method.remote()"
|
|
872
910
|
)
|
|
873
911
|
|
|
874
912
|
return _Function._from_loader(
|
|
875
|
-
|
|
913
|
+
error_loader,
|
|
876
914
|
rep=f"UnboundMethod({self._name}.{k})",
|
|
877
915
|
deps=lambda: [],
|
|
878
916
|
hydrate_lazily=True,
|
|
917
|
+
load_context_overrides=self._load_context_overrides,
|
|
879
918
|
)
|
|
880
919
|
|
|
881
920
|
def _is_local(self) -> bool:
|
modal/cls.pyi
CHANGED
|
@@ -5,6 +5,7 @@ import modal._functions
|
|
|
5
5
|
import modal._object
|
|
6
6
|
import modal._partial_function
|
|
7
7
|
import modal.app
|
|
8
|
+
import modal.client
|
|
8
9
|
import modal.cloud_bucket_mount
|
|
9
10
|
import modal.functions
|
|
10
11
|
import modal.gpu
|
|
@@ -379,6 +380,7 @@ class _Cls(modal._object._Object):
|
|
|
379
380
|
*,
|
|
380
381
|
namespace: typing.Any = None,
|
|
381
382
|
environment_name: typing.Optional[str] = None,
|
|
383
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
382
384
|
) -> _Cls:
|
|
383
385
|
"""Reference a Cls from a deployed App by its name.
|
|
384
386
|
|
|
@@ -537,6 +539,7 @@ class Cls(modal.object.Object):
|
|
|
537
539
|
*,
|
|
538
540
|
namespace: typing.Any = None,
|
|
539
541
|
environment_name: typing.Optional[str] = None,
|
|
542
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
540
543
|
) -> Cls:
|
|
541
544
|
"""Reference a Cls from a deployed App by its name.
|
|
542
545
|
|
modal/dict.py
CHANGED
|
@@ -12,6 +12,7 @@ from synchronicity.async_wrap import asynccontextmanager
|
|
|
12
12
|
from modal._utils.grpc_utils import Retry
|
|
13
13
|
from modal_proto import api_pb2
|
|
14
14
|
|
|
15
|
+
from ._load_context import LoadContext
|
|
15
16
|
from ._object import (
|
|
16
17
|
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
|
17
18
|
_get_environment_name,
|
|
@@ -347,6 +348,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
347
348
|
namespace=None, # mdmd:line-hidden
|
|
348
349
|
environment_name: Optional[str] = None,
|
|
349
350
|
create_if_missing: bool = False,
|
|
351
|
+
client: Optional[_Client] = None,
|
|
350
352
|
) -> "_Dict":
|
|
351
353
|
"""Reference a named Dict, creating if necessary.
|
|
352
354
|
|
|
@@ -368,20 +370,27 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
368
370
|
"Passing data to `modal.Dict.from_name` is deprecated and will stop working in a future release.",
|
|
369
371
|
)
|
|
370
372
|
|
|
371
|
-
async def _load(self: _Dict, resolver: Resolver, existing_object_id: Optional[str]):
|
|
373
|
+
async def _load(self: _Dict, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
|
|
372
374
|
serialized = _serialize_dict(data if data is not None else {})
|
|
373
375
|
req = api_pb2.DictGetOrCreateRequest(
|
|
374
376
|
deployment_name=name,
|
|
375
|
-
environment_name=
|
|
377
|
+
environment_name=load_context.environment_name,
|
|
376
378
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
|
377
379
|
data=serialized,
|
|
378
380
|
)
|
|
379
|
-
response = await
|
|
381
|
+
response = await load_context.client.stub.DictGetOrCreate(req)
|
|
380
382
|
logger.debug(f"Created dict with id {response.dict_id}")
|
|
381
|
-
self._hydrate(response.dict_id,
|
|
383
|
+
self._hydrate(response.dict_id, load_context.client, response.metadata)
|
|
382
384
|
|
|
383
385
|
rep = _Dict._repr(name, environment_name)
|
|
384
|
-
return _Dict._from_loader(
|
|
386
|
+
return _Dict._from_loader(
|
|
387
|
+
_load,
|
|
388
|
+
rep,
|
|
389
|
+
is_another_app=True,
|
|
390
|
+
hydrate_lazily=True,
|
|
391
|
+
name=name,
|
|
392
|
+
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
393
|
+
)
|
|
385
394
|
|
|
386
395
|
@staticmethod
|
|
387
396
|
async def delete(
|
modal/dict.pyi
CHANGED
|
@@ -448,6 +448,7 @@ class _Dict(modal._object._Object):
|
|
|
448
448
|
namespace=None,
|
|
449
449
|
environment_name: typing.Optional[str] = None,
|
|
450
450
|
create_if_missing: bool = False,
|
|
451
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
451
452
|
) -> _Dict:
|
|
452
453
|
"""Reference a named Dict, creating if necessary.
|
|
453
454
|
|
|
@@ -666,6 +667,7 @@ class Dict(modal.object.Object):
|
|
|
666
667
|
namespace=None,
|
|
667
668
|
environment_name: typing.Optional[str] = None,
|
|
668
669
|
create_if_missing: bool = False,
|
|
670
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
669
671
|
) -> Dict:
|
|
670
672
|
"""Reference a named Dict, creating if necessary.
|
|
671
673
|
|
modal/environments.py
CHANGED
|
@@ -8,6 +8,7 @@ from google.protobuf.wrappers_pb2 import StringValue
|
|
|
8
8
|
|
|
9
9
|
from modal_proto import api_pb2
|
|
10
10
|
|
|
11
|
+
from ._load_context import LoadContext
|
|
11
12
|
from ._object import _Object
|
|
12
13
|
from ._resolver import Resolver
|
|
13
14
|
from ._utils.async_utils import synchronize_api, synchronizer
|
|
@@ -52,6 +53,7 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
52
53
|
name: str,
|
|
53
54
|
*,
|
|
54
55
|
create_if_missing: bool = False,
|
|
56
|
+
client: Optional[_Client] = None,
|
|
55
57
|
):
|
|
56
58
|
if name:
|
|
57
59
|
# Allow null names for the case where we want to look up the "default" environment,
|
|
@@ -61,7 +63,9 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
61
63
|
# environments as part of public API when we make this class more useful.
|
|
62
64
|
check_object_name(name, "Environment")
|
|
63
65
|
|
|
64
|
-
async def _load(
|
|
66
|
+
async def _load(
|
|
67
|
+
self: _Environment, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
68
|
+
):
|
|
65
69
|
request = api_pb2.EnvironmentGetOrCreateRequest(
|
|
66
70
|
deployment_name=name,
|
|
67
71
|
object_creation_type=(
|
|
@@ -70,12 +74,17 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
70
74
|
else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
|
|
71
75
|
),
|
|
72
76
|
)
|
|
73
|
-
response = await
|
|
77
|
+
response = await load_context.client.stub.EnvironmentGetOrCreate(request)
|
|
74
78
|
logger.debug(f"Created environment with id {response.environment_id}")
|
|
75
|
-
self._hydrate(response.environment_id,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
self._hydrate(response.environment_id, load_context.client, response.metadata)
|
|
80
|
+
|
|
81
|
+
return _Environment._from_loader(
|
|
82
|
+
_load,
|
|
83
|
+
f"Environment.from_name({name!r})",
|
|
84
|
+
is_another_app=True,
|
|
85
|
+
hydrate_lazily=True,
|
|
86
|
+
load_context_overrides=LoadContext(client=client),
|
|
87
|
+
)
|
|
79
88
|
|
|
80
89
|
|
|
81
90
|
Environment = synchronize_api(_Environment)
|
|
@@ -88,7 +97,7 @@ ENVIRONMENT_CACHE: dict[str, _Environment] = {}
|
|
|
88
97
|
async def _get_environment_cached(name: str, client: _Client) -> _Environment:
|
|
89
98
|
if name in ENVIRONMENT_CACHE:
|
|
90
99
|
return ENVIRONMENT_CACHE[name]
|
|
91
|
-
environment = await _Environment.from_name(name).hydrate(
|
|
100
|
+
environment = await _Environment.from_name(name, client=client).hydrate()
|
|
92
101
|
ENVIRONMENT_CACHE[name] = environment
|
|
93
102
|
return environment
|
|
94
103
|
|
modal/environments.pyi
CHANGED
|
@@ -45,7 +45,9 @@ class _Environment(modal._object._Object):
|
|
|
45
45
|
|
|
46
46
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
|
47
47
|
@staticmethod
|
|
48
|
-
def from_name(
|
|
48
|
+
def from_name(
|
|
49
|
+
name: str, *, create_if_missing: bool = False, client: typing.Optional[modal.client._Client] = None
|
|
50
|
+
): ...
|
|
49
51
|
|
|
50
52
|
class Environment(modal.object.Object):
|
|
51
53
|
_settings: EnvironmentSettings
|
|
@@ -56,7 +58,9 @@ class Environment(modal.object.Object):
|
|
|
56
58
|
|
|
57
59
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
|
58
60
|
@staticmethod
|
|
59
|
-
def from_name(
|
|
61
|
+
def from_name(
|
|
62
|
+
name: str, *, create_if_missing: bool = False, client: typing.Optional[modal.client.Client] = None
|
|
63
|
+
): ...
|
|
60
64
|
|
|
61
65
|
async def _get_environment_cached(name: str, client: modal.client._Client) -> _Environment: ...
|
|
62
66
|
|
modal/functions.pyi
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import collections.abc
|
|
2
2
|
import google.protobuf.message
|
|
3
3
|
import modal._functions
|
|
4
|
+
import modal._load_context
|
|
4
5
|
import modal._utils.async_utils
|
|
5
6
|
import modal._utils.function_utils
|
|
6
7
|
import modal.app
|
|
@@ -66,7 +67,7 @@ class Function(
|
|
|
66
67
|
@staticmethod
|
|
67
68
|
def from_local(
|
|
68
69
|
info: modal._utils.function_utils.FunctionInfo,
|
|
69
|
-
app,
|
|
70
|
+
app: typing.Optional[modal.app.App],
|
|
70
71
|
image: modal.image.Image,
|
|
71
72
|
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
72
73
|
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
@@ -245,10 +246,16 @@ class Function(
|
|
|
245
246
|
keep_warm: __keep_warm_spec[typing_extensions.Self]
|
|
246
247
|
|
|
247
248
|
@classmethod
|
|
248
|
-
def _from_name(cls, app_name: str, name: str,
|
|
249
|
+
def _from_name(cls, app_name: str, name: str, *, load_context_overrides: modal._load_context.LoadContext): ...
|
|
249
250
|
@classmethod
|
|
250
251
|
def from_name(
|
|
251
|
-
cls: type[Function],
|
|
252
|
+
cls: type[Function],
|
|
253
|
+
app_name: str,
|
|
254
|
+
name: str,
|
|
255
|
+
*,
|
|
256
|
+
namespace=None,
|
|
257
|
+
environment_name: typing.Optional[str] = None,
|
|
258
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
252
259
|
) -> Function:
|
|
253
260
|
"""Reference a Function from a deployed App by its name.
|
|
254
261
|
|
|
@@ -714,7 +721,6 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
|
|
|
714
721
|
...
|
|
715
722
|
|
|
716
723
|
def _invocation(self): ...
|
|
717
|
-
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
718
724
|
|
|
719
725
|
class __num_inputs_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
720
726
|
def __call__(self, /) -> int:
|