modAL 1.4.2__tar.gz → 1.4.2.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.4.2 → modal-1.4.2.dev0}/PKG-INFO +1 -1
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_functions.py +3 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/asgi.py +2 -5
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/grpc_utils.py +5 -29
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/rand_pb_testing.py +1 -3
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/sandbox_fs_utils.py +0 -37
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/task_command_router_client.py +24 -79
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/app.py +6 -2
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/app.pyi +8 -4
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/app.py +21 -171
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/container.py +2 -20
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/entry_point.py +24 -8
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/import_refs.py +2 -21
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/run.py +3 -3
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/selector.py +4 -12
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/shell.py +1 -1
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/utils.py +17 -10
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/client.py +0 -7
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/client.pyi +10 -14
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/config.py +1 -2
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/dict.py +5 -5
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/dict.pyi +8 -8
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/exception.py +0 -6
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/file_io.py +2 -2
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/functions.pyi +1 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/image.py +0 -2
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/image.pyi +0 -2
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/queue.py +5 -5
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/queue.pyi +8 -8
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/sandbox.py +22 -97
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/sandbox.pyi +27 -87
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/sandbox_fs.py +7 -44
- modal-1.4.2.dev0/modal/sandbox_fs.pyi +627 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/secret.py +5 -5
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/secret.pyi +8 -8
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/volume.py +5 -5
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/volume.pyi +8 -8
- {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/SOURCES.txt +0 -2
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/mdmd/mdmd.py +13 -30
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_grpc.py +0 -128
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2.py +1051 -1250
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2.pyi +5 -453
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.py +0 -266
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/api_pb2_grpc.pyi +0 -84
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/modal_api_grpc.py +0 -8
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_grpc.py +0 -16
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.py +9 -19
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2.pyi +0 -17
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -34
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -12
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_version/__init__.py +1 -1
- {modal-1.4.2 → modal-1.4.2.dev0}/pyproject.toml +3 -3
- modal-1.4.2/modal/cli/bootstrap.py +0 -136
- modal-1.4.2/modal/cli/logo.py +0 -70
- modal-1.4.2/modal/sandbox_fs.pyi +0 -672
- {modal-1.4.2 → modal-1.4.2.dev0}/LICENSE +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/README.md +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/__main__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_billing.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_ipython.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_load_context.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_location.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_logs.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_object.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/manager.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/pty.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/rich.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_output/status.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_partial_function.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_resolver.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_resources.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_serialization.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_server.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_traceback.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_tunnel.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_type_manager.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_vendor/version.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/_watcher.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/billing.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/README.md +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/call_graph.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/_download.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/billing.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/changelog.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/config.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/dict.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/environment.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/launch.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/profile.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/queues.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/secret.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/token.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cli/volume.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cls.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/cls.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/container_process.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/container_process.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/environments.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/environments.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/file_io.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/functions.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/io_streams.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/mount.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/mount.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/network_file_system.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/object.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/object.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/output.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/parallel_map.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/partial_function.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/proxy.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/proxy.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/py.typed +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/retries.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/runner.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/runner.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/running_app.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/schedule.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/server.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/server.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/serving.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/serving.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/snapshot.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/stream_type.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/token_flow.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_proto/py.typed +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/modal_version/__main__.py +0 -0
- {modal-1.4.2 → modal-1.4.2.dev0}/setup.cfg +0 -0
|
@@ -700,6 +700,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
700
700
|
ephemeral_disk: Optional[int] = None,
|
|
701
701
|
include_source: bool = True,
|
|
702
702
|
experimental_options: Optional[dict[str, str]] = None,
|
|
703
|
+
_experimental_proxy_ip: Optional[str] = None,
|
|
703
704
|
restrict_output: bool = False,
|
|
704
705
|
http_config: Optional[api_pb2.HTTPConfig] = None,
|
|
705
706
|
) -> "_Function":
|
|
@@ -1040,6 +1041,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1040
1041
|
# ---
|
|
1041
1042
|
_experimental_group_size=cluster_size or 0, # Experimental: Clustered functions
|
|
1042
1043
|
_experimental_concurrent_cancellations=True,
|
|
1044
|
+
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
1043
1045
|
# --- These are deprecated in favor of autoscaler_settings
|
|
1044
1046
|
warm_pool_size=min_containers or 0,
|
|
1045
1047
|
concurrency_limit=max_containers or 0,
|
|
@@ -1082,6 +1084,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1082
1084
|
_experimental_group_size=function_definition._experimental_group_size,
|
|
1083
1085
|
_experimental_buffer_containers=function_definition._experimental_buffer_containers,
|
|
1084
1086
|
_experimental_custom_scaling=function_definition._experimental_custom_scaling,
|
|
1087
|
+
_experimental_proxy_ip=function_definition._experimental_proxy_ip,
|
|
1085
1088
|
snapshot_debug=function_definition.snapshot_debug,
|
|
1086
1089
|
runtime_perf_record=function_definition.runtime_perf_record,
|
|
1087
1090
|
function_schema=function_schema,
|
|
@@ -474,11 +474,8 @@ async def _proxy_lifespan_request(base_url, scope, receive, send) -> None:
|
|
|
474
474
|
auto_decompress=False,
|
|
475
475
|
read_bufsize=1024 * 1024, # 1 MiB
|
|
476
476
|
connector=aiohttp.TCPConnector(
|
|
477
|
-
limit=1000
|
|
478
|
-
|
|
479
|
-
# from stale pooled connections.
|
|
480
|
-
force_close=True,
|
|
481
|
-
),
|
|
477
|
+
limit=1000
|
|
478
|
+
), # 100 is the default max, but 1000 is the max for `@modal.concurrent`.
|
|
482
479
|
# Note: these values will need to be kept in sync.
|
|
483
480
|
**(
|
|
484
481
|
# These options were introduced in aiohttp 3.9, and we can remove the
|
|
@@ -216,11 +216,7 @@ def create_channel(
|
|
|
216
216
|
for k, v in metadata.items():
|
|
217
217
|
event.metadata[k] = v
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
if idempotency_key is None:
|
|
221
|
-
logger.debug(f"Sending request to {event.method_name}")
|
|
222
|
-
else:
|
|
223
|
-
logger.debug(f"Sending request to {event.method_name} ({idempotency_key[:8]})")
|
|
219
|
+
logger.debug(f"Sending request to {event.method_name}")
|
|
224
220
|
|
|
225
221
|
grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
|
|
226
222
|
|
|
@@ -292,14 +288,12 @@ def process_exception_before_retry(
|
|
|
292
288
|
n_retries: int,
|
|
293
289
|
delay: float,
|
|
294
290
|
idempotency_key: str,
|
|
295
|
-
rpc_elapsed: float,
|
|
296
291
|
):
|
|
297
292
|
"""Process exception before retry, used by `_retry_transient_errors`."""
|
|
298
293
|
with suppress_tb_frame():
|
|
299
294
|
if final_attempt:
|
|
300
295
|
logger.debug(
|
|
301
|
-
f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} {
|
|
302
|
-
f"for {fn_name} ({idempotency_key[:8]})"
|
|
296
|
+
f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})"
|
|
303
297
|
)
|
|
304
298
|
if isinstance(exc, OSError):
|
|
305
299
|
raise ConnectionError(str(exc))
|
|
@@ -316,10 +310,7 @@ def process_exception_before_retry(
|
|
|
316
310
|
# we handle in the retry logic once we drop this check!
|
|
317
311
|
raise exc
|
|
318
312
|
|
|
319
|
-
logger.debug(
|
|
320
|
-
f"Retryable failure {repr(exc)} {n_retries=} {delay=} {rpc_elapsed=:0.2f}s "
|
|
321
|
-
f"for {fn_name} ({idempotency_key[:8]})"
|
|
322
|
-
)
|
|
313
|
+
logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})")
|
|
323
314
|
|
|
324
315
|
|
|
325
316
|
async def _retry_transient_errors(
|
|
@@ -382,7 +373,6 @@ async def _retry_transient_errors(
|
|
|
382
373
|
else:
|
|
383
374
|
timeout = None
|
|
384
375
|
|
|
385
|
-
attempt_started_at = time.monotonic()
|
|
386
376
|
try:
|
|
387
377
|
with suppress_tb_frame():
|
|
388
378
|
return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
|
|
@@ -419,13 +409,7 @@ async def _retry_transient_errors(
|
|
|
419
409
|
|
|
420
410
|
with suppress_tb_frame():
|
|
421
411
|
process_exception_before_retry(
|
|
422
|
-
exc,
|
|
423
|
-
final_attempt,
|
|
424
|
-
fn.name,
|
|
425
|
-
n_retries,
|
|
426
|
-
server_delay,
|
|
427
|
-
idempotency_key,
|
|
428
|
-
time.monotonic() - attempt_started_at,
|
|
412
|
+
exc, final_attempt, fn.name, n_retries, server_delay, idempotency_key
|
|
429
413
|
)
|
|
430
414
|
|
|
431
415
|
now = time.time()
|
|
@@ -454,15 +438,7 @@ async def _retry_transient_errors(
|
|
|
454
438
|
final_attempt = False
|
|
455
439
|
|
|
456
440
|
with suppress_tb_frame():
|
|
457
|
-
process_exception_before_retry(
|
|
458
|
-
exc,
|
|
459
|
-
final_attempt,
|
|
460
|
-
fn.name,
|
|
461
|
-
n_retries,
|
|
462
|
-
delay,
|
|
463
|
-
idempotency_key,
|
|
464
|
-
time.monotonic() - attempt_started_at,
|
|
465
|
-
)
|
|
441
|
+
process_exception_before_retry(exc, final_attempt, fn.name, n_retries, delay, idempotency_key)
|
|
466
442
|
|
|
467
443
|
n_retries += 1
|
|
468
444
|
|
|
@@ -52,9 +52,8 @@ def _fill(msg, desc: Descriptor, rand: Random) -> None:
|
|
|
52
52
|
if hasattr(field, "is_repeated"):
|
|
53
53
|
is_repeated = field.is_repeated # type: ignore
|
|
54
54
|
else:
|
|
55
|
-
is_repeated = field.label == FieldDescriptor.LABEL_REPEATED
|
|
55
|
+
is_repeated = field.label == FieldDescriptor.LABEL_REPEATED
|
|
56
56
|
if is_message:
|
|
57
|
-
assert field.message_type is not None
|
|
58
57
|
msg_field = getattr(msg, field.name)
|
|
59
58
|
if is_repeated:
|
|
60
59
|
num = rand.randint(0, 2)
|
|
@@ -65,7 +64,6 @@ def _fill(msg, desc: Descriptor, rand: Random) -> None:
|
|
|
65
64
|
_fill(msg_field, field.message_type, rand)
|
|
66
65
|
else:
|
|
67
66
|
if field.type == FieldDescriptor.TYPE_ENUM:
|
|
68
|
-
assert field.enum_type is not None
|
|
69
67
|
enum_values = [x.number for x in field.enum_type.values]
|
|
70
68
|
generator = lambda rand: rand.choice(enum_values) # noqa: E731
|
|
71
69
|
|
|
@@ -18,7 +18,6 @@ from ..exception import (
|
|
|
18
18
|
SandboxFilesystemIsADirectoryError,
|
|
19
19
|
SandboxFilesystemNotADirectoryError,
|
|
20
20
|
SandboxFilesystemNotFoundError,
|
|
21
|
-
SandboxFilesystemPathAlreadyExistsError,
|
|
22
21
|
SandboxFilesystemPermissionError,
|
|
23
22
|
ServiceError,
|
|
24
23
|
)
|
|
@@ -189,42 +188,6 @@ def raise_remove_error(returncode: int, stderr: Union[str, bytes], remote_path:
|
|
|
189
188
|
raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
|
|
190
189
|
|
|
191
190
|
|
|
192
|
-
def make_make_directory_command(remote_path: str, create_parents: bool) -> str:
|
|
193
|
-
"""Build the JSON command string for a MakeDirectory operation.
|
|
194
|
-
|
|
195
|
-
The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
|
|
196
|
-
Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
|
|
197
|
-
this schema like protobuf changes: fields must not be removed or renamed,
|
|
198
|
-
only added with backwards-compatible defaults.
|
|
199
|
-
"""
|
|
200
|
-
return json.dumps({"MakeDirectory": {"path": remote_path, "parents": create_parents}})
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def raise_make_directory_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
|
|
204
|
-
if payload := try_parse_error_payload(stderr):
|
|
205
|
-
logger.debug(
|
|
206
|
-
f"sandbox-fs-tools make_directory error: path={remote_path}, "
|
|
207
|
-
f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
|
|
208
|
-
)
|
|
209
|
-
if payload.error_kind == "NotFound":
|
|
210
|
-
raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
|
|
211
|
-
if payload.error_kind == "PathAlreadyExists":
|
|
212
|
-
raise SandboxFilesystemPathAlreadyExistsError(f"{payload.message}: {remote_path}")
|
|
213
|
-
if payload.error_kind == "NotDirectory":
|
|
214
|
-
raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
|
|
215
|
-
if payload.error_kind == "PermissionDenied":
|
|
216
|
-
raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
|
|
217
|
-
if payload.error_kind == "NotSupported":
|
|
218
|
-
raise InvalidError(
|
|
219
|
-
f"{payload.message}: {remote_path} - this operation is not supported for CloudBucketMounts"
|
|
220
|
-
)
|
|
221
|
-
raise SandboxFilesystemError(payload.message)
|
|
222
|
-
|
|
223
|
-
if stderr_text := _stderr_to_text(stderr):
|
|
224
|
-
logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
|
|
225
|
-
raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
|
|
226
|
-
|
|
227
|
-
|
|
228
191
|
def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
|
|
229
192
|
if not PurePosixPath(remote_path).is_absolute():
|
|
230
193
|
raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
|
|
@@ -122,18 +122,6 @@ async def fetch_command_router_access(server_client, task_id: str) -> api_pb2.Ta
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
|
|
125
|
-
async def fetch_command_router_access_v2(
|
|
126
|
-
server_client, sandbox_id: str
|
|
127
|
-
) -> api_pb2.SandboxGetCommandRouterAccessResponse:
|
|
128
|
-
"""Fetch direct command router access info from Modal server for a V2 sandbox."""
|
|
129
|
-
assert server_client._auth_token_manager
|
|
130
|
-
auth_token = await server_client._auth_token_manager.get_token()
|
|
131
|
-
return await server_client.stub.SandboxGetCommandRouterAccess(
|
|
132
|
-
api_pb2.SandboxGetCommandRouterAccessRequest(sandbox_id=sandbox_id),
|
|
133
|
-
metadata=[("x-modal-auth-token", auth_token)],
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
125
|
def _finalize_channel(loop, channel):
|
|
138
126
|
if not loop.is_closed():
|
|
139
127
|
# only run if loop has not shut down
|
|
@@ -151,19 +139,27 @@ class TaskCommandRouterClient:
|
|
|
151
139
|
"""
|
|
152
140
|
|
|
153
141
|
@classmethod
|
|
154
|
-
async def
|
|
142
|
+
async def try_init(
|
|
155
143
|
cls,
|
|
156
144
|
server_client,
|
|
157
145
|
task_id: str,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
146
|
+
) -> Optional["TaskCommandRouterClient"]:
|
|
147
|
+
"""Attempt to initialize a TaskCommandRouterClient by fetching direct access.
|
|
148
|
+
|
|
149
|
+
Returns None if command router access is not enabled (FAILED_PRECONDITION).
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
resp = await fetch_command_router_access(server_client, task_id)
|
|
153
|
+
except ConflictError:
|
|
154
|
+
logger.debug(f"Command router access is not enabled for task {task_id}")
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
logger.debug(f"Using command router access for task {task_id}")
|
|
158
|
+
|
|
159
|
+
# Build and connect a channel to the task command router now that we have access info.
|
|
160
|
+
o = urllib.parse.urlparse(resp.url)
|
|
165
161
|
if o.scheme != "https":
|
|
166
|
-
raise ValueError(f"Task router URL must be https, got: {url}")
|
|
162
|
+
raise ValueError(f"Task router URL must be https, got: {resp.url}")
|
|
167
163
|
|
|
168
164
|
host, _, port_str = o.netloc.partition(":")
|
|
169
165
|
port = int(port_str) if port_str else 443
|
|
@@ -193,38 +189,7 @@ class TaskCommandRouterClient:
|
|
|
193
189
|
loop = asyncio.get_running_loop()
|
|
194
190
|
jwt_refresh_lock = asyncio.Lock()
|
|
195
191
|
|
|
196
|
-
return cls(server_client, task_id, url, jwt, channel, loop, jwt_refresh_lock
|
|
197
|
-
|
|
198
|
-
@classmethod
|
|
199
|
-
async def try_init(
|
|
200
|
-
cls,
|
|
201
|
-
server_client,
|
|
202
|
-
task_id: str,
|
|
203
|
-
) -> Optional["TaskCommandRouterClient"]:
|
|
204
|
-
"""Attempt to initialize a TaskCommandRouterClient by fetching direct access.
|
|
205
|
-
|
|
206
|
-
Returns None if command router access is not enabled (FAILED_PRECONDITION).
|
|
207
|
-
"""
|
|
208
|
-
try:
|
|
209
|
-
resp = await fetch_command_router_access(server_client, task_id)
|
|
210
|
-
except ConflictError:
|
|
211
|
-
logger.debug(f"Command router access is not enabled for task {task_id}")
|
|
212
|
-
return None
|
|
213
|
-
|
|
214
|
-
logger.debug(f"Using command router access for task {task_id}")
|
|
215
|
-
return await cls._connect(server_client, task_id, resp.url, resp.jwt)
|
|
216
|
-
|
|
217
|
-
@classmethod
|
|
218
|
-
async def init_v2(
|
|
219
|
-
cls,
|
|
220
|
-
server_client,
|
|
221
|
-
sandbox_id: str,
|
|
222
|
-
task_id: str,
|
|
223
|
-
) -> "TaskCommandRouterClient":
|
|
224
|
-
"""Initialize a TaskCommandRouterClient for a V2 sandbox."""
|
|
225
|
-
resp = await fetch_command_router_access_v2(server_client, sandbox_id)
|
|
226
|
-
logger.debug(f"Using command router access for sandbox {sandbox_id}")
|
|
227
|
-
return await cls._connect(server_client, task_id, resp.url, resp.jwt, sandbox_id=sandbox_id)
|
|
192
|
+
return cls(server_client, task_id, resp.url, resp.jwt, channel, loop, jwt_refresh_lock)
|
|
228
193
|
|
|
229
194
|
def __init__(
|
|
230
195
|
self,
|
|
@@ -236,7 +201,6 @@ class TaskCommandRouterClient:
|
|
|
236
201
|
loop: asyncio.AbstractEventLoop,
|
|
237
202
|
jwt_refresh_lock: asyncio.Lock,
|
|
238
203
|
*,
|
|
239
|
-
sandbox_id: Optional[str] = None,
|
|
240
204
|
stream_stdio_retry_delay_secs: float = 0.01,
|
|
241
205
|
stream_stdio_retry_delay_factor: float = 2,
|
|
242
206
|
stream_stdio_max_retries: int = 10,
|
|
@@ -249,7 +213,6 @@ class TaskCommandRouterClient:
|
|
|
249
213
|
# Attach bearer token on all requests to the worker-side router service.
|
|
250
214
|
self._server_client = server_client
|
|
251
215
|
self._task_id = task_id
|
|
252
|
-
self._sandbox_id = sandbox_id
|
|
253
216
|
self._server_url = server_url
|
|
254
217
|
self._jwt = jwt
|
|
255
218
|
self._channel = channel
|
|
@@ -274,10 +237,6 @@ class TaskCommandRouterClient:
|
|
|
274
237
|
|
|
275
238
|
self._stub = TaskCommandRouterStub(self._channel)
|
|
276
239
|
|
|
277
|
-
@property
|
|
278
|
-
def _is_v2_sandbox(self) -> bool:
|
|
279
|
-
return self._sandbox_id is not None
|
|
280
|
-
|
|
281
240
|
def _get_metadata(self):
|
|
282
241
|
return {"authorization": f"Bearer {self._jwt}"}
|
|
283
242
|
|
|
@@ -493,22 +452,14 @@ class TaskCommandRouterClient:
|
|
|
493
452
|
)
|
|
494
453
|
return
|
|
495
454
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
logger.debug(f"Finished refreshing JWT for exec with sandbox ID {self._sandbox_id}")
|
|
500
|
-
jwt, url = v2_resp.jwt, v2_resp.url
|
|
501
|
-
else:
|
|
502
|
-
logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
|
|
503
|
-
v1_resp = await fetch_command_router_access(self._server_client, self._task_id)
|
|
504
|
-
logger.debug(f"Finished refreshing JWT for exec with task ID {self._task_id}")
|
|
505
|
-
jwt, url = v1_resp.jwt, v1_resp.url
|
|
455
|
+
logger.debug(f"Refreshing JWT for exec with task ID {self._task_id}")
|
|
456
|
+
resp = await fetch_command_router_access(self._server_client, self._task_id)
|
|
457
|
+
logger.debug(f"Finished refreshing JWT for exec with task ID {self._task_id}")
|
|
506
458
|
|
|
507
459
|
# Ensure the server URL remains stable for the lifetime of this client.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
self.
|
|
511
|
-
self._jwt_exp = _parse_jwt_expiration(jwt)
|
|
460
|
+
assert resp.url == self._server_url, "Task router URL changed during session"
|
|
461
|
+
self._jwt = resp.jwt
|
|
462
|
+
self._jwt_exp = _parse_jwt_expiration(resp.jwt)
|
|
512
463
|
|
|
513
464
|
async def _call_with_auth_retry(self, func, *args, **kwargs):
|
|
514
465
|
try:
|
|
@@ -620,12 +571,6 @@ class TaskCommandRouterClient:
|
|
|
620
571
|
lambda: self._call_with_auth_retry(self._stub.TaskMountDirectory, request)
|
|
621
572
|
)
|
|
622
573
|
|
|
623
|
-
async def unmount_image(self, request: sr_pb2.TaskUnmountDirectoryRequest):
|
|
624
|
-
with grpc_error_converter():
|
|
625
|
-
return await call_with_retries_on_transient_errors(
|
|
626
|
-
lambda: self._call_with_auth_retry(self._stub.TaskUnmountDirectory, request)
|
|
627
|
-
)
|
|
628
|
-
|
|
629
574
|
async def snapshot_directory(
|
|
630
575
|
self, request: sr_pb2.TaskSnapshotDirectoryRequest
|
|
631
576
|
) -> sr_pb2.TaskSnapshotDirectoryResponse:
|
|
@@ -753,6 +753,7 @@ class _App:
|
|
|
753
753
|
include_source: Optional[bool] = None,
|
|
754
754
|
experimental_options: Optional[dict[str, Any]] = None,
|
|
755
755
|
# Parameters below here are experimental. Use with caution!
|
|
756
|
+
_experimental_proxy_ip: Optional[str] = None, # IP address of proxy
|
|
756
757
|
_experimental_restrict_output: bool = False, # Don't use pickle for return values
|
|
757
758
|
# Parameters below here are deprecated. Please update your code as suggested
|
|
758
759
|
max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
|
|
@@ -913,6 +914,7 @@ class _App:
|
|
|
913
914
|
rdma=rdma,
|
|
914
915
|
include_source=include_source if include_source is not None else local_state.include_source_default,
|
|
915
916
|
experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
|
|
917
|
+
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
916
918
|
restrict_output=_experimental_restrict_output,
|
|
917
919
|
)
|
|
918
920
|
|
|
@@ -966,6 +968,7 @@ class _App:
|
|
|
966
968
|
include_source: Optional[bool] = None, # When `False`, don't automatically add the App source to the container.
|
|
967
969
|
experimental_options: Optional[dict[str, Any]] = None,
|
|
968
970
|
# Parameters below here are experimental. Use with caution!
|
|
971
|
+
_experimental_proxy_ip: Optional[str] = None, # IP address of proxy
|
|
969
972
|
_experimental_restrict_output: bool = False, # Don't use pickle for return values
|
|
970
973
|
# Parameters below here are deprecated. Please update your code as suggested
|
|
971
974
|
max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
|
|
@@ -1112,6 +1115,7 @@ class _App:
|
|
|
1112
1115
|
rdma=rdma,
|
|
1113
1116
|
include_source=include_source if include_source is not None else local_state.include_source_default,
|
|
1114
1117
|
experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
|
|
1118
|
+
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
1115
1119
|
restrict_output=_experimental_restrict_output,
|
|
1116
1120
|
)
|
|
1117
1121
|
|
|
@@ -1286,12 +1290,12 @@ class _App:
|
|
|
1286
1290
|
|
|
1287
1291
|
```python
|
|
1288
1292
|
app_a = modal.App("a")
|
|
1289
|
-
@
|
|
1293
|
+
@app.function()
|
|
1290
1294
|
def foo():
|
|
1291
1295
|
...
|
|
1292
1296
|
|
|
1293
1297
|
app_b = modal.App("b")
|
|
1294
|
-
@
|
|
1298
|
+
@app.function()
|
|
1295
1299
|
def bar():
|
|
1296
1300
|
...
|
|
1297
1301
|
|
|
@@ -497,6 +497,7 @@ class _App:
|
|
|
497
497
|
i6pn: typing.Optional[bool] = None,
|
|
498
498
|
include_source: typing.Optional[bool] = None,
|
|
499
499
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
500
|
+
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
500
501
|
_experimental_restrict_output: bool = False,
|
|
501
502
|
max_inputs: typing.Optional[int] = None,
|
|
502
503
|
) -> _FunctionDecoratorType:
|
|
@@ -544,6 +545,7 @@ class _App:
|
|
|
544
545
|
i6pn: typing.Optional[bool] = None,
|
|
545
546
|
include_source: typing.Optional[bool] = None,
|
|
546
547
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
548
|
+
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
547
549
|
_experimental_restrict_output: bool = False,
|
|
548
550
|
max_inputs: typing.Optional[int] = None,
|
|
549
551
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
|
|
@@ -616,12 +618,12 @@ class _App:
|
|
|
616
618
|
|
|
617
619
|
```python
|
|
618
620
|
app_a = modal.App("a")
|
|
619
|
-
@
|
|
621
|
+
@app.function()
|
|
620
622
|
def foo():
|
|
621
623
|
...
|
|
622
624
|
|
|
623
625
|
app_b = modal.App("b")
|
|
624
|
-
@
|
|
626
|
+
@app.function()
|
|
625
627
|
def bar():
|
|
626
628
|
...
|
|
627
629
|
|
|
@@ -1213,6 +1215,7 @@ class App:
|
|
|
1213
1215
|
i6pn: typing.Optional[bool] = None,
|
|
1214
1216
|
include_source: typing.Optional[bool] = None,
|
|
1215
1217
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1218
|
+
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
1216
1219
|
_experimental_restrict_output: bool = False,
|
|
1217
1220
|
max_inputs: typing.Optional[int] = None,
|
|
1218
1221
|
) -> _FunctionDecoratorType:
|
|
@@ -1260,6 +1263,7 @@ class App:
|
|
|
1260
1263
|
i6pn: typing.Optional[bool] = None,
|
|
1261
1264
|
include_source: typing.Optional[bool] = None,
|
|
1262
1265
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1266
|
+
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
1263
1267
|
_experimental_restrict_output: bool = False,
|
|
1264
1268
|
max_inputs: typing.Optional[int] = None,
|
|
1265
1269
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
|
|
@@ -1330,12 +1334,12 @@ class App:
|
|
|
1330
1334
|
|
|
1331
1335
|
```python
|
|
1332
1336
|
app_a = modal.App("a")
|
|
1333
|
-
@
|
|
1337
|
+
@app.function()
|
|
1334
1338
|
def foo():
|
|
1335
1339
|
...
|
|
1336
1340
|
|
|
1337
1341
|
app_b = modal.App("b")
|
|
1338
|
-
@
|
|
1342
|
+
@app.function()
|
|
1339
1343
|
def bar():
|
|
1340
1344
|
...
|
|
1341
1345
|
|