modal 0.68.42__py3-none-any.whl → 0.68.50__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 [
@@ -570,12 +578,15 @@ class FunctionCreationStatus:
570
578
 
571
579
  elif self.response.function.web_url:
572
580
  url_info = self.response.function.web_url_info
581
+ requires_proxy_auth = self.response.function.webhook_config.requires_proxy_auth
582
+ proxy_auth_suffix = " 🔑" if requires_proxy_auth else ""
573
583
  # Ensure terms used here match terms used in modal.com/docs/guide/webhook-urls doc.
574
584
  suffix = _get_suffix_from_web_url_info(url_info)
575
585
  # TODO: this is only printed when we're showing progress. Maybe move this somewhere else.
576
586
  web_url = self.response.handle_metadata.web_url
577
587
  self.status_row.finish(
578
- f"Created web function {self.tag} => [magenta underline]{web_url}[/magenta underline]{suffix}"
588
+ f"Created web function {self.tag} => [magenta underline]{web_url}[/magenta underline]"
589
+ f"{proxy_auth_suffix}{suffix}"
579
590
  )
580
591
 
581
592
  # Print custom domain in terminal
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.42"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.50"
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.42"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.50"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
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/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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.68.42
3
+ Version: 0.68.50
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=4DWFNIb933Dy-UuKJ37mbZWeoMUoSewFTsbVnd5HJKs,7280
22
+ modal/client.pyi,sha256=F97Ao08XxQn1RMnJx2ZYX_R2bP9k-d_rekRUjHvVzEE,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
@@ -36,14 +36,14 @@ 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=snttn47K81lKhmrCLWNVZelZTDhNsbxtw4l1DlLDR74,25317
41
- modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
41
+ modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
42
42
  modal/image.py,sha256=sv45bYaF5Jlmk8mQE3EDADYyXLi14hOe2CUM0Zb8Xao,82243
43
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
48
  modal/network_file_system.py,sha256=INj1TfN_Fsmabmlte7anvey1epodjbMmjBW_TIJSST4,14406
49
49
  modal/network_file_system.pyi,sha256=61M-sdWrtaRjmuNVsvusI6kf1Qw-jUOVXvEAeOkM8Aw,7751
@@ -77,7 +77,7 @@ modal/token_flow.pyi,sha256=gOYtYujrWt_JFZeiI8EmfahXPx5GCR5Na-VaPQcWgEY,1937
77
77
  modal/volume.py,sha256=T-pLxCYqmqRO6OolpAXlPxomMu0RWjti2e4kUpaj2cQ,29229
78
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=4LYFbNY5aHc96QitwP4Ty-dBl45SD1HjfZrvBFUF-ko,25343
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=a0PljflDY_DkAkO-G7eB-EomkggfZAf1AqEjBk9y6hY,149
168
- modal-0.68.42.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
169
- modal-0.68.42.dist-info/METADATA,sha256=3I3X8d0raDyeijHD8jRNhpSDTbLe-VXo-Cp3-sMnSM0,2329
170
- modal-0.68.42.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
171
- modal-0.68.42.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
172
- modal-0.68.42.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
173
- modal-0.68.42.dist-info/RECORD,,
167
+ modal_version/_version_generated.py,sha256=ut0wAyoP-7s5gVURkLb4fKBrb9HgRbFc7l23f9RyI24,149
168
+ modal-0.68.50.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
169
+ modal-0.68.50.dist-info/METADATA,sha256=bfjb4nM-LUkYVatsQ2-SdK_EOyiNICQFyKfYV5mRQjw,2329
170
+ modal-0.68.50.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
171
+ modal-0.68.50.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
172
+ modal-0.68.50.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
173
+ modal-0.68.50.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);
modal_proto/api_grpc.py CHANGED
@@ -37,6 +37,10 @@ class ModalClientBase(abc.ABC):
37
37
  async def AppGetByDeploymentName(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.AppGetByDeploymentNameRequest, modal_proto.api_pb2.AppGetByDeploymentNameResponse]') -> None:
38
38
  pass
39
39
 
40
+ @abc.abstractmethod
41
+ async def AppGetLayout(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.AppGetLayoutRequest, modal_proto.api_pb2.AppGetLayoutResponse]') -> None:
42
+ pass
43
+
40
44
  @abc.abstractmethod
41
45
  async def AppGetLogs(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.AppGetLogsRequest, modal_proto.api_pb2.TaskLogsBatch]') -> None:
42
46
  pass
@@ -577,6 +581,12 @@ class ModalClientBase(abc.ABC):
577
581
  modal_proto.api_pb2.AppGetByDeploymentNameRequest,
578
582
  modal_proto.api_pb2.AppGetByDeploymentNameResponse,
579
583
  ),
584
+ '/modal.client.ModalClient/AppGetLayout': grpclib.const.Handler(
585
+ self.AppGetLayout,
586
+ grpclib.const.Cardinality.UNARY_UNARY,
587
+ modal_proto.api_pb2.AppGetLayoutRequest,
588
+ modal_proto.api_pb2.AppGetLayoutResponse,
589
+ ),
580
590
  '/modal.client.ModalClient/AppGetLogs': grpclib.const.Handler(
581
591
  self.AppGetLogs,
582
592
  grpclib.const.Cardinality.UNARY_STREAM,
@@ -1375,6 +1385,12 @@ class ModalClientStub:
1375
1385
  modal_proto.api_pb2.AppGetByDeploymentNameRequest,
1376
1386
  modal_proto.api_pb2.AppGetByDeploymentNameResponse,
1377
1387
  )
1388
+ self.AppGetLayout = grpclib.client.UnaryUnaryMethod(
1389
+ channel,
1390
+ '/modal.client.ModalClient/AppGetLayout',
1391
+ modal_proto.api_pb2.AppGetLayoutRequest,
1392
+ modal_proto.api_pb2.AppGetLayoutResponse,
1393
+ )
1378
1394
  self.AppGetLogs = grpclib.client.UnaryStreamMethod(
1379
1395
  channel,
1380
1396
  '/modal.client.ModalClient/AppGetLogs',