modal 0.72.21__py3-none-any.whl → 0.72.22__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.
@@ -50,8 +50,8 @@ from ._runtime.container_io_manager import (
50
50
  from ._runtime.execution_context import _set_current_context_ids
51
51
 
52
52
  if TYPE_CHECKING:
53
+ import modal._object
53
54
  import modal._runtime.container_io_manager
54
- import modal.object
55
55
 
56
56
 
57
57
  class DaemonizedThreadPool:
modal/_object.py ADDED
@@ -0,0 +1,279 @@
1
+ # Copyright Modal Labs 2022
2
+ import typing
3
+ import uuid
4
+ from collections.abc import Awaitable, Hashable, Sequence
5
+ from functools import wraps
6
+ from typing import Callable, ClassVar, Optional
7
+
8
+ from google.protobuf.message import Message
9
+ from typing_extensions import Self
10
+
11
+ from modal._utils.async_utils import aclosing
12
+
13
+ from ._resolver import Resolver
14
+ from .client import _Client
15
+ from .config import config, logger
16
+ from .exception import ExecutionError, InvalidError
17
+
18
+ EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
19
+
20
+
21
+ def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
22
+ if environment_name:
23
+ return environment_name
24
+ elif resolver and resolver.environment_name:
25
+ return resolver.environment_name
26
+ else:
27
+ return config.get("environment")
28
+
29
+
30
+ class _Object:
31
+ _type_prefix: ClassVar[Optional[str]] = None
32
+ _prefix_to_type: ClassVar[dict[str, type]] = {}
33
+
34
+ # For constructors
35
+ _load: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]]
36
+ _preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]]
37
+ _rep: str
38
+ _is_another_app: bool
39
+ _hydrate_lazily: bool
40
+ _deps: Optional[Callable[..., Sequence["_Object"]]]
41
+ _deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
42
+
43
+ # For hydrated objects
44
+ _object_id: Optional[str]
45
+ _client: Optional[_Client]
46
+ _is_hydrated: bool
47
+ _is_rehydrated: bool
48
+
49
+ @classmethod
50
+ def __init_subclass__(cls, type_prefix: Optional[str] = None):
51
+ super().__init_subclass__()
52
+ if type_prefix is not None:
53
+ cls._type_prefix = type_prefix
54
+ cls._prefix_to_type[type_prefix] = cls
55
+
56
+ def __init__(self, *args, **kwargs):
57
+ raise InvalidError(f"Class {type(self).__name__} has no constructor. Use class constructor methods instead.")
58
+
59
+ def _init(
60
+ self,
61
+ rep: str,
62
+ load: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
63
+ is_another_app: bool = False,
64
+ preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
65
+ hydrate_lazily: bool = False,
66
+ deps: Optional[Callable[..., Sequence["_Object"]]] = None,
67
+ deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
68
+ ):
69
+ self._local_uuid = str(uuid.uuid4())
70
+ self._load = load
71
+ self._preload = preload
72
+ self._rep = rep
73
+ self._is_another_app = is_another_app
74
+ self._hydrate_lazily = hydrate_lazily
75
+ self._deps = deps
76
+ self._deduplication_key = deduplication_key
77
+
78
+ self._object_id = None
79
+ self._client = None
80
+ self._is_hydrated = False
81
+ self._is_rehydrated = False
82
+
83
+ self._initialize_from_empty()
84
+
85
+ def _unhydrate(self):
86
+ self._object_id = None
87
+ self._client = None
88
+ self._is_hydrated = False
89
+
90
+ def _initialize_from_empty(self):
91
+ # default implementation, can be overriden in subclasses
92
+ pass
93
+
94
+ def _initialize_from_other(self, other):
95
+ # default implementation, can be overriden in subclasses
96
+ self._object_id = other._object_id
97
+ self._is_hydrated = other._is_hydrated
98
+ self._client = other._client
99
+
100
+ def _hydrate(self, object_id: str, client: _Client, metadata: Optional[Message]):
101
+ assert isinstance(object_id, str) and self._type_prefix is not None
102
+ if not object_id.startswith(self._type_prefix):
103
+ raise ExecutionError(
104
+ f"Can not hydrate {type(self)}:"
105
+ f" it has type prefix {self._type_prefix}"
106
+ f" but the object_id starts with {object_id[:3]}"
107
+ )
108
+ self._object_id = object_id
109
+ self._client = client
110
+ self._hydrate_metadata(metadata)
111
+ self._is_hydrated = True
112
+
113
+ def _hydrate_metadata(self, metadata: Optional[Message]):
114
+ # override this is subclasses that need additional data (other than an object_id) for a functioning Handle
115
+ pass
116
+
117
+ def _get_metadata(self) -> Optional[Message]:
118
+ # return the necessary metadata from this handle to be able to re-hydrate in another context if one is needed
119
+ # used to provide a handle's handle_metadata for serializing/pickling a live handle
120
+ # the object_id is already provided by other means
121
+ return None
122
+
123
+ def _validate_is_hydrated(self):
124
+ if not self._is_hydrated:
125
+ object_type = self.__class__.__name__.strip("_")
126
+ if hasattr(self, "_app") and getattr(self._app, "_running_app", "") is None: # type: ignore
127
+ # The most common cause of this error: e.g., user called a Function without using App.run()
128
+ reason = ", because the App it is defined on is not running"
129
+ else:
130
+ # Technically possible, but with an ambiguous cause.
131
+ reason = ""
132
+ raise ExecutionError(
133
+ f"{object_type} has not been hydrated with the metadata it needs to run on Modal{reason}."
134
+ )
135
+
136
+ def clone(self) -> Self:
137
+ """mdmd:hidden Clone a given hydrated object."""
138
+
139
+ # Object to clone must already be hydrated, otherwise from_loader is more suitable.
140
+ self._validate_is_hydrated()
141
+ obj = type(self).__new__(type(self))
142
+ obj._initialize_from_other(self)
143
+ return obj
144
+
145
+ @classmethod
146
+ def _from_loader(
147
+ cls,
148
+ load: Callable[[Self, Resolver, Optional[str]], Awaitable[None]],
149
+ rep: str,
150
+ is_another_app: bool = False,
151
+ preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
152
+ hydrate_lazily: bool = False,
153
+ deps: Optional[Callable[..., Sequence["_Object"]]] = None,
154
+ deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
155
+ ):
156
+ # TODO(erikbern): flip the order of the two first arguments
157
+ obj = _Object.__new__(cls)
158
+ obj._init(rep, load, is_another_app, preload, hydrate_lazily, deps, deduplication_key)
159
+ return obj
160
+
161
+ @staticmethod
162
+ def _get_type_from_id(object_id: str) -> type["_Object"]:
163
+ parts = object_id.split("-")
164
+ if len(parts) != 2:
165
+ raise InvalidError(f"Object id {object_id} has no dash in it")
166
+ prefix = parts[0]
167
+ if prefix not in _Object._prefix_to_type:
168
+ raise InvalidError(f"Object prefix {prefix} does not correspond to a type")
169
+ return _Object._prefix_to_type[prefix]
170
+
171
+ @classmethod
172
+ def _is_id_type(cls, object_id) -> bool:
173
+ return cls._get_type_from_id(object_id) == cls
174
+
175
+ @classmethod
176
+ def _new_hydrated(
177
+ cls, object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
178
+ ) -> Self:
179
+ obj_cls: type[Self]
180
+ if cls._type_prefix is not None:
181
+ # This is called directly on a subclass, e.g. Secret.from_id
182
+ # validate the id matching the expected id type of the Object subclass
183
+ if not object_id.startswith(cls._type_prefix + "-"):
184
+ raise InvalidError(f"Object {object_id} does not start with {cls._type_prefix}")
185
+
186
+ obj_cls = cls
187
+ else:
188
+ # this means the method is used directly on _Object
189
+ # typically during deserialization of objects
190
+ obj_cls = typing.cast(type[Self], cls._get_type_from_id(object_id))
191
+
192
+ # Instantiate provider
193
+ obj = _Object.__new__(obj_cls)
194
+ rep = f"Object({object_id})" # TODO(erikbern): dumb
195
+ obj._init(rep, is_another_app=is_another_app)
196
+ obj._hydrate(object_id, client, handle_metadata)
197
+
198
+ return obj
199
+
200
+ def _hydrate_from_other(self, other: Self):
201
+ self._hydrate(other.object_id, other.client, other._get_metadata())
202
+
203
+ def __repr__(self):
204
+ return self._rep
205
+
206
+ @property
207
+ def local_uuid(self):
208
+ """mdmd:hidden"""
209
+ return self._local_uuid
210
+
211
+ @property
212
+ def object_id(self) -> str:
213
+ """mdmd:hidden"""
214
+ if self._object_id is None:
215
+ raise AttributeError(f"Attempting to get object_id of unhydrated {self}")
216
+ return self._object_id
217
+
218
+ @property
219
+ def client(self) -> _Client:
220
+ """mdmd:hidden"""
221
+ if self._client is None:
222
+ raise AttributeError(f"Attempting to get client of unhydrated {self}")
223
+ return self._client
224
+
225
+ @property
226
+ def is_hydrated(self) -> bool:
227
+ """mdmd:hidden"""
228
+ return self._is_hydrated
229
+
230
+ @property
231
+ def deps(self) -> Callable[..., Sequence["_Object"]]:
232
+ """mdmd:hidden"""
233
+
234
+ def default_deps(*args, **kwargs) -> Sequence["_Object"]:
235
+ return []
236
+
237
+ return self._deps if self._deps is not None else default_deps
238
+
239
+ async def resolve(self, client: Optional[_Client] = None):
240
+ """mdmd:hidden"""
241
+ if self._is_hydrated:
242
+ # memory snapshots capture references which must be rehydrated
243
+ # on restore to handle staleness.
244
+ if self.client._snapshotted and not self._is_rehydrated:
245
+ logger.debug(f"rehydrating {self} after snapshot")
246
+ self._is_hydrated = False # un-hydrate and re-resolve
247
+ c = client if client is not None else await _Client.from_env()
248
+ resolver = Resolver(c)
249
+ await resolver.load(typing.cast(_Object, self))
250
+ self._is_rehydrated = True
251
+ logger.debug(f"rehydrated {self} with client {id(c)}")
252
+ return
253
+ elif not self._hydrate_lazily:
254
+ self._validate_is_hydrated()
255
+ else:
256
+ # TODO: this client and/or resolver can't be changed by a caller to X.from_name()
257
+ c = client if client is not None else await _Client.from_env()
258
+ resolver = Resolver(c)
259
+ await resolver.load(self)
260
+
261
+
262
+ def live_method(method):
263
+ @wraps(method)
264
+ async def wrapped(self, *args, **kwargs):
265
+ await self.resolve()
266
+ return await method(self, *args, **kwargs)
267
+
268
+ return wrapped
269
+
270
+
271
+ def live_method_gen(method):
272
+ @wraps(method)
273
+ async def wrapped(self, *args, **kwargs):
274
+ await self.resolve()
275
+ async with aclosing(method(self, *args, **kwargs)) as stream:
276
+ async for item in stream:
277
+ yield item
278
+
279
+ return wrapped
modal/_resolver.py CHANGED
@@ -15,7 +15,7 @@ from .exception import NotFoundError
15
15
  if TYPE_CHECKING:
