modal 1.4.4.dev21__tar.gz → 1.5.0__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.4.4.dev21 → modal-1.5.0}/PKG-INFO +1 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_environments.py +1 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_server.py +3 -3
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/app.py +5 -5
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/app.pyi +6 -6
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/image.py +7 -7
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/utils.py +8 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/client.pyi +2 -10
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/sandbox.py +54 -11
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/sandbox.pyi +12 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/server.pyi +1 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/api_pb2.py +421 -406
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/api_pb2.pyi +21 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_version/__init__.py +1 -1
- {modal-1.4.4.dev21 → modal-1.5.0}/LICENSE +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/README.md +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/__main__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_billing.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_clustered_functions.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_function_variants.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_functions.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_grpc_client.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_image.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_ipython.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_load_context.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_location.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_logs.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_object.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_output/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_output/manager.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_output/pty.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_output/rich.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_output/status.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_partial_function.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_resolver.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_resources.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_serialization.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_traceback.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_tunnel.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_tunnel.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_type_manager.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/logger.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_vendor/version.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_watcher.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/_workspace.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/billing.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/README.md +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/builder/base-images.json +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/call_graph.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/_download.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/_help.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/_traceback.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/app.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/billing.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/bootstrap.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/changelog.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/cluster.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/config.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/container.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/dashboard.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/dict.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/entry_point.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/environment.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/import_refs.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/launch.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/logo.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/profile.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/queues.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/run.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/secret.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/selector.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/shell.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/skills.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/token.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cli/volume.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/client.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cls.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/cls.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/config.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/container_process.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/container_process.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/dict.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/dict.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/environments.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/environments.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/exception.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/experimental/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/experimental/flash.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/experimental/ipython.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/file_io.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/file_io.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/functions.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/functions.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/image.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/image.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/io_streams.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/io_streams.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/mount.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/mount.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/network_file_system.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/network_file_system.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/object.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/object.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/output.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/parallel_map.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/parallel_map.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/partial_function.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/partial_function.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/proxy.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/proxy.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/py.typed +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/queue.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/queue.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/retries.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/runner.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/runner.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/running_app.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/sandbox_fs.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/sandbox_fs.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/schedule.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/scheduler_placement.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/secret.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/secret.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/server.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/serving.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/serving.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/snapshot.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/snapshot.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/stream_type.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/token_flow.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/token_flow.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/volume.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/volume.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/workspace.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal/workspace.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_docs/mdmd/types.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/__init__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/api_grpc.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/py.typed +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/modal_version/__main__.py +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/pyproject.toml +0 -0
- {modal-1.4.4.dev21 → modal-1.5.0}/setup.cfg +0 -0
|
@@ -104,7 +104,7 @@ class _EnvironmentManager:
|
|
|
104
104
|
MemberRole = Literal["viewer", "contributor"]
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
def _role_to_proto(role: str) -> api_pb2.EnvironmentRole.ValueType:
|
|
107
|
+
def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
|
|
108
108
|
match role:
|
|
109
109
|
case "viewer":
|
|
110
110
|
return api_pb2.ENVIRONMENT_ROLE_VIEWER
|
|
@@ -34,9 +34,9 @@ def validate_http_server_config(
|
|
|
34
34
|
if not is_server and exit_grace_period is not None and exit_grace_period > 25:
|
|
35
35
|
raise InvalidError("The `exit_grace_period` argument must not exceed 25 seconds.")
|
|
36
36
|
|
|
37
|
-
if not proxy_regions:
|
|
37
|
+
if not proxy_regions or not proxy_regions[0]:
|
|
38
38
|
if is_server:
|
|
39
|
-
raise InvalidError("The `
|
|
39
|
+
raise InvalidError("The `routing_region` argument must be passed.")
|
|
40
40
|
raise InvalidError("The `proxy_regions` argument must be non-empty.")
|
|
41
41
|
|
|
42
42
|
|
|
@@ -49,7 +49,7 @@ class _Server:
|
|
|
49
49
|
Instead, use the [`@app._experimental_server()`](https://modal.com/docs/reference/modal.App#server) decorator.
|
|
50
50
|
|
|
51
51
|
```python notest
|
|
52
|
-
@app._experimental_server(port=8000,
|
|
52
|
+
@app._experimental_server(port=8000, routing_region="us-east")
|
|
53
53
|
class MyServer:
|
|
54
54
|
@modal.enter()
|
|
55
55
|
def start_server(self):
|
|
@@ -1289,7 +1289,7 @@ class _App:
|
|
|
1289
1289
|
port: int = 8000, # Port the HTTP server listens on
|
|
1290
1290
|
startup_timeout: int = 30, # Maximum startup time in seconds
|
|
1291
1291
|
exit_grace_period: int = 0, # Grace period for in-flight requests on shutdown
|
|
1292
|
-
|
|
1292
|
+
routing_region: str = "us-east", # Region to route Server requests through
|
|
1293
1293
|
h2_enabled: bool = False, # Enable HTTP/2
|
|
1294
1294
|
target_concurrency: int | None = None, # Target concurrency for the server; 0 disables autoscaling
|
|
1295
1295
|
cloud: str | None = None, # Cloud provider (aws, gcp, oci, auto)
|
|
@@ -1331,7 +1331,7 @@ class _App:
|
|
|
1331
1331
|
port: Port the HTTP server listens on.
|
|
1332
1332
|
startup_timeout: Maximum startup time in seconds.
|
|
1333
1333
|
exit_grace_period: Grace period for in-flight requests on shutdown.
|
|
1334
|
-
|
|
1334
|
+
routing_region: Region to route Server requests through.
|
|
1335
1335
|
h2_enabled: Enable HTTP/2.
|
|
1336
1336
|
target_concurrency: Target concurrency for the server; 0 disables autoscaling.
|
|
1337
1337
|
cloud: Cloud provider (aws, gcp, oci, auto).
|
|
@@ -1344,7 +1344,7 @@ class _App:
|
|
|
1344
1344
|
|
|
1345
1345
|
Examples:
|
|
1346
1346
|
```python
|
|
1347
|
-
@app._experimental_server(port=8000,
|
|
1347
|
+
@app._experimental_server(port=8000, routing_region="us-east")
|
|
1348
1348
|
class MyServer:
|
|
1349
1349
|
@modal.enter()
|
|
1350
1350
|
def start(self):
|
|
@@ -1361,7 +1361,7 @@ class _App:
|
|
|
1361
1361
|
# Validate HTTP server config
|
|
1362
1362
|
validate_http_server_config(
|
|
1363
1363
|
port=port,
|
|
1364
|
-
proxy_regions=
|
|
1364
|
+
proxy_regions=[routing_region],
|
|
1365
1365
|
startup_timeout=startup_timeout,
|
|
1366
1366
|
exit_grace_period=exit_grace_period,
|
|
1367
1367
|
is_server=True,
|
|
@@ -1373,7 +1373,7 @@ class _App:
|
|
|
1373
1373
|
|
|
1374
1374
|
http_config = api_pb2.HTTPConfig(
|
|
1375
1375
|
port=port,
|
|
1376
|
-
proxy_regions=
|
|
1376
|
+
proxy_regions=[routing_region],
|
|
1377
1377
|
startup_timeout=startup_timeout,
|
|
1378
1378
|
exit_grace_period=exit_grace_period,
|
|
1379
1379
|
h2_enabled=h2_enabled,
|
|
@@ -752,7 +752,7 @@ class _App:
|
|
|
752
752
|
port: int = 8000,
|
|
753
753
|
startup_timeout: int = 30,
|
|
754
754
|
exit_grace_period: int = 0,
|
|
755
|
-
|
|
755
|
+
routing_region: str = "us-east",
|
|
756
756
|
h2_enabled: bool = False,
|
|
757
757
|
target_concurrency: typing.Optional[int] = None,
|
|
758
758
|
cloud: typing.Optional[str] = None,
|
|
@@ -794,7 +794,7 @@ class _App:
|
|
|
794
794
|
port: Port the HTTP server listens on.
|
|
795
795
|
startup_timeout: Maximum startup time in seconds.
|
|
796
796
|
exit_grace_period: Grace period for in-flight requests on shutdown.
|
|
797
|
-
|
|
797
|
+
routing_region: Region to route Server requests through.
|
|
798
798
|
h2_enabled: Enable HTTP/2.
|
|
799
799
|
target_concurrency: Target concurrency for the server; 0 disables autoscaling.
|
|
800
800
|
cloud: Cloud provider (aws, gcp, oci, auto).
|
|
@@ -807,7 +807,7 @@ class _App:
|
|
|
807
807
|
|
|
808
808
|
Examples:
|
|
809
809
|
```python
|
|
810
|
-
@app._experimental_server(port=8000,
|
|
810
|
+
@app._experimental_server(port=8000, routing_region="us-east")
|
|
811
811
|
class MyServer:
|
|
812
812
|
@modal.enter()
|
|
813
813
|
def start(self):
|
|
@@ -1750,7 +1750,7 @@ class App:
|
|
|
1750
1750
|
port: int = 8000,
|
|
1751
1751
|
startup_timeout: int = 30,
|
|
1752
1752
|
exit_grace_period: int = 0,
|
|
1753
|
-
|
|
1753
|
+
routing_region: str = "us-east",
|
|
1754
1754
|
h2_enabled: bool = False,
|
|
1755
1755
|
target_concurrency: typing.Optional[int] = None,
|
|
1756
1756
|
cloud: typing.Optional[str] = None,
|
|
@@ -1790,7 +1790,7 @@ class App:
|
|
|
1790
1790
|
port: Port the HTTP server listens on.
|
|
1791
1791
|
startup_timeout: Maximum startup time in seconds.
|
|
1792
1792
|
exit_grace_period: Grace period for in-flight requests on shutdown.
|
|
1793
|
-
|
|
1793
|
+
routing_region: Region to route Server requests through.
|
|
1794
1794
|
h2_enabled: Enable HTTP/2.
|
|
1795
1795
|
target_concurrency: Target concurrency for the server; 0 disables autoscaling.
|
|
1796
1796
|
cloud: Cloud provider (aws, gcp, oci, auto).
|
|
@@ -1803,7 +1803,7 @@ class App:
|
|
|
1803
1803
|
|
|
1804
1804
|
Examples:
|
|
1805
1805
|
```python
|
|
1806
|
-
@app._experimental_server(port=8000,
|
|
1806
|
+
@app._experimental_server(port=8000, routing_region="us-east")
|
|
1807
1807
|
class MyServer:
|
|
1808
1808
|
@modal.enter()
|
|
1809
1809
|
def start(self):
|
|
@@ -19,12 +19,12 @@ from modal_proto import api_pb2
|
|
|
19
19
|
|
|
20
20
|
from ._help import ModalGroup
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
IMAGE_NAMES_LIST_PAGE_SIZE = 100
|
|
23
|
+
IMAGE_NAMES_LIST_PAGE_DELAY_SECONDS = 1.0
|
|
24
24
|
|
|
25
25
|
image_cli = ModalGroup(name="image", help="Manage Images.")
|
|
26
|
-
|
|
27
|
-
image_cli.add_command(
|
|
26
|
+
image_names_cli = ModalGroup(name="names", help="Manage Modal Image names.")
|
|
27
|
+
image_cli.add_command(image_names_cli)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _print_result_summary(count: int) -> None:
|
|
@@ -60,7 +60,7 @@ async def _iter_tag_pages(
|
|
|
60
60
|
api_pb2.ImageListTagsRequest(
|
|
61
61
|
environment_name=env,
|
|
62
62
|
tag_prefix=prefix,
|
|
63
|
-
max_objects=
|
|
63
|
+
max_objects=IMAGE_NAMES_LIST_PAGE_SIZE,
|
|
64
64
|
page_token=page_token,
|
|
65
65
|
)
|
|
66
66
|
)
|
|
@@ -70,10 +70,10 @@ async def _iter_tag_pages(
|
|
|
70
70
|
page_token = response.next_page_token
|
|
71
71
|
if not page_token:
|
|
72
72
|
return
|
|
73
|
-
await asyncio.sleep(
|
|
73
|
+
await asyncio.sleep(IMAGE_NAMES_LIST_PAGE_DELAY_SECONDS)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
@
|
|
76
|
+
@image_names_cli.command("list", help="List named Images.")
|
|
77
77
|
@env_option
|
|
78
78
|
@click.option("--prefix", default="", help="Only include named image tags that start with this prefix.")
|
|
79
79
|
@click.option("--json", is_flag=True, default=False)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import asyncio
|
|
3
3
|
import io
|
|
4
|
+
import re
|
|
4
5
|
import sys
|
|
5
6
|
from collections.abc import Sequence
|
|
6
7
|
from contextlib import nullcontext
|
|
@@ -126,6 +127,11 @@ def _plain(text: Text | str) -> str:
|
|
|
126
127
|
return text.plain if isinstance(text, Text) else text
|
|
127
128
|
|
|
128
129
|
|
|
130
|
+
def _col_name_to_json_key(name: str) -> str:
|
|
131
|
+
"""Convert a display column name like "App ID" to a snake_case JSON key like "app_id"."""
|
|
132
|
+
return re.sub(r"[^a-zA-Z0-9]+", "_", name).lower().strip("_")
|
|
133
|
+
|
|
134
|
+
|
|
129
135
|
def is_tty() -> bool:
|
|
130
136
|
return OutputManager.get().is_terminal
|
|
131
137
|
|
|
@@ -145,7 +151,8 @@ def display_table(
|
|
|
145
151
|
|
|
146
152
|
output = OutputManager.get()
|
|
147
153
|
if json:
|
|
148
|
-
|
|
154
|
+
json_keys = [_col_name_to_json_key(col_to_str(col)) for col in columns]
|
|
155
|
+
json_data = [{json_keys[i]: _plain(row[i]) for i in range(len(columns))} for row in rows]
|
|
149
156
|
output.print_json(dumps(json_data))
|
|
150
157
|
elif csv:
|
|
151
158
|
csv_buffer = io.StringIO()
|
|
@@ -31,11 +31,7 @@ class _Client:
|
|
|
31
31
|
client_type: int
|
|
32
32
|
|
|
33
33
|
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
server_url: str,
|
|
36
|
-
client_type: int,
|
|
37
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
38
|
-
version: str = "1.4.4.dev21",
|
|
34
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.5.0"
|
|
39
35
|
):
|
|
40
36
|
"""mdmd:hidden
|
|
41
37
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -201,11 +197,7 @@ class Client:
|
|
|
201
197
|
client_type: int
|
|
202
198
|
|
|
203
199
|
def __init__(
|
|
204
|
-
self,
|
|
205
|
-
server_url: str,
|
|
206
|
-
client_type: int,
|
|
207
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
208
|
-
version: str = "1.4.4.dev21",
|
|
200
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.5.0"
|
|
209
201
|
):
|
|
210
202
|
"""mdmd:hidden
|
|
211
203
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -3,12 +3,13 @@ import asyncio
|
|
|
3
3
|
import builtins
|
|
4
4
|
import enum
|
|
5
5
|
import json
|
|
6
|
+
import logging
|
|
6
7
|
import os
|
|
7
8
|
import time
|
|
8
9
|
import typing
|
|
9
10
|
import uuid
|
|
10
11
|
import weakref
|
|
11
|
-
from collections.abc import AsyncGenerator, AsyncIterator, Collection, Sequence
|
|
12
|
+
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Collection, Sequence
|
|
12
13
|
from dataclasses import dataclass
|
|
13
14
|
from pathlib import PurePosixPath
|
|
14
15
|
from typing import TYPE_CHECKING, Any, Literal, Union, overload
|
|
@@ -71,6 +72,42 @@ from .stream_type import StreamType
|
|
|
71
72
|
_default_image: _Image = _Image.debian_slim()
|
|
72
73
|
|
|
73
74
|
|
|
75
|
+
async def _gather_load_with_timings(
|
|
76
|
+
load_coros: Sequence[Awaitable[Any]],
|
|
77
|
+
) -> list[tuple[str, float]]:
|
|
78
|
+
"""Await all loader coroutines concurrently and return [(object_id, elapsed_seconds)] per load."""
|
|
79
|
+
timings: list[tuple[str, float]] = []
|
|
80
|
+
|
|
81
|
+
async def timed(coro: Awaitable[Any]) -> None:
|
|
82
|
+
start = time.monotonic()
|
|
83
|
+
obj = await coro
|
|
84
|
+
timings.append((obj.object_id, time.monotonic() - start))
|
|
85
|
+
|
|
86
|
+
await asyncio.gather(*(timed(c) for c in load_coros))
|
|
87
|
+
return timings
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _format_sandbox_create_timing_log(
|
|
91
|
+
sandbox_id: str,
|
|
92
|
+
total_seconds: float,
|
|
93
|
+
rpc_seconds: float,
|
|
94
|
+
dep_timings: Sequence[tuple[str, float]],
|
|
95
|
+
) -> str:
|
|
96
|
+
"""Format the Sandbox create debug log line, listing the slowest deps first."""
|
|
97
|
+
if dep_timings:
|
|
98
|
+
deps_sorted = sorted(dep_timings, key=lambda t: t[1], reverse=True)
|
|
99
|
+
shown = deps_sorted[:10]
|
|
100
|
+
dep_summary = ", ".join(f"{label}: {elapsed:.2f}s" for label, elapsed in shown)
|
|
101
|
+
if len(deps_sorted) > 10:
|
|
102
|
+
dep_summary += f", +{len(deps_sorted) - 10} more"
|
|
103
|
+
else:
|
|
104
|
+
dep_summary = "none"
|
|
105
|
+
return (
|
|
106
|
+
f"Sandbox {sandbox_id} created in {total_seconds:.2f}s "
|
|
107
|
+
f"(create rpc: {rpc_seconds:.2f}s; dependencies: {dep_summary})"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
74
111
|
# The maximum number of bytes that can be passed to an exec on Linux.
|
|
75
112
|
# Though this is technically a 'server side' limit, it is unlikely to change.
|
|
76
113
|
# getconf ARG_MAX will show this value on a host.
|
|
@@ -349,6 +386,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
349
386
|
pty_info = _Sandbox._default_pty_info()
|
|
350
387
|
|
|
351
388
|
async def _load(self: _Sandbox, resolver: Resolver, load_context: LoadContext, _existing_object_id: str | None):
|
|
389
|
+
load_start = time.monotonic()
|
|
352
390
|
# An already-hydrated image (e.g. one returned by
|
|
353
391
|
# `Sandbox.snapshot_directory`) is skipped — there's nothing to load.
|
|
354
392
|
dep_tasks: list = []
|
|
@@ -365,8 +403,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
365
403
|
dep_tasks.append(resolver.load(cloud_bucket_mount.secret, load_context))
|
|
366
404
|
if proxy:
|
|
367
405
|
dep_tasks.append(resolver.load(proxy, load_context))
|
|
368
|
-
if dep_tasks
|
|
369
|
-
await asyncio.gather(*dep_tasks)
|
|
406
|
+
dep_timings = await _gather_load_with_timings(dep_tasks) if dep_tasks else []
|
|
370
407
|
|
|
371
408
|
# Validate that the same volume (by object_id) isn't mounted at multiple paths
|
|
372
409
|
validate_volumes_by_object_id(validated_volumes)
|
|
@@ -444,10 +481,16 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
444
481
|
create_req = api_pb2.SandboxCreateRequest(
|
|
445
482
|
app_id=load_context.app_id, definition=definition, tags=tag_protos
|
|
446
483
|
)
|
|
484
|
+
rpc_start = time.monotonic()
|
|
447
485
|
create_resp = await load_context.client.stub.SandboxCreate(create_req)
|
|
486
|
+
rpc_elapsed = time.monotonic() - rpc_start
|
|
448
487
|
sandbox_id = create_resp.sandbox_id
|
|
449
488
|
self._hydrate(sandbox_id, load_context.client, None)
|
|
450
489
|
|
|
490
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
491
|
+
total_elapsed = time.monotonic() - load_start
|
|
492
|
+
logger.debug(_format_sandbox_create_timing_log(sandbox_id, total_elapsed, rpc_elapsed, dep_timings))
|
|
493
|
+
|
|
451
494
|
return _Sandbox._from_loader(_load, "Sandbox()", load_context_overrides=LoadContext.empty())
|
|
452
495
|
|
|
453
496
|
@staticmethod
|
|
@@ -864,6 +907,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
864
907
|
)
|
|
865
908
|
|
|
866
909
|
async def _load(self: _Sandbox, resolver: Resolver, load_context: LoadContext, _existing_object_id: str | None):
|
|
910
|
+
load_start = time.monotonic()
|
|
867
911
|
dep_tasks: list = []
|
|
868
912
|
if not image._is_hydrated:
|
|
869
913
|
dep_tasks.append(resolver.load(image, load_context))
|
|
@@ -874,8 +918,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
874
918
|
for _, cloud_bucket_mount in cloud_bucket_mounts:
|
|
875
919
|
if cloud_bucket_mount.secret:
|
|
876
920
|
dep_tasks.append(resolver.load(cloud_bucket_mount.secret, load_context))
|
|
877
|
-
if dep_tasks
|
|
878
|
-
await asyncio.gather(*dep_tasks)
|
|
921
|
+
dep_timings = await _gather_load_with_timings(dep_tasks) if dep_tasks else []
|
|
879
922
|
|
|
880
923
|
validate_volumes_by_object_id(validated_volumes)
|
|
881
924
|
|
|
@@ -909,9 +952,11 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
909
952
|
create_req = api_pb2.SandboxCreateV2Request(app_id=load_context.app_id, definition=definition)
|
|
910
953
|
assert load_context.client._auth_token_manager
|
|
911
954
|
auth_token = await load_context.client._auth_token_manager.get_token()
|
|
955
|
+
rpc_start = time.monotonic()
|
|
912
956
|
create_resp = await load_context.client.stub.SandboxCreateV2(
|
|
913
957
|
create_req, metadata=[("x-modal-auth-token", auth_token)]
|
|
914
958
|
)
|
|
959
|
+
rpc_elapsed = time.monotonic() - rpc_start
|
|
915
960
|
sandbox_id = create_resp.sandbox_id
|
|
916
961
|
self._hydrate(sandbox_id, load_context.client, None)
|
|
917
962
|
self._is_v2 = True
|
|
@@ -922,6 +967,10 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
922
967
|
for t in create_resp.tunnels
|
|
923
968
|
}
|
|
924
969
|
|
|
970
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
971
|
+
total_elapsed = time.monotonic() - load_start
|
|
972
|
+
logger.debug(_format_sandbox_create_timing_log(sandbox_id, total_elapsed, rpc_elapsed, dep_timings))
|
|
973
|
+
|
|
925
974
|
obj = _Sandbox._from_loader(_load, "Sandbox()", load_context_overrides=LoadContext.empty())
|
|
926
975
|
|
|
927
976
|
app_id: str | None = None
|
|
@@ -1247,8 +1296,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1247
1296
|
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1248
1297
|
```
|
|
1249
1298
|
"""
|
|
1250
|
-
self._ensure_v1("mount_image")
|
|
1251
|
-
|
|
1252
1299
|
if not isinstance(image, _Image):
|
|
1253
1300
|
raise TypeError(f"Sandbox.mount_image(image=...) expects an Image object, got {image!r}")
|
|
1254
1301
|
|
|
@@ -1299,8 +1346,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1299
1346
|
path: Absolute mount point directory to unmount.
|
|
1300
1347
|
|
|
1301
1348
|
"""
|
|
1302
|
-
self._ensure_v1("unmount_image")
|
|
1303
|
-
|
|
1304
1349
|
task_id = await self._get_task_id()
|
|
1305
1350
|
command_router_client = await self._get_command_router_client(task_id)
|
|
1306
1351
|
|
|
@@ -1344,8 +1389,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1344
1389
|
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1345
1390
|
```
|
|
1346
1391
|
"""
|
|
1347
|
-
self._ensure_v1("snapshot_directory")
|
|
1348
|
-
|
|
1349
1392
|
wire_ttl_seconds = _ttl_to_wire_ttl(ttl)
|
|
1350
1393
|
|
|
1351
1394
|
task_id = await self._get_task_id()
|
|
@@ -29,6 +29,18 @@ import pathlib
|
|
|
29
29
|
import typing
|
|
30
30
|
import typing_extensions
|
|
31
31
|
|
|
32
|
+
async def _gather_load_with_timings(
|
|
33
|
+
load_coros: collections.abc.Sequence[collections.abc.Awaitable[typing.Any]],
|
|
34
|
+
) -> list[tuple[str, float]]:
|
|
35
|
+
"""Await all loader coroutines concurrently and return [(object_id, elapsed_seconds)] per load."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
def _format_sandbox_create_timing_log(
|
|
39
|
+
sandbox_id: str, total_seconds: float, rpc_seconds: float, dep_timings: collections.abc.Sequence[tuple[str, float]]
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Format the Sandbox create debug log line, listing the slowest deps first."""
|
|
42
|
+
...
|
|
43
|
+
|
|
32
44
|
def _ttl_to_wire_ttl(ttl: typing.Optional[int]) -> int:
|
|
33
45
|
"""Convert a TTL value to the wire format, validating the input."""
|
|
34
46
|
...
|
|
@@ -14,7 +14,7 @@ class Server:
|
|
|
14
14
|
Instead, use the [`@app._experimental_server()`](https://modal.com/docs/reference/modal.App#server) decorator.
|
|
15
15
|
|
|
16
16
|
```python notest
|
|
17
|
-
@app._experimental_server(port=8000,
|
|
17
|
+
@app._experimental_server(port=8000, routing_region="us-east")
|
|
18
18
|
class MyServer:
|
|
19
19
|
@modal.enter()
|
|
20
20
|
def start_server(self):
|