modal 0.70.1__py3-none-any.whl → 0.70.3__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.
@@ -37,6 +37,12 @@ def extract_copy_command_patterns(dockerfile_lines: Sequence[str]) -> list[str]:
37
37
  args = match.group(1)
38
38
  parts = shlex.split(args)
39
39
 
40
+ # COPY --from=... commands reference external sources and do not need a context mount.
41
+ # https://docs.docker.com/reference/dockerfile/#copy---from
42
+ if parts[0].startswith("--from="):
43
+ current_command = ""
44
+ continue
45
+
40
46
  if len(parts) >= 2:
41
47
  # Last part is destination, everything else is a mount source
42
48
  sources = parts[:-1]
modal/app.py CHANGED
@@ -463,8 +463,8 @@ class _App:
463
463
  if self._running_app:
464
464
  # If this is inside a container, then objects can be defined after app initialization.
465
465
  # So we may have to initialize objects once they get bound to the app.
466
- if function.tag in self._running_app.tag_to_object_id:
467
- object_id: str = self._running_app.tag_to_object_id[function.tag]
466
+ if function.tag in self._running_app.function_ids:
467
+ object_id: str = self._running_app.function_ids[function.tag]
468
468
  metadata: Message = self._running_app.object_handle_metadata[object_id]
469
469
  function._hydrate(object_id, self._client, metadata)
470
470
 
@@ -476,8 +476,8 @@ class _App:
476
476
  if self._running_app:
477
477
  # If this is inside a container, then objects can be defined after app initialization.
478
478
  # So we may have to initialize objects once they get bound to the app.
479
- if tag in self._running_app.tag_to_object_id:
480
- object_id: str = self._running_app.tag_to_object_id[tag]
479
+ if tag in self._running_app.class_ids:
480
+ object_id: str = self._running_app.class_ids[tag]
481
481
  metadata: Message = self._running_app.object_handle_metadata[object_id]
482
482
  cls._hydrate(object_id, self._client, metadata)
483
483
 
@@ -490,19 +490,19 @@ class _App:
490
490
 
491
491
  _App._container_app = running_app
492
492
 
493
- # Hydrate objects on app -- hydrating functions first so that when a class is being hydrated its
494
- # corresponding class service function is already hydrated.
495
- def hydrate_objects(objects_dict):
496
- for tag, object_id in running_app.tag_to_object_id.items():
497
- if tag in objects_dict:
498
- obj = objects_dict[tag]
499
- handle_metadata = running_app.object_handle_metadata[object_id]
500
- obj._hydrate(object_id, client, handle_metadata)
501
-
502
493
  # Hydrate function objects
503
- hydrate_objects(self._functions)
494
+ for tag, object_id in running_app.function_ids.items():
495
+ if tag in self._functions:
496
+ obj = self._functions[tag]
497
+ handle_metadata = running_app.object_handle_metadata[object_id]
498
+ obj._hydrate(object_id, client, handle_metadata)
499
+
504
500
  # Hydrate class objects
505
- hydrate_objects(self._classes)
501
+ for tag, object_id in running_app.class_ids.items():
502
+ if tag in self._classes:
503
+ obj = self._classes[tag]
504
+ handle_metadata = running_app.object_handle_metadata[object_id]
505
+ obj._hydrate(object_id, client, handle_metadata)
506
506
 
507
507
  @property
508
508
  def registered_functions(self) -> dict[str, _Function]:
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.70.1"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.70.3"
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.70.1"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.70.3"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/runner.py CHANGED
@@ -6,7 +6,7 @@ import time
6
6
  import typing
7
7
  from collections.abc import AsyncGenerator
8
8
  from multiprocessing.synchronize import Event
9
- from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar
9
+ from typing import TYPE_CHECKING, Any, Optional, TypeVar
10
10
 
11
11
  from grpclib import GRPCError, Status
12
12
  from synchronicity.async_wrap import asynccontextmanager