16
16
  from rich.tree import Tree
17
17
 
18
- from modal.object import _Object
18
+ import modal._object
19
19
 
20
20
 
21
21
  class StatusRow:
@@ -33,7 +33,7 @@ class StatusRow:
33
33
  self._spinner.update(text=message)
34
34
 
35
35
  def finish(self, message):
36
- if self._step_node is not None:
36
+ if self._step_node is not None and self._spinner is not None:
37
37
  from ._output import OutputManager
38
38
 
39
39
  self._spinner.update(text=message)
@@ -89,7 +89,7 @@ class Resolver:
89
89
  if obj._preload is not None:
90
90
  await obj._preload(obj, self, existing_object_id)
91
91
 
92
- async def load(self, obj: "_Object", existing_object_id: Optional[str] = None):
92
+ async def load(self, obj: "modal._object._Object", existing_object_id: Optional[str] = None):
93
93
  if obj._is_hydrated and obj._is_another_app:
94
94
  # No need to reload this, it won't typically change
95
95
  if obj.local_uuid not in self._local_uuid_to_future:
@@ -124,6 +124,8 @@ class Resolver:
124
124
  await TaskContext.gather(*[self.load(dep) for dep in obj.deps()])
125
125
 
126
126
  # Load the object itself
127
+ if not obj._load:
128
+ raise Exception(f"Object {obj} has no loader function")
127
129
  try:
