modal 0.68.40__py3-none-any.whl → 0.68.49__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/asgi.py CHANGED
@@ -26,6 +26,7 @@ class LifespanManager:
26
26
  shutdown: asyncio.Future
27
27
  queue: asyncio.Queue
28
28
  has_run_init: bool = False
29
+ lifespan_supported: bool = False
29
30
 
30
31
  def __init__(self, asgi_app, state):
31
32
  self.asgi_app = asgi_app
@@ -46,6 +47,7 @@ class LifespanManager:
46
47
  await self.ensure_init()
47
48
 
48
49
  async def receive():
50
+ self.lifespan_supported = True
49
51
  return await self.queue.get()
50
52
 
51
53
  async def send(message):
@@ -63,16 +65,21 @@ class LifespanManager:
63
65
  try:
64
66
  await self.asgi_app({"type": "lifespan", "state": self.state}, receive, send)
65
67
  except Exception as e:
68
+ if not self.lifespan_supported:
69
+ logger.info(f"ASGI lifespan task exited before receiving any messages with exception:\n{e}")
70
+ self.startup.set_result(None)
71
+ self.shutdown.set_result(None)
72
+ return
73
+
66
74
  logger.error(f"Error in ASGI lifespan task: {e}")
67
75
  if not self.startup.done():
68
76
  self.startup.set_exception(ExecutionError("ASGI lifespan task exited startup"))
69
77
  if not self.shutdown.done():
70
78
  self.shutdown.set_exception(ExecutionError("ASGI lifespan task exited shutdown"))
71
79
  else:
72
- if not self.startup.done():
73
- self.startup.set_result("ASGI Lifespan protocol is probably not supported by this library")
74
- if not self.shutdown.done():
75
- self.shutdown.set_result("ASGI Lifespan protocol is probably not supported by this library")
80
+ logger.info("ASGI Lifespan protocol is probably not supported by this library")
81
+ self.startup.set_result(None)
82
+ self.shutdown.set_result(None)
76
83
 
77
84
  async def lifespan_startup(self):
78
85
  await self.ensure_init()
@@ -99,16 +99,18 @@ def get_function_type(is_generator: Optional[bool]) -> "api_pb2.Function.Functio
99
99
 
100
100
 
101
101
  class FunctionInfo:
102
- """Class that helps us extract a bunch of information about a locally defined function.
102
+ """Utility that determines serialization/deserialization mechanisms for functions
103
103
 
