modal 1.1.4.dev30__tar.gz → 1.1.5.dev0__tar.gz
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-1.1.4.dev30 → modal-1.1.5.dev0}/PKG-INFO +1 -1
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/client.pyi +2 -2
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/experimental/__init__.py +1 -4
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/experimental/flash.py +81 -8
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/experimental/flash.pyi +51 -6
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/functions.pyi +6 -6
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_version/__init__.py +1 -1
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/LICENSE +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/README.md +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/__main__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_functions.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_ipython.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_location.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_object.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_output.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_partial_function.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_pty.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_resolver.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_resources.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_serialization.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_traceback.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_tunnel.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_type_manager.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/_watcher.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/app.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/app.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/README.md +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/call_graph.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/_download.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/app.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/config.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/container.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/dict.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/environment.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/launch.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/profile.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/queues.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/run.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/secret.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/token.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/utils.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cli/volume.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/client.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cls.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/cls.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/config.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/container_process.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/container_process.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/dict.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/dict.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/environments.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/environments.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/exception.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/file_io.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/file_io.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/functions.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/gpu.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/image.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/image.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/io_streams.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/mount.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/mount.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/network_file_system.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/object.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/object.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/output.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/parallel_map.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/partial_function.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/proxy.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/proxy.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/py.typed +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/queue.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/queue.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/retries.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/runner.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/runner.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/running_app.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/sandbox.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/sandbox.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/schedule.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/secret.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/secret.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/serving.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/serving.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/snapshot.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/stream_type.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/token_flow.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/volume.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal/volume.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/api.proto +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/api_pb2.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/options.proto +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_proto/py.typed +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/modal_version/__main__.py +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/pyproject.toml +0 -0
- {modal-1.1.4.dev30 → modal-1.1.5.dev0}/setup.cfg +0 -0
@@ -33,7 +33,7 @@ class _Client:
|
|
33
33
|
server_url: str,
|
34
34
|
client_type: int,
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
36
|
-
version: str = "1.1.
|
36
|
+
version: str = "1.1.5.dev0",
|
37
37
|
):
|
38
38
|
"""mdmd:hidden
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -164,7 +164,7 @@ class Client:
|
|
164
164
|
server_url: str,
|
165
165
|
client_type: int,
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
167
|
-
version: str = "1.1.
|
167
|
+
version: str = "1.1.5.dev0",
|
168
168
|
):
|
169
169
|
"""mdmd:hidden
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -317,14 +317,11 @@ async def notebook_base_image(*, python_version: Optional[str] = None, force_bui
|
|
317
317
|
# Install uv since it's faster than pip for installing packages.
|
318
318
|
"pip install uv",
|
319
319
|
# https://github.com/astral-sh/uv/issues/11480
|
320
|
-
"pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/
|
320
|
+
"pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu129",
|
321
321
|
f"uv pip install --system {shlex.join(sorted(environment_packages))}",
|
322
322
|
f"uv pip install --system {shlex.join(sorted(kernelshim_packages))}",
|
323
323
|
]
|
324
324
|
|
325
|
-
# TODO: Also install the CUDA Toolkit, so `nvcc` is available.
|
326
|
-
# https://github.com/charlesfrye/cuda-modal/blob/7fef8db12402986cf42d9c8cca8c63d1da6d7700/cuda/use_cuda.py#L158-L188
|
327
|
-
|
328
325
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
329
326
|
return DockerfileSpec(
|
330
327
|
commands=[
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2025
|
2
2
|
import asyncio
|
3
3
|
import math
|
4
|
+
import os
|
5
|
+
import subprocess
|
4
6
|
import sys
|
5
7
|
import time
|
6
8
|
import traceback
|
@@ -19,28 +21,87 @@ from ..client import _Client
|
|
19
21
|
from ..config import logger
|
20
22
|
from ..exception import InvalidError
|
21
23
|
|
24
|
+
MAX_FAILURES = 3
|
25
|
+
|
22
26
|
|
23
27
|
class _FlashManager:
|
24
|
-
def __init__(
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
client: _Client,
|
31
|
+
port: int,
|
32
|
+
process: Optional[subprocess.Popen] = None,
|
33
|
+
health_check_url: Optional[str] = None,
|
34
|
+
):
|
25
35
|
self.client = client
|
26
36
|
self.port = port
|
37
|
+
# Health check is not currently being used
|
27
38
|
self.health_check_url = health_check_url
|
39
|
+
self.process = process
|
28
40
|
self.tunnel_manager = _forward_tunnel(port, client=client)
|
29
41
|
self.stopped = False
|
42
|
+
self.num_failures = 0
|
43
|
+
self.task_id = os.environ["MODAL_TASK_ID"]
|
44
|
+
|
45
|
+
async def check_port_connection(self, process: Optional[subprocess.Popen], timeout: int = 10):
|
46
|
+
import socket
|
47
|
+
|
48
|
+
start_time = time.monotonic()
|
49
|
+
|
50
|
+
while time.monotonic() - start_time < timeout:
|
51
|
+
try:
|
52
|
+
if process is not None and process.poll() is not None:
|
53
|
+
return Exception(f"Process {process.pid} exited with code {process.returncode}")
|
54
|
+
with socket.create_connection(("localhost", self.port), timeout=1):
|
55
|
+
return
|
56
|
+
except (ConnectionRefusedError, OSError):
|
57
|
+
await asyncio.sleep(0.1)
|
58
|
+
|
59
|
+
return Exception(f"Waited too long for port {self.port} to start accepting connections")
|
30
60
|
|
31
61
|
async def _start(self):
|
32
62
|
self.tunnel = await self.tunnel_manager.__aenter__()
|
33
|
-
|
34
63
|
parsed_url = urlparse(self.tunnel.url)
|
35
64
|
host = parsed_url.hostname
|
36
65
|
port = parsed_url.port or 443
|
37
66
|
|
38
67
|
self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
|
68
|
+
self.drain_task = asyncio.create_task(self._drain_container())
|
69
|
+
|
70
|
+
async def _drain_container(self):
|
71
|
+
"""
|
72
|
+
Background task that checks if we've encountered too many failures and drains the container if so.
|
73
|
+
"""
|
74
|
+
while True:
|
75
|
+
try:
|
76
|
+
# Check if the container should be drained (e.g., too many failures)
|
77
|
+
if self.num_failures > MAX_FAILURES:
|
78
|
+
logger.warning(
|
79
|
+
f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
|
80
|
+
)
|
81
|
+
await self.stop()
|
82
|
+
# handle close upon container exit
|
83
|
+
|
84
|
+
if self.task_id:
|
85
|
+
await self.client.stub.ContainerStop(api_pb2.ContainerStopRequest(task_id=self.task_id))
|
86
|
+
return
|
87
|
+
except asyncio.CancelledError:
|
88
|
+
logger.warning("[Modal Flash] Shutting down...")
|
89
|
+
return
|
90
|
+
except Exception as e:
|
91
|
+
logger.error(f"[Modal Flash] Error draining container: {e}")
|
92
|
+
await asyncio.sleep(1)
|
93
|
+
|
94
|
+
try:
|
95
|
+
await asyncio.sleep(1)
|
96
|
+
except asyncio.CancelledError:
|
97
|
+
logger.warning("[Modal Flash] Shutting down...")
|
98
|
+
return
|
39
99
|
|
40
100
|
async def _run_heartbeat(self, host: str, port: int):
|
41
101
|
first_registration = True
|
42
102
|
while True:
|
43
103
|
try:
|
104
|
+
await self.check_port_connection(process=self.process)
|
44
105
|
resp = await self.client.stub.FlashContainerRegister(
|
45
106
|
api_pb2.FlashContainerRegisterRequest(
|
46
107
|
priority=10,
|
@@ -50,14 +111,25 @@ class _FlashManager:
|
|
50
111
|
),
|
51
112
|
timeout=10,
|
52
113
|
)
|
114
|
+
self.num_failures = 0
|
53
115
|
if first_registration:
|
54
|
-
logger.warning(
|
116
|
+
logger.warning(
|
117
|
+
f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
|
118
|
+
)
|
55
119
|
first_registration = False
|
56
120
|
except asyncio.CancelledError:
|
57
121
|
logger.warning("[Modal Flash] Shutting down...")
|
58
122
|
break
|
59
123
|
except Exception as e:
|
60
124
|
logger.error(f"[Modal Flash] Heartbeat failed: {e}")
|
125
|
+
self.num_failures += 1
|
126
|
+
logger.error(
|
127
|
+
f"[Modal Flash] Deregistering container {self.tunnel.url}, num_failures: {self.num_failures}"
|
128
|
+
)
|
129
|
+
await retry_transient_errors(
|
130
|
+
self.client.stub.FlashContainerDeregister,
|
131
|
+
api_pb2.FlashContainerDeregisterRequest(),
|
132
|
+
)
|
61
133
|
|
62
134
|
try:
|
63
135
|
await asyncio.sleep(1)
|
@@ -94,16 +166,17 @@ FlashManager = synchronize_api(_FlashManager)
|
|
94
166
|
|
95
167
|
|
96
168
|
@synchronizer.create_blocking
|
97
|
-
async def flash_forward(
|
169
|
+
async def flash_forward(
|
170
|
+
port: int, process: Optional[subprocess.Popen] = None, health_check_url: Optional[str] = None
|
171
|
+
) -> _FlashManager:
|
98
172
|
"""
|
99
173
|
Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
100
|
-
|
101
174
|
This is a highly experimental method that can break or be removed at any time without warning.
|
102
175
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
103
176
|
"""
|
104
177
|
client = await _Client.from_env()
|
105
178
|
|
106
|
-
manager = _FlashManager(client, port, health_check_url)
|
179
|
+
manager = _FlashManager(client, port, process=process, health_check_url=health_check_url)
|
107
180
|
await manager._start()
|
108
181
|
return manager
|
109
182
|
|
@@ -127,6 +200,8 @@ class _FlashPrometheusAutoscaler:
|
|
127
200
|
scale_down_stabilization_window_seconds: int,
|
128
201
|
autoscaling_interval_seconds: int,
|
129
202
|
):
|
203
|
+
import aiohttp
|
204
|
+
|
130
205
|
if scale_up_stabilization_window_seconds > self._max_window_seconds:
|
131
206
|
raise InvalidError(
|
132
207
|
f"scale_up_stabilization_window_seconds must be less than or equal to {self._max_window_seconds}"
|
@@ -138,8 +213,6 @@ class _FlashPrometheusAutoscaler:
|
|
138
213
|
if target_metric_value <= 0:
|
139
214
|
raise InvalidError("target_metric_value must be greater than 0")
|
140
215
|
|
141
|
-
import aiohttp
|
142
|
-
|
143
216
|
self.client = client
|
144
217
|
self.app_name = app_name
|
145
218
|
self.cls_name = cls_name
|
@@ -1,14 +1,26 @@
|
|
1
1
|
import modal.client
|
2
2
|
import modal_proto.api_pb2
|
3
|
+
import subprocess
|
3
4
|
import typing
|
4
5
|
import typing_extensions
|
5
6
|
|
6
7
|
class _FlashManager:
|
7
|
-
def __init__(
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
client: modal.client._Client,
|
11
|
+
port: int,
|
12
|
+
process: typing.Optional[subprocess.Popen] = None,
|
13
|
+
health_check_url: typing.Optional[str] = None,
|
14
|
+
):
|
8
15
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
9
16
|
...
|
10
17
|
|
18
|
+
async def check_port_connection(self, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
|
11
19
|
async def _start(self): ...
|
20
|
+
async def _drain_container(self):
|
21
|
+
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
22
|
+
...
|
23
|
+
|
12
24
|
async def _run_heartbeat(self, host: str, port: int): ...
|
13
25
|
def get_container_url(self): ...
|
14
26
|
async def stop(self): ...
|
@@ -17,7 +29,19 @@ class _FlashManager:
|
|
17
29
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
18
30
|
|
19
31
|
class FlashManager:
|
20
|
-
def __init__(
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
client: modal.client.Client,
|
35
|
+
port: int,
|
36
|
+
process: typing.Optional[subprocess.Popen] = None,
|
37
|
+
health_check_url: typing.Optional[str] = None,
|
38
|
+
): ...
|
39
|
+
|
40
|
+
class __check_port_connection_spec(typing_extensions.Protocol[SUPERSELF]):
|
41
|
+
def __call__(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
|
42
|
+
async def aio(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
|
43
|
+
|
44
|
+
check_port_connection: __check_port_connection_spec[typing_extensions.Self]
|
21
45
|
|
22
46
|
class ___start_spec(typing_extensions.Protocol[SUPERSELF]):
|
23
47
|
def __call__(self, /): ...
|
@@ -25,6 +49,17 @@ class FlashManager:
|
|
25
49
|
|
26
50
|
_start: ___start_spec[typing_extensions.Self]
|
27
51
|
|
52
|
+
class ___drain_container_spec(typing_extensions.Protocol[SUPERSELF]):
|
53
|
+
def __call__(self, /):
|
54
|
+
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
55
|
+
...
|
56
|
+
|
57
|
+
async def aio(self, /):
|
58
|
+
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
59
|
+
...
|
60
|
+
|
61
|
+
_drain_container: ___drain_container_spec[typing_extensions.Self]
|
62
|
+
|
28
63
|
class ___run_heartbeat_spec(typing_extensions.Protocol[SUPERSELF]):
|
29
64
|
def __call__(self, /, host: str, port: int): ...
|
30
65
|
async def aio(self, /, host: str, port: int): ...
|
@@ -46,17 +81,27 @@ class FlashManager:
|
|
46
81
|
close: __close_spec[typing_extensions.Self]
|
47
82
|
|
48
83
|
class __flash_forward_spec(typing_extensions.Protocol):
|
49
|
-
def __call__(
|
84
|
+
def __call__(
|
85
|
+
self,
|
86
|
+
/,
|
87
|
+
port: int,
|
88
|
+
process: typing.Optional[subprocess.Popen] = None,
|
89
|
+
health_check_url: typing.Optional[str] = None,
|
90
|
+
) -> FlashManager:
|
50
91
|
"""Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
51
|
-
|
52
92
|
This is a highly experimental method that can break or be removed at any time without warning.
|
53
93
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
54
94
|
"""
|
55
95
|
...
|
56
96
|
|
57
|
-
async def aio(
|
97
|
+
async def aio(
|
98
|
+
self,
|
99
|
+
/,
|
100
|
+
port: int,
|
101
|
+
process: typing.Optional[subprocess.Popen] = None,
|
102
|
+
health_check_url: typing.Optional[str] = None,
|
103
|
+
) -> FlashManager:
|
58
104
|
"""Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
59
|
-
|
60
105
|
This is a highly experimental method that can break or be removed at any time without warning.
|
61
106
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
62
107
|
"""
|
@@ -445,7 +445,7 @@ class Function(
|
|
445
445
|
|
446
446
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
447
447
|
|
448
|
-
class __remote_spec(typing_extensions.Protocol[
|
448
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
449
449
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
450
450
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
451
451
|
...
|
@@ -454,7 +454,7 @@ class Function(
|
|
454
454
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
455
455
|
...
|
456
456
|
|
457
|
-
remote: __remote_spec[modal._functions.
|
457
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
458
458
|
|
459
459
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
460
460
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
@@ -481,7 +481,7 @@ class Function(
|
|
481
481
|
"""
|
482
482
|
...
|
483
483
|
|
484
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
484
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
485
485
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
486
486
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
487
487
|
|
@@ -505,7 +505,7 @@ class Function(
|
|
505
505
|
...
|
506
506
|
|
507
507
|
_experimental_spawn: ___experimental_spawn_spec[
|
508
|
-
modal._functions.
|
508
|
+
modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
|
509
509
|
]
|
510
510
|
|
511
511
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
@@ -514,7 +514,7 @@ class Function(
|
|
514
514
|
|
515
515
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
516
516
|
|
517
|
-
class __spawn_spec(typing_extensions.Protocol[
|
517
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
518
518
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
519
519
|
"""Calls the function with the given arguments, without waiting for the results.
|
520
520
|
|
@@ -535,7 +535,7 @@ class Function(
|
|
535
535
|
"""
|
536
536
|
...
|
537
537
|
|
538
|
-
spawn: __spawn_spec[modal._functions.
|
538
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
539
539
|
|
540
540
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
541
541
|
"""Return the inner Python object wrapped by this Modal Function."""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|