128
130
  await obj._load(obj, self, existing_object_id)
129
131
  except GRPCError as exc:
@@ -154,8 +156,8 @@ class Resolver:
154
156
  # TODO(elias): print original exception/trace rather than the Resolver-internal trace
155
157
  return await cached_future
156
158
 
157
- def objects(self) -> list["_Object"]:
158
- unique_objects: dict[str, "_Object"] = {}
159
+ def objects(self) -> list["modal._object._Object"]:
160
+ unique_objects: dict[str, "modal._object._Object"] = {}
159
161
  for fut in self._local_uuid_to_future.values():
160
162
  if not fut.done():
161
163
  # this will raise an exception if not all loads have been awaited, but that *should* never happen
@@ -3,11 +3,11 @@ import importlib
3
3
  import typing
4
4
  from abc import ABCMeta, abstractmethod
5
5
  from dataclasses import dataclass
6
- from typing import Any, Callable, Optional
6
+ from typing import Any, Callable, Optional, Sequence
7
7
 
8
+ import modal._object
8
9
  import modal._runtime.container_io_manager
9
10
  import modal.cls
10
- import modal.object
11
11
  from modal import Function
12
12
  from modal._utils.async_utils import synchronizer
13
13
  from modal._utils.function_utils import LocalFunctionError, is_async as get_is_async, is_global_object