@@ -124,10 +124,12 @@ async def _init_local_app_from_name(
124
124
  async def _create_all_objects(
125
125
  client: _Client,
126
126
  running_app: RunningApp,
127
- indexed_objects: dict[str, _Object],
127
+ functions: dict[str, _Function],
128
+ classes: dict[str, _Cls],
128
129
  environment_name: str,
129
130
  ) -> None:
130
131
  """Create objects that have been defined but not created on the server."""
132
+ indexed_objects: dict[str, _Object] = {**functions, **classes}
131
133
  resolver = Resolver(
132
134
  client,
133
135
  environment_name=environment_name,
@@ -135,8 +137,9 @@ async def _create_all_objects(
135
137
  )
136
138
  with resolver.display():
137
139
  # Get current objects, and reset all objects
138
- tag_to_object_id = running_app.tag_to_object_id
139
- running_app.tag_to_object_id = {}
140
+ tag_to_object_id = {**running_app.function_ids, **running_app.class_ids}
141
+ running_app.function_ids = {}
142
+ running_app.class_ids = {}
140
143
 
141
144
  # Assign all objects
142
145
  for tag, obj in indexed_objects.items():
@@ -163,7 +166,12 @@ async def _create_all_objects(
163
166
  async def _load(tag, obj):
164
167
  existing_object_id = tag_to_object_id.get(tag)
165
168
  await resolver.load(obj, existing_object_id)
166
- running_app.tag_to_object_id[tag] = obj.object_id
169
+ if _Function._is_id_type(obj.object_id):
170
+ running_app.function_ids[tag] = obj.object_id
171
+ elif _Cls._is_id_type(obj.object_id):
172
+ running_app.class_ids[tag] = obj.object_id
173
+ else:
174
+ raise RuntimeError(f"Unexpected object {obj.object_id}")
167
175
 
168
176
  await TaskContext.gather(*(_load(tag, obj) for tag, obj in indexed_objects.items()))
169
177
 
@@ -172,30 +180,22 @@ async def _publish_app(
172
180
  client: _Client,
173
181
  running_app: RunningApp,
174
182
  app_state: int, # api_pb2.AppState.value
175
- indexed_objects: dict[str, _Object],
183
+ functions: dict[str, _Function],
184
+ classes: dict[str, _Cls],
176
185
  name: str = "", # Only relevant for deployments
177
186
  tag: str = "", # Only relevant for deployments
178
187
  ) -> tuple[str, list[api_pb2.Warning]]:
179
188
  """Wrapper for AppPublish RPC."""
180
189
 
181
- # Could simplify this function some changing the internal representation to use
182
- # function_ids / class_ids rather than the current tag_to_object_id (i.e. "indexed_objects")
183
- def filter_values(full_dict: dict[str, V], condition: Callable[[V], bool]) -> dict[str, V]:
184
- return {k: v for k, v in full_dict.items() if condition(v)}
185
-
186
- function_ids = filter_values(running_app.tag_to_object_id, _Function._is_id_type)
187
- class_ids = filter_values(running_app.tag_to_object_id, _Cls._is_id_type)
188
-
189
- function_objs = filter_values(indexed_objects, lambda v: v.object_id in function_ids.values())
190
- definition_ids = {obj.object_id: obj._get_metadata().definition_id for obj in function_objs.values()} # type: ignore
190
+ definition_ids = {obj.object_id: obj._get_metadata().definition_id for obj in functions.values()} # type: ignore
191
191
 
192
192
  request = api_pb2.AppPublishRequest(
193
193
  app_id=running_app.app_id,
194
194
  name=name,
195
195
  deployment_tag=tag,
196
196
  app_state=app_state, # type: ignore : should be a api_pb2.AppState.value
197
- function_ids=function_ids,
198
- class_ids=class_ids,
197
+ function_ids=running_app.function_ids,
198
+ class_ids=running_app.class_ids,
199
199
  definition_ids=definition_ids,
200
200
  )
201
201
  try:
@@ -329,13 +329,11 @@ async def _run_app(
329
329
  )
330
330
 
331
331
  try:
332
- indexed_objects = dict(**app._functions, **app._classes) # TODO(erikbern): remove
333
-
334
332
  # Create all members
335
- await _create_all_objects(client, running_app, indexed_objects, environment_name)
333
+ await _create_all_objects(client, running_app, app._functions, app._classes, environment_name)
336
334
 
337
335
  # Publish the app
338
- await _publish_app(client, running_app, app_state, indexed_objects)
336
+ await _publish_app(client, running_app, app_state, app._functions, app._classes)
339
337
  except asyncio.CancelledError as e:
340
338
  # this typically happens on sigint/ctrl-C during setup (the KeyboardInterrupt happens in the main thread)
341
339
  if output_mgr := _get_output_manager():
@@ -428,18 +426,17 @@ async def _serve_update(
428
426
  try:
429
427
  running_app: RunningApp = await _init_local_app_existing(client, existing_app_id, environment_name)
430
428
 
431
- indexed_objects = dict(**app._functions, **app._classes) # TODO(erikbern): remove
432
-
433
429
  # Create objects
434
430
  await _create_all_objects(
435
431
  client,
436
432
  running_app,
437
- indexed_objects,
433
+ app._functions,
434
+ app._classes,
438
435
  environment_name,
439
436
  )
440
437
 
441
438
  # Publish the updated app
442
- await _publish_app(client, running_app, api_pb2.APP_STATE_UNSPECIFIED, indexed_objects)
439
+ await _publish_app(client, running_app, api_pb2.APP_STATE_UNSPECIFIED, app._functions, app._classes)
443
440
 
444
441
  # Communicate to the parent process
445
442
  is_ready.set()
@@ -527,19 +524,18 @@ async def _deploy_app(
527
524
 
528
525
  tc.infinite_loop(heartbeat, sleep=HEARTBEAT_INTERVAL)
529
526
 
530
- indexed_objects = dict(**app._functions, **app._classes) # TODO(erikbern): remove
531
-
532
527
  try:
533
528
  # Create all members
534
529
  await _create_all_objects(
535
530
  client,
536
531
  running_app,
537
- indexed_objects,
532
+ app._functions,
533
+ app._classes,
538
534
  environment_name=environment_name,
539
535
  )
540
536
 
541
537
  app_url, warnings = await _publish_app(
542
- client, running_app, api_pb2.APP_STATE_DEPLOYED, indexed_objects, name, tag
538
+ client, running_app, api_pb2.APP_STATE_DEPLOYED, app._functions, app._classes, name, tag
543
539
  )
544
540
  except Exception as e:
545
541
  # Note that AppClientDisconnect only stops the app if it's still initializing, and is a no-op otherwise.
modal/runner.pyi CHANGED
@@ -1,5 +1,6 @@
1
1
  import modal.client
2
- import modal.object
2
+ import modal.cls
3
+ import modal.functions
3
4
  import modal.running_app
4
5
  import modal_proto.api_pb2
5
6
  import multiprocessing.synchronize
@@ -28,14 +29,16 @@ async def _init_local_app_from_name(
28
29
  async def _create_all_objects(
29
30
  client: modal.client._Client,
30
31
  running_app: modal.running_app.RunningApp,
31
- indexed_objects: dict[str, modal.object._Object],
32
+ functions: dict[str, modal.functions._Function],
33
+ classes: dict[str, modal.cls._Cls],
32
34
  environment_name: str,
33
35
  ) -> None: ...
34
36
  async def _publish_app(
35
37
  client: modal.client._Client,
36
38
  running_app: modal.running_app.RunningApp,
37
39
  app_state: int,
38
- indexed_objects: dict[str, modal.object._Object],
40
+ functions: dict[str, modal.functions._Function],
41
+ classes: dict[str, modal.cls._Cls],
39
42
  name: str = "",
40
43
  tag: str = "",
41
44
  ) -> tuple[str, list[modal_proto.api_pb2.Warning]]: ...
modal/running_app.py CHANGED
@@ -17,7 +17,8 @@ class RunningApp:
17
17
  environment_name: Optional[str] = None
18
18
  app_page_url: Optional[str] = None
19
19
  app_logs_url: Optional[str] = None
20
- tag_to_object_id: dict[str, str] = field(default_factory=dict)
20
+ function_ids: dict[str, str] = field(default_factory=dict)
21
+ class_ids: dict[str, str] = field(default_factory=dict)
21
22
  object_handle_metadata: dict[str, Optional[Message]] = field(default_factory=dict)
22
23
  interactive: bool = False
23
24
 
@@ -29,7 +30,6 @@ def running_app_from_layout(
29
30
  environment_name: Optional[str] = None,
30
31
  app_page_url: Optional[str] = None,
31
32
  ) -> RunningApp:
32
- tag_to_object_id = dict(**app_layout.function_ids, **app_layout.class_ids)
33
33
  object_handle_metadata = {}
34
34
  for obj in app_layout.objects:
35
35
  handle_metadata: Optional[Message] = get_proto_oneof(obj, "handle_metadata_oneof")
@@ -39,7 +39,8 @@ def running_app_from_layout(
39
39
  app_id,
40
40
  client,
41
41
  environment_name=environment_name,
42
- tag_to_object_id=tag_to_object_id,
42
+ function_ids=dict(app_layout.function_ids),
43
+ class_ids=dict(app_layout.class_ids),
43
44
  object_handle_metadata=object_handle_metadata,
44
45
  app_page_url=app_page_url,
45
46
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.70.1
3
+ Version: 0.70.3
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -15,11 +15,11 @@ modal/_traceback.py,sha256=IZQzB3fVlUfMHOSyKUgw0H6qv4yHnpyq-XVCNZKfUdA,5023
15
15
  modal/_tunnel.py,sha256=o-jJhS4vQ6-XswDhHcJWGMZZmD03SC0e9i8fEu1JTjo,6310
16
16
  modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
17
17
  modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
18
- modal/app.py,sha256=JWefPs4yB70BKQwSZejB_4_muhxn63cC9UmnNvpQ9XY,45526
18
+ modal/app.py,sha256=jzqFUHK-tNJ2Ah4zv4eLfZimHwLC2M3J2c3RbveMBAw,45474
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=_USFlgo8YNSNDQ-4lgJrafvJME1RR1aLK-iNsW_gvGk,7278
22
+ modal/client.pyi,sha256=gYox-KDSDT-Sk7i-alSUPPAYGqQ-X3C755COzC8SnTo,7278
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
@@ -60,9 +60,9 @@ modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  modal/queue.py,sha256=zMUQtdAyqZzBg-2iAo3c3G54HLP7TEWfVhiQXLjewb4,18556
61
61
  modal/queue.pyi,sha256=gGV97pWelSSYqMV9Bl4ys3mSP7q82fS71oqSWeAwyDE,9818
62
62
  modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
63
- modal/runner.py,sha256=1nPBsIfef2sOr2ebQ348EmDemvYFDhp1-_Gr3BKsjdM,24542
64
- modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
65
- modal/running_app.py,sha256=FSr0XoL4mPLPCBMj2TozWuEvcvApdY_t68nUowwf8x4,1372
63
+ modal/runner.py,sha256=miYYjZcXU8q4zG20kPoPgtSmcGxpi07FCdboaVhyWVs,24196
64
+ modal/runner.pyi,sha256=YmP4EOCNjjkwSIPi2Gl6hF_ji_ytkxz9dw3iB9KXaOI,5275
65
+ modal/running_app.py,sha256=_fUf-qJXdtXgblETH7JpgvOtcnzrtnGcMKc2v3pBqJg,1404
66
66
  modal/sandbox.py,sha256=c-Qli3QJPN7bBQzsTk4iS51zurNlq--InZ2eRR-B6No,28106
67
67
  modal/sandbox.pyi,sha256=k8_vHjN3oigxSCF13Cm2HfcSHuliGuSb8ryd3CGqwoA,19815
68
68
  modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
@@ -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/docker_utils.py,sha256=rft2WVGhEgYik2zFRZbX63X57b2jnLyy88byykDH2Xo,1963
91
+ modal/_utils/docker_utils.py,sha256=FLz1q0YicL6i_Iq-4inkgDVFfEINVG6YPT2s_P6ly0o,2264
92
92
  modal/_utils/function_utils.py,sha256=4LYFbNY5aHc96QitwP4Ty-dBl45SD1HjfZrvBFUF-ko,25343
93
93
  modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
94
94
  modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
@@ -165,10 +165,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
165
165
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
166
  modal_version/__init__.py,sha256=N9Kh4DrM2649_INTJG4Lp3NKdux7cxGuiDtXpq_hkFY,470
167
167
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
168
- modal_version/_version_generated.py,sha256=ryiYirApbEdrIBIic3kQU9ffkciEs4sM79E_mwfmbaA,148
169
- modal-0.70.1.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
170
- modal-0.70.1.dist-info/METADATA,sha256=oWSDkTZ74lyZLLIR5lolTo9A0A_TvjPukqd5aHLQZFk,2328
171
- modal-0.70.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
172
- modal-0.70.1.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
173
- modal-0.70.1.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
174
- modal-0.70.1.dist-info/RECORD,,
168
+ modal_version/_version_generated.py,sha256=C3_yPhD3BROKMT-d8--G4NKBC4UkPBzHESdS0qSFJB4,148
169
+ modal-0.70.3.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
170
+ modal-0.70.3.dist-info/METADATA,sha256=Y65xpw-VrQOe_s50VTrCEIkl6FQKMSN18NSOmMBhZl0,2328
171
+ modal-0.70.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
172
+ modal-0.70.3.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
173
+ modal-0.70.3.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
174
+ modal-0.70.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2024
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 1 # git: 060c28e
4
+ build_number = 3 # git: c164251
File without changes