modAL 1.3.1.dev25__tar.gz → 1.3.1.dev27__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.3.1.dev25 → modal-1.3.1.dev27}/PKG-INFO +1 -1
- modal-1.3.1.dev27/modal/_server.py +246 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/client.pyi +2 -2
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/experimental/flash.py +2 -7
- modal-1.3.1.dev27/modal/server.py +5 -0
- modal-1.3.1.dev27/modal/server.pyi +147 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal.egg-info/SOURCES.txt +3 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/api.proto +3 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/api_pb2.py +668 -668
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/api_pb2.pyi +10 -2
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_version/__init__.py +1 -1
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/LICENSE +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/README.md +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/__main__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_billing.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_clustered_functions.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_functions.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_grpc_client.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_ipython.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_load_context.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_location.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_object.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_output.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_partial_function.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_pty.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_resolver.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_resources.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_serialization.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_traceback.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_tunnel.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_tunnel.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_type_manager.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/logger.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/_watcher.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/app.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/app.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/billing.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/README.md +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/builder/base-images.json +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/call_graph.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/_download.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/_traceback.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/app.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/cluster.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/config.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/container.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/dict.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/entry_point.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/environment.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/import_refs.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/launch.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/profile.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/queues.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/run.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/secret.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/shell.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/token.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/utils.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cli/volume.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/client.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cls.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/cls.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/config.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/container_process.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/container_process.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/dict.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/dict.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/environments.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/environments.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/exception.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/experimental/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/experimental/ipython.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/file_io.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/file_io.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/functions.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/functions.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/gpu.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/image.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/image.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/io_streams.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/io_streams.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/mount.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/mount.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/network_file_system.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/network_file_system.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/object.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/object.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/output.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/parallel_map.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/parallel_map.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/partial_function.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/partial_function.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/proxy.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/proxy.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/py.typed +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/queue.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/queue.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/retries.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/runner.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/runner.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/running_app.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/sandbox.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/sandbox.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/schedule.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/scheduler_placement.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/secret.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/secret.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/serving.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/serving.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/snapshot.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/snapshot.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/stream_type.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/token_flow.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/token_flow.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/volume.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal/volume.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_docs/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/__init__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/api_grpc.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/py.typed +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/task_command_router.proto +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/modal_version/__main__.py +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/pyproject.toml +0 -0
- {modal-1.3.1.dev25 → modal-1.3.1.dev27}/setup.cfg +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
import inspect
|
|
3
|
+
import typing
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ._functions import _Function
|
|
7
|
+
from ._load_context import LoadContext
|
|
8
|
+
from ._object import live_method
|
|
9
|
+
from ._partial_function import (
|
|
10
|
+
_find_partial_methods_for_user_cls,
|
|
11
|
+
_PartialFunction,
|
|
12
|
+
_PartialFunctionFlags,
|
|
13
|
+
)
|
|
14
|
+
from .client import _Client
|
|
15
|
+
from .cls import is_parameter
|
|
16
|
+
from .exception import InvalidError
|
|
17
|
+
|
|
18
|
+
if typing.TYPE_CHECKING:
|
|
19
|
+
import modal.app
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_http_server_config(
|
|
23
|
+
port: int,
|
|
24
|
+
proxy_regions: list[str], # The regions to proxy the HTTP server to.
|
|
25
|
+
startup_timeout: int, # Maximum number of seconds to wait for the HTTP server to start.
|
|
26
|
+
exit_grace_period: Optional[int], # The time to wait for the HTTP server to exit gracefully.
|
|
27
|
+
):
|
|
28
|
+
if not isinstance(port, int) or port < 1 or port > 65535:
|
|
29
|
+
raise InvalidError("Port must be a positive integer between 1 and 65535.")
|
|
30
|
+
if startup_timeout <= 0:
|
|
31
|
+
raise InvalidError("The `startup_timeout` argument must be positive.")
|
|
32
|
+
if exit_grace_period is not None and exit_grace_period < 0:
|
|
33
|
+
raise InvalidError("The `exit_grace_period` argument must be non-negative.")
|
|
34
|
+
if not proxy_regions:
|
|
35
|
+
raise InvalidError("The `proxy_regions` argument must be non-empty.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _Server:
|
|
39
|
+
"""Server runs an HTTP server started in an `@modal.enter` method.
|
|
40
|
+
|
|
41
|
+
See [lifecycle hooks](https://modal.com/docs/guide/lifecycle-functions) for more information.
|
|
42
|
+
|
|
43
|
+
Generally, you will not construct a Server directly.
|
|
44
|
+
Instead, use the [`@app.server()`](https://modal.com/docs/reference/modal.App#server) decorator.
|
|
45
|
+
|
|
46
|
+
```python notest
|
|
47
|
+
@app.server(port=8000, proxy_regions=["us-east", "us-west"])
|
|
48
|
+
class MyServer:
|
|
49
|
+
@modal.enter()
|
|
50
|
+
def start_server(self):
|
|
51
|
+
self.process = subprocess.Popen(["python3", "-m", "http.server", "8080"])
|
|
52
|
+
```
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
_user_cls: Optional[type] = None # None if remote
|
|
56
|
+
_service_function: _Function
|
|
57
|
+
_app: Optional["modal.app._App"] = None # None if remote
|
|
58
|
+
|
|
59
|
+
def _get_user_cls(self) -> type:
|
|
60
|
+
assert self._user_cls is not None
|
|
61
|
+
return self._user_cls
|
|
62
|
+
|
|
63
|
+
def _get_app(self) -> "modal.app._App":
|
|
64
|
+
assert self._app
|
|
65
|
+
return self._app
|
|
66
|
+
|
|
67
|
+
def _get_service_function(self) -> _Function:
|
|
68
|
+
return self._service_function
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _extract_user_cls(wrapped_user_cls: "type | _PartialFunction") -> type:
|
|
72
|
+
if isinstance(wrapped_user_cls, _PartialFunction):
|
|
73
|
+
assert wrapped_user_cls.user_cls
|
|
74
|
+
return wrapped_user_cls.user_cls
|
|
75
|
+
else:
|
|
76
|
+
return wrapped_user_cls
|
|
77
|
+
|
|
78
|
+
# ============ Live Methods ============
|
|
79
|
+
|
|
80
|
+
@live_method
|
|
81
|
+
async def get_urls(self) -> Optional[list[str]]:
|
|
82
|
+
return await self._get_service_function()._experimental_get_flash_urls()
|
|
83
|
+
|
|
84
|
+
@live_method
|
|
85
|
+
async def update_autoscaler(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
min_containers: Optional[int] = None,
|
|
89
|
+
max_containers: Optional[int] = None,
|
|
90
|
+
buffer_containers: Optional[int] = None,
|
|
91
|
+
scaledown_window: Optional[int] = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Override the current autoscaler behavior for this Server.
|
|
94
|
+
|
|
95
|
+
Unspecified parameters will retain their current value.
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
```python notest
|
|
99
|
+
server = modal.Server.from_name("my-app", "Server")
|
|
100
|
+
|
|
101
|
+
# Always have at least 2 containers running, with an extra buffer of 2 containers
|
|
102
|
+
server.update_autoscaler(min_containers=2, buffer_containers=1)
|
|
103
|
+
|
|
104
|
+
# Limit this Server to avoid spinning up more than 5 containers
|
|
105
|
+
server.update_autoscaler(max_containers=5)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
return await self._get_service_function().update_autoscaler(
|
|
110
|
+
min_containers=min_containers,
|
|
111
|
+
max_containers=max_containers,
|
|
112
|
+
scaledown_window=scaledown_window,
|
|
113
|
+
buffer_containers=buffer_containers,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# ============ Hydration ============
|
|
117
|
+
async def hydrate(self, client: Optional[_Client] = None) -> "_Server":
|
|
118
|
+
# This is required since we want to support @livemethod() decorated methods
|
|
119
|
+
service_function = self._get_service_function()
|
|
120
|
+
await service_function.hydrate(client)
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
# ============ Construction ============
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _from_local(
|
|
126
|
+
wrapped_user_cls: "type | _PartialFunction",
|
|
127
|
+
app: "modal.app._App",
|
|
128
|
+
service_function: _Function,
|
|
129
|
+
) -> "_Server":
|
|
130
|
+
"""Create a Server from a local class definition."""
|
|
131
|
+
|
|
132
|
+
# Note: Validation should be done by the caller (app.server()) BEFORE creating the Server.
|
|
133
|
+
# Extract the underlying class if wrapped in a _PartialFunction (e.g., from @modal.clustered())
|
|
134
|
+
user_cls = _Server._extract_user_cls(wrapped_user_cls)
|
|
135
|
+
|
|
136
|
+
server = _Server()
|
|
137
|
+
server._app = app
|
|
138
|
+
server._user_cls = user_cls
|
|
139
|
+
server._service_function = service_function
|
|
140
|
+
return server
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_name(
|
|
144
|
+
cls: type["_Server"],
|
|
145
|
+
app_name: str,
|
|
146
|
+
name: str,
|
|
147
|
+
*,
|
|
148
|
+
environment_name: Optional[str] = None,
|
|
149
|
+
client: Optional[_Client] = None,
|
|
150
|
+
) -> "_Server":
|
|
151
|
+
"""Reference a Server from a deployed App by its name.
|
|
152
|
+
|
|
153
|
+
This is a lazy method that defers hydrating the local
|
|
154
|
+
object with metadata from Modal servers until the first
|
|
155
|
+
time it is actually used.
|
|
156
|
+
|
|
157
|
+
```python notest
|
|
158
|
+
server = modal.Server.from_name("other-app", "Server")
|
|
159
|
+
```
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
load_context_overrides = LoadContext(client=client, environment_name=environment_name)
|
|
163
|
+
|
|
164
|
+
server = _Server()
|
|
165
|
+
server._service_function = _Function._from_name(
|
|
166
|
+
app_name,
|
|
167
|
+
name,
|
|
168
|
+
load_context_overrides=load_context_overrides,
|
|
169
|
+
)
|
|
170
|
+
return server
|
|
171
|
+
|
|
172
|
+
def _is_local(self) -> bool:
|
|
173
|
+
"""Returns True if this Server has local source code available."""
|
|
174
|
+
return self._user_cls is not None
|
|
175
|
+
|
|
176
|
+
# ============ Validation ============
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _validate_wrapped_user_cls_decorators(
|
|
180
|
+
wrapped_user_cls: "type | _PartialFunction", enable_memory_snapshot: bool
|
|
181
|
+
):
|
|
182
|
+
user_cls = _Server._extract_user_cls(wrapped_user_cls)
|
|
183
|
+
|
|
184
|
+
if not inspect.isclass(user_cls):
|
|
185
|
+
raise TypeError("The @app.server() decorator must be used on a class.")
|
|
186
|
+
|
|
187
|
+
# Check for modal.parameter() - not allowed on server classes
|
|
188
|
+
params = {k: v for k, v in user_cls.__dict__.items() if is_parameter(v)}
|
|
189
|
+
if params:
|
|
190
|
+
raise InvalidError(
|
|
191
|
+
f"Server class {user_cls.__name__} cannot use modal.parameter(). "
|
|
192
|
+
"Servers do not support parameterization."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not _find_partial_methods_for_user_cls(
|
|
196
|
+
user_cls, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
|
|
197
|
+
) and not _find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.ENTER_POST_SNAPSHOT):
|
|
198
|
+
raise InvalidError("Server class must have an @modal.enter() to setup the server.")
|
|
199
|
+
|
|
200
|
+
# Check for disallowed decorators
|
|
201
|
+
# @modal.method() not allowed
|
|
202
|
+
if _find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.CALLABLE_INTERFACE).values():
|
|
203
|
+
raise InvalidError(
|
|
204
|
+
f"Server class {user_cls.__name__} cannot have `@modal.method()` decorated functions. "
|
|
205
|
+
"Servers only expose HTTP endpoints."
|
|
206
|
+
)
|
|
207
|
+
# @enter with snap=True without enable_memory_snapshot
|
|
208
|
+
if (
|
|
209
|
+
_find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT)
|
|
210
|
+
and not enable_memory_snapshot
|
|
211
|
+
):
|
|
212
|
+
raise InvalidError(
|
|
213
|
+
"Server must have `enable_memory_snapshot=True` to use `snap=True` on `@modal.enter` methods."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if isinstance(wrapped_user_cls, _PartialFunction):
|
|
217
|
+
# @modal.concurrent not allowed on server classes
|
|
218
|
+
if wrapped_user_cls.flags & _PartialFunctionFlags.CONCURRENT:
|
|
219
|
+
raise InvalidError(
|
|
220
|
+
f"Server class {user_cls.__name__} cannot be decorated with `@modal.concurrent()`. "
|
|
221
|
+
"Please use `target_concurrency` param instead."
|
|
222
|
+
)
|
|
223
|
+
# @modal.http_server not allowed on server classes
|
|
224
|
+
if wrapped_user_cls.flags & _PartialFunctionFlags.HTTP_WEB_INTERFACE:
|
|
225
|
+
raise InvalidError(
|
|
226
|
+
f"Server class {user_cls.__name__} cannot have @modal.experimental.http_server() decorator. "
|
|
227
|
+
"Servers already expose HTTP endpoints."
|
|
228
|
+
)
|
|
229
|
+
# @modal.web_server not allowed on server classes
|
|
230
|
+
if wrapped_user_cls.flags & _PartialFunctionFlags.WEB_INTERFACE:
|
|
231
|
+
raise InvalidError(
|
|
232
|
+
f"Server class {user_cls.__name__} cannot be decorated with `@modal.web_server()`. "
|
|
233
|
+
"Servers already expose HTTP endpoints."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _validate_construction_mechanism(wrapped_user_cls: "type | _PartialFunction"):
|
|
238
|
+
"""Validate that the server class doesn't have a custom constructor."""
|
|
239
|
+
# Extract the underlying class if wrapped in a _PartialFunction (e.g., from @modal.clustered())
|
|
240
|
+
user_cls = _Server._extract_user_cls(wrapped_user_cls)
|
|
241
|
+
|
|
242
|
+
if user_cls.__init__ != object.__init__: # type: ignore
|
|
243
|
+
raise InvalidError(
|
|
244
|
+
f"Server class {user_cls.__name__} cannot have a custom __init__ method. "
|
|
245
|
+
"Use @modal.enter() for initialization logic instead."
|
|
246
|
+
)
|
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.3.1.
|
|
36
|
+
version: str = "1.3.1.dev27",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
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.3.1.
|
|
166
|
+
version: str = "1.3.1.dev27",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -15,6 +15,7 @@ from modal.cls import _Cls
|
|
|
15
15
|
from modal.dict import _Dict
|
|
16
16
|
from modal_proto import api_pb2
|
|
17
17
|
|
|
18
|
+
from .._server import validate_http_server_config
|
|
18
19
|
from .._tunnel import _forward as _forward_tunnel
|
|
19
20
|
from .._utils.async_utils import synchronize_api, synchronizer
|
|
20
21
|
from ..client import _Client
|
|
@@ -142,7 +143,6 @@ class _FlashManager:
|
|
|
142
143
|
)
|
|
143
144
|
self.num_failures += 1
|
|
144
145
|
await self.client.stub.FlashContainerDeregister(api_pb2.FlashContainerDeregisterRequest())
|
|
145
|
-
|
|
146
146
|
except asyncio.CancelledError:
|
|
147
147
|
logger.warning("[Modal Flash] Shutting down...")
|
|
148
148
|
break
|
|
@@ -670,12 +670,7 @@ def _http_server(
|
|
|
670
670
|
raise InvalidError(
|
|
671
671
|
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.http_server()`."
|
|
672
672
|
)
|
|
673
|
-
|
|
674
|
-
raise InvalidError("First argument of `@http_server` must be a local port, such as `@http_server(8000)`.")
|
|
675
|
-
if startup_timeout <= 0:
|
|
676
|
-
raise InvalidError("The `startup_timeout` argument of `@http_server` must be positive.")
|
|
677
|
-
if exit_grace_period is not None and exit_grace_period < 0:
|
|
678
|
-
raise InvalidError("The `exit_grace_period` argument of `@http_server` must be non-negative.")
|
|
673
|
+
validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period)
|
|
679
674
|
|
|
680
675
|
from modal._partial_function import _PartialFunction, _PartialFunctionParams
|
|
681
676
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import modal.app
|
|
2
|
+
import modal.client
|
|
3
|
+
import modal.functions
|
|
4
|
+
import modal.partial_function
|
|
5
|
+
import typing
|
|
6
|
+
import typing_extensions
|
|
7
|
+
|
|
8
|
+
class Server:
|
|
9
|
+
"""Server runs an HTTP server started in an `@modal.enter` method.
|
|
10
|
+
|
|
11
|
+
See [lifecycle hooks](https://modal.com/docs/guide/lifecycle-functions) for more information.
|
|
12
|
+
|
|
13
|
+
Generally, you will not construct a Server directly.
|
|
14
|
+
Instead, use the [`@app.server()`](https://modal.com/docs/reference/modal.App#server) decorator.
|
|
15
|
+
|
|
16
|
+
```python notest
|
|
17
|
+
@app.server(port=8000, proxy_regions=["us-east", "us-west"])
|
|
18
|
+
class MyServer:
|
|
19
|
+
@modal.enter()
|
|
20
|
+
def start_server(self):
|
|
21
|
+
self.process = subprocess.Popen(["python3", "-m", "http.server", "8080"])
|
|
22
|
+
```
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
_user_cls: typing.Optional[type]
|
|
26
|
+
_service_function: modal.functions.Function
|
|
27
|
+
_app: typing.Optional[modal.app.App]
|
|
28
|
+
|
|
29
|
+
def __init__(self, /, *args, **kwargs):
|
|
30
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def _get_user_cls(self) -> type: ...
|
|
34
|
+
def _get_app(self) -> modal.app.App: ...
|
|
35
|
+
def _get_service_function(self) -> modal.functions.Function: ...
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _extract_user_cls(wrapped_user_cls: typing.Union[type, modal.partial_function.PartialFunction]) -> type: ...
|
|
38
|
+
|
|
39
|
+
class __get_urls_spec(typing_extensions.Protocol):
|
|
40
|
+
def __call__(self, /) -> typing.Optional[list[str]]: ...
|
|
41
|
+
async def aio(self, /) -> typing.Optional[list[str]]: ...
|
|
42
|
+
|
|
43
|
+
get_urls: __get_urls_spec
|
|
44
|
+
|
|
45
|
+
class __update_autoscaler_spec(typing_extensions.Protocol):
|
|
46
|
+
def __call__(
|
|
47
|
+
self,
|
|
48
|
+
/,
|
|
49
|
+
*,
|
|
50
|
+
min_containers: typing.Optional[int] = None,
|
|
51
|
+
max_containers: typing.Optional[int] = None,
|
|
52
|
+
buffer_containers: typing.Optional[int] = None,
|
|
53
|
+
scaledown_window: typing.Optional[int] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Override the current autoscaler behavior for this Server.
|
|
56
|
+
|
|
57
|
+
Unspecified parameters will retain their current value.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
```python notest
|
|
61
|
+
server = modal.Server.from_name("my-app", "Server")
|
|
62
|
+
|
|
63
|
+
# Always have at least 2 containers running, with an extra buffer of 2 containers
|
|
64
|
+
server.update_autoscaler(min_containers=2, buffer_containers=1)
|
|
65
|
+
|
|
66
|
+
# Limit this Server to avoid spinning up more than 5 containers
|
|
67
|
+
server.update_autoscaler(max_containers=5)
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
async def aio(
|
|
73
|
+
self,
|
|
74
|
+
/,
|
|
75
|
+
*,
|
|
76
|
+
min_containers: typing.Optional[int] = None,
|
|
77
|
+
max_containers: typing.Optional[int] = None,
|
|
78
|
+
buffer_containers: typing.Optional[int] = None,
|
|
79
|
+
scaledown_window: typing.Optional[int] = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Override the current autoscaler behavior for this Server.
|
|
82
|
+
|
|
83
|
+
Unspecified parameters will retain their current value.
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
```python notest
|
|
87
|
+
server = modal.Server.from_name("my-app", "Server")
|
|
88
|
+
|
|
89
|
+
# Always have at least 2 containers running, with an extra buffer of 2 containers
|
|
90
|
+
server.update_autoscaler(min_containers=2, buffer_containers=1)
|
|
91
|
+
|
|
92
|
+
# Limit this Server to avoid spinning up more than 5 containers
|
|
93
|
+
server.update_autoscaler(max_containers=5)
|
|
94
|
+
```
|
|
95
|
+
"""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
update_autoscaler: __update_autoscaler_spec
|
|
99
|
+
|
|
100
|
+
class __hydrate_spec(typing_extensions.Protocol):
|
|
101
|
+
def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> Server: ...
|
|
102
|
+
async def aio(self, /, client: typing.Optional[modal.client.Client] = None) -> Server: ...
|
|
103
|
+
|
|
104
|
+
hydrate: __hydrate_spec
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _from_local(
|
|
108
|
+
wrapped_user_cls: typing.Union[type, modal.partial_function.PartialFunction],
|
|
109
|
+
app: modal.app.App,
|
|
110
|
+
service_function: modal.functions.Function,
|
|
111
|
+
) -> Server:
|
|
112
|
+
"""Create a Server from a local class definition."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def from_name(
|
|
117
|
+
cls: type[Server],
|
|
118
|
+
app_name: str,
|
|
119
|
+
name: str,
|
|
120
|
+
*,
|
|
121
|
+
environment_name: typing.Optional[str] = None,
|
|
122
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
123
|
+
) -> Server:
|
|
124
|
+
"""Reference a Server from a deployed App by its name.
|
|
125
|
+
|
|
126
|
+
This is a lazy method that defers hydrating the local
|
|
127
|
+
object with metadata from Modal servers until the first
|
|
128
|
+
time it is actually used.
|
|
129
|
+
|
|
130
|
+
```python notest
|
|
131
|
+
server = modal.Server.from_name("other-app", "Server")
|
|
132
|
+
```
|
|
133
|
+
"""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
def _is_local(self) -> bool:
|
|
137
|
+
"""Returns True if this Server has local source code available."""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _validate_wrapped_user_cls_decorators(
|
|
142
|
+
wrapped_user_cls: typing.Union[type, modal.partial_function.PartialFunction], enable_memory_snapshot: bool
|
|
143
|
+
): ...
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _validate_construction_mechanism(wrapped_user_cls: typing.Union[type, modal.partial_function.PartialFunction]):
|
|
146
|
+
"""Validate that the server class doesn't have a custom constructor."""
|
|
147
|
+
...
|
|
@@ -19,6 +19,7 @@ modal/_pty.py
|
|
|
19
19
|
modal/_resolver.py
|
|
20
20
|
modal/_resources.py
|
|
21
21
|
modal/_serialization.py
|
|
22
|
+
modal/_server.py
|
|
22
23
|
modal/_traceback.py
|
|
23
24
|
modal/_tunnel.py
|
|
24
25
|
modal/_tunnel.pyi
|
|
@@ -78,6 +79,8 @@ modal/schedule.py
|
|
|
78
79
|
modal/scheduler_placement.py
|
|
79
80
|
modal/secret.py
|
|
80
81
|
modal/secret.pyi
|
|
82
|
+
modal/server.py
|
|
83
|
+
modal/server.pyi
|
|
81
84
|
modal/serving.py
|
|
82
85
|
modal/serving.pyi
|
|
83
86
|
modal/snapshot.py
|
|
@@ -1561,6 +1561,7 @@ message Function {
|
|
|
1561
1561
|
|
|
1562
1562
|
bool single_use_containers = 91; // When True, containers will shut down after handling a single input
|
|
1563
1563
|
|
|
1564
|
+
bool is_server = 92; // When True, the function is a server-side function.
|
|
1564
1565
|
}
|
|
1565
1566
|
|
|
1566
1567
|
message FunctionAsyncInvokeRequest {
|
|
@@ -1744,6 +1745,8 @@ message FunctionData {
|
|
|
1744
1745
|
// Attribute on the module with the implementation, which may differ from function_name
|
|
1745
1746
|
// when the user provided a custom name= for the Function inside the Application namespace
|
|
1746
1747
|
string implementation_name = 40;
|
|
1748
|
+
|
|
1749
|
+
bool is_server = 41; // When True, the function is a server-side function.
|
|
1747
1750
|
}
|
|
1748
1751
|
|
|
1749
1752
|
message FunctionExtended {
|