modal 0.66.50__py3-none-any.whl → 0.66.52__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/client.pyi +2 -2
- modal/functions.pyi +6 -6
- modal/image.py +81 -67
- modal/image.pyi +12 -7
- modal/object.py +2 -2
- modal/object.pyi +4 -2
- modal/volume.py +13 -6
- modal/volume.pyi +10 -4
- {modal-0.66.50.dist-info → modal-0.66.52.dist-info}/METADATA +1 -1
- {modal-0.66.50.dist-info → modal-0.66.52.dist-info}/RECORD +15 -15
- modal_version/_version_generated.py +1 -1
- {modal-0.66.50.dist-info → modal-0.66.52.dist-info}/LICENSE +0 -0
- {modal-0.66.50.dist-info → modal-0.66.52.dist-info}/WHEEL +0 -0
- {modal-0.66.50.dist-info → modal-0.66.52.dist-info}/entry_points.txt +0 -0
- {modal-0.66.50.dist-info → modal-0.66.52.dist-info}/top_level.txt +0 -0
modal/client.pyi
CHANGED
@@ -31,7 +31,7 @@ class _Client:
|
|
31
31
|
server_url: str,
|
32
32
|
client_type: int,
|
33
33
|
credentials: typing.Optional[typing.Tuple[str, str]],
|
34
|
-
version: str = "0.66.
|
34
|
+
version: str = "0.66.52",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -90,7 +90,7 @@ class Client:
|
|
90
90
|
server_url: str,
|
91
91
|
client_type: int,
|
92
92
|
credentials: typing.Optional[typing.Tuple[str, str]],
|
93
|
-
version: str = "0.66.
|
93
|
+
version: str = "0.66.52",
|
94
94
|
): ...
|
95
95
|
def is_closed(self) -> bool: ...
|
96
96
|
@property
|
modal/functions.pyi
CHANGED
@@ -446,11 +446,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
446
446
|
|
447
447
|
_call_generator_nowait: ___call_generator_nowait_spec
|
448
448
|
|
449
|
-
class __remote_spec(typing_extensions.Protocol[
|
449
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
450
450
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
451
451
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
452
452
|
|
453
|
-
remote: __remote_spec[
|
453
|
+
remote: __remote_spec[ReturnType, P]
|
454
454
|
|
455
455
|
class __remote_gen_spec(typing_extensions.Protocol):
|
456
456
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -462,17 +462,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
462
462
|
def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
|
463
463
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
464
464
|
|
465
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
465
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
466
466
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
467
467
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
468
468
|
|
469
|
-
_experimental_spawn: ___experimental_spawn_spec[
|
469
|
+
_experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
|
470
470
|
|
471
|
-
class __spawn_spec(typing_extensions.Protocol[
|
471
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
472
472
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
473
473
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
474
474
|
|
475
|
-
spawn: __spawn_spec[
|
475
|
+
spawn: __spawn_spec[ReturnType, P]
|
476
476
|
|
477
477
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
478
478
|
|
modal/image.py
CHANGED
@@ -19,7 +19,6 @@ from typing import (
|
|
19
19
|
Optional,
|
20
20
|
Sequence,
|
21
21
|
Set,
|
22
|
-
Tuple,
|
23
22
|
Union,
|
24
23
|
cast,
|
25
24
|
get_args,
|
@@ -36,6 +35,7 @@ from ._utils.async_utils import synchronize_api
|
|
36
35
|
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
37
36
|
from ._utils.function_utils import FunctionInfo
|
38
37
|
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
|
38
|
+
from .client import _Client
|
39
39
|
from .cloud_bucket_mount import _CloudBucketMount
|
40
40
|
from .config import config, logger, user_config_path
|
41
41
|
from .environments import _get_environment_cached
|
@@ -52,7 +52,6 @@ from .volume import _Volume
|
|
52
52
|
if typing.TYPE_CHECKING:
|
53
53
|
import modal.functions
|
54
54
|
|
55
|
-
|
56
55
|
# This is used for both type checking and runtime validation
|
57
56
|
ImageBuilderVersion = Literal["2023.12", "2024.04", "2024.10"]
|
58
57
|
|
@@ -147,8 +146,8 @@ def _get_modal_requirements_command(version: ImageBuilderVersion) -> str:
|
|
147
146
|
return f"{prefix} -r {CONTAINER_REQUIREMENTS_PATH}"
|
148
147
|
|
149
148
|
|
150
|
-
def _flatten_str_args(function_name: str, arg_name: str, args:
|
151
|
-
"""Takes a
|
149
|
+
def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[str, List[str]]]) -> List[str]:
|
150
|
+
"""Takes a sequence of strings, or string lists, and flattens it.
|
152
151
|
|
153
152
|
Raises an error if any of the elements are not strings or string lists.
|
154
153
|
"""
|
@@ -244,7 +243,7 @@ class _ImageRegistryConfig:
|
|
244
243
|
def __init__(
|
245
244
|
self,
|
246
245
|
# TODO: change to _PUBLIC after worker starts handling it.
|
247
|
-
registry_auth_type:
|
246
|
+
registry_auth_type: "api_pb2.RegistryAuthType.ValueType" = api_pb2.REGISTRY_AUTH_TYPE_UNSPECIFIED,
|
248
247
|
secret: Optional[_Secret] = None,
|
249
248
|
):
|
250
249
|
self.registry_auth_type = registry_auth_type
|
@@ -253,7 +252,7 @@ class _ImageRegistryConfig:
|
|
253
252
|
def get_proto(self) -> api_pb2.ImageRegistryConfig:
|
254
253
|
return api_pb2.ImageRegistryConfig(
|
255
254
|
registry_auth_type=self.registry_auth_type,
|
256
|
-
secret_id=(self.secret.object_id if self.secret else
|
255
|
+
secret_id=(self.secret.object_id if self.secret else ""),
|
257
256
|
)
|
258
257
|
|
259
258
|
|
@@ -264,6 +263,45 @@ class DockerfileSpec:
|
|
264
263
|
context_files: Dict[str, str]
|
265
264
|
|
266
265
|
|
266
|
+
async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.ImageJoinStreamingResponse:
|
267
|
+
last_entry_id: str = ""
|
268
|
+
result_response: Optional[api_pb2.ImageJoinStreamingResponse] = None
|
269
|
+
|
270
|
+
async def join():
|
271
|
+
nonlocal last_entry_id, result_response
|
272
|
+
|
273
|
+
request = api_pb2.ImageJoinStreamingRequest(image_id=image_id, timeout=55, last_entry_id=last_entry_id)
|
274
|
+
async for response in client.stub.ImageJoinStreaming.unary_stream(request):
|
275
|
+
if response.entry_id:
|
276
|
+
last_entry_id = response.entry_id
|
277
|
+
if response.result.status:
|
278
|
+
result_response = response
|
279
|
+
# can't return yet, since there may still be logs streaming back in subsequent responses
|
280
|
+
for task_log in response.task_logs:
|
281
|
+
if task_log.task_progress.pos or task_log.task_progress.len:
|
282
|
+
assert task_log.task_progress.progress_type == api_pb2.IMAGE_SNAPSHOT_UPLOAD
|
283
|
+
if output_mgr := _get_output_manager():
|
284
|
+
output_mgr.update_snapshot_progress(image_id, task_log.task_progress)
|
285
|
+
elif task_log.data:
|
286
|
+
if output_mgr := _get_output_manager():
|
287
|
+
await output_mgr.put_log_content(task_log)
|
288
|
+
if output_mgr := _get_output_manager():
|
289
|
+
output_mgr.flush_lines()
|
290
|
+
|
291
|
+
# Handle up to n exceptions while fetching logs
|
292
|
+
retry_count = 0
|
293
|
+
while result_response is None:
|
294
|
+
try:
|
295
|
+
await join()
|
296
|
+
except (StreamTerminatedError, GRPCError) as exc:
|
297
|
+
if isinstance(exc, GRPCError) and exc.status not in RETRYABLE_GRPC_STATUS_CODES:
|
298
|
+
raise exc
|
299
|
+
retry_count += 1
|
300
|
+
if retry_count >= 3:
|
301
|
+
raise exc
|
302
|
+
return result_response
|
303
|
+
|
304
|
+
|
267
305
|
class _Image(_Object, type_prefix="im"):
|
268
306
|
"""Base class for container images to run functions in.
|
269
307
|
|
@@ -292,7 +330,7 @@ class _Image(_Object, type_prefix="im"):
|
|
292
330
|
self._serve_mounts = other._serve_mounts
|
293
331
|
self._deferred_mounts = other._deferred_mounts
|
294
332
|
|
295
|
-
def _hydrate_metadata(self,
|
333
|
+
def _hydrate_metadata(self, metadata: Optional[Message]):
|
296
334
|
env_image_id = config.get("image_id") # set as an env var in containers
|
297
335
|
if env_image_id == self.object_id:
|
298
336
|
for exc in self.inside_exceptions:
|
@@ -300,9 +338,9 @@ class _Image(_Object, type_prefix="im"):
|
|
300
338
|
# if the hydrated image is the one used by the container
|
301
339
|
raise exc
|
302
340
|
|
303
|
-
if
|
304
|
-
assert isinstance(
|
305
|
-
self._metadata =
|
341
|
+
if metadata:
|
342
|
+
assert isinstance(metadata, api_pb2.ImageMetadata)
|
343
|
+
self._metadata = metadata
|
306
344
|
|
307
345
|
def _add_mount_layer_or_copy(self, mount: _Mount, copy: bool = False):
|
308
346
|
if copy:
|
@@ -318,7 +356,7 @@ class _Image(_Object, type_prefix="im"):
|
|
318
356
|
return _Image._from_loader(_load, "Image(local files)", deps=lambda: [base_image, mount])
|
319
357
|
|
320
358
|
@property
|
321
|
-
def _mount_layers(self) -> typing.
|
359
|
+
def _mount_layers(self) -> typing.Sequence[_Mount]:
|
322
360
|
"""Non-evaluated mount layers on the image
|
323
361
|
|
324
362
|
When the image is used by a Modal container, these mounts need to be attached as well to
|
@@ -362,7 +400,7 @@ class _Image(_Object, type_prefix="im"):
|
|
362
400
|
context_mount: Optional[_Mount] = None,
|
363
401
|
force_build: bool = False,
|
364
402
|
# For internal use only.
|
365
|
-
_namespace:
|
403
|
+
_namespace: "api_pb2.DeploymentNamespace.ValueType" = api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
366
404
|
_do_assert_no_mount_layers: bool = True,
|
367
405
|
):
|
368
406
|
if base_images is None:
|
@@ -382,14 +420,14 @@ class _Image(_Object, type_prefix="im"):
|
|
382
420
|
if build_function and len(base_images) != 1:
|
383
421
|
raise InvalidError("Cannot run a build function with multiple base images!")
|
384
422
|
|
385
|
-
def _deps() ->
|
386
|
-
deps
|
423
|
+
def _deps() -> Sequence[_Object]:
|
424
|
+
deps = tuple(base_images.values()) + tuple(secrets)
|
387
425
|
if build_function:
|
388
|
-
deps
|
426
|
+
deps += (build_function,)
|
389
427
|
if context_mount:
|
390
|
-
deps
|
391
|
-
if image_registry_config.secret:
|
392
|
-
deps
|
428
|
+
deps += (context_mount,)
|
429
|
+
if image_registry_config and image_registry_config.secret:
|
430
|
+
deps += (image_registry_config.secret,)
|
393
431
|
return deps
|
394
432
|
|
395
433
|
async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
|
@@ -398,6 +436,7 @@ class _Image(_Object, type_prefix="im"):
|
|
398
436
|
# base images can't have
|
399
437
|
image._assert_no_mount_layers()
|
400
438
|
|
439
|
+
assert resolver.app_id # type narrowing
|
401
440
|
environment = await _get_environment_cached(resolver.environment_name or "", resolver.client)
|
402
441
|
# A bit hacky,but assume that the environment provides a valid builder version
|
403
442
|
image_builder_version = cast(ImageBuilderVersion, environment._settings.image_builder_version)
|
@@ -432,7 +471,6 @@ class _Image(_Object, type_prefix="im"):
|
|
432
471
|
|
433
472
|
if build_function:
|
434
473
|
build_function_id = build_function.object_id
|
435
|
-
|
436
474
|
globals = build_function._get_info().get_globals()
|
437
475
|
attrs = build_function._get_info().get_cls_var_attrs()
|
438
476
|
globals = {**globals, **attrs}
|
@@ -454,14 +492,14 @@ class _Image(_Object, type_prefix="im"):
|
|
454
492
|
|
455
493
|
# Cloudpickle function serialization produces unstable values.
|
456
494
|
# TODO: better way to filter out types that don't have a stable hash?
|
457
|
-
build_function_globals = serialize(filtered_globals) if filtered_globals else
|
495
|
+
build_function_globals = serialize(filtered_globals) if filtered_globals else b""
|
458
496
|
_build_function = api_pb2.BuildFunction(
|
459
497
|
definition=build_function.get_build_def(),
|
460
498
|
globals=build_function_globals,
|
461
499
|
input=build_function_input,
|
462
500
|
)
|
463
501
|
else:
|
464
|
-
build_function_id =
|
502
|
+
build_function_id = ""
|
465
503
|
_build_function = None
|
466
504
|
|
467
505
|
image_definition = api_pb2.Image(
|
@@ -470,7 +508,7 @@ class _Image(_Object, type_prefix="im"):
|
|
470
508
|
context_files=context_file_pb2s,
|
471
509
|
secret_ids=[secret.object_id for secret in secrets],
|
472
510
|
gpu=bool(gpu_config.type), # Note: as of 2023-01-27, server still uses this
|
473
|
-
context_mount_id=(context_mount.object_id if context_mount else
|
511
|
+
context_mount_id=(context_mount.object_id if context_mount else ""),
|
474
512
|
gpu_config=gpu_config, # Note: as of 2023-01-27, server ignores this
|
475
513
|
image_registry_config=image_registry_config.get_proto(),
|
476
514
|
runtime=config.get("function_runtime"),
|
@@ -481,7 +519,7 @@ class _Image(_Object, type_prefix="im"):
|
|
481
519
|
req = api_pb2.ImageGetOrCreateRequest(
|
482
520
|
app_id=resolver.app_id,
|
483
521
|
image=image_definition,
|
484
|
-
existing_image_id=existing_object_id, # TODO: ignored
|
522
|
+
existing_image_id=existing_object_id or "", # TODO: ignored
|
485
523
|
build_function_id=build_function_id,
|
486
524
|
force_build=config.get("force_build") or force_build,
|
487
525
|
namespace=_namespace,
|
@@ -492,46 +530,22 @@ class _Image(_Object, type_prefix="im"):
|
|
492
530
|
)
|
493
531
|
resp = await retry_transient_errors(resolver.client.stub.ImageGetOrCreate, req)
|
494
532
|
image_id = resp.image_id
|
533
|
+
result: api_pb2.GenericResult
|
534
|
+
metadata: Optional[api_pb2.ImageMetadata] = None
|
535
|
+
|
536
|
+
if resp.result.status:
|
537
|
+
# image already built
|
538
|
+
result = resp.result
|
539
|
+
if resp.HasField("metadata"):
|
540
|
+
metadata = resp.metadata
|
541
|
+
else:
|
542
|
+
# not built or in the process of building - wait for build
|
543
|
+
logger.debug("Waiting for image %s" % image_id)
|
544
|
+
resp = await _image_await_build_result(image_id, resolver.client)
|
545
|
+
result = resp.result
|
546
|
+
if resp.HasField("metadata"):
|
547
|
+
metadata = resp.metadata
|
495
548
|
|
496
|
-
logger.debug("Waiting for image %s" % image_id)
|
497
|
-
last_entry_id: Optional[str] = None
|
498
|
-
result_response: Optional[api_pb2.ImageJoinStreamingResponse] = None
|
499
|
-
|
500
|
-
async def join():
|
501
|
-
nonlocal last_entry_id, result_response
|
502
|
-
|
503
|
-
request = api_pb2.ImageJoinStreamingRequest(image_id=image_id, timeout=55, last_entry_id=last_entry_id)
|
504
|
-
|
505
|
-
async for response in resolver.client.stub.ImageJoinStreaming.unary_stream(request):
|
506
|
-
if response.entry_id:
|
507
|
-
last_entry_id = response.entry_id
|
508
|
-
if response.result.status:
|
509
|
-
result_response = response
|
510
|
-
# can't return yet, since there may still be logs streaming back in subsequent responses
|
511
|
-
for task_log in response.task_logs:
|
512
|
-
if task_log.task_progress.pos or task_log.task_progress.len:
|
513
|
-
assert task_log.task_progress.progress_type == api_pb2.IMAGE_SNAPSHOT_UPLOAD
|
514
|
-
if output_mgr := _get_output_manager():
|
515
|
-
output_mgr.update_snapshot_progress(image_id, task_log.task_progress)
|
516
|
-
elif task_log.data:
|
517
|
-
if output_mgr := _get_output_manager():
|
518
|
-
await output_mgr.put_log_content(task_log)
|
519
|
-
if output_mgr := _get_output_manager():
|
520
|
-
output_mgr.flush_lines()
|
521
|
-
|
522
|
-
# Handle up to n exceptions while fetching logs
|
523
|
-
retry_count = 0
|
524
|
-
while result_response is None:
|
525
|
-
try:
|
526
|
-
await join()
|
527
|
-
except (StreamTerminatedError, GRPCError) as exc:
|
528
|
-
if isinstance(exc, GRPCError) and exc.status not in RETRYABLE_GRPC_STATUS_CODES:
|
529
|
-
raise exc
|
530
|
-
retry_count += 1
|
531
|
-
if retry_count >= 3:
|
532
|
-
raise exc
|
533
|
-
|
534
|
-
result = result_response.result
|
535
549
|
if result.status == api_pb2.GenericResult.GENERIC_STATUS_FAILURE:
|
536
550
|
raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
|
537
551
|
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
|
@@ -545,7 +559,7 @@ class _Image(_Object, type_prefix="im"):
|
|
545
559
|
else:
|
546
560
|
raise RemoteError("Unknown status %s!" % result.status)
|
547
561
|
|
548
|
-
self._hydrate(image_id, resolver.client,
|
562
|
+
self._hydrate(image_id, resolver.client, metadata)
|
549
563
|
local_mounts = set()
|
550
564
|
for base in base_images.values():
|
551
565
|
local_mounts |= base._serve_mounts
|
@@ -666,7 +680,7 @@ class _Image(_Object, type_prefix="im"):
|
|
666
680
|
context_mount=mount,
|
667
681
|
)
|
668
682
|
|
669
|
-
def _add_local_python_packages(self, *packages:
|
683
|
+
def _add_local_python_packages(self, *packages: str, copy: bool = False) -> "_Image":
|
670
684
|
"""Adds Python package files to containers
|
671
685
|
|
672
686
|
Adds all files from the specified Python packages to containers running the Image.
|
@@ -1632,7 +1646,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1632
1646
|
function = _Function.from_args(
|
1633
1647
|
info,
|
1634
1648
|
app=None,
|
1635
|
-
image=self,
|
1649
|
+
image=self, # type: ignore[reportArgumentType] # TODO: probably conflict with type stub?
|
1636
1650
|
secrets=secrets,
|
1637
1651
|
gpu=gpu,
|
1638
1652
|
mounts=mounts,
|
@@ -1700,7 +1714,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1700
1714
|
"""
|
1701
1715
|
|
1702
1716
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
1703
|
-
commands = ["FROM base", f"WORKDIR {shlex.quote(path)}"]
|
1717
|
+
commands = ["FROM base", f"WORKDIR {shlex.quote(str(path))}"]
|
1704
1718
|
return DockerfileSpec(commands=commands, context_files={})
|
1705
1719
|
|
1706
1720
|
return _Image._from_args(
|
@@ -1744,7 +1758,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1744
1758
|
|
1745
1759
|
This method is considered private since its interface may change - use it at your own risk!
|
1746
1760
|
"""
|
1747
|
-
last_entry_id:
|
1761
|
+
last_entry_id: str = ""
|
1748
1762
|
|
1749
1763
|
request = api_pb2.ImageJoinStreamingRequest(
|
1750
1764
|
image_id=self._object_id, timeout=55, last_entry_id=last_entry_id, include_logs_for_finished=True
|
modal/image.pyi
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import google.protobuf.message
|
2
|
+
import modal.client
|
2
3
|
import modal.cloud_bucket_mount
|
3
4
|
import modal.functions
|
4
5
|
import modal.gpu
|
@@ -28,7 +29,7 @@ def _get_modal_requirements_path(
|
|
28
29
|
) -> str: ...
|
29
30
|
def _get_modal_requirements_command(version: typing.Literal["2023.12", "2024.04", "2024.10"]) -> str: ...
|
30
31
|
def _flatten_str_args(
|
31
|
-
function_name: str, arg_name: str, args: typing.
|
32
|
+
function_name: str, arg_name: str, args: typing.Sequence[typing.Union[str, typing.List[str]]]
|
32
33
|
) -> typing.List[str]: ...
|
33
34
|
def _validate_packages(packages: typing.List[str]) -> bool: ...
|
34
35
|
def _warn_invalid_packages(old_command: str) -> None: ...
|
@@ -55,6 +56,10 @@ class DockerfileSpec:
|
|
55
56
|
def __repr__(self): ...
|
56
57
|
def __eq__(self, other): ...
|
57
58
|
|
59
|
+
async def _image_await_build_result(
|
60
|
+
image_id: str, client: modal.client._Client
|
61
|
+
) -> modal_proto.api_pb2.ImageJoinStreamingResponse: ...
|
62
|
+
|
58
63
|
class _Image(modal.object._Object):
|
59
64
|
force_build: bool
|
60
65
|
inside_exceptions: typing.List[Exception]
|
@@ -64,10 +69,10 @@ class _Image(modal.object._Object):
|
|
64
69
|
|
65
70
|
def _initialize_from_empty(self): ...
|
66
71
|
def _initialize_from_other(self, other: _Image): ...
|
67
|
-
def _hydrate_metadata(self,
|
72
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
68
73
|
def _add_mount_layer_or_copy(self, mount: modal.mount._Mount, copy: bool = False): ...
|
69
74
|
@property
|
70
|
-
def _mount_layers(self) -> typing.
|
75
|
+
def _mount_layers(self) -> typing.Sequence[modal.mount._Mount]: ...
|
71
76
|
def _assert_no_mount_layers(self): ...
|
72
77
|
@staticmethod
|
73
78
|
def _from_args(
|
@@ -109,7 +114,7 @@ class _Image(modal.object._Object):
|
|
109
114
|
def copy_local_file(
|
110
115
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
|
111
116
|
) -> _Image: ...
|
112
|
-
def _add_local_python_packages(self, *module_names, copy: bool = False) -> _Image: ...
|
117
|
+
def _add_local_python_packages(self, *module_names: str, copy: bool = False) -> _Image: ...
|
113
118
|
def copy_local_dir(
|
114
119
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "."
|
115
120
|
) -> _Image: ...
|
@@ -321,10 +326,10 @@ class Image(modal.object.Object):
|
|
321
326
|
def __init__(self, *args, **kwargs): ...
|
322
327
|
def _initialize_from_empty(self): ...
|
323
328
|
def _initialize_from_other(self, other: Image): ...
|
324
|
-
def _hydrate_metadata(self,
|
329
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
325
330
|
def _add_mount_layer_or_copy(self, mount: modal.mount.Mount, copy: bool = False): ...
|
326
331
|
@property
|
327
|
-
def _mount_layers(self) -> typing.
|
332
|
+
def _mount_layers(self) -> typing.Sequence[modal.mount.Mount]: ...
|
328
333
|
def _assert_no_mount_layers(self): ...
|
329
334
|
@staticmethod
|
330
335
|
def _from_args(
|
@@ -366,7 +371,7 @@ class Image(modal.object.Object):
|
|
366
371
|
def copy_local_file(
|
367
372
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
|
368
373
|
) -> Image: ...
|
369
|
-
def _add_local_python_packages(self, *module_names, copy: bool = False) -> Image: ...
|
374
|
+
def _add_local_python_packages(self, *module_names: str, copy: bool = False) -> Image: ...
|
370
375
|
def copy_local_dir(
|
371
376
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "."
|
372
377
|
) -> Image: ...
|
modal/object.py
CHANGED
@@ -17,7 +17,7 @@ O = TypeVar("O", bound="_Object")
|
|
17
17
|
|
18
18
|
_BLOCKING_O = synchronize_api(O)
|
19
19
|
|
20
|
-
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP = 300
|
20
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
|
21
21
|
|
22
22
|
|
23
23
|
def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
|
@@ -205,7 +205,7 @@ class _Object:
|
|
205
205
|
return self._local_uuid
|
206
206
|
|
207
207
|
@property
|
208
|
-
def object_id(self):
|
208
|
+
def object_id(self) -> str:
|
209
209
|
"""mdmd:hidden"""
|
210
210
|
return self._object_id
|
211
211
|
|
modal/object.pyi
CHANGED
@@ -86,7 +86,7 @@ class _Object:
|
|
86
86
|
@property
|
87
87
|
def local_uuid(self): ...
|
88
88
|
@property
|
89
|
-
def object_id(self): ...
|
89
|
+
def object_id(self) -> str: ...
|
90
90
|
@property
|
91
91
|
def is_hydrated(self) -> bool: ...
|
92
92
|
@property
|
@@ -188,7 +188,7 @@ class Object:
|
|
188
188
|
@property
|
189
189
|
def local_uuid(self): ...
|
190
190
|
@property
|
191
|
-
def object_id(self): ...
|
191
|
+
def object_id(self) -> str: ...
|
192
192
|
@property
|
193
193
|
def is_hydrated(self) -> bool: ...
|
194
194
|
@property
|
@@ -202,3 +202,5 @@ class Object:
|
|
202
202
|
|
203
203
|
def live_method(method): ...
|
204
204
|
def live_method_gen(method): ...
|
205
|
+
|
206
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int
|
modal/volume.py
CHANGED
@@ -122,14 +122,21 @@ class _Volume(_Object, type_prefix="vo"):
|
|
122
122
|
```
|
123
123
|
"""
|
124
124
|
|
125
|
-
_lock: asyncio.Lock
|
125
|
+
_lock: Optional[asyncio.Lock] = None
|
126
126
|
|
127
|
-
def
|
127
|
+
async def _get_lock(self):
|
128
128
|
# To (mostly*) prevent multiple concurrent operations on the same volume, which can cause problems under
|
129
129
|
# some unlikely circumstances.
|
130
130
|
# *: You can bypass this by creating multiple handles to the same volume, e.g. via lookup. But this
|
131
131
|
# covers the typical case = good enough.
|
132
|
-
|
132
|
+
|
133
|
+
# Note: this function runs no async code but is marked as async to ensure it's
|
134
|
+
# being run inside the synchronicity event loop and binds the lock to the
|
135
|
+
# correct event loop on Python 3.9 which eagerly assigns event loops on
|
136
|
+
# constructions of locks
|
137
|
+
if self._lock is None:
|
138
|
+
self._lock = asyncio.Lock()
|
139
|
+
return self._lock
|
133
140
|
|
134
141
|
@staticmethod
|
135
142
|
def new():
|
@@ -188,7 +195,7 @@ class _Volume(_Object, type_prefix="vo"):
|
|
188
195
|
environment_name: Optional[str] = None,
|
189
196
|
version: "typing.Optional[modal_proto.api_pb2.VolumeFsVersion.ValueType]" = None,
|
190
197
|
_heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
191
|
-
) ->
|
198
|
+
) -> AsyncGenerator["_Volume", None]:
|
192
199
|
"""Creates a new ephemeral volume within a context manager:
|
193
200
|
|
194
201
|
Usage:
|
@@ -269,7 +276,7 @@ class _Volume(_Object, type_prefix="vo"):
|
|
269
276
|
|
270
277
|
@live_method
|
271
278
|
async def _do_reload(self, lock=True):
|
272
|
-
async with self.
|
279
|
+
async with (await self._get_lock()) if lock else asyncnullcontext():
|
273
280
|
req = api_pb2.VolumeReloadRequest(volume_id=self.object_id)
|
274
281
|
_ = await retry_transient_errors(self._client.stub.VolumeReload, req)
|
275
282
|
|
@@ -280,7 +287,7 @@ class _Volume(_Object, type_prefix="vo"):
|
|
280
287
|
If successful, the changes made are now persisted in durable storage and available to other containers accessing
|
281
288
|
the volume.
|
282
289
|
"""
|
283
|
-
async with self.
|
290
|
+
async with await self._get_lock():
|
284
291
|
req = api_pb2.VolumeCommitRequest(volume_id=self.object_id)
|
285
292
|
try:
|
286
293
|
# TODO(gongy): only apply indefinite retries on 504 status.
|
modal/volume.pyi
CHANGED
@@ -33,9 +33,9 @@ class FileEntry:
|
|
33
33
|
def __hash__(self): ...
|
34
34
|
|
35
35
|
class _Volume(modal.object._Object):
|
36
|
-
_lock: asyncio.locks.Lock
|
36
|
+
_lock: typing.Optional[asyncio.locks.Lock]
|
37
37
|
|
38
|
-
def
|
38
|
+
async def _get_lock(self): ...
|
39
39
|
@staticmethod
|
40
40
|
def new(): ...
|
41
41
|
@staticmethod
|
@@ -122,10 +122,16 @@ class _VolumeUploadContextManager:
|
|
122
122
|
) -> modal_proto.api_pb2.MountFile: ...
|
123
123
|
|
124
124
|
class Volume(modal.object.Object):
|
125
|
-
_lock: asyncio.locks.Lock
|
125
|
+
_lock: typing.Optional[asyncio.locks.Lock]
|
126
126
|
|
127
127
|
def __init__(self, *args, **kwargs): ...
|
128
|
-
|
128
|
+
|
129
|
+
class ___get_lock_spec(typing_extensions.Protocol):
|
130
|
+
def __call__(self): ...
|
131
|
+
async def aio(self): ...
|
132
|
+
|
133
|
+
_get_lock: ___get_lock_spec
|
134
|
+
|
129
135
|
@staticmethod
|
130
136
|
def new(): ...
|
131
137
|
@staticmethod
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=ZQux8ZGLblIWbKHn7s15mucx97EwbjJso9WKRTYYOf0,45208
|
|
19
19
|
modal/app.pyi,sha256=sX2BXX_178lp8O_GvwZqsxDdxQi1j3DjNfthMvlMlJU,25273
|
20
20
|
modal/call_graph.py,sha256=l-Wi6vM8aosCdHTWegcCyGeVJGFdZ_fzlCmbRVPBXFI,2593
|
21
21
|
modal/client.py,sha256=4SpWb4n0nolITR36kADZl1tYLOg6avukmzZU56UQjCo,16385
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=fZ4BnAY7mf5yMxkRhmEJsqP6ioP6e0IZZA42KcCxluk,7372
|
23
23
|
modal/cloud_bucket_mount.py,sha256=eWQhCtMIczpokjfTZEgNBCGO_s5ft46PqTSLfKBykq4,5748
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=tTF7M4FR9bTA30cFkz8qq3ZTlFL19NHU_36e_5GgAGA,1424
|
25
25
|
modal/cls.py,sha256=apKnBOHKYEpBiMC8mRvHtCDJl1g0vP0tG1r8mUZ1yH0,24684
|
@@ -34,18 +34,18 @@ modal/environments.pyi,sha256=oScvFAclF55-tL9UioLIL_SPBwgy_9O-BBvJ-PLbRgY,3542
|
|
34
34
|
modal/exception.py,sha256=K-czk1oK8wFvK8snWrytXSByo2WNb9SJAlgBVPGWZBs,6417
|
35
35
|
modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
|
36
36
|
modal/functions.py,sha256=BxccB-3a1migZQ6JA6iiHZJQ7WQ-jYpmg9DEZoTxzcc,71639
|
37
|
-
modal/functions.pyi,sha256=
|
37
|
+
modal/functions.pyi,sha256=5JGM4Mhpm674Ia7h3OTsPBmZA32goyOs2oBCCUG8A3I,24800
|
38
38
|
modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
|
39
|
-
modal/image.py,sha256=
|
40
|
-
modal/image.pyi,sha256=
|
39
|
+
modal/image.py,sha256=f4OB4gfyaSz3mQjumzEMeZT4Uq0SzsGBMN5NkPQQSec,79550
|
40
|
+
modal/image.pyi,sha256=3rfae_E0KuNHqdi5j33nHXp_7P3tTkt7QKH5cXYczUc,24672
|
41
41
|
modal/io_streams.py,sha256=XUsNsxRzDrhkjyb2Hx0hugCoOEz266SHQF8wP-VgsfY,14582
|
42
42
|
modal/io_streams.pyi,sha256=WJmSI1WvZITUNBO7mnIuJgYdSKdbLaHk10V4GbttAVw,4452
|
43
43
|
modal/mount.py,sha256=QZ4nabpbNU9tjLIPCq86rlHor9CXzADMkhJWBYfKKgg,27750
|
44
44
|
modal/mount.pyi,sha256=nywUmeUELLY2OEnAc1NNBHmSxuEylTWBzkh6nuXkkuc,9965
|
45
45
|
modal/network_file_system.py,sha256=P_LsILecyda1SRHU76Hk4Lq3M1HSx9shFJbaLThzw0U,14071
|
46
46
|
modal/network_file_system.pyi,sha256=XLyUnDx55ExbJcF_xlKxRax_r06XTvSsQh-a-_EyCOU,7239
|
47
|
-
modal/object.py,sha256=
|
48
|
-
modal/object.pyi,sha256=
|
47
|
+
modal/object.py,sha256=Qgee_lQJY_583YsGIVrSNuDF_gJA_qmTAeVTVI1tf-g,9637
|
48
|
+
modal/object.pyi,sha256=uGGD5A2B_mj8jxLfFiHama5wzCcBS_GNvPSKsIfsCO0,8518
|
49
49
|
modal/output.py,sha256=FtPR7yvjZMgdSKD_KYkIcwYgCOiV9EKYjaj7K55Hjvg,1940
|
50
50
|
modal/parallel_map.py,sha256=lf8Wer6FAf8-dYqPqoL45cz7FYDU66-TF-h5CO2Kf5Q,16052
|
51
51
|
modal/parallel_map.pyi,sha256=pOhT0P3DDYlwLx0fR3PTsecA7DI8uOdXC1N8i-ZkyOY,2328
|
@@ -71,8 +71,8 @@ modal/serving.pyi,sha256=0KWUH5rdYnihSv1XB8bK9GokzpfzrCq8Sf6nYlUvQI8,1689
|
|
71
71
|
modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
|
72
72
|
modal/token_flow.py,sha256=lsVpJACut76AeJLw44vJKMSlpcqp8wcvxdUOoX6CIOc,6754
|
73
73
|
modal/token_flow.pyi,sha256=qEYP7grgqSA440w7kBREU9Ezeo_NxCT67OciIPgDzcc,1958
|
74
|
-
modal/volume.py,sha256=
|
75
|
-
modal/volume.pyi,sha256=
|
74
|
+
modal/volume.py,sha256=5IdcerxXjP9MpAZm9QXPTWRDYZD5UJSFebWGglCha8k,29301
|
75
|
+
modal/volume.pyi,sha256=3lB6wiC75u3o44cwJVqDsmvR4wsP2JXSxJrVXi9KrK4,11127
|
76
76
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
77
77
|
modal/_runtime/asgi.py,sha256=WoAwIiGKpk089MOca3_iA73h36v0uBuoPx0-87ajIDY,19843
|
78
78
|
modal/_runtime/container_io_manager.py,sha256=_MEhwyCSYeCaPQnztPxkm0anRXa3CPcwIKi403N53uo,44120
|
@@ -159,10 +159,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
159
159
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
160
160
|
modal_version/__init__.py,sha256=UnAuHBPuPSstqgdCOx0SBVdfhpeJnMlY_oxEbu44Izg,470
|
161
161
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
162
|
-
modal_version/_version_generated.py,sha256=
|
163
|
-
modal-0.66.
|
164
|
-
modal-0.66.
|
165
|
-
modal-0.66.
|
166
|
-
modal-0.66.
|
167
|
-
modal-0.66.
|
168
|
-
modal-0.66.
|
162
|
+
modal_version/_version_generated.py,sha256=ejUtktb37whw_RO7gJu2mzTcbyyje5SrnzvpI_2mGrs,149
|
163
|
+
modal-0.66.52.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
164
|
+
modal-0.66.52.dist-info/METADATA,sha256=KVM51Rvw6DWszOpsfhoC4drFw8BsskaqrjACjmrlF_c,2329
|
165
|
+
modal-0.66.52.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
166
|
+
modal-0.66.52.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
167
|
+
modal-0.66.52.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
168
|
+
modal-0.66.52.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|