@@ -41,7 +41,7 @@ class Service(metaclass=ABCMeta):
41
41
 
42
42
  user_cls_instance: Any
43
43
  app: Optional["modal.app._App"]
44
- code_deps: Optional[list["modal.object._Object"]]
44
+ code_deps: Optional[Sequence["modal._object._Object"]]
45
45
 
46
46
  @abstractmethod
47
47
  def get_finalized_functions(
@@ -94,7 +94,7 @@ def construct_webhook_callable(
94
94
  class ImportedFunction(Service):
95
95
  user_cls_instance: Any
96
96
  app: Optional["modal.app._App"]
97
- code_deps: Optional[list["modal.object._Object"]]
97
+ code_deps: Optional[Sequence["modal._object._Object"]]
98
98
 
99
99
  _user_defined_callable: Callable[..., Any]
100
100
 
@@ -137,7 +137,7 @@ class ImportedFunction(Service):
137
137
  class ImportedClass(Service):
138
138
  user_cls_instance: Any
139
139
  app: Optional["modal.app._App"]
140
- code_deps: Optional[list["modal.object._Object"]]
140
+ code_deps: Optional[Sequence["modal._object._Object"]]
141
141
 
142
142
  _partial_functions: dict[str, "modal.partial_function._PartialFunction"]
143
143
 
@@ -229,7 +229,7 @@ def import_single_function_service(
229
229
  """
230
230
  user_defined_callable: Callable
231
231
  function: Optional[_Function] = None
232
- code_deps: Optional[list["modal.object._Object"]] = None
232
+ code_deps: Optional[Sequence["modal._object._Object"]] = None
233
233
  active_app: Optional[modal.app._App] = None
234
234
 
235
235
  if ser_fun is not None:
@@ -306,7 +306,7 @@ def import_class_service(
306
306
  See import_function.
307
307
  """
308
308
  active_app: Optional["modal.app._App"]
309
- code_deps: Optional[list["modal.object._Object"]]
309
+ code_deps: Optional[Sequence["modal._object._Object"]]
310
310
  cls: typing.Union[type, modal.cls.Cls]
311
311
 
312
312
  if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
modal/_serialization.py CHANGED
@@ -8,10 +8,11 @@ from typing import Any
8
8
  from modal._utils.async_utils import synchronizer
9
9
  from modal_proto import api_pb2
10
10
 
11
+ from ._object import _Object
11
12
  from ._vendor import cloudpickle
12
13
  from .config import logger
13
14
  from .exception import DeserializationError, ExecutionError, InvalidError
14
- from .object import Object, _Object
15
+ from .object import Object
15
16
 
16
17
  PICKLE_PROTOCOL = 4 # Support older Python versions.
17
18
 
@@ -48,8 +49,8 @@ class Pickler(cloudpickle.Pickler):
48
49
  return ("sync", (impl_object.__class__, attributes))
49
50
  else:
50
51
  return
51
- if not obj.object_id:
52
- raise InvalidError(f"Can't serialize object {obj} which hasn't been created.")
52
+ if not obj.is_hydrated:
53
+ raise InvalidError(f"Can't serialize object {obj} which hasn't been hydrated.")
53
54
  return (obj.object_id, flag, obj._get_metadata())
54
55
 
55
56
 
modal/app.py CHANGED
@@ -21,6 +21,7 @@ from synchronicity.async_wrap import asynccontextmanager
21
21
  from modal_proto import api_pb2
22
22
 
23
23
  from ._ipython import is_notebook
24
+ from ._object import _get_environment_name, _Object
24
25
  from ._utils.async_utils import synchronize_api
25
26
  from ._utils.deprecation import deprecation_error, deprecation_warning, renamed_parameter
26
27
  from ._utils.function_utils import FunctionInfo, is_global_object, is_method_fn
@@ -36,7 +37,6 @@ from .gpu import GPU_T
36
37
  from .image import _Image
37
38
  from .mount import _Mount
38
39
  from .network_file_system import _NetworkFileSystem
39
- from .object import _get_environment_name, _Object
40
40
  from .partial_function import (
41
41
  PartialFunction,
42
42
  _find_partial_methods_for_user_cls,
modal/app.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections.abc
2
+ import modal._object
2
3
  import modal._utils.function_utils
3
4
  import modal.client
4
5
  import modal.cloud_bucket_mount
@@ -115,9 +116,9 @@ class _App:
115
116
  def set_description(self, description: str): ...
116
117
  def _validate_blueprint_value(self, key: str, value: typing.Any): ...
117
118
  def __getitem__(self, tag: str): ...
118
- def __setitem__(self, tag: str, obj: modal.object._Object): ...
119
+ def __setitem__(self, tag: str, obj: modal._object._Object): ...
119
120
  def __getattr__(self, tag: str): ...
120
- def __setattr__(self, tag: str, obj: modal.object._Object): ...
121
+ def __setattr__(self, tag: str, obj: modal._object._Object): ...
121
122
  @property
122
123
  def image(self) -> modal.image._Image: ...
123
124
  @image.setter
@@ -146,7 +147,7 @@ class _App:
146
147
  @property
147
148
  def registered_entrypoints(self) -> dict[str, _LocalEntrypoint]: ...
148
149
  @property
149
- def indexed_objects(self) -> dict[str, modal.object._Object]: ...
150
+ def indexed_objects(self) -> dict[str, modal._object._Object]: ...
150
151
  @property
151
152
  def registered_web_endpoints(self) -> list[str]: ...
152
153
  def local_entrypoint(
modal/cli/app.py CHANGED
@@ -9,11 +9,11 @@ from rich.table import Column
9
9
  from rich.text import Text
10
10
  from typer import Argument
11
11
 
12
+ from modal._object import _get_environment_name
12
13
  from modal._utils.async_utils import synchronizer
13
14
  from modal._utils.deprecation import deprecation_warning
14
15
  from modal.client import _Client
15
16
  from modal.environments import ensure_env
16
- from modal.object import _get_environment_name
17
17
  from modal_proto import api_pb2
18
18
 
19
19
  from .utils import ENV_OPTION, display_table, get_app_id_from_name, stream_app_logs, timestamp_to_local
modal/cli/container.py CHANGED
@@ -4,6 +4,7 @@ from typing import Optional, Union
4
4
  import typer
5
5
  from rich.text import Text
6
6
 
7
+ from modal._object import _get_environment_name
7
8
  from modal._pty import get_pty_info
8
9
  from modal._utils.async_utils import synchronizer
9
10
  from modal._utils.grpc_utils import retry_transient_errors
@@ -12,7 +13,6 @@ from modal.client import _Client
12
13
  from modal.config import config
13
14
  from modal.container_process import _ContainerProcess
14
15
  from modal.environments import ensure_env
15
- from modal.object import _get_environment_name
16
16
  from modal.stream_type import StreamType
17
17
  from modal_proto import api_pb2
18
18
 
modal/client.py CHANGED
@@ -73,6 +73,7 @@ class _Client:
73
73
  _cancellation_context: TaskContext
74
74
  _cancellation_context_event_loop: asyncio.AbstractEventLoop = None
75
75
  _stub: Optional[api_grpc.ModalClientStub]
76
+ _snapshotted: bool
76
77
 
77
78
  def __init__(
78
79
  self,
modal/client.pyi CHANGED
@@ -24,9 +24,10 @@ class _Client:
24
24
  _cancellation_context: modal._utils.async_utils.TaskContext
25
25
  _cancellation_context_event_loop: asyncio.events.AbstractEventLoop
26
26
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
27
+ _snapshotted: bool
27
28
 
28
29
  def __init__(
29
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.21"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.22"
30
31
  ): ...
31
32
  def is_closed(self) -> bool: ...
32
33
  @property
@@ -79,9 +80,10 @@ class Client:
79
80
  _cancellation_context: modal._utils.async_utils.TaskContext
80
81
  _cancellation_context_event_loop: asyncio.events.AbstractEventLoop
81
82
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
83
+ _snapshotted: bool
82
84
 
83
85
  def __init__(
84
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.21"
86
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.72.22"
85
87
  ): ...
86
88
  def is_closed(self) -> bool: ...
87
89
  @property
modal/cls.py CHANGED
@@ -11,6 +11,7 @@ from grpclib import GRPCError, Status
11
11
  from modal._utils.function_utils import CLASS_PARAM_TYPE_MAP
12
12
  from modal_proto import api_pb2
13
13
 
14
+ from ._object import _get_environment_name, _Object
14
15
  from ._resolver import Resolver
15
16
  from ._resources import convert_fn_config_to_resources_config
16
17
  from ._serialization import check_valid_cls_constructor_arg
@@ -23,7 +24,6 @@ from .client import _Client
23
24
  from .exception import ExecutionError, InvalidError, NotFoundError, VersionError
24
25
  from .functions import _Function, _parse_retries
25
26
  from .gpu import GPU_T
26
- from .object import _get_environment_name, _Object
27
27
  from .partial_function import (
28
28
  _find_callables_for_obj,
29
29
  _find_partial_methods_for_user_cls,
modal/cls.pyi CHANGED
@@ -1,6 +1,7 @@
1
1
  import collections.abc
2
2
  import google.protobuf.message
3
3
  import inspect
4
+ import modal._object
4
5
  import modal.app
5
6
  import modal.client
6
7
  import modal.functions
@@ -91,7 +92,7 @@ class Obj:
91
92
  async def _aenter(self): ...
92
93
  def __getattr__(self, k): ...
93
94
 
94
- class _Cls(modal.object._Object):
95
+ class _Cls(modal._object._Object):
95
96
  _user_cls: typing.Optional[type]
96
97
  _class_service_function: typing.Optional[modal.functions._Function]
97
98
  _method_functions: typing.Optional[dict[str, modal.functions._Function]]
modal/dict.py CHANGED
@@ -7,6 +7,7 @@ from synchronicity.async_wrap import asynccontextmanager
7
7
 
8
8
  from modal_proto import api_pb2
9
9
 
10
+ from ._object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
10
11
  from ._resolver import Resolver
11
12
  from ._serialization import deserialize, serialize
12
13
  from ._utils.async_utils import TaskContext, synchronize_api
@@ -16,7 +17,6 @@ from ._utils.name_utils import check_object_name
16
17
  from .client import _Client
17
18
  from .config import logger
18
19
  from .exception import RequestSizeError
19
- from .object import EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, _get_environment_name, _Object, live_method, live_method_gen
20
20
 
21
21
 
22
22
  def _serialize_dict(data):
modal/dict.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections.abc
2
+ import modal._object
2
3
  import modal.client
3
4
  import modal.object
4
5
  import synchronicity.combined_types
@@ -7,7 +8,7 @@ import typing_extensions
7
8
 
8
9
  def _serialize_dict(data): ...
9
10
 
10
- class _Dict(modal.object._Object):
11
+ class _Dict(modal._object._Object):
11
12
  def __init__(self, data={}): ...
12
13
  @classmethod
13
14
  def ephemeral(
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 ._object import _Object
11
12
  from ._resolver import Resolver
12
13
  from ._utils.async_utils import synchronize_api, synchronizer
13
14
  from ._utils.deprecation import renamed_parameter
@@ -15,7 +16,6 @@ from ._utils.grpc_utils import retry_transient_errors
15
16
  from ._utils.name_utils import check_object_name
16
17
  from .client import _Client
17
18
  from .config import config, logger
18
- from .object import _Object
19
19
 
20
20
 
21
21
  @dataclass(frozen=True)
modal/environments.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import google.protobuf.message
2
+ import modal._object
2
3
  import modal.client
3
4
  import modal.object
4
5
  import modal_proto.api_pb2
@@ -16,7 +17,7 @@ class EnvironmentSettings:
16
17
  def __delattr__(self, name): ...
17
18
  def __hash__(self): ...
18
19
 
19
- class _Environment(modal.object._Object):
20
+ class _Environment(modal._object._Object):
20
21
  _settings: EnvironmentSettings
21
22
 
22
23
  def __init__(self): ...