104
- Used for populating the definition of a remote function, and for making .local() calls
105
- on a host with the local definition available.
104
+ * Stored as file vs serialized
105
+ * If serialized: how to serialize the function
106
+ * If file: which module/function name should be used to retrieve
107
+
108
+ Used for populating the definition of a remote function
106
109
  """
107
110
 
108
111
  raw_f: Optional[Callable[..., Any]] # if None - this is a "class service function"
109
112
  function_name: str
110
113
  user_cls: Optional[type[Any]]
111
- definition_type: "modal_proto.api_pb2.Function.DefinitionType.ValueType"
112
114
  module_name: Optional[str]
113
115
 
114
116
  _type: FunctionInfoType
@@ -116,6 +118,12 @@ class FunctionInfo:
116
118
  _base_dir: str
117
119
  _remote_dir: Optional[PurePosixPath] = None
118
120
 
121
+ def get_definition_type(self) -> "modal_proto.api_pb2.Function.DefinitionType.ValueType":
122
+ if self.is_serialized():
123
+ return modal_proto.api_pb2.Function.DEFINITION_TYPE_SERIALIZED
124
+ else:
125
+ return modal_proto.api_pb2.Function.DEFINITION_TYPE_FILE
126
+
119
127
  def is_service_class(self):
120
128
  if self.raw_f is None:
121
129
  assert self.user_cls
@@ -172,7 +180,7 @@ class FunctionInfo:
172
180
  self._base_dir = base_dirs[0]
173
181
  self.module_name = module.__spec__.name
174
182
  self._remote_dir = ROOT_DIR / PurePosixPath(module.__package__.split(".")[0])
175
- self.definition_type = api_pb2.Function.DEFINITION_TYPE_FILE
183
+ self._is_serialized = False
176
184
  self._type = FunctionInfoType.PACKAGE
177
185
  elif hasattr(module, "__file__") and not serialized:
178
186
  # This generally covers the case where it's invoked with
@@ -182,18 +190,18 @@ class FunctionInfo:
182
190
  self._file = os.path.abspath(inspect.getfile(module))
183
191
  self.module_name = inspect.getmodulename(self._file)
184
192
  self._base_dir = os.path.dirname(self._file)
185
- self.definition_type = api_pb2.Function.DEFINITION_TYPE_FILE
193
+ self._is_serialized = False
186
194
  self._type = FunctionInfoType.FILE
187
195
  else:
188
196
  self.module_name = None
189
197
  self._base_dir = os.path.abspath("") # get current dir
190
- self.definition_type = api_pb2.Function.DEFINITION_TYPE_SERIALIZED
191
- if serialized:
198
+ self._is_serialized = True # either explicitly, or by being in a notebook
199
+ if serialized: # if explicit
192
200
  self._type = FunctionInfoType.SERIALIZED
193
201
  else:
194
202
  self._type = FunctionInfoType.NOTEBOOK
195
203
 
196
- if self.definition_type == api_pb2.Function.DEFINITION_TYPE_FILE:
204
+ if not self.is_serialized():
197
205
  # Sanity check that this function is defined in global scope
198
206
  # Unfortunately, there's no "clean" way to do this in Python
199
207
  qualname = f.__qualname__ if f else user_cls.__qualname__
@@ -203,7 +211,7 @@ class FunctionInfo:
203
211
  )
204
212
 
205
213
  def is_serialized(self) -> bool:
206
- return self.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED
214
+ return self._is_serialized
207
215
 
208
216
  def serialized_function(self) -> bytes:
209
217
  # Note: this should only be called from .load() and not at function decoration time
@@ -312,7 +320,7 @@ class FunctionInfo:
312
320
  if self._type == FunctionInfoType.PACKAGE:
313
321
  if config.get("automount"):
314
322
  return [_Mount.from_local_python_packages(self.module_name)]
315
- elif self.definition_type == api_pb2.Function.DEFINITION_TYPE_FILE:
323
+ elif not self.is_serialized():
316
324
  # mount only relevant file and __init__.py:s
317
325
  return [
318
326
  _Mount.from_local_dir(
@@ -322,7 +330,7 @@ class FunctionInfo:
322
330
  condition=entrypoint_only_package_mount_condition(self._file),
323
331
  )
324
332
  ]
325
- elif self.definition_type == api_pb2.Function.DEFINITION_TYPE_FILE:
333
+ elif not self.is_serialized():
326
334
  remote_path = ROOT_DIR / Path(self._file).name
327
335
  if not _is_modal_path(remote_path):
328
336
  return [
modal/client.pyi CHANGED
@@ -26,7 +26,7 @@ class _Client:
26
26
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
27
27
 
28
28
  def __init__(
29
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.40"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.49"
30
30
  ): ...
31
31
  def is_closed(self) -> bool: ...
32
32
  @property
@@ -81,7 +81,7 @@ class Client:
81
81
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
82
82
 
83
83
  def __init__(
84
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.40"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.49"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/dict.py CHANGED
@@ -10,7 +10,7 @@ from modal_proto import api_pb2
10
10
  from ._resolver import Resolver
11
11
  from ._serialization import deserialize, serialize
12
12
  from ._utils.async_utils import TaskContext, synchronize_api
13
- from ._utils.deprecation import deprecation_error, renamed_parameter
13
+ from ._utils.deprecation import renamed_parameter
14
14
  from ._utils.grpc_utils import retry_transient_errors
15
15
  from ._utils.name_utils import check_object_name
16
16
  from .client import _Client
@@ -58,15 +58,6 @@ class _Dict(_Object, type_prefix="di"):
58
58
  For more examples, see the [guide](/docs/guide/dicts-and-queues#modal-dicts).
59
59
  """
60
60
 
61
- @staticmethod
62
- def new(data: Optional[dict] = None):
63
- """mdmd:hidden"""
64
- message = (
65
- "`Dict.new` is deprecated."
66
- " Please use `Dict.from_name` (for persisted) or `Dict.ephemeral` (for ephemeral) dicts instead."
67
- )
68
- deprecation_error((2024, 3, 19), message)
69
-
70
61
  def __init__(self, data={}):
71
62
  """mdmd:hidden"""
