modal 1.5.6.dev20__tar.gz → 1.5.6.dev22__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.5.6.dev20 → modal-1.5.6.dev22}/PKG-INFO +1 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/pty.py +2 -2
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/rich.py +16 -4
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/user_code_imports.py +1 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_tunnel.py +0 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/app.py +22 -2
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/app.pyi +8 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/entry_point.py +0 -4
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/client.pyi +2 -2
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/__init__.py +4 -0
- modal-1.5.6.dev22/modal/experimental/_flash_container_entry.py +66 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/flash.py +5 -67
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/flash.pyi +0 -30
- modal-1.5.6.dev22/modal/experimental/flash_server.py +39 -0
- modal-1.5.6.dev22/modal/experimental/flash_server.pyi +38 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/functions.pyi +6 -6
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox.py +6 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/SOURCES.txt +3 -4
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_grpc.py +48 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2.py +1038 -901
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2.pyi +314 -2
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2_grpc.py +99 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2_grpc.pyi +30 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/modal_api_grpc.py +3 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_version/__init__.py +1 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/pyproject.toml +0 -2
- modal-1.5.6.dev20/modal/cli/launch.py +0 -141
- modal-1.5.6.dev20/modal/cli/programs/run_jupyter.py +0 -95
- modal-1.5.6.dev20/modal/cli/programs/vscode.py +0 -110
- modal-1.5.6.dev20/modal_docs/mdmd/__init__.py +0 -1
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/LICENSE +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/README.md +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/__main__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_billing.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_clustered_functions.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_environments.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_function_variants.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_functions.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_image.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_location.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_logs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_object.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/manager.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/status.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_partial_function.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_resources.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_server.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_workspace.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/billing.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_logs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_stats.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/app.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/cluster.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/config.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/container.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/function.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/image.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/import_refs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/profile.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/server.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/token.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/utils.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/volume.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/workspace.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/client.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cls.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cls.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/config.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/container_process.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/dict.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/environments.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/exception.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/functions.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/image.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/image.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/io_streams.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/io_streams.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/mount.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/network_file_system.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/network_file_system.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/object.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/object.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/outbound_policy.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/outbound_policy.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/output.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/partial_function.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/partial_function.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/proxy.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/py.typed +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/queue.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/retries.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/runner.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/running_app.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox_fs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/schedule.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/secret.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/server.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/server.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/serving.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/token_flow.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/types.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/volume.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/volume.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/workspace.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/workspace.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev20/modal/cli/programs → modal-1.5.6.dev22/modal_docs}/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev20/modal_docs → modal-1.5.6.dev22/modal_docs/mdmd}/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev20 → modal-1.5.6.dev22}/setup.cfg +0 -0
|
@@ -128,8 +128,8 @@ async def get_app_logs_loop(
|
|
|
128
128
|
async def _put_log(log_batch: api_pb2.TaskLogsBatch, log: api_pb2.TaskLogs):
|
|
129
129
|
if log.task_state:
|
|
130
130
|
output_mgr.update_task_state(log_batch.task_id, log.task_state)
|
|
131
|
-
if
|
|
132
|
-
#
|
|
131
|
+
if log_batch.function_id:
|
|
132
|
+
# Any task state the server reports means the task is on a worker, so close queueing progress
|
|
133
133
|
output_mgr.update_queueing_progress(
|
|
134
134
|
function_id=log_batch.function_id, completed=1, total=1, description=None
|
|
135
135
|
)
|
|
@@ -14,6 +14,7 @@ import io
|
|
|
14
14
|
import platform
|
|
15
15
|
import re
|
|
16
16
|
import sys
|
|
17
|
+
import time
|
|
17
18
|
from collections.abc import Callable, Generator
|
|
18
19
|
from datetime import timedelta
|
|
19
20
|
from pathlib import Path
|
|
@@ -218,6 +219,9 @@ _FD_COLORS: dict[int, AnsiColor] = {
|
|
|
218
219
|
}
|
|
219
220
|
_ANSI_RESET = "\033[0m"
|
|
220
221
|
|
|
222
|
+
# Seconds to wait after a task is scheduled before showing queueing progress again for a Function
|
|
223
|
+
QUEUEING_STRAGGLER_GRACE_SECS = 30.0
|
|
224
|
+
|
|
221
225
|
|
|
222
226
|
class RichOutputManager(OutputManager):
|
|
223
227
|
"""Rich-based implementation of OutputManager.
|
|
@@ -230,6 +234,7 @@ class RichOutputManager(OutputManager):
|
|
|
230
234
|
_stderr_console: Console
|
|
231
235
|
_task_states: dict[str, int]
|
|
232
236
|
_task_progress_items: dict[tuple[str, int], TaskID]
|
|
237
|
+
_function_scheduled_at: dict[str, float]
|
|
233
238
|
_current_render_group: Group | None
|
|
234
239
|
_function_progress: Progress | None
|
|
235
240
|
_function_queueing_progress: Progress | None
|
|
@@ -264,6 +269,7 @@ class RichOutputManager(OutputManager):
|
|
|
264
269
|
self._stderr_console = _make_console(stderr=True, highlight=True)
|
|
265
270
|
self._task_states = {}
|
|
266
271
|
self._task_progress_items = {}
|
|
272
|
+
self._function_scheduled_at = {}
|
|
267
273
|
self._current_render_group = None
|
|
268
274
|
self._function_progress = None
|
|
269
275
|
self._function_queueing_progress = None
|
|
@@ -587,6 +593,9 @@ class RichOutputManager(OutputManager):
|
|
|
587
593
|
task_description = description or f"'{function_id}' function waiting on worker"
|
|
588
594
|
task_description = escape(task_description)
|
|
589
595
|
task_desc = f"[yellow]{task_description}. Time in queue:"
|
|
596
|
+
is_completion = completed == total
|
|
597
|
+
if is_completion:
|
|
598
|
+
self._function_scheduled_at[function_id] = time.monotonic()
|
|
590
599
|
if task_key in self._task_progress_items:
|
|
591
600
|
progress_task_id = self._task_progress_items[task_key]
|
|
592
601
|
try:
|
|
@@ -596,14 +605,17 @@ class RichOutputManager(OutputManager):
|
|
|
596
605
|
completed=completed,
|
|
597
606
|
total=total,
|
|
598
607
|
)
|
|
599
|
-
if
|
|
608
|
+
if is_completion:
|
|
600
609
|
del self._task_progress_items[task_key]
|
|
601
610
|
self._function_queueing_progress_bar.remove_task(progress_task_id)
|
|
602
611
|
except KeyError:
|
|
603
612
|
pass
|
|
604
|
-
elif
|
|
605
|
-
|
|
606
|
-
|
|
613
|
+
elif not is_completion:
|
|
614
|
+
scheduled_at = self._function_scheduled_at.get(function_id)
|
|
615
|
+
if scheduled_at is None or (time.monotonic() - scheduled_at) >= QUEUEING_STRAGGLER_GRACE_SECS:
|
|
616
|
+
# Create new bar for queued function
|
|
617
|
+
progress_task_id = self._function_queueing_progress_bar.add_task(task_desc, start=True, total=None)
|
|
618
|
+
self._task_progress_items[task_key] = progress_task_id
|
|
607
619
|
|
|
608
620
|
def _get_line_buffer(self, fd: int, mode: str, callback: Callable[[str], None]) -> LineBufferedOutput:
|
|
609
621
|
"""Get or create a LineBufferedOutput for the given file descriptor and mode.
|
|
@@ -29,7 +29,7 @@ from modal._utils.function_utils import (
|
|
|
29
29
|
from modal.app import _App
|
|
30
30
|
from modal.config import logger
|
|
31
31
|
from modal.exception import ExecutionError, InvalidError
|
|
32
|
-
from modal.experimental.
|
|
32
|
+
from modal.experimental._flash_container_entry import _FlashContainerEntry
|
|
33
33
|
from modal_proto import api_pb2
|
|
34
34
|
|
|
35
35
|
if typing.TYPE_CHECKING:
|
|
@@ -173,7 +173,6 @@ async def _forward(
|
|
|
173
173
|
forwarding code in an `@enter` lifecycle method of an @app.cls, to only make a single
|
|
174
174
|
ssh server and port for each container (and not one for each input to the function).
|
|
175
175
|
"""
|
|
176
|
-
|
|
177
176
|
if not isinstance(port, int):
|
|
178
177
|
raise InvalidError(f"The port argument should be an int, not {port!r}")
|
|
179
178
|
if port < 1 or port > 65535:
|
|
@@ -1332,6 +1332,7 @@ class _App:
|
|
|
1332
1332
|
memory: int | tuple[int, int] | None = None, # Memory in MiB to request
|
|
1333
1333
|
ephemeral_disk: int | None = None, # Ephemeral disk size in MiB
|
|
1334
1334
|
target_concurrency: float | None = None, # Target concurrency for the server; 0 disables autoscaling
|
|
1335
|
+
max_concurrency: int | None = None, # Maximum concurrent requests per container
|
|
1335
1336
|
min_containers: int | None = None, # Minimum number of containers to keep warm
|
|
1336
1337
|
max_containers: int | None = None, # Maximum number of containers
|
|
1337
1338
|
buffer_containers: int | None = None, # Additional idle containers under active load
|
|
@@ -1381,6 +1382,9 @@ class _App:
|
|
|
1381
1382
|
target_concurrency:
|
|
1382
1383
|
Target number of concurrent requests per container; 0 disables autoscaling. May be
|
|
1383
1384
|
fractional, e.g. 1.5 to target three concurrent requests per two containers.
|
|
1385
|
+
max_concurrency:
|
|
1386
|
+
Maximum number of concurrent requests per container. Requests above this limit
|
|
1387
|
+
receive a 503 response. If set to 0 or unset, request concurrency is unlimited.
|
|
1384
1388
|
min_containers: Minimum number of containers to keep running regardless of demand.
|
|
1385
1389
|
max_containers: Limit on the number of containers that can be concurrently running.
|
|
1386
1390
|
buffer_containers: Extra containers to scale up beyond current demand.
|
|
@@ -1430,6 +1434,22 @@ class _App:
|
|
|
1430
1434
|
if target_concurrency is not None:
|
|
1431
1435
|
validate_target_concurrency(target_concurrency, "target_concurrency", allow_fractional=True)
|
|
1432
1436
|
|
|
1437
|
+
if max_concurrency is not None:
|
|
1438
|
+
if experimental_options is not None and "max_concurrency" in experimental_options:
|
|
1439
|
+
raise InvalidError(
|
|
1440
|
+
"`max_concurrency` cannot be set both as an app.server parameter and an experimental option."
|
|
1441
|
+
)
|
|
1442
|
+
if isinstance(max_concurrency, bool) or not isinstance(max_concurrency, (int, float)):
|
|
1443
|
+
raise InvalidError(
|
|
1444
|
+
f"The `max_concurrency` argument must be a number, not {type(max_concurrency).__name__}."
|
|
1445
|
+
)
|
|
1446
|
+
if not isinstance(max_concurrency, int):
|
|
1447
|
+
raise InvalidError("The `max_concurrency` argument must be an integer.")
|
|
1448
|
+
if max_concurrency < 0:
|
|
1449
|
+
raise InvalidError("The `max_concurrency` argument must be non-negative.")
|
|
1450
|
+
if max_concurrency and target_concurrency is not None and target_concurrency > max_concurrency:
|
|
1451
|
+
raise InvalidError("The `target_concurrency` argument cannot be greater than `max_concurrency`.")
|
|
1452
|
+
|
|
1433
1453
|
if scaleup_window is not None and scaleup_window <= 0:
|
|
1434
1454
|
raise InvalidError("`scaleup_window` must be > 0")
|
|
1435
1455
|
|
|
@@ -1491,8 +1511,8 @@ class _App:
|
|
|
1491
1511
|
scaledown_window=scaledown_window,
|
|
1492
1512
|
proxy=proxy,
|
|
1493
1513
|
retries=None, # No support for Server level retries
|
|
1494
|
-
max_concurrent_inputs=
|
|
1495
|
-
target_concurrent_inputs=target_concurrency,
|
|
1514
|
+
max_concurrent_inputs=max_concurrency,
|
|
1515
|
+
target_concurrent_inputs=target_concurrency,
|
|
1496
1516
|
batch_max_size=None, # No support for Server level batching
|
|
1497
1517
|
batch_wait_ms=None, # No support for Server level batching
|
|
1498
1518
|
startup_timeout=startup_timeout,
|
|
@@ -759,6 +759,7 @@ class _App:
|
|
|
759
759
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
|
760
760
|
ephemeral_disk: typing.Optional[int] = None,
|
|
761
761
|
target_concurrency: typing.Optional[float] = None,
|
|
762
|
+
max_concurrency: typing.Optional[int] = None,
|
|
762
763
|
min_containers: typing.Optional[int] = None,
|
|
763
764
|
max_containers: typing.Optional[int] = None,
|
|
764
765
|
buffer_containers: typing.Optional[int] = None,
|
|
@@ -808,6 +809,9 @@ class _App:
|
|
|
808
809
|
target_concurrency:
|
|
809
810
|
Target number of concurrent requests per container; 0 disables autoscaling. May be
|
|
810
811
|
fractional, e.g. 1.5 to target three concurrent requests per two containers.
|
|
812
|
+
max_concurrency:
|
|
813
|
+
Maximum number of concurrent requests per container. Requests above this limit
|
|
814
|
+
receive a 503 response. If set to 0 or unset, request concurrency is unlimited.
|
|
811
815
|
min_containers: Minimum number of containers to keep running regardless of demand.
|
|
812
816
|
max_containers: Limit on the number of containers that can be concurrently running.
|
|
813
817
|
buffer_containers: Extra containers to scale up beyond current demand.
|
|
@@ -1808,6 +1812,7 @@ class App:
|
|
|
1808
1812
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
|
1809
1813
|
ephemeral_disk: typing.Optional[int] = None,
|
|
1810
1814
|
target_concurrency: typing.Optional[float] = None,
|
|
1815
|
+
max_concurrency: typing.Optional[int] = None,
|
|
1811
1816
|
min_containers: typing.Optional[int] = None,
|
|
1812
1817
|
max_containers: typing.Optional[int] = None,
|
|
1813
1818
|
buffer_containers: typing.Optional[int] = None,
|
|
@@ -1857,6 +1862,9 @@ class App:
|
|
|
1857
1862
|
target_concurrency:
|
|
1858
1863
|
Target number of concurrent requests per container; 0 disables autoscaling. May be
|
|
1859
1864
|
fractional, e.g. 1.5 to target three concurrent requests per two containers.
|
|
1865
|
+
max_concurrency:
|
|
1866
|
+
Maximum number of concurrent requests per container. Requests above this limit
|
|
1867
|
+
receive a 503 response. If set to 0 or unset, request concurrency is unlimited.
|
|
1860
1868
|
min_containers: Minimum number of containers to keep running regardless of demand.
|
|
1861
1869
|
max_containers: Limit on the number of containers that can be concurrently running.
|
|
1862
1870
|
buffer_containers: Extra containers to scale up beyond current demand.
|
|
@@ -22,7 +22,6 @@ from .endpoint import endpoint_cli
|
|
|
22
22
|
from .environment import environment_cli
|
|
23
23
|
from .function import function_cli
|
|
24
24
|
from .image import image_cli
|
|
25
|
-
from .launch import launch_cli
|
|
26
25
|
from .logo import print_logo
|
|
27
26
|
from .network_file_system import nfs_cli
|
|
28
27
|
from .profile import profile_cli
|
|
@@ -112,9 +111,6 @@ entrypoint_cli.add_command(run.deploy, "deploy", panel="Commands")
|
|
|
112
111
|
entrypoint_cli.add_command(run.serve, "serve", panel="Commands")
|
|
113
112
|
entrypoint_cli.add_command(shell_module.shell, "shell", panel="Commands")
|
|
114
113
|
entrypoint_cli.add_command(run.run, "run", panel="Commands")
|
|
115
|
-
# launch is hidden as it's experimental and we're tracking towards removing it
|
|
116
|
-
entrypoint_cli.add_command(launch_cli, hidden=True)
|
|
117
|
-
|
|
118
114
|
entrypoint_cli.add_command(app_cli, panel="Deployments")
|
|
119
115
|
entrypoint_cli.add_command(container_cli, panel="Deployments")
|
|
120
116
|
entrypoint_cli.add_command(endpoint_cli, panel="Deployments")
|
|
@@ -77,7 +77,7 @@ class _Client:
|
|
|
77
77
|
server_url: str,
|
|
78
78
|
client_type: int,
|
|
79
79
|
credentials: typing.Optional[tuple[str, str]],
|
|
80
|
-
version: str = "1.5.6.
|
|
80
|
+
version: str = "1.5.6.dev22",
|
|
81
81
|
*,
|
|
82
82
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
83
83
|
):
|
|
@@ -277,7 +277,7 @@ class Client:
|
|
|
277
277
|
server_url: str,
|
|
278
278
|
client_type: int,
|
|
279
279
|
credentials: typing.Optional[tuple[str, str]],
|
|
280
|
-
version: str = "1.5.6.
|
|
280
|
+
version: str = "1.5.6.dev22",
|
|
281
281
|
*,
|
|
282
282
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
283
283
|
):
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from modal._clustered_functions import get_cluster_info
|
|
6
|
+
from modal_proto import api_pb2
|
|
7
|
+
|
|
8
|
+
from .._runtime.task_lifecycle_manager import UserException
|
|
9
|
+
from ..config import logger
|
|
10
|
+
from ..exception import InvalidError
|
|
11
|
+
from ..experimental.flash_server import server_forward
|
|
12
|
+
from .flash import flash_forward
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _FlashLifecycleManager(Protocol):
|
|
16
|
+
def stop(self) -> None: ...
|
|
17
|
+
|
|
18
|
+
def close(self) -> None: ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _FlashContainerEntry:
|
|
22
|
+
"""
|
|
23
|
+
A class that manages the lifecycle of Flash manager for Flash containers.
|
|
24
|
+
|
|
25
|
+
It is intentional that stop() runs before exit handlers and close(): new requests stop arriving
|
|
26
|
+
before exit handlers run, and in-flight requests keep working until the exit grace period has
|
|
27
|
+
elapsed. For server containers both calls are no-ops.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
flash_manager: _FlashLifecycleManager | None
|
|
31
|
+
|
|
32
|
+
def __init__(self, http_config: api_pb2.HTTPConfig, is_server: bool = False):
|
|
33
|
+
self.http_config: api_pb2.HTTPConfig = http_config
|
|
34
|
+
self.flash_manager = None
|
|
35
|
+
self.is_server = is_server
|
|
36
|
+
|
|
37
|
+
def enter(self):
|
|
38
|
+
if self.http_config != api_pb2.HTTPConfig():
|
|
39
|
+
try:
|
|
40
|
+
if self.is_server:
|
|
41
|
+
self.flash_manager = server_forward()
|
|
42
|
+
else:
|
|
43
|
+
try:
|
|
44
|
+
rank = get_cluster_info().rank
|
|
45
|
+
if rank != 0:
|
|
46
|
+
return
|
|
47
|
+
except InvalidError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
self.flash_manager = flash_forward(
|
|
51
|
+
self.http_config.port,
|
|
52
|
+
startup_timeout=self.http_config.startup_timeout,
|
|
53
|
+
exit_grace_period=self.http_config.exit_grace_period,
|
|
54
|
+
h2_enabled=self.http_config.h2_enabled,
|
|
55
|
+
)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.warning(f"[Modal Flash] Startup failed: {e}")
|
|
58
|
+
raise UserException()
|
|
59
|
+
|
|
60
|
+
def stop(self):
|
|
61
|
+
if self.flash_manager:
|
|
62
|
+
self.flash_manager.stop()
|
|
63
|
+
|
|
64
|
+
def close(self):
|
|
65
|
+
if self.flash_manager:
|
|
66
|
+
self.flash_manager.close()
|
|
@@ -8,12 +8,10 @@ from collections.abc import Callable
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
from urllib.parse import urlparse
|
|
10
10
|
|
|
11
|
-
from modal._clustered_functions import get_cluster_info
|
|
12
11
|
from modal._partial_function import _PartialFunctionFlags
|
|
13
12
|
from modal.cls import _Cls
|
|
14
13
|
from modal_proto import api_pb2
|
|
15
14
|
|
|
16
|
-
from .._runtime.task_lifecycle_manager import UserException
|
|
17
15
|
from .._server import validate_http_server_config
|
|
18
16
|
from .._tunnel import _forward as _forward_tunnel
|
|
19
17
|
from .._utils.async_utils import synchronize_api, synchronizer
|
|
@@ -35,17 +33,15 @@ class _FlashManager:
|
|
|
35
33
|
startup_timeout: int = 30,
|
|
36
34
|
exit_grace_period: int = 0,
|
|
37
35
|
h2_enabled: bool = False,
|
|
38
|
-
is_server: bool = False,
|
|
39
36
|
):
|
|
40
37
|
self.client = client
|
|
41
38
|
self.port = port
|
|
42
39
|
self.process = process
|
|
40
|
+
self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
|
|
43
41
|
# Health check is not currently being used
|
|
44
42
|
self.health_check_url = health_check_url
|
|
45
43
|
self.startup_timeout = startup_timeout
|
|
46
44
|
self.exit_grace_period = exit_grace_period
|
|
47
|
-
self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
|
|
48
|
-
self.is_server = is_server
|
|
49
45
|
self.stopped = False
|
|
50
46
|
self.num_heartbeat_failures = 0
|
|
51
47
|
self.task_id = os.environ["MODAL_TASK_ID"]
|
|
@@ -85,16 +81,8 @@ class _FlashManager:
|
|
|
85
81
|
host = parsed_url.hostname
|
|
86
82
|
assert host is not None, f"Tunnel URL has no host: {self.tunnel.url}"
|
|
87
83
|
port = parsed_url.port or 443
|
|
88
|
-
|
|
89
|
-
if self.is_server:
|
|
90
|
-
await self._start_server_tunnel()
|
|
91
|
-
return
|
|
92
84
|
await self._start_flash_registration(host, port)
|
|
93
85
|
|
|
94
|
-
async def _start_server_tunnel(self) -> None:
|
|
95
|
-
# Worker-side HTTP relay owns Flash registration and drain for server tasks.
|
|
96
|
-
logger.warning(f"[Modal Flash] Server tunnel opened at {self.tunnel.url}.")
|
|
97
|
-
|
|
98
86
|
async def _start_flash_registration(self, host: str, port: int) -> None:
|
|
99
87
|
try:
|
|
100
88
|
await self._wait_for_port_success(host, port)
|
|
@@ -231,12 +219,9 @@ class _FlashManager:
|
|
|
231
219
|
async def close(self):
|
|
232
220
|
if not self.stopped:
|
|
233
221
|
await self.stop()
|
|
234
|
-
|
|
235
|
-
#
|
|
236
|
-
|
|
237
|
-
if not self.is_server:
|
|
238
|
-
await asyncio.sleep(self.exit_grace_period)
|
|
239
|
-
|
|
222
|
+
# The container is already deregistered at this point, so waiting out the grace period
|
|
223
|
+
# gives in-flight requests time to finish before the tunnel goes away.
|
|
224
|
+
await asyncio.sleep(self.exit_grace_period)
|
|
240
225
|
logger.warning(f"[Modal Flash] Closing tunnel on {self.tunnel.url}.")
|
|
241
226
|
await self.tunnel_manager.__aexit__(*sys.exc_info())
|
|
242
227
|
|
|
@@ -252,7 +237,6 @@ async def flash_forward(
|
|
|
252
237
|
startup_timeout: int = 30,
|
|
253
238
|
exit_grace_period: int = 0,
|
|
254
239
|
h2_enabled: bool = False,
|
|
255
|
-
is_server: bool = False,
|
|
256
240
|
) -> _FlashManager:
|
|
257
241
|
"""
|
|
258
242
|
Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
|
|
@@ -269,7 +253,6 @@ async def flash_forward(
|
|
|
269
253
|
startup_timeout=startup_timeout,
|
|
270
254
|
exit_grace_period=exit_grace_period,
|
|
271
255
|
h2_enabled=h2_enabled,
|
|
272
|
-
is_server=is_server,
|
|
273
256
|
)
|
|
274
257
|
await manager._start()
|
|
275
258
|
return manager
|
|
@@ -314,7 +297,7 @@ def _http_server(
|
|
|
314
297
|
raise InvalidError(
|
|
315
298
|
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.http_server()`."
|
|
316
299
|
)
|
|
317
|
-
validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period
|
|
300
|
+
validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period)
|
|
318
301
|
|
|
319
302
|
from modal._partial_function import _PartialFunction, _PartialFunctionParams
|
|
320
303
|
|
|
@@ -342,48 +325,3 @@ def _http_server(
|
|
|
342
325
|
|
|
343
326
|
|
|
344
327
|
http_server = synchronize_api(_http_server, target_module=__name__)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
class _FlashContainerEntry:
|
|
348
|
-
"""
|
|
349
|
-
A class that manages the lifecycle of Flash manager for Flash containers.
|
|
350
|
-
|
|
351
|
-
It is intentional that stop() runs before exit handlers and close().
|
|
352
|
-
This ensures the container is deregistered first, preventing new requests from being routed to it
|
|
353
|
-
while exit handlers execute and the exit grace period elapses, before finally closing the tunnel.
|
|
354
|
-
"""
|
|
355
|
-
|
|
356
|
-
flash_manager: FlashManager | None # type: ignore
|
|
357
|
-
|
|
358
|
-
def __init__(self, http_config: api_pb2.HTTPConfig, is_server: bool = False):
|
|
359
|
-
self.http_config: api_pb2.HTTPConfig = http_config
|
|
360
|
-
self.flash_manager = None
|
|
361
|
-
self.is_server = is_server
|
|
362
|
-
|
|
363
|
-
def enter(self):
|
|
364
|
-
if self.http_config != api_pb2.HTTPConfig():
|
|
365
|
-
try:
|
|
366
|
-
rank = get_cluster_info().rank
|
|
367
|
-
if rank != 0:
|
|
368
|
-
return
|
|
369
|
-
except InvalidError:
|
|
370
|
-
pass
|
|
371
|
-
try:
|
|
372
|
-
self.flash_manager = flash_forward(
|
|
373
|
-
self.http_config.port,
|
|
374
|
-
startup_timeout=self.http_config.startup_timeout,
|
|
375
|
-
exit_grace_period=self.http_config.exit_grace_period,
|
|
376
|
-
h2_enabled=self.http_config.h2_enabled,
|
|
377
|
-
is_server=self.is_server,
|
|
378
|
-
)
|
|
379
|
-
except Exception as e:
|
|
380
|
-
logger.warning(f"[Modal Flash] Startup failed: {e}")
|
|
381
|
-
raise UserException()
|
|
382
|
-
|
|
383
|
-
def stop(self):
|
|
384
|
-
if self.flash_manager:
|
|
385
|
-
self.flash_manager.stop()
|
|
386
|
-
|
|
387
|
-
def close(self):
|
|
388
|
-
if self.flash_manager:
|
|
389
|
-
self.flash_manager.close()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import modal.client
|
|
2
|
-
import modal_proto.api_pb2
|
|
3
2
|
import subprocess
|
|
4
3
|
import typing
|
|
5
4
|
import typing_extensions
|
|
@@ -14,7 +13,6 @@ class _FlashManager:
|
|
|
14
13
|
startup_timeout: int = 30,
|
|
15
14
|
exit_grace_period: int = 0,
|
|
16
15
|
h2_enabled: bool = False,
|
|
17
|
-
is_server: bool = False,
|
|
18
16
|
):
|
|
19
17
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
20
18
|
...
|
|
@@ -23,7 +21,6 @@ class _FlashManager:
|
|
|
23
21
|
self, process: typing.Optional[subprocess.Popen], timeout: float = 0.5
|
|
24
22
|
) -> tuple[bool, typing.Optional[Exception]]: ...
|
|
25
23
|
async def _start(self): ...
|
|
26
|
-
async def _start_server_tunnel(self) -> None: ...
|
|
27
24
|
async def _start_flash_registration(self, host: str, port: int) -> None: ...
|
|
28
25
|
async def _deregister(self): ...
|
|
29
26
|
async def _drain_container(self):
|
|
@@ -46,7 +43,6 @@ class FlashManager:
|
|
|
46
43
|
startup_timeout: int = 30,
|
|
47
44
|
exit_grace_period: int = 0,
|
|
48
45
|
h2_enabled: bool = False,
|
|
49
|
-
is_server: bool = False,
|
|
50
46
|
): ...
|
|
51
47
|
|
|
52
48
|
class __is_port_connection_healthy_spec(typing_extensions.Protocol):
|
|
@@ -65,12 +61,6 @@ class FlashManager:
|
|
|
65
61
|
|
|
66
62
|
_start: ___start_spec
|
|
67
63
|
|
|
68
|
-
class ___start_server_tunnel_spec(typing_extensions.Protocol):
|
|
69
|
-
def __call__(self, /) -> None: ...
|
|
70
|
-
async def aio(self, /) -> None: ...
|
|
71
|
-
|
|
72
|
-
_start_server_tunnel: ___start_server_tunnel_spec
|
|
73
|
-
|
|
74
64
|
class ___start_flash_registration_spec(typing_extensions.Protocol):
|
|
75
65
|
def __call__(self, /, host: str, port: int) -> None: ...
|
|
76
66
|
async def aio(self, /, host: str, port: int) -> None: ...
|
|
@@ -130,7 +120,6 @@ class __flash_forward_spec(typing_extensions.Protocol):
|
|
|
130
120
|
startup_timeout: int = 30,
|
|
131
121
|
exit_grace_period: int = 0,
|
|
132
122
|
h2_enabled: bool = False,
|
|
133
|
-
is_server: bool = False,
|
|
134
123
|
) -> FlashManager:
|
|
135
124
|
"""Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
|
|
136
125
|
This is a highly experimental method that can break or be removed at any time without warning.
|
|
@@ -147,7 +136,6 @@ class __flash_forward_spec(typing_extensions.Protocol):
|
|
|
147
136
|
startup_timeout: int = 30,
|
|
148
137
|
exit_grace_period: int = 0,
|
|
149
138
|
h2_enabled: bool = False,
|
|
150
|
-
is_server: bool = False,
|
|
151
139
|
) -> FlashManager:
|
|
152
140
|
"""Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
|
|
153
141
|
This is a highly experimental method that can break or be removed at any time without warning.
|
|
@@ -215,21 +203,3 @@ def http_server(
|
|
|
215
203
|
exit_grace_period: The time to wait for the HTTP server to exit gracefully.
|
|
216
204
|
"""
|
|
217
205
|
...
|
|
218
|
-
|
|
219
|
-
class _FlashContainerEntry:
|
|
220
|
-
"""A class that manages the lifecycle of Flash manager for Flash containers.
|
|
221
|
-
|
|
222
|
-
It is intentional that stop() runs before exit handlers and close().
|
|
223
|
-
This ensures the container is deregistered first, preventing new requests from being routed to it
|
|
224
|
-
while exit handlers execute and the exit grace period elapses, before finally closing the tunnel.
|
|
225
|
-
"""
|
|
226
|
-
|
|
227
|
-
flash_manager: typing.Optional[FlashManager]
|
|
228
|
-
|
|
229
|
-
def __init__(self, http_config: modal_proto.api_pb2.HTTPConfig, is_server: bool = False):
|
|
230
|
-
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
231
|
-
...
|
|
232
|
-
|
|
233
|
-
def enter(self): ...
|
|
234
|
-
def stop(self): ...
|
|
235
|
-
def close(self): ...
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from modal_proto import api_pb2
|
|
5
|
+
|
|
6
|
+
from .._utils.async_utils import synchronize_api, synchronizer
|
|
7
|
+
from ..client import _Client
|
|
8
|
+
from ..config import logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _ServerManager:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
client: _Client,
|
|
15
|
+
):
|
|
16
|
+
self.client = client
|
|
17
|
+
self.task_id = os.environ["MODAL_TASK_ID"]
|
|
18
|
+
|
|
19
|
+
async def _start(self):
|
|
20
|
+
await self.client.stub.ContainerServerLifecycleReady(api_pb2.ContainerServerLifecycleReadyRequest())
|
|
21
|
+
logger.warning(f"[Modal Server] Ready for task {self.task_id}.")
|
|
22
|
+
|
|
23
|
+
async def stop(self):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
async def close(self):
|
|
27
|
+
logger.warning(f"[Modal Server] Drained for task {self.task_id}.")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
ServerManager = synchronize_api(_ServerManager, target_module=__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@synchronizer.create_blocking
|
|
34
|
+
async def server_forward() -> _ServerManager:
|
|
35
|
+
client = await _Client.from_env()
|
|
36
|
+
|
|
37
|
+
manager = _ServerManager(client)
|
|
38
|
+
await manager._start()
|
|
39
|
+
return manager
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import modal.client
|
|
2
|
+
import typing_extensions
|
|
3
|
+
|
|
4
|
+
class _ServerManager:
|
|
5
|
+
def __init__(self, client: modal.client._Client):
|
|
6
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
7
|
+
...
|
|
8
|
+
|
|
9
|
+
async def _start(self): ...
|
|
10
|
+
async def stop(self): ...
|
|
11
|
+
async def close(self): ...
|
|
12
|
+
|
|
13
|
+
class ServerManager:
|
|
14
|
+
def __init__(self, client: modal.client.Client): ...
|
|
15
|
+
|
|
16
|
+
class ___start_spec(typing_extensions.Protocol):
|
|
17
|
+
def __call__(self, /): ...
|
|
18
|
+
async def aio(self, /): ...
|
|
19
|
+
|
|
20
|
+
_start: ___start_spec
|
|
21
|
+
|
|
22
|
+
class __stop_spec(typing_extensions.Protocol):
|
|
23
|
+
def __call__(self, /): ...
|
|
24
|
+
async def aio(self, /): ...
|
|
25
|
+
|
|
26
|
+
stop: __stop_spec
|
|
27
|
+
|
|
28
|
+
class __close_spec(typing_extensions.Protocol):
|
|
29
|
+
def __call__(self, /): ...
|
|
30
|
+
async def aio(self, /): ...
|
|
31
|
+
|
|
32
|
+
close: __close_spec
|
|
33
|
+
|
|
34
|
+
class __server_forward_spec(typing_extensions.Protocol):
|
|
35
|
+
def __call__(self, /) -> ServerManager: ...
|
|
36
|
+
async def aio(self, /) -> ServerManager: ...
|
|
37
|
+
|
|
38
|
+
server_forward: __server_forward_spec
|
|
@@ -653,7 +653,7 @@ class Function(
|
|
|
653
653
|
|
|
654
654
|
_call_generator: ___call_generator_spec
|
|
655
655
|
|
|
656
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
656
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
657
657
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
658
658
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
659
659
|
|
|
@@ -678,7 +678,7 @@ class Function(
|
|
|
678
678
|
"""
|
|
679
679
|
...
|
|
680
680
|
|
|
681
|
-
remote: __remote_spec[modal._functions.
|
|
681
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
682
682
|
|
|
683
683
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
684
684
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -728,7 +728,7 @@ class Function(
|
|
|
728
728
|
"""
|
|
729
729
|
...
|
|
730
730
|
|
|
731
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
731
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
732
732
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
733
733
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
734
734
|
|
|
@@ -761,7 +761,7 @@ class Function(
|
|
|
761
761
|
"""
|
|
762
762
|
...
|
|
763
763
|
|
|
764
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
764
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
765
765
|
|
|
766
766
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
767
767
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -769,7 +769,7 @@ class Function(
|
|
|
769
769
|
|
|
770
770
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
771
771
|
|
|
772
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
772
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
773
773
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
774
774
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
775
775
|
|
|
@@ -802,7 +802,7 @@ class Function(
|
|
|
802
802
|
"""
|
|
803
803
|
...
|
|
804
804
|
|
|
805
|
-
spawn: __spawn_spec[modal._functions.
|
|
805
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
806
806
|
|
|
807
807
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
808
808
|
"""Return the inner Python object wrapped by this Modal Function.
|