modal 1.2.5.dev4__tar.gz → 1.2.5.dev10__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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/PKG-INFO +1 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_functions.py +3 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_partial_function.py +4 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/user_code_imports.py +95 -77
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/function_utils.py +2 -2
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/grpc_utils.py +3 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/task_command_router_client.py +8 -6
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/app.py +19 -3
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/client.pyi +2 -2
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/experimental/__init__.py +6 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/experimental/flash.py +86 -12
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/experimental/flash.pyi +38 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/functions.pyi +1 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/io_streams.py +78 -99
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/io_streams.pyi +28 -26
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/runner.py +1 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_version/__init__.py +1 -1
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/LICENSE +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/README.md +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/__main__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_billing.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_clustered_functions.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_grpc_client.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_ipython.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_load_context.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_location.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_object.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_output.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_pty.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_resolver.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_resources.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_serialization.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_traceback.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_tunnel.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_tunnel.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_type_manager.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/logger.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/_watcher.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/app.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/billing.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/README.md +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/builder/base-images.json +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/call_graph.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/_download.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/_traceback.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/app.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/cluster.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/config.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/container.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/dict.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/entry_point.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/environment.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/import_refs.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/launch.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/profile.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/queues.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/run.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/secret.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/shell.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/token.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/utils.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cli/volume.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/client.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cls.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/cls.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/config.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/container_process.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/container_process.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/dict.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/dict.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/environments.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/environments.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/exception.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/experimental/ipython.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/file_io.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/file_io.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/functions.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/gpu.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/image.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/image.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/mount.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/mount.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/network_file_system.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/network_file_system.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/object.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/object.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/output.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/parallel_map.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/parallel_map.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/partial_function.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/partial_function.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/proxy.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/proxy.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/py.typed +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/queue.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/queue.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/retries.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/runner.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/running_app.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/sandbox.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/sandbox.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/schedule.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/scheduler_placement.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/secret.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/secret.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/serving.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/serving.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/snapshot.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/snapshot.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/stream_type.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/token_flow.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/token_flow.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/volume.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal/volume.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_docs/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/__init__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/api.proto +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/py.typed +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/modal_version/__main__.py +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/pyproject.toml +0 -0
- {modal-1.2.5.dev4 → modal-1.2.5.dev10}/setup.cfg +0 -0
|
@@ -701,6 +701,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
701
701
|
_experimental_proxy_ip: Optional[str] = None,
|
|
702
702
|
_experimental_custom_scaling_factor: Optional[float] = None,
|
|
703
703
|
restrict_output: bool = False,
|
|
704
|
+
http_config: Optional[api_pb2.HTTPConfig] = None,
|
|
704
705
|
) -> "_Function":
|
|
705
706
|
"""mdmd:hidden
|
|
706
707
|
|
|
@@ -1046,6 +1047,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1046
1047
|
function_schema=function_schema,
|
|
1047
1048
|
supported_input_formats=supported_input_formats,
|
|
1048
1049
|
supported_output_formats=supported_output_formats,
|
|
1050
|
+
http_config=http_config,
|
|
1049
1051
|
)
|
|
1050
1052
|
|
|
1051
1053
|
if isinstance(gpu, list):
|
|
@@ -1083,6 +1085,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1083
1085
|
untrusted=function_definition.untrusted,
|
|
1084
1086
|
supported_input_formats=supported_input_formats,
|
|
1085
1087
|
supported_output_formats=supported_output_formats,
|
|
1088
|
+
http_config=http_config,
|
|
1086
1089
|
)
|
|
1087
1090
|
|
|
1088
1091
|
ranked_functions = []
|
|
@@ -46,6 +46,7 @@ class _PartialFunctionFlags(enum.IntFlag):
|
|
|
46
46
|
BATCHED = 64
|
|
47
47
|
CONCURRENT = 128
|
|
48
48
|
CLUSTERED = 256 # Experimental: Clustered functions
|
|
49
|
+
HTTP_WEB_INTERFACE = 512 # Experimental: HTTP server
|
|
49
50
|
|
|
50
51
|
@staticmethod
|
|
51
52
|
def all() -> int:
|
|
@@ -76,6 +77,7 @@ class _PartialFunctionParams:
|
|
|
76
77
|
target_concurrent_inputs: Optional[int] = None
|
|
77
78
|
build_timeout: Optional[int] = None
|
|
78
79
|
rdma: Optional[bool] = None
|
|
80
|
+
http_config: Optional[api_pb2.HTTPConfig] = None
|
|
79
81
|
|
|
80
82
|
def update(self, other: "_PartialFunctionParams") -> None:
|
|
81
83
|
"""Update self with params set in other."""
|
|
@@ -158,8 +160,9 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
|
158
160
|
raise InvalidError("Interface decorators cannot be combined with lifecycle decorators.")
|
|
159
161
|
|
|
160
162
|
has_web_interface = self.flags & _PartialFunctionFlags.WEB_INTERFACE
|
|
163
|
+
has_http_web_interface = self.flags & _PartialFunctionFlags.HTTP_WEB_INTERFACE
|
|
161
164
|
has_callable_interface = self.flags & _PartialFunctionFlags.CALLABLE_INTERFACE
|
|
162
|
-
if has_web_interface and has_callable_interface:
|
|
165
|
+
if (has_web_interface or has_http_web_interface) and has_callable_interface:
|
|
163
166
|
self.registered = True # Hacky, avoid false-positive warning
|
|
164
167
|
raise InvalidError("Callable decorators cannot be combined with web interface decorators.")
|
|
165
168
|
|
|
@@ -64,12 +64,10 @@ def call_lifecycle_functions(
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
@contextmanager
|
|
67
|
-
def
|
|
67
|
+
def lifecycle_asgi(
|
|
68
68
|
event_loop: UserCodeEventLoop,
|
|
69
69
|
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
70
|
-
function_def: api_pb2.Function,
|
|
71
70
|
finalized_functions: dict[str, FinalizedFunction],
|
|
72
|
-
exit_callback: Optional[Callable[[], None]],
|
|
73
71
|
) -> Generator[None, None, None]:
|
|
74
72
|
lifespan_background_tasks = []
|
|
75
73
|
try:
|
|
@@ -82,38 +80,34 @@ def _run_service_lifecycle(
|
|
|
82
80
|
event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
|
|
83
81
|
yield
|
|
84
82
|
finally:
|
|
85
|
-
# Run exit handlers. From this point onward, ignore all SIGINT signals that come from
|
|
86
|
-
# graceful shutdowns originating on the worker, as well as stray SIGUSR1 signals that
|
|
87
|
-
# may have been sent to cancel inputs.
|
|
88
|
-
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
89
|
-
usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
|
|
90
83
|
try:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
|
|
97
|
-
finally:
|
|
98
|
-
# no need to keep the lifespan asgi call around - we send it no more messages
|
|
99
|
-
for task in lifespan_background_tasks:
|
|
100
|
-
task.cancel()
|
|
101
|
-
|
|
102
|
-
# Identify "exit" methods and run them.
|
|
103
|
-
# want to make sure this is called even if the lifespan manager fails
|
|
104
|
-
if exit_callback:
|
|
105
|
-
exit_callback()
|
|
106
|
-
|
|
107
|
-
# Finally, commit on exit to catch uncommitted volume changes and surface background
|
|
108
|
-
# commit errors.
|
|
109
|
-
container_io_manager.volume_commit(
|
|
110
|
-
[v.volume_id for v in function_def.volume_mounts if v.allow_background_commits]
|
|
111
|
-
)
|
|
84
|
+
# run lifespan shutdown for asgi apps
|
|
85
|
+
for finalized_function in finalized_functions.values():
|
|
86
|
+
if finalized_function.lifespan_manager:
|
|
87
|
+
with container_io_manager.handle_user_exception():
|
|
88
|
+
event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
|
|
112
89
|
finally:
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
90
|
+
# no need to keep the lifespan asgi call around - we send it no more messages
|
|
91
|
+
for task in lifespan_background_tasks:
|
|
92
|
+
task.cancel()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def disable_signals():
|
|
96
|
+
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
97
|
+
usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
|
|
98
|
+
return int_handler, usr1_handler
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def try_enable_signals(int_handler, usr1_handler):
|
|
102
|
+
if int_handler is not None and usr1_handler is not None:
|
|
103
|
+
signal.signal(signal.SIGINT, int_handler)
|
|
104
|
+
signal.signal(signal.SIGUSR1, usr1_handler)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def volume_commit(
|
|
108
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager", function_def: api_pb2.Function
|
|
109
|
+
):
|
|
110
|
+
container_io_manager.volume_commit([v.volume_id for v in function_def.volume_mounts if v.allow_background_commits])
|
|
117
111
|
|
|
118
112
|
|
|
119
113
|
class Service(metaclass=ABCMeta):
|
|
@@ -127,14 +121,30 @@ class Service(metaclass=ABCMeta):
|
|
|
127
121
|
user_cls_instance: Any
|
|
128
122
|
app: "modal.app._App"
|
|
129
123
|
service_deps: Optional[Sequence["modal._object._Object"]]
|
|
124
|
+
function_def: api_pb2.Function
|
|
130
125
|
|
|
131
126
|
@abstractmethod
|
|
132
127
|
def get_finalized_functions(
|
|
133
128
|
self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
|
|
134
129
|
) -> dict[str, "FinalizedFunction"]: ...
|
|
135
130
|
|
|
131
|
+
@abstractmethod
|
|
136
132
|
@contextmanager
|
|
133
|
+
def lifecycle_presnapshot(
|
|
134
|
+
self,
|
|
135
|
+
event_loop: UserCodeEventLoop,
|
|
136
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
137
|
+
) -> Generator[None, None, None]: ...
|
|
138
|
+
|
|
137
139
|
@abstractmethod
|
|
140
|
+
@contextmanager
|
|
141
|
+
def lifecycle_postsnapshot(
|
|
142
|
+
self,
|
|
143
|
+
event_loop: UserCodeEventLoop,
|
|
144
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
145
|
+
) -> Generator[None, None, None]: ...
|
|
146
|
+
|
|
147
|
+
@contextmanager
|
|
138
148
|
def execution_context(
|
|
139
149
|
self,
|
|
140
150
|
event_loop: UserCodeEventLoop,
|
|
@@ -150,6 +160,34 @@ class Service(metaclass=ABCMeta):
|
|
|
150
160
|
6. Yield finalized_functions for execution
|
|
151
161
|
7. Handles cleanup (lifespan shutdown, 'exit' methods)
|
|
152
162
|
"""
|
|
163
|
+
int_handler, usr1_handler = None, None
|
|
164
|
+
try:
|
|
165
|
+
# 1. Pre-snapshot Enter
|
|
166
|
+
with self.lifecycle_presnapshot(event_loop, container_io_manager):
|
|
167
|
+
# 2. Snapshot -- If this container is being used to create a checkpoint, checkpoint the container after
|
|
168
|
+
# global imports and initialization. Checkpointed containers run from this point onwards.
|
|
169
|
+
maybe_snapshot(container_io_manager, self.function_def)
|
|
170
|
+
# 3. Breakpoint wrapper
|
|
171
|
+
create_breakpoint_wrapper(container_io_manager)
|
|
172
|
+
# 4. Post-snapshot Enter
|
|
173
|
+
with self.lifecycle_postsnapshot(event_loop, container_io_manager):
|
|
174
|
+
# Get Functions
|
|
175
|
+
with container_io_manager.handle_user_exception():
|
|
176
|
+
finalized_functions = self.get_finalized_functions(self.function_def, container_io_manager)
|
|
177
|
+
# 5. Start ASGI lifespan
|
|
178
|
+
with lifecycle_asgi(event_loop, container_io_manager, finalized_functions):
|
|
179
|
+
# 6. Yield Finalized Functions
|
|
180
|
+
try:
|
|
181
|
+
yield finalized_functions
|
|
182
|
+
finally:
|
|
183
|
+
int_handler, usr1_handler = disable_signals()
|
|
184
|
+
finally:
|
|
185
|
+
# 9. Volume commit - runs OUTSIDE all lifecycle managers so exit handlers
|
|
186
|
+
# have a chance to write to disk before we commit volumes
|
|
187
|
+
try:
|
|
188
|
+
volume_commit(container_io_manager, self.function_def)
|
|
189
|
+
finally:
|
|
190
|
+
try_enable_signals(int_handler, usr1_handler)
|
|
153
191
|
|
|
154
192
|
|
|
155
193
|
def construct_webhook_callable(
|
|
@@ -265,27 +303,22 @@ class ImportedFunction(Service):
|
|
|
265
303
|
}
|
|
266
304
|
|
|
267
305
|
@contextmanager
|
|
268
|
-
def
|
|
306
|
+
def lifecycle_presnapshot(
|
|
269
307
|
self,
|
|
270
308
|
event_loop: UserCodeEventLoop,
|
|
271
309
|
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
272
|
-
)
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
maybe_snapshot(container_io_manager, self.function_def)
|
|
276
|
-
create_breakpoint_wrapper(container_io_manager)
|
|
277
|
-
|
|
278
|
-
with container_io_manager.handle_user_exception():
|
|
279
|
-
finalized_functions = self.get_finalized_functions(self.function_def, container_io_manager)
|
|
310
|
+
):
|
|
311
|
+
# This is a no-op for imported functions since @enter methods are not supported
|
|
312
|
+
yield
|
|
280
313
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
314
|
+
@contextmanager
|
|
315
|
+
def lifecycle_postsnapshot(
|
|
316
|
+
self,
|
|
317
|
+
event_loop: UserCodeEventLoop,
|
|
318
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
319
|
+
):
|
|
320
|
+
# This is a no-op for imported functions since @enter methods are not supported
|
|
321
|
+
yield
|
|
289
322
|
|
|
290
323
|
|
|
291
324
|
@dataclass
|
|
@@ -340,53 +373,38 @@ class ImportedClass(Service):
|
|
|
340
373
|
return finalized_functions
|
|
341
374
|
|
|
342
375
|
@contextmanager
|
|
343
|
-
def
|
|
376
|
+
def lifecycle_presnapshot(
|
|
344
377
|
self,
|
|
345
378
|
event_loop: UserCodeEventLoop,
|
|
346
379
|
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
347
|
-
)
|
|
348
|
-
# 1. Pre-snapshot Enter
|
|
380
|
+
):
|
|
349
381
|
# Identify all "enter" methods that need to run before we snapshot.
|
|
350
382
|
if not self.function_def.is_auto_snapshot:
|
|
351
383
|
pre_snapshot_methods = _find_callables_for_obj(
|
|
352
384
|
self.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
|
|
353
385
|
)
|
|
354
386
|
call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
|
|
387
|
+
yield
|
|
355
388
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
create_breakpoint_wrapper(container_io_manager)
|
|
363
|
-
|
|
364
|
-
# 4. Post-snapshot Enter
|
|
389
|
+
@contextmanager
|
|
390
|
+
def lifecycle_postsnapshot(
|
|
391
|
+
self,
|
|
392
|
+
event_loop: UserCodeEventLoop,
|
|
393
|
+
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
394
|
+
):
|
|
365
395
|
# Identify the "enter" methods to run after resuming from a snapshot.
|
|
366
396
|
if not self.function_def.is_auto_snapshot:
|
|
367
397
|
post_snapshot_methods = _find_callables_for_obj(
|
|
368
398
|
self.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
|
|
369
399
|
)
|
|
370
400
|
call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
finalized_functions = self.get_finalized_functions(self.function_def, container_io_manager)
|
|
375
|
-
|
|
376
|
-
def exit_callback():
|
|
401
|
+
try:
|
|
402
|
+
yield
|
|
403
|
+
finally:
|
|
377
404
|
if not self.function_def.is_auto_snapshot:
|
|
378
405
|
exit_methods = _find_callables_for_obj(self.user_cls_instance, _PartialFunctionFlags.EXIT)
|
|
379
406
|
call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
|
|
380
407
|
|
|
381
|
-
with _run_service_lifecycle(
|
|
382
|
-
event_loop,
|
|
383
|
-
container_io_manager,
|
|
384
|
-
self.function_def,
|
|
385
|
-
finalized_functions,
|
|
386
|
-
exit_callback,
|
|
387
|
-
):
|
|
388
|
-
yield finalized_functions
|
|
389
|
-
|
|
390
408
|
|
|
391
409
|
def get_user_class_instance(_cls: modal.cls._Cls, args: tuple[Any, ...], kwargs: dict[str, Any]) -> typing.Any:
|
|
392
410
|
"""Returns instance of the underlying class to be used as the `self`
|
|
@@ -75,8 +75,8 @@ def is_global_object(object_qual_name: str):
|
|
|
75
75
|
return "<locals>" not in object_qual_name.split(".")
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def is_flash_object(experimental_options: Optional[dict[str, Any]]) -> bool:
|
|
79
|
-
return experimental_options.get("flash", False)
|
|
78
|
+
def is_flash_object(experimental_options: Optional[dict[str, Any]], http_config: Optional[api_pb2.HTTPConfig]) -> bool:
|
|
79
|
+
return bool(experimental_options and experimental_options.get("flash", False)) or http_config is not None
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def is_method_fn(object_qual_name: str):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import asyncio
|
|
3
3
|
import contextlib
|
|
4
|
+
import os
|
|
4
5
|
import platform
|
|
5
6
|
import socket
|
|
6
7
|
import time
|
|
@@ -396,7 +397,8 @@ async def _retry_transient_errors(
|
|
|
396
397
|
):
|
|
397
398
|
last_server_retry_warning_time = now
|
|
398
399
|
logger.warning(
|
|
399
|
-
f"Warning: Received {exc.status}
|
|
400
|
+
f"Warning: Received {exc.status}{os.linesep}"
|
|
401
|
+
f"{exc.message}{os.linesep}"
|
|
400
402
|
f"Will retry in {server_delay:0.2f} seconds."
|
|
401
403
|
)
|
|
402
404
|
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import ssl
|
|
6
6
|
import time
|
|
7
7
|
import urllib.parse
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import AsyncGenerator, Optional
|
|
9
9
|
|
|
10
10
|
import grpclib.client
|
|
11
11
|
import grpclib.config
|
|
@@ -18,6 +18,7 @@ from modal.exception import ExecTimeoutError
|
|
|
18
18
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
19
19
|
from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
20
20
|
|
|
21
|
+
from .async_utils import aclosing
|
|
21
22
|
from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES, connect_channel
|
|
22
23
|
|
|
23
24
|
|
|
@@ -242,7 +243,7 @@ class TaskCommandRouterClient:
|
|
|
242
243
|
# Quotes around the type required for protobuf 3.19.
|
|
243
244
|
file_descriptor: "api_pb2.FileDescriptor.ValueType",
|
|
244
245
|
deadline: Optional[float] = None,
|
|
245
|
-
) ->
|
|
246
|
+
) -> AsyncGenerator[sr_pb2.TaskExecStdioReadResponse, None]:
|
|
246
247
|
"""Stream stdout/stderr batches from the task, properly retrying on transient errors.
|
|
247
248
|
|
|
248
249
|
Args:
|
|
@@ -253,7 +254,7 @@ class TaskCommandRouterClient:
|
|
|
253
254
|
None, wait forever. If the deadline is exceeded, raises an
|
|
254
255
|
ExecTimeoutError.
|
|
255
256
|
Returns:
|
|
256
|
-
|
|
257
|
+
AsyncGenerator[sr_pb2.TaskExecStdioReadResponse, None]: A stream of stdout/stderr batches.
|
|
257
258
|
Raises:
|
|
258
259
|
ExecTimeoutError: If the deadline is exceeded.
|
|
259
260
|
Other errors: If retries are exhausted on transient errors or if there's an error
|
|
@@ -268,8 +269,9 @@ class TaskCommandRouterClient:
|
|
|
268
269
|
else:
|
|
269
270
|
raise ValueError(f"Invalid file descriptor: {file_descriptor}")
|
|
270
271
|
|
|
271
|
-
async
|
|
272
|
-
|
|
272
|
+
async with aclosing(self._stream_stdio(task_id, exec_id, sr_fd, deadline)) as stream:
|
|
273
|
+
async for item in stream:
|
|
274
|
+
yield item
|
|
273
275
|
|
|
274
276
|
async def exec_stdin_write(
|
|
275
277
|
self, task_id: str, exec_id: str, offset: int, data: bytes, eof: bool
|
|
@@ -453,7 +455,7 @@ class TaskCommandRouterClient:
|
|
|
453
455
|
# Quotes around the type required for protobuf 3.19.
|
|
454
456
|
file_descriptor: "sr_pb2.TaskExecStdioFileDescriptor.ValueType",
|
|
455
457
|
deadline: Optional[float] = None,
|
|
456
|
-
) ->
|
|
458
|
+
) -> AsyncGenerator[sr_pb2.TaskExecStdioReadResponse, None]:
|
|
457
459
|
"""Stream stdio from the task, properly updating the offset and retrying on transient errors.
|
|
458
460
|
Raises ExecTimeoutError if the deadline is exceeded.
|
|
459
461
|
"""
|
|
@@ -827,7 +827,7 @@ class _App:
|
|
|
827
827
|
batch_max_size = f.params.batch_max_size
|
|
828
828
|
batch_wait_ms = f.params.batch_wait_ms
|
|
829
829
|
if f.flags & _PartialFunctionFlags.CONCURRENT:
|
|
830
|
-
verify_concurrent_params(params=f.params, is_flash=is_flash_object(experimental_options))
|
|
830
|
+
verify_concurrent_params(params=f.params, is_flash=is_flash_object(experimental_options, None))
|
|
831
831
|
max_concurrent_inputs = f.params.max_concurrent_inputs
|
|
832
832
|
target_concurrent_inputs = f.params.target_concurrent_inputs
|
|
833
833
|
else:
|
|
@@ -1025,11 +1025,17 @@ class _App:
|
|
|
1025
1025
|
def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
|
|
1026
1026
|
local_state = self._local_state
|
|
1027
1027
|
# Check if the decorated object is a class
|
|
1028
|
+
http_config = None
|
|
1028
1029
|
if isinstance(wrapped_cls, _PartialFunction):
|
|
1029
1030
|
wrapped_cls.registered = True
|
|
1030
1031
|
user_cls = wrapped_cls.user_cls
|
|
1032
|
+
if wrapped_cls.flags & _PartialFunctionFlags.HTTP_WEB_INTERFACE:
|
|
1033
|
+
http_config = wrapped_cls.params.http_config
|
|
1031
1034
|
if wrapped_cls.flags & _PartialFunctionFlags.CONCURRENT:
|
|
1032
|
-
verify_concurrent_params(
|
|
1035
|
+
verify_concurrent_params(
|
|
1036
|
+
params=wrapped_cls.params,
|
|
1037
|
+
is_flash=is_flash_object(experimental_options or {}, http_config=http_config),
|
|
1038
|
+
)
|
|
1033
1039
|
max_concurrent_inputs = wrapped_cls.params.max_concurrent_inputs
|
|
1034
1040
|
target_concurrent_inputs = wrapped_cls.params.target_concurrent_inputs
|
|
1035
1041
|
else:
|
|
@@ -1039,6 +1045,7 @@ class _App:
|
|
|
1039
1045
|
if wrapped_cls.flags & _PartialFunctionFlags.CLUSTERED:
|
|
1040
1046
|
cluster_size = wrapped_cls.params.cluster_size
|
|
1041
1047
|
rdma = wrapped_cls.params.rdma
|
|
1048
|
+
|
|
1042
1049
|
else:
|
|
1043
1050
|
cluster_size = None
|
|
1044
1051
|
rdma = None
|
|
@@ -1083,10 +1090,17 @@ class _App:
|
|
|
1083
1090
|
"The `@modal.concurrent` decorator cannot be used on methods; decorate the class instead."
|
|
1084
1091
|
)
|
|
1085
1092
|
|
|
1093
|
+
for method in _find_partial_methods_for_user_cls(
|
|
1094
|
+
user_cls, _PartialFunctionFlags.HTTP_WEB_INTERFACE
|
|
1095
|
+
).values():
|
|
1096
|
+
method.registered = True # Avoid warning about not registering the method (hacky!)
|
|
1097
|
+
raise InvalidError(
|
|
1098
|
+
"The `@modal.http_server` decorator cannot be used on methods; decorate the class instead."
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1086
1101
|
info = FunctionInfo(None, serialized=serialized, user_cls=user_cls)
|
|
1087
1102
|
|
|
1088
1103
|
i6pn_enabled = i6pn or cluster_size is not None
|
|
1089
|
-
|
|
1090
1104
|
cls_func = _Function.from_local(
|
|
1091
1105
|
info,
|
|
1092
1106
|
app=self,
|
|
@@ -1117,6 +1131,7 @@ class _App:
|
|
|
1117
1131
|
block_network=block_network,
|
|
1118
1132
|
restrict_modal_access=restrict_modal_access,
|
|
1119
1133
|
max_inputs=max_inputs,
|
|
1134
|
+
http_config=http_config,
|
|
1120
1135
|
i6pn_enabled=i6pn_enabled,
|
|
1121
1136
|
cluster_size=cluster_size,
|
|
1122
1137
|
rdma=rdma,
|
|
@@ -1130,6 +1145,7 @@ class _App:
|
|
|
1130
1145
|
self._add_function(cls_func, is_web_endpoint=False)
|
|
1131
1146
|
|
|
1132
1147
|
cls: _Cls = _Cls.from_local(user_cls, self, cls_func)
|
|
1148
|
+
|
|
1133
1149
|
for method_name, partial_function in cls._method_partials.items():
|
|
1134
1150
|
if partial_function.params.webhook_config is not None:
|
|
1135
1151
|
full_name = f"{user_cls.__name__}.{method_name}"
|
|
@@ -32,7 +32,7 @@ class _Client:
|
|
|
32
32
|
server_url: str,
|
|
33
33
|
client_type: int,
|
|
34
34
|
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.5.
|
|
35
|
+
version: str = "1.2.5.dev10",
|
|
36
36
|
):
|
|
37
37
|
"""mdmd:hidden
|
|
38
38
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -163,7 +163,7 @@ class Client:
|
|
|
163
163
|
server_url: str,
|
|
164
164
|
client_type: int,
|
|
165
165
|
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.2.5.
|
|
166
|
+
version: str = "1.2.5.dev10",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -19,7 +19,12 @@ from ..cls import _Cls
|
|
|
19
19
|
from ..exception import InvalidError
|
|
20
20
|
from ..image import DockerfileSpec, ImageBuilderVersion, _Image, _ImageRegistryConfig
|
|
21
21
|
from ..secret import _Secret
|
|
22
|
-
from .flash import
|
|
22
|
+
from .flash import ( # noqa: F401
|
|
23
|
+
flash_forward,
|
|
24
|
+
flash_get_containers,
|
|
25
|
+
flash_prometheus_autoscaler,
|
|
26
|
+
http_server,
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
def stop_fetching_inputs():
|
|
@@ -7,9 +7,10 @@ import sys
|
|
|
7
7
|
import time
|
|
8
8
|
import traceback
|
|
9
9
|
from collections import defaultdict
|
|
10
|
-
from typing import Any, Optional
|
|
10
|
+
from typing import Any, Callable, Optional, Union
|
|
11
11
|
from urllib.parse import urlparse
|
|
12
12
|
|
|
13
|
+
from modal._partial_function import _PartialFunctionFlags
|
|
13
14
|
from modal.cls import _Cls
|
|
14
15
|
from modal.dict import _Dict
|
|
15
16
|
from modal_proto import api_pb2
|
|
@@ -28,15 +29,17 @@ class _FlashManager:
|
|
|
28
29
|
self,
|
|
29
30
|
client: _Client,
|
|
30
31
|
port: int,
|
|
31
|
-
process: Optional[subprocess.Popen] = None,
|
|
32
|
+
process: Optional[subprocess.Popen] = None, # to be deprecated
|
|
32
33
|
health_check_url: Optional[str] = None,
|
|
34
|
+
startup_timeout: int = 30,
|
|
33
35
|
h2_enabled: bool = False,
|
|
34
36
|
):
|
|
35
37
|
self.client = client
|
|
36
38
|
self.port = port
|
|
39
|
+
self.process = process
|
|
37
40
|
# Health check is not currently being used
|
|
38
41
|
self.health_check_url = health_check_url
|
|
39
|
-
self.
|
|
42
|
+
self.startup_timeout = startup_timeout
|
|
40
43
|
self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
|
|
41
44
|
self.stopped = False
|
|
42
45
|
self.num_failures = 0
|
|
@@ -49,10 +52,15 @@ class _FlashManager:
|
|
|
49
52
|
|
|
50
53
|
start_time = time.monotonic()
|
|
51
54
|
|
|
55
|
+
def check_process_is_running() -> Optional[Exception]:
|
|
56
|
+
if process is not None and process.poll() is not None:
|
|
57
|
+
return Exception(f"Process {process.pid} exited with code {process.returncode}")
|
|
58
|
+
return None
|
|
59
|
+
|
|
52
60
|
while time.monotonic() - start_time < timeout:
|
|
53
61
|
try:
|
|
54
|
-
if
|
|
55
|
-
return False,
|
|
62
|
+
if error := check_process_is_running():
|
|
63
|
+
return False, error
|
|
56
64
|
with socket.create_connection(("localhost", self.port), timeout=0.5):
|
|
57
65
|
return True, None
|
|
58
66
|
except (ConnectionRefusedError, OSError):
|
|
@@ -101,6 +109,7 @@ class _FlashManager:
|
|
|
101
109
|
|
|
102
110
|
async def _run_heartbeat(self, host: str, port: int):
|
|
103
111
|
first_registration = True
|
|
112
|
+
start_time = time.monotonic()
|
|
104
113
|
while True:
|
|
105
114
|
try:
|
|
106
115
|
port_check_resp, port_check_error = await self.is_port_connection_healthy(process=self.process)
|
|
@@ -122,12 +131,15 @@ class _FlashManager:
|
|
|
122
131
|
)
|
|
123
132
|
first_registration = False
|
|
124
133
|
else:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
if first_registration and (time.monotonic() - start_time < self.startup_timeout):
|
|
135
|
+
continue
|
|
136
|
+
else:
|
|
137
|
+
logger.error(
|
|
138
|
+
f"[Modal Flash] Deregistering container {self.task_id} on {self.tunnel.url} "
|
|
139
|
+
f"due to error: {port_check_error}, num_failures: {self.num_failures}"
|
|
140
|
+
)
|
|
141
|
+
self.num_failures += 1
|
|
142
|
+
await self.client.stub.FlashContainerDeregister(api_pb2.FlashContainerDeregisterRequest())
|
|
131
143
|
|
|
132
144
|
except asyncio.CancelledError:
|
|
133
145
|
logger.warning("[Modal Flash] Shutting down...")
|
|
@@ -171,6 +183,7 @@ async def flash_forward(
|
|
|
171
183
|
port: int,
|
|
172
184
|
process: Optional[subprocess.Popen] = None,
|
|
173
185
|
health_check_url: Optional[str] = None,
|
|
186
|
+
startup_timeout: int = 30,
|
|
174
187
|
h2_enabled: bool = False,
|
|
175
188
|
) -> _FlashManager:
|
|
176
189
|
"""
|
|
@@ -180,7 +193,14 @@ async def flash_forward(
|
|
|
180
193
|
"""
|
|
181
194
|
client = await _Client.from_env()
|
|
182
195
|
|
|
183
|
-
manager = _FlashManager(
|
|
196
|
+
manager = _FlashManager(
|
|
197
|
+
client,
|
|
198
|
+
port,
|
|
199
|
+
process=process,
|
|
200
|
+
health_check_url=health_check_url,
|
|
201
|
+
startup_timeout=startup_timeout,
|
|
202
|
+
h2_enabled=h2_enabled,
|
|
203
|
+
)
|
|
184
204
|
await manager._start()
|
|
185
205
|
return manager
|
|
186
206
|
|
|
@@ -618,3 +638,57 @@ async def flash_get_containers(app_name: str, cls_name: str) -> list[dict[str, A
|
|
|
618
638
|
req = api_pb2.FlashContainerListRequest(function_id=fn.object_id)
|
|
619
639
|
resp = await client.stub.FlashContainerList(req)
|
|
620
640
|
return resp.containers
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def _http_server(
|
|
644
|
+
port: Optional[int] = None,
|
|
645
|
+
*,
|
|
646
|
+
proxy_regions: list[str] = [], # The regions to proxy the HTTP server to.
|
|
647
|
+
startup_timeout: int = 30, # Maximum number of seconds to wait for the HTTP server to start.
|
|
648
|
+
exit_grace_period: Optional[int] = None, # The time to wait for the HTTP server to exit gracefully.
|
|
649
|
+
):
|
|
650
|
+
"""Decorator for Flash-enabled HTTP servers on Modal classes.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
port: The local port to forward to the HTTP server.
|
|
654
|
+
proxy_regions: The regions to proxy the HTTP server to.
|
|
655
|
+
startup_timeout: The maximum time to wait for the HTTP server to start.
|
|
656
|
+
exit_grace_period: The time to wait for the HTTP server to exit gracefully.
|
|
657
|
+
|
|
658
|
+
"""
|
|
659
|
+
if port is None:
|
|
660
|
+
raise InvalidError(
|
|
661
|
+
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.http_server()`."
|
|
662
|
+
)
|
|
663
|
+
if not isinstance(port, int) or port < 1 or port > 65535:
|
|
664
|
+
raise InvalidError("First argument of `@http_server` must be a local port, such as `@http_server(8000)`.")
|
|
665
|
+
if startup_timeout <= 0:
|
|
666
|
+
raise InvalidError("The `startup_timeout` argument of `@http_server` must be positive.")
|
|
667
|
+
if exit_grace_period is not None and exit_grace_period < 0:
|
|
668
|
+
raise InvalidError("The `exit_grace_period` argument of `@http_server` must be non-negative.")
|
|
669
|
+
|
|
670
|
+
from modal._partial_function import _PartialFunction, _PartialFunctionParams
|
|
671
|
+
|
|
672
|
+
params = _PartialFunctionParams(
|
|
673
|
+
http_config=api_pb2.HTTPConfig(
|
|
674
|
+
port=port,
|
|
675
|
+
proxy_regions=proxy_regions,
|
|
676
|
+
startup_timeout=startup_timeout or 0,
|
|
677
|
+
exit_grace_period=exit_grace_period or 0,
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
def wrapper(obj: Union[Callable[..., Any], _PartialFunction]) -> _PartialFunction:
|
|
682
|
+
flags = _PartialFunctionFlags.HTTP_WEB_INTERFACE
|
|
683
|
+
|
|
684
|
+
if isinstance(obj, _PartialFunction):
|
|
685
|
+
pf = obj.stack(flags, params)
|
|
686
|
+
else:
|
|
687
|
+
pf = _PartialFunction(obj, flags, params)
|
|
688
|
+
pf.validate_obj_compatibility("`http_server`")
|
|
689
|
+
return pf
|
|
690
|
+
|
|
691
|
+
return wrapper
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
http_server = synchronize_api(_http_server, target_module=__name__)
|