72
63
  raise RuntimeError(
modal/dict.pyi CHANGED
@@ -8,8 +8,6 @@ import typing_extensions
8
8
  def _serialize_dict(data): ...
9
9
 
10
10
  class _Dict(modal.object._Object):
11
- @staticmethod
12
- def new(data: typing.Optional[dict] = None): ...
13
11
  def __init__(self, data={}): ...
14
12
  @classmethod
15
13
  def ephemeral(
@@ -60,8 +58,6 @@ class _Dict(modal.object._Object):
60
58
 
61
59
  class Dict(modal.object.Object):
62
60
  def __init__(self, data={}): ...
63
- @staticmethod
64
- def new(data: typing.Optional[dict] = None): ...
65
61
  @classmethod
66
62
  def ephemeral(
67
63
  cls: type[Dict],
modal/functions.py CHANGED
@@ -753,7 +753,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
753
753
  mount_ids=loaded_mount_ids,
754
754
  secret_ids=[secret.object_id for secret in secrets],
755
755
  image_id=(image.object_id if image else ""),
756
- definition_type=info.definition_type,
756
+ definition_type=info.get_definition_type(),
757
757
  function_serialized=function_serialized or b"",
758
758
  class_serialized=class_serialized or b"",
759
759
  function_type=function_type,
modal/gpu.py CHANGED
@@ -137,6 +137,27 @@ class H100(_GPUConfig):
137
137
  return f"GPU(H100, count={self.count})"
138
138
 
139
139
 
140
+ class L40S(_GPUConfig):
141
+ """
142
+ [NVIDIA L40S](https://www.nvidia.com/en-us/data-center/l40s/) GPU class.
143
+
144
+ The L40S is a data center GPU for the Ada Lovelace architecture. It has 48 GB of on-chip
145
+ GDDR6 RAM and enhanced support for FP8 precision.
146
+ """
147
+
148
+ def __init__(
149
+ self,
150
+ *,
151
+ # Number of GPUs per container. Defaults to 1.
152
+ # Useful if you have very large models that don't fit on a single GPU.
153
+ count: int = 1,
154
+ ):
155
+ super().__init__(api_pb2.GPU_TYPE_L40S, count)
156
+
157
+ def __repr__(self):
158
+ return f"GPU(L40S, count={self.count})"
159
+
160
+
140
161
  class Any(_GPUConfig):
141
162
  """Selects any one of the GPU classes available within Modal, according to availability."""
142
163
 
@@ -154,6 +175,7 @@ STRING_TO_GPU_CONFIG: dict[str, Callable] = {
154
175
  "a100-80gb": lambda: A100(size="80GB"),
155
176
  "h100": H100,
156
177
  "a10g": A10G,
178
+ "l40s": L40S,
157
179
  "any": Any,
158
180
  }
159
181
  display_string_to_config = "\n".join(f'- "{key}" → `{c()}`' for key, c in STRING_TO_GPU_CONFIG.items() if key != "inf2")
modal/image.py CHANGED
@@ -571,21 +571,6 @@ class _Image(_Object, type_prefix="im"):
571
571
  obj.force_build = force_build
572
572
  return obj
573
573
 
574
- def extend(self, **kwargs) -> "_Image":
575
- """mdmd:hidden"""
576
- deprecation_error(
577
- (2024, 3, 7),
578
- "`Image.extend` is deprecated; please use a higher-level method, such as `Image.dockerfile_commands`.",
579
- )
580
-
581
- def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
582
- return DockerfileSpec(
583
- commands=kwargs.pop("dockerfile_commands", []),
584
- context_files=kwargs.pop("context_files", {}),
585
- )
586
-
587
- return _Image._from_args(base_images={"base": self}, dockerfile_function=build_dockerfile, **kwargs)
588
-
589
574
  def copy_mount(self, mount: _Mount, remote_path: Union[str, Path] = ".") -> "_Image":
590
575
  """Copy the entire contents of a `modal.Mount` into an image.
591
576
  Useful when files only available locally are required during the image
modal/image.pyi CHANGED
@@ -92,19 +92,6 @@ class _Image(modal.object._Object):
92
92
  _namespace: int = 1,
93
93
  _do_assert_no_mount_layers: bool = True,
94
94
  ): ...
95
- def extend(
96
- self,
97
- *,
98
- secrets: typing.Optional[collections.abc.Sequence[modal.secret._Secret]] = None,
99
- gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
100
- build_function: typing.Optional[modal.functions._Function] = None,
101
- build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
102
- image_registry_config: typing.Optional[_ImageRegistryConfig] = None,
103
- context_mount: typing.Optional[modal.mount._Mount] = None,
104
- force_build: bool = False,
105
- _namespace: int = 1,
106
- _do_assert_no_mount_layers: bool = True,
107
- ) -> _Image: ...
108
95
  def copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image: ...
109
96
  def add_local_file(
110
97
  self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
@@ -364,19 +351,6 @@ class Image(modal.object.Object):
364
351
  _namespace: int = 1,
365
352
  _do_assert_no_mount_layers: bool = True,
366
353
  ): ...
367
- def extend(
368
- self,
369
- *,
370
- secrets: typing.Optional[collections.abc.Sequence[modal.secret.Secret]] = None,
371
- gpu_config: typing.Optional[modal_proto.api_pb2.GPUConfig] = None,
372
- build_function: typing.Optional[modal.functions.Function] = None,
373
- build_function_input: typing.Optional[modal_proto.api_pb2.FunctionInput] = None,
374
- image_registry_config: typing.Optional[_ImageRegistryConfig] = None,
375
- context_mount: typing.Optional[modal.mount.Mount] = None,
376
- force_build: bool = False,
377
- _namespace: int = 1,
378
- _do_assert_no_mount_layers: bool = True,
379
- ) -> Image: ...
380
354
  def copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image: ...
381
355
  def add_local_file(
382
356
  self, local_path: typing.Union[str, pathlib.Path], remote_path: str, *, copy: bool = False
modal/mount.py CHANGED
@@ -105,7 +105,7 @@ class _MountFile(_MountEntry):
105
105
  return str(self.local_file)
106
106
 
107
107
  def get_files_to_upload(self):
108
- local_file = self.local_file.expanduser().absolute()
108
+ local_file = self.local_file.resolve()
109
109
  if not local_file.exists():
110
110
  raise FileNotFoundError(local_file)
111
111
 
@@ -131,6 +131,8 @@ class _MountDir(_MountEntry):
131
131
  return str(self.local_dir.expanduser().absolute())
132
132
 
133
133
  def get_files_to_upload(self):
134
+ # we can't use .resolve() eagerly here since that could end up "renaming" symlinked files
135
+ # see test_mount_directory_with_symlinked_file
134
136
  local_dir = self.local_dir.expanduser().absolute()
135
137
 
136
138
  if not local_dir.exists():
@@ -145,10 +147,11 @@ class _MountDir(_MountEntry):
145
147
  gen = (dir_entry.path for dir_entry in os.scandir(local_dir) if dir_entry.is_file())
146
148
 
147
149
  for local_filename in gen:
148
- if not self.ignore(Path(local_filename)):
149
- local_relpath = Path(local_filename).expanduser().absolute().relative_to(local_dir)
150
+ local_path = Path(local_filename)
151
+ if not self.ignore(local_path):
152
+ local_relpath = local_path.expanduser().absolute().relative_to(local_dir)
150
153
  mount_path = self.remote_path / local_relpath.as_posix()
151
- yield local_filename, mount_path
154
+ yield local_path.resolve(), mount_path
152
155
 
153
156
  def watch_entry(self):
154
157
  return self.local_dir.resolve().expanduser(), None
@@ -15,7 +15,7 @@ from modal_proto import api_pb2
15
15
  from ._resolver import Resolver
16
16
  from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
17
17
  from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
18
- from ._utils.deprecation import deprecation_error, renamed_parameter
18
+ from ._utils.deprecation import renamed_parameter
19
19
  from ._utils.grpc_utils import retry_transient_errors
20
20
  from ._utils.hash_utils import get_sha256_hex
21
21
  from ._utils.name_utils import check_object_name
@@ -90,16 +90,6 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
90
90
  ```
91
91
  """
92
92
 
93
- @staticmethod
94
- def new(cloud: Optional[str] = None):
95
- """mdmd:hidden"""
96
- message = (
97
- "`NetworkFileSystem.new` is deprecated."
98
- " Please use `NetworkFileSystem.from_name` (for persisted)"
99
- " or `NetworkFileSystem.ephemeral` (for ephemeral) network filesystems instead."
100
- )
101
- deprecation_error((2024, 3, 20), message)
102
-
103
93
  @staticmethod
104
94
  @renamed_parameter((2024, 12, 18), "label", "name")
105
95
  def from_name(
@@ -13,8 +13,6 @@ def network_file_system_mount_protos(
13
13
  ) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
14
14
 
15
15
  class _NetworkFileSystem(modal.object._Object):
16
- @staticmethod
17
- def new(cloud: typing.Optional[str] = None): ...
18
16
  @staticmethod
19
17
  def from_name(
20
18
  name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
@@ -71,8 +69,6 @@ class _NetworkFileSystem(modal.object._Object):
71
69
  class NetworkFileSystem(modal.object.Object):
72
70
  def __init__(self, *args, **kwargs): ...
73
71
  @staticmethod
74
- def new(cloud: typing.Optional[str] = None): ...
75
- @staticmethod
76
72
  def from_name(
77
73
  name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
78
74
  ) -> NetworkFileSystem: ...
modal/partial_function.py CHANGED
@@ -611,12 +611,6 @@ def _exit(_warn_parentheses_missing=None) -> Callable[[ExitHandlerType], _Partia
611
611
  if isinstance(f, _PartialFunction):
612
612
  _disallow_wrapping_method(f, "exit")
613
613
 
614
- if callable_has_non_self_params(f):
615
- message = (
616
- "Support for decorating parameterized methods with `@exit` has been deprecated."
617
- " Please update your code by removing the parameters."
618
- )
619
- deprecation_error((2024, 2, 23), message)
620
614
  return _PartialFunction(f, _PartialFunctionFlags.EXIT)
621
615
 
622
616
  return wrapper
modal/queue.py CHANGED
@@ -13,7 +13,7 @@ from modal_proto import api_pb2
13
13
  from ._resolver import Resolver
14
14
  from ._serialization import deserialize, serialize
15
15
  from ._utils.async_utils import TaskContext, synchronize_api, warn_if_generator_is_not_consumed
16
- from ._utils.deprecation import deprecation_error, renamed_parameter
16
+ from ._utils.deprecation import renamed_parameter
17
17
  from ._utils.grpc_utils import retry_transient_errors
18
18
  from ._utils.name_utils import check_object_name
19
19
  from .client import _Client
@@ -94,15 +94,6 @@ class _Queue(_Object, type_prefix="qu"):
94
94
  Partition keys must be non-empty and must not exceed 64 bytes.
95
95
  """
96
96
 
97
- @staticmethod
98
- def new():
99
- """mdmd:hidden"""
100
- message = (
101
- "`Queue.new` is deprecated."
102
- " Please use `Queue.from_name` (for persisted) or `Queue.ephemeral` (for ephemeral) queues instead."
103
- )
104
- deprecation_error((2024, 3, 19), message)
105
-
106
97
  def __init__(self):
107
98
  """mdmd:hidden"""
108
99
  raise RuntimeError("Queue() is not allowed. Please use `Queue.from_name(...)` or `Queue.ephemeral()` instead.")
modal/queue.pyi CHANGED
@@ -6,8 +6,6 @@ import typing
6
6
  import typing_extensions
7
7
 
8
8
  class _Queue(modal.object._Object):
9
- @staticmethod
10
- def new(): ...
11
9
  def __init__(self): ...
12
10
  @staticmethod
13
11
  def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
@@ -89,8 +87,6 @@ class _Queue(modal.object._Object):
89
87
  class Queue(modal.object.Object):
90
88
  def __init__(self): ...
91
89
  @staticmethod
92
- def new(): ...
93
- @staticmethod
94
90
  def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
95
91
  @classmethod
96
92
  def ephemeral(
modal/volume.py CHANGED
@@ -134,15 +134,6 @@ class _Volume(_Object, type_prefix="vo"):
134
134
  self._lock = asyncio.Lock()
135
135
  return self._lock
136
136
 
137
- @staticmethod
138
- def new():
139
- """mdmd:hidden"""
140
- message = (
141
- "`Volume.new` is deprecated."
142
- " Please use `Volume.from_name` (for persisted) or `Volume.ephemeral` (for ephemeral) volumes instead."
143
- )
144
- deprecation_error((2024, 3, 20), message)
145
-
146
137
  @staticmethod
147
138
  @renamed_parameter((2024, 12, 18), "label", "name")
148
139
  def from_name(
modal/volume.pyi CHANGED
@@ -38,8 +38,6 @@ class _Volume(modal.object._Object):
38
38
 
39
39
  async def _get_lock(self): ...
40
40
  @staticmethod
41
- def new(): ...
42
- @staticmethod
43
41
  def from_name(
44
42
  name: str,
45
43
  namespace=1,
@@ -133,8 +131,6 @@ class Volume(modal.object.Object):
133
131
 
134
132
  _get_lock: ___get_lock_spec
135
133
 
136
- @staticmethod
137
- def new(): ...
138
134
  @staticmethod
139
135
  def from_name(
140
136
  name: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.40
3
+ Version: 0.68.49
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -21,7 +21,7 @@ Requires-Dist: fastapi
21
21
  Requires-Dist: grpclib (==0.4.7)
22
22
  Requires-Dist: protobuf (!=4.24.0,<6.0,>=3.19)
23
23
  Requires-Dist: rich (>=12.0.0)
24
- Requires-Dist: synchronicity (~=0.9.7)
24
+ Requires-Dist: synchronicity (~=0.9.8)
25
25
  Requires-Dist: toml
26
26
  Requires-Dist: typer (>=0.9)
27
27
  Requires-Dist: types-certifi
@@ -19,7 +19,7 @@ modal/app.py,sha256=JWefPs4yB70BKQwSZejB_4_muhxn63cC9UmnNvpQ9XY,45526
19
19
  modal/app.pyi,sha256=FYPCEJNhof4YF6HIuNP_2yG6s2PgZnKW9tO1hFE6sfA,25194
20
20
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
21
21
  modal/client.py,sha256=JAnd4-GCN093BwkvOFAK5a6iy5ycxofjpUncMxlrIMw,15253
22
- modal/client.pyi,sha256=CQbCKhsAe4AofNeWfAUM72XtuomEDr_gb_A-jCJJ-18,7280
22
+ modal/client.pyi,sha256=Rik6-3dxB5m65vniRcLjBuLaYVlw6tNh8VStRzAD7eE,7280
23
23
  modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
24
24
  modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
25
25
  modal/cls.py,sha256=3hjb0JcoPjxKZNeK22f5rR43bZRBjoRI7_EMZXY7YrE,31172
@@ -27,8 +27,8 @@ modal/cls.pyi,sha256=ooDU2IaGgD5VQ3jDX2KCrqb3a91-AdKv8yKYSkjnW8Y,8230
27
27
  modal/config.py,sha256=BzhZYUUwOmvVwf6x5kf0ywMC257s648dmuhsnB6g3gk,11041
28
28
  modal/container_process.py,sha256=WTqLn01dJPVkPpwR_0w_JH96ceN5mV4TGtiu1ZR2RRA,6108
29
29
  modal/container_process.pyi,sha256=dqtqBmyRpXXpRrDooESL6WBVU_1Rh6OG-66P2Hk9E5U,2666
30
- modal/dict.py,sha256=K1rwsI5x7FdzD1NMb7kKCGVGk0gtgerwFn8EoTDsHvU,12833
31
- modal/dict.pyi,sha256=a8l6FR_X4U-K49pZWvPLitmf626mi6FbEvmpe0JMe_k,7257
30
+ modal/dict.py,sha256=ei9jsA5iTj4UFGPJxTAed6vjd49W47ezDtj0koUmVts,12497
31
+ modal/dict.pyi,sha256=VmbzxltA2vFlIHZCxpNGtd-ieXwcUwdw3iyy3WCweqU,7115
32
32
  modal/environments.py,sha256=wbv9ttFCbzATGfwcmvYiG608PfHovx0AQmawsg-jmic,6660
33
33
  modal/environments.pyi,sha256=rF7oaaELoSNuoD6qImGnIbuGPtgWwR5SlcExyYJ61hQ,3515
34
34
  modal/exception.py,sha256=GEV6xMnVnkle0gsFZVLB4B7cUMyw8HzVDvAvPr34ZV4,5185
@@ -36,29 +36,29 @@ modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
36
36
  modal/file_io.py,sha256=pDOFNQU5m-x-k3oJauck4fOp3bZ55Vc-_LvSaN5_Bow,16465
37
37
  modal/file_io.pyi,sha256=GMhCCRyMftXYI3HqI9EdGPOx70CbCNi-VC5Sfy5TYnc,7631
38
38
  modal/file_pattern_matcher.py,sha256=V6P74Vc7LAuBFe_uepIaZmoDJiuAvqjFibe0GcMJwxo,5119
39
- modal/functions.py,sha256=-PHjDWuGBfoHYDiZc8eJtD2W9ka-c4jla2vHvA0z1fI,67821
39
+ modal/functions.py,sha256=aXXXr3rk7BCeh5OWMvxGksGm8FQoYCyrBDGV74FPoPE,67827
40
40
  modal/functions.pyi,sha256=oMmcExtQxHwPej06jQ3uBe1tUlSR3VbAx7u3Vm-Ohhg,25317
41
- modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
42
- modal/image.py,sha256=-swOPK80OXnFuhxoG3tT2zmJhas8yO-lr0jkOebBdKQ,82858
43
- modal/image.pyi,sha256=Ryd3x_Bic7j40DPe0MveGqqwvNtum2qA-6lUd5lnHvk,25503
41
+ modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
42
+ modal/image.py,sha256=sv45bYaF5Jlmk8mQE3EDADYyXLi14hOe2CUM0Zb8Xao,82243
43
+ modal/image.pyi,sha256=VY_4HnDBhW8u_Zd3n-YBZ1H9idbTorWGwzsAzY7-B70,24213
44
44
  modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
45
45
  modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
46
- modal/mount.py,sha256=tlHjosr7aY52wtOvnZxiVXmBOBSC2JeAxqwSJCutQx0,29175
46
+ modal/mount.py,sha256=Miu9V5LB80uoMasSXxxf0aYTC7H1G08PjnjmmjQdyRc,29346
47
47
  modal/mount.pyi,sha256=7dKl_JeVka3g4oKw7D-FFRU-Zpadt9LJEcfNUnhj540,10491
48
- modal/network_file_system.py,sha256=pDN_QSo_kG2IJrPDjJekh0t9bP8W4m7UApN6J9UC8BI,14810
49
- modal/network_file_system.pyi,sha256=pIh0Ds0blFN26C5ZVz706S8VaHYaYCb8ht7C1PFU_oU,7893
48
+ modal/network_file_system.py,sha256=INj1TfN_Fsmabmlte7anvey1epodjbMmjBW_TIJSST4,14406
49
+ modal/network_file_system.pyi,sha256=61M-sdWrtaRjmuNVsvusI6kf1Qw-jUOVXvEAeOkM8Aw,7751
50
50
  modal/object.py,sha256=HZs3N59C6JxlMuPQWJYvrWV1FEEkH9txUovVDorVUbs,9763
51
51
  modal/object.pyi,sha256=MO78H9yFSE5i1gExPEwyyQzLdlshkcGHN1aQ0ylyvq0,8802
52
52
  modal/output.py,sha256=N0xf4qeudEaYrslzdAl35VKV8rapstgIM2e9wO8_iy0,1967
53
53
  modal/parallel_map.py,sha256=4aoMXIrlG3wl5Ifk2YDNOQkXsGRsm6Xbfm6WtJ2t3WY,16002
54
54
  modal/parallel_map.pyi,sha256=pOhT0P3DDYlwLx0fR3PTsecA7DI8uOdXC1N8i-ZkyOY,2328
55
- modal/partial_function.py,sha256=61ctSak-xQrMfxSjf-rGlhkbnTWyylSvUpD1QSWV3uk,28166
55
+ modal/partial_function.py,sha256=pDDNR6KTaIIPpuKQaoO1vgP83_LTwxMhtOn6sVRrmC8,27862
56
56
  modal/partial_function.pyi,sha256=pO6kf8i5HVsZ7CF0z_KkzLk4Aeq7NJhFJ_VNIycRXaU,9260
57
57
  modal/proxy.py,sha256=ZrOsuQP7dSZFq1OrIxalNnt0Zvsnp1h86Th679sSL40,1417
58
58
  modal/proxy.pyi,sha256=UvygdOYneLTuoDY6hVaMNCyZ947Tmx93IdLjErUqkvM,368
59
59
  modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- modal/queue.py,sha256=Lz3QZaFfRGjG4PlnpVgAvHA4fiMcykANuP-nh_Zh1mQ,18869
61
- modal/queue.pyi,sha256=yuPLcAic8RD97pt0PSHkbJSJqJUU0c0qqQi5zsVTgtE,9892
60
+ modal/queue.py,sha256=zMUQtdAyqZzBg-2iAo3c3G54HLP7TEWfVhiQXLjewb4,18556
61
+ modal/queue.pyi,sha256=gGV97pWelSSYqMV9Bl4ys3mSP7q82fS71oqSWeAwyDE,9818
62
62
  modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
63
63
  modal/runner.py,sha256=qfkB0OM97kb_-oP-D5KPj_jUwfd8ePUA3R_zLkjSTBQ,24586
64
64
  modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
@@ -74,10 +74,10 @@ modal/serving.pyi,sha256=ncV-9jY_vZYFnGs5ZnMb3ffrX8LmcLdIMHBC56xRbtE,1711
74
74
  modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
75
75
  modal/token_flow.py,sha256=LcgSce_MSQ2p7j55DPwpVRpiAtCDe8GRSEwzO7muNR8,6774
76
76
  modal/token_flow.pyi,sha256=gOYtYujrWt_JFZeiI8EmfahXPx5GCR5Na-VaPQcWgEY,1937
77
- modal/volume.py,sha256=iZlczGiu7oAejn_5qLC9iKCRzMvlGgZYgnB2ddXyxNY,29527
78
- modal/volume.pyi,sha256=XcfxqY8cG3dncBzvwE0PgD2HJAQEspMg2vy9UamxydU,11174
77
+ modal/volume.py,sha256=T-pLxCYqmqRO6OolpAXlPxomMu0RWjti2e4kUpaj2cQ,29229
78
+ modal/volume.pyi,sha256=eekb2dnAAwFK_NO9ciAOOTthl8NP1iAmMFrCGgjDA2k,11100
79
79
  modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
80
- modal/_runtime/asgi.py,sha256=H68KAN8bz8Zp7EcRl2c_ite1Y3kP1MHvjQAf-uUpCx8,21691
80
+ modal/_runtime/asgi.py,sha256=Mjs859pSgOmtZL-YmEsSKN557v1A2Ax_5-ERgPfj55E,21920
81
81
  modal/_runtime/container_io_manager.py,sha256=ctgyNFiHjq1brCrabXmlurkAXjnrCeWPRvTVa735vRw,44215
82
82
  modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
83
83
  modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
@@ -88,7 +88,7 @@ modal/_utils/async_utils.py,sha256=9ubwMkwiDB4gzOYG2jL9j7Fs-5dxHjcifZe3r7JRg-k,2
88
88
  modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14509
89
89
  modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
90
90
  modal/_utils/deprecation.py,sha256=dycySRBxyZf3ITzEqPNM6MxXTk9-0VVLA8oCPQ5j_Os,3426
91
- modal/_utils/function_utils.py,sha256=LgcveUUb4XU_dWxtqgK_3ujZBvS3cGVzcDOkljyFZ2w,25066
91
+ modal/_utils/function_utils.py,sha256=7vIYYezYcKA-19wBJFaoquVMLfD4LKCTlZlo92an-nY,25141
92
92
  modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
93
93
  modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
94
94
  modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
@@ -147,13 +147,13 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
147
147
  modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
148
148
  modal_global_objects/mounts/python_standalone.py,sha256=SL_riIxpd8mP4i4CLDCWiFFNj0Ltknm9c_UIGfX5d60,1836
149
149
  modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
150
- modal_proto/api.proto,sha256=Fdc1QR9kxirCiZW7o8fslrYd8vk-gR2dWOWj2uCbkHk,79553
151
- modal_proto/api_grpc.py,sha256=DveC4ejFYEhCLiWbQShnmY31_FWGYU675Bmr7nHhsgs,101342
152
- modal_proto/api_pb2.py,sha256=EwN_R_HDiLP0s61rpSRQ5r6Kwof_XiPtH-lqeX6gC9I,292335
153
- modal_proto/api_pb2.pyi,sha256=Ggl_mfNlUQWPQaxDUMqeC4Rglf1xXSf4Kx8vIFPiXp8,390741
154
- modal_proto/api_pb2_grpc.py,sha256=2PEP6JPOoTw2rDC5qYjLNuumP68ZwAouRhCoayisAhY,219162
155
- modal_proto/api_pb2_grpc.pyi,sha256=uWtCxVEd0cFpOZ1oOGfZNO7Dv45OP4kp09jMnNyx9D4,51098
156
- modal_proto/modal_api_grpc.py,sha256=-8mLby_om5MYo6yu1zA_hqhz0yLsQW7k2YWBVZW1iVs,13546
150
+ modal_proto/api.proto,sha256=LdD5XC5IhLP4rL_7nLeO0FL3emTs7jpEZGON2JIJDs4,79741
151
+ modal_proto/api_grpc.py,sha256=AiUCWTHQQFC9RFB_XuavB_OnVMr7GJMRLEwcf4FSTio,102088
152
+ modal_proto/api_pb2.py,sha256=rvBtjxpHVIXZmecc0OZS5PgZm4n1DcI4q1EHE675iW0,293583
153
+ modal_proto/api_pb2.pyi,sha256=LNvol6MFzFuG1J_wKA4AKPiSKSLkYfNW1kF7c5XXABk,391781
154
+ modal_proto/api_pb2_grpc.py,sha256=dFxVTgosyp_o8NCI1JIySlR0qTzG4ILm9mq8MNo4jYc,220795
155
+ modal_proto/api_pb2_grpc.pyi,sha256=yJgwEl-1YU42m7MU_Sm5SK3rB9xdkisPk3nZB-mlqkg,51463
156
+ modal_proto/modal_api_grpc.py,sha256=MyNzvY_WqB7QTMOQjoH6lsqCY5-6_s1HP-knsOSjANs,13640
157
157
  modal_proto/modal_options_grpc.py,sha256=qJ1cuwA54oRqrdTyPTbvfhFZYd9HhJKK5UCwt523r3Y,120
158
158
  modal_proto/options.proto,sha256=a-siq4swVbZPfaFRXAipRZzGP2bq8OsdUvjlyzAeodQ,488
159
159
  modal_proto/options_grpc.py,sha256=M18X3d-8F_cNYSVM3I25dUTO5rZ0rd-vCCfynfh13Nc,125
@@ -164,10 +164,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
164
164
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
165
165
  modal_version/__init__.py,sha256=RT6zPoOdFO99u5Wcxxaoir4ZCuPTbQ22cvzFAXl3vUY,470
166
166
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
167
- modal_version/_version_generated.py,sha256=Z2p-_Omfs_bNgzgr0oxB9ru4FEV0pjR6u5odGdI2j9Y,149
168
- modal-0.68.40.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
169
- modal-0.68.40.dist-info/METADATA,sha256=M7VytBJYlDDf9YLF4W9p8H9w5ErD0fMi1AW2E2wtFTU,2329
170
- modal-0.68.40.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
171
- modal-0.68.40.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
172
- modal-0.68.40.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
173
- modal-0.68.40.dist-info/RECORD,,
167
+ modal_version/_version_generated.py,sha256=TJegGbOMTEgViX-4rKkRQVJOLfd0ljcg-Q5dRYNs7kc,149
168
+ modal-0.68.49.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
169
+ modal-0.68.49.dist-info/METADATA,sha256=R8kXW2CA57SO2XcJQ_rDphLvAP2kKbI0qGSbzh06Udw,2329
170
+ modal-0.68.49.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
171
+ modal-0.68.49.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
172
+ modal-0.68.49.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
173
+ modal-0.68.49.dist-info/RECORD,,
modal_proto/api.proto CHANGED
@@ -321,6 +321,14 @@ message AppGetByDeploymentNameResponse {
321
321
  string app_id = 1;
322
322
  }
323
323
 
324
+ message AppGetLayoutRequest {
325
+ string app_id = 1;
326
+ }
327
+
328
+ message AppGetLayoutResponse {
329
+ AppLayout app_layout = 1;
330
+ }
331
+
324
332
  message AppGetLogsRequest {
325
333
  string app_id = 1;
326
334
  float timeout = 2;
@@ -2700,6 +2708,7 @@ service ModalClient {
2700
2708
  rpc AppDeploy(AppDeployRequest) returns (AppDeployResponse);
2701
2709
  rpc AppDeploymentHistory(AppDeploymentHistoryRequest) returns (AppDeploymentHistoryResponse);
2702
2710
  rpc AppGetByDeploymentName(AppGetByDeploymentNameRequest) returns (AppGetByDeploymentNameResponse);
2711
+ rpc AppGetLayout(AppGetLayoutRequest) returns (AppGetLayoutResponse);
2703
2712
  rpc AppGetLogs(AppGetLogsRequest) returns (stream TaskLogsBatch);
2704
2713
  rpc AppGetObjects(AppGetObjectsRequest) returns (AppGetObjectsResponse);
2705
2714
  rpc AppGetOrCreate(AppGetOrCreateRequest) returns (AppGetOrCreateResponse);