modal 1.5.6.dev19__tar.gz → 1.5.6.dev20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/PKG-INFO +1 -1
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/__init__.py +2 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_functions.py +0 -2
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/user_code_imports.py +0 -1
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/task_command_router_client.py +6 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/app.py +1 -1
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/utils.py +14 -5
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/client.pyi +2 -2
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/functions.pyi +6 -6
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/network_file_system.py +12 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/network_file_system.pyi +1 -0
- modal-1.5.6.dev20/modal/outbound_policy.py +167 -0
- modal-1.5.6.dev20/modal/outbound_policy.pyi +184 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/sandbox.py +54 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/sandbox.pyi +69 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/types.py +20 -11
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal.egg-info/SOURCES.txt +2 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/gen_reference_docs.py +4 -2
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/api_pb2.py +1046 -1036
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/api_pb2.pyi +28 -35
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_version/__init__.py +1 -1
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/LICENSE +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/README.md +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/__main__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_billing.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_clustered_functions.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_environments.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_function_variants.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_image.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_location.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_logs.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_object.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_output/manager.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_output/pty.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_output/rich.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_output/status.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_partial_function.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_resources.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_server.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_tunnel.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/_workspace.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/app.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/app.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/billing.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/_logs.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/_stats.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/cluster.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/config.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/container.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/entry_point.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/function.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/image.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/import_refs.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/launch.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/profile.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/server.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/token.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/volume.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cli/workspace.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/client.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cls.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/cls.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/config.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/container_process.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/dict.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/environments.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/exception.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/experimental/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/experimental/flash.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/functions.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/image.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/image.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/io_streams.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/io_streams.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/mount.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/object.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/object.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/output.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/partial_function.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/partial_function.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/proxy.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/py.typed +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/queue.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/retries.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/runner.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/running_app.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/sandbox_fs.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/schedule.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/secret.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/server.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/server.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/serving.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/token_flow.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/volume.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/volume.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/workspace.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal/workspace.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/pyproject.toml +0 -0
- {modal-1.5.6.dev19 → modal-1.5.6.dev20}/setup.cfg +0 -0
|
@@ -23,6 +23,7 @@ try:
|
|
|
23
23
|
from .functions import Function, FunctionCall
|
|
24
24
|
from .image import Image
|
|
25
25
|
from .network_file_system import NetworkFileSystem
|
|
26
|
+
from .outbound_policy import OutboundPolicy
|
|
26
27
|
from .output import enable_output
|
|
27
28
|
from .partial_function import (
|
|
28
29
|
asgi_app,
|
|
@@ -71,6 +72,7 @@ __all__ = [
|
|
|
71
72
|
"FunctionCall",
|
|
72
73
|
"Image",
|
|
73
74
|
"NetworkFileSystem",
|
|
75
|
+
"OutboundPolicy",
|
|
74
76
|
"Period",
|
|
75
77
|
"Probe",
|
|
76
78
|
"Proxy",
|
|
@@ -1103,8 +1103,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1103
1103
|
is_class=function_definition.is_class,
|
|
1104
1104
|
class_parameter_info=function_definition.class_parameter_info,
|
|
1105
1105
|
is_method=function_definition.is_method,
|
|
1106
|
-
use_function_id=function_definition.use_function_id,
|
|
1107
|
-
use_method_name=function_definition.use_method_name,
|
|
1108
1106
|
method_definitions=function_definition.method_definitions,
|
|
1109
1107
|
method_definitions_set=function_definition.method_definitions_set,
|
|
1110
1108
|
experimental_options=experimental_options or {},
|
|
@@ -562,7 +562,6 @@ def _get_cls_or_user_cls(
|
|
|
562
562
|
f"Internal error: Invalid 'service function' identifier {qual_name}. Please contact Modal support"
|
|
563
563
|
)
|
|
564
564
|
|
|
565
|
-
assert not function_def.use_method_name # new "placeholder methods" should not be invoked directly!
|
|
566
565
|
cls_name = parts[0]
|
|
567
566
|
cls_or_user_cls = getattr(module, cls_name)
|
|
568
567
|
return cls_or_user_cls
|
|
@@ -1077,6 +1077,12 @@ class TaskCommandRouterClient:
|
|
|
1077
1077
|
lambda: self._call_with_auth_retry(self._stub.TaskSetNetworkAccess, request)
|
|
1078
1078
|
)
|
|
1079
1079
|
|
|
1080
|
+
async def set_outbound_policy(self, request: sr_pb2.TaskSetOutboundPolicyRequest):
|
|
1081
|
+
with grpc_error_converter():
|
|
1082
|
+
return await call_with_retries_on_transient_errors(
|
|
1083
|
+
lambda: self._call_with_auth_retry(self._stub.TaskSetOutboundPolicy, request)
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1080
1086
|
async def reload_volumes(
|
|
1081
1087
|
self, task_id: str, timeout: float, container_id: str = ""
|
|
1082
1088
|
) -> sr_pb2.TaskReloadVolumesResponse:
|
|
@@ -726,7 +726,7 @@ async def info(app_identifier: str, *, env: str | None = None, json: bool = Fals
|
|
|
726
726
|
if len(hardware) > 1:
|
|
727
727
|
metadata.append(f" ({', '.join(hardware[1:])})", style=None if no_color else "bright_black")
|
|
728
728
|
if summary.web_function:
|
|
729
|
-
metadata.append(" · Web
|
|
729
|
+
metadata.append(" · Web Function")
|
|
730
730
|
if schedule := _app_function_schedule(summary.schedule):
|
|
731
731
|
metadata.append(" · ")
|
|
732
732
|
metadata.append(schedule)
|
|
@@ -248,12 +248,21 @@ async def _resolve_function_id(
|
|
|
248
248
|
raise
|
|
249
249
|
|
|
250
250
|
print_server_warnings(response.server_warnings)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
function_id = response.function_id
|
|
252
|
+
function = response.function
|
|
253
|
+
metadata = response.handle_metadata
|
|
254
|
+
elif _is_function_id(function_identifier):
|
|
254
255
|
get_by_id_response = await client.stub.FunctionGetById(
|
|
255
256
|
api_pb2.FunctionGetByIdRequest(function_id=function_identifier)
|
|
256
257
|
)
|
|
257
|
-
|
|
258
|
+
function_id = function_identifier
|
|
259
|
+
function = get_by_id_response.function
|
|
260
|
+
metadata = get_by_id_response.handle_metadata
|
|
261
|
+
else:
|
|
262
|
+
raise UsageError(usage)
|
|
263
|
+
|
|
264
|
+
if function.is_server != (object_type == "Server"):
|
|
265
|
+
actual_type = "Server" if function.is_server else "Function"
|
|
266
|
+
raise UsageError(f"'{function_identifier}' is a {actual_type}. Use `modal {actual_type.lower()} {command}`.")
|
|
258
267
|
|
|
259
|
-
|
|
268
|
+
return function_id, metadata
|
|
@@ -77,7 +77,7 @@ class _Client:
|
|
|
77
77
|
server_url: str,
|
|
78
78
|
client_type: int,
|
|
79
79
|
credentials: typing.Optional[tuple[str, str]],
|
|
80
|
-
version: str = "1.5.6.
|
|
80
|
+
version: str = "1.5.6.dev20",
|
|
81
81
|
*,
|
|
82
82
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
83
83
|
):
|
|
@@ -277,7 +277,7 @@ class Client:
|
|
|
277
277
|
server_url: str,
|
|
278
278
|
client_type: int,
|
|
279
279
|
credentials: typing.Optional[tuple[str, str]],
|
|
280
|
-
version: str = "1.5.6.
|
|
280
|
+
version: str = "1.5.6.dev20",
|
|
281
281
|
*,
|
|
282
282
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
283
283
|
):
|
|
@@ -653,7 +653,7 @@ class Function(
|
|
|
653
653
|
|
|
654
654
|
_call_generator: ___call_generator_spec
|
|
655
655
|
|
|
656
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
656
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
657
657
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
658
658
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
659
659
|
|
|
@@ -678,7 +678,7 @@ class Function(
|
|
|
678
678
|
"""
|
|
679
679
|
...
|
|
680
680
|
|
|
681
|
-
remote: __remote_spec[modal._functions.
|
|
681
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
682
682
|
|
|
683
683
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
684
684
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -728,7 +728,7 @@ class Function(
|
|
|
728
728
|
"""
|
|
729
729
|
...
|
|
730
730
|
|
|
731
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
731
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
732
732
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
733
733
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
734
734
|
|
|
@@ -761,7 +761,7 @@ class Function(
|
|
|
761
761
|
"""
|
|
762
762
|
...
|
|
763
763
|
|
|
764
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
764
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
765
765
|
|
|
766
766
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
767
767
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -769,7 +769,7 @@ class Function(
|
|
|
769
769
|
|
|
770
770
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
771
771
|
|
|
772
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
772
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
773
773
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
774
774
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
775
775
|
|
|
@@ -802,7 +802,7 @@ class Function(
|
|
|
802
802
|
"""
|
|
803
803
|
...
|
|
804
804
|
|
|
805
|
-
spawn: __spawn_spec[modal._functions.
|
|
805
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
806
806
|
|
|
807
807
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
808
808
|
"""Return the inner Python object wrapped by this Modal Function.
|
|
@@ -22,6 +22,7 @@ from ._object import (
|
|
|
22
22
|
from ._resolver import Resolver
|
|
23
23
|
from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
|
|
24
24
|
from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
|
|
25
|
+
from ._utils.deprecation import deprecation_warning
|
|
25
26
|
from ._utils.hash_utils import get_sha256_hex
|
|
26
27
|
from ._utils.name_utils import check_object_name
|
|
27
28
|
from .client import _Client
|
|
@@ -33,6 +34,14 @@ NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
|
|
|
33
34
|
) # 10 min max for transferring files (does not include upload time to s3)
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def _warn_network_file_system_deprecated() -> None:
|
|
38
|
+
deprecation_warning(
|
|
39
|
+
(2026, 9, 16),
|
|
40
|
+
"`modal.NetworkFileSystem` is deprecated and will be removed in a future release. Use `modal.Volume` instead.",
|
|
41
|
+
show_source=False,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
36
45
|
def network_file_system_mount_protos(
|
|
37
46
|
validated_network_file_systems: list[tuple[str, "_NetworkFileSystem"]],
|
|
38
47
|
) -> list[api_pb2.SharedVolumeMount]:
|
|
@@ -119,6 +128,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
119
128
|
```
|
|
120
129
|
"""
|
|
121
130
|
check_object_name(name, "NetworkFileSystem")
|
|
131
|
+
_warn_network_file_system_deprecated()
|
|
122
132
|
|
|
123
133
|
async def _load(
|
|
124
134
|
self: _NetworkFileSystem, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None
|
|
@@ -175,6 +185,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
175
185
|
assert await nfs.listdir("/") == []
|
|
176
186
|
```
|
|
177
187
|
"""
|
|
188
|
+
_warn_network_file_system_deprecated()
|
|
178
189
|
if client is None:
|
|
179
190
|
client = await _Client.from_env()
|
|
180
191
|
request = api_pb2.SharedVolumeGetOrCreateRequest(
|
|
@@ -201,6 +212,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
201
212
|
) -> str:
|
|
202
213
|
"""mdmd:hidden"""
|
|
203
214
|
check_object_name(deployment_name, "NetworkFileSystem")
|
|
215
|
+
_warn_network_file_system_deprecated()
|
|
204
216
|
if client is None:
|
|
205
217
|
client = await _Client.from_env()
|
|
206
218
|
request = api_pb2.SharedVolumeGetOrCreateRequest(
|
|
@@ -9,6 +9,7 @@ import synchronicity.combined_types
|
|
|
9
9
|
import typing
|
|
10
10
|
import typing_extensions
|
|
11
11
|
|
|
12
|
+
def _warn_network_file_system_deprecated() -> None: ...
|
|
12
13
|
def network_file_system_mount_protos(
|
|
13
14
|
validated_network_file_systems: list[tuple[str, _NetworkFileSystem]],
|
|
14
15
|
) -> list[modal_proto.api_pb2.SharedVolumeMount]:
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
"""Immutable configuration for replacing headers in outbound HTTPS traffic."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Mapping, Sequence
|
|
9
|
+
|
|
10
|
+
from modal_proto import api_pb2
|
|
11
|
+
|
|
12
|
+
from ._utils.async_utils import synchronize_api
|
|
13
|
+
from .exception import InvalidError
|
|
14
|
+
from .secret import _Secret
|
|
15
|
+
|
|
16
|
+
_DOMAIN_RE = re.compile(
|
|
17
|
+
r"^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\Z"
|
|
18
|
+
)
|
|
19
|
+
_HEADER_NAME_RE = re.compile(r"^[A-Za-z0-9!#$%&'*+.^_`|~-]+\Z")
|
|
20
|
+
|
|
21
|
+
_MAX_HEADER_REPLACEMENTS = 25
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _header_value_has_invalid_chars(value: str) -> bool:
|
|
25
|
+
return any((ord(c) < 0x20 and c != "\t") or ord(c) == 0x7F for c in value)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _template_references_key(template: str) -> bool:
|
|
29
|
+
"""Whether a header value template references a `$KEY`."""
|
|
30
|
+
i = template.find("$")
|
|
31
|
+
while i != -1 and i + 1 < len(template):
|
|
32
|
+
c = template[i + 1]
|
|
33
|
+
if c == "$":
|
|
34
|
+
i = template.find("$", i + 2)
|
|
35
|
+
continue
|
|
36
|
+
if c.isascii() and (c.isalpha() or c == "_"):
|
|
37
|
+
return True
|
|
38
|
+
i = template.find("$", i + 1)
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class _HeaderReplacement:
|
|
44
|
+
domain: str
|
|
45
|
+
headers: dict[str, str]
|
|
46
|
+
secret: _Secret | None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _OutboundPolicy:
|
|
50
|
+
"""Immutable configuration for replacing headers in outbound HTTPS requests
|
|
51
|
+
from a Sandbox.
|
|
52
|
+
|
|
53
|
+
Header values support templating with keys in a replacement's secret: a
|
|
54
|
+
`$`-prefixed key name in the secret is replaced with the secret value.
|
|
55
|
+
Literal `$` characters are written `$$`.
|
|
56
|
+
|
|
57
|
+
Secret values never enter the Sandbox: they are resolved and injected into
|
|
58
|
+
matching requests outside the container.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
```python
|
|
62
|
+
import modal
|
|
63
|
+
|
|
64
|
+
secret = modal.Secret.from_name("api-token")
|
|
65
|
+
|
|
66
|
+
outbound_policy = (
|
|
67
|
+
modal.OutboundPolicy()
|
|
68
|
+
# Inject a secret-backed Authorization header.
|
|
69
|
+
.with_header_replacement(
|
|
70
|
+
domain="example.com",
|
|
71
|
+
secret=secret,
|
|
72
|
+
headers={"Authorization": "Bearer $API_TOKEN"},
|
|
73
|
+
)
|
|
74
|
+
# Inject a static header into requests to another domain.
|
|
75
|
+
.with_header_replacement(
|
|
76
|
+
domain="modal.com",
|
|
77
|
+
headers={"X-Trace-Token": "trace_abcd"},
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
sb = modal.Sandbox.create(outbound_policy=outbound_policy)
|
|
82
|
+
```
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
_replacements: tuple[_HeaderReplacement, ...]
|
|
86
|
+
|
|
87
|
+
def __init__(self, _replacements: tuple[_HeaderReplacement, ...] = ()):
|
|
88
|
+
self._replacements = _replacements
|
|
89
|
+
|
|
90
|
+
def with_header_replacement(
|
|
91
|
+
self,
|
|
92
|
+
*,
|
|
93
|
+
domain: str,
|
|
94
|
+
headers: Mapping[str, str],
|
|
95
|
+
secret: _Secret | None = None,
|
|
96
|
+
) -> _OutboundPolicy:
|
|
97
|
+
"""Return a new `OutboundPolicy` with an added header replacement.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
domain: Domain the replacements are scoped to. Supports `*.` wildcard
|
|
101
|
+
prefixes (matching the apex domain and subdomains) and a bare `"*"`.
|
|
102
|
+
headers: Header name -> header value. Values support `$KEY` templates
|
|
103
|
+
referencing keys in the replacement's `secret`.
|
|
104
|
+
secret: Named Secret (e.g. from `Secret.from_name`) whose keys may be
|
|
105
|
+
referenced in the header value templates. Static replacements pass no
|
|
106
|
+
secret.
|
|
107
|
+
"""
|
|
108
|
+
replacement = _HeaderReplacement(domain=domain, headers=dict(headers), secret=secret)
|
|
109
|
+
return _OutboundPolicy(self._replacements + (replacement,))
|
|
110
|
+
|
|
111
|
+
def _validate(self) -> None:
|
|
112
|
+
"""Check all replacements, raising `InvalidError` on the first violation found."""
|
|
113
|
+
total = 0
|
|
114
|
+
for replacement in self._replacements:
|
|
115
|
+
if replacement.domain != "*" and not _DOMAIN_RE.match(replacement.domain):
|
|
116
|
+
raise InvalidError(f"Invalid domain: {replacement.domain!r}")
|
|
117
|
+
if not replacement.headers:
|
|
118
|
+
raise InvalidError("`headers` must contain at least one header")
|
|
119
|
+
for name, value in replacement.headers.items():
|
|
120
|
+
if not _HEADER_NAME_RE.match(name):
|
|
121
|
+
raise InvalidError(f"Invalid header name: {name!r}")
|
|
122
|
+
if _header_value_has_invalid_chars(value):
|
|
123
|
+
raise InvalidError(f"Header value for {name!r} must not contain control characters (except tab)")
|
|
124
|
+
if replacement.secret is None and _template_references_key(value):
|
|
125
|
+
raise InvalidError(f"Header value for {name!r} references a secret key, but no `secret` was passed")
|
|
126
|
+
if replacement.secret is not None and replacement.secret._is_ephemeral:
|
|
127
|
+
raise InvalidError(
|
|
128
|
+
"Outbound policies only support named secrets (e.g. `Secret.from_name`); "
|
|
129
|
+
"ephemeral secrets (e.g. `Secret.from_dict`) are not yet supported"
|
|
130
|
+
)
|
|
131
|
+
total += len(replacement.headers)
|
|
132
|
+
if total > _MAX_HEADER_REPLACEMENTS:
|
|
133
|
+
raise InvalidError(f"Outbound policy cannot have more than {_MAX_HEADER_REPLACEMENTS} header replacements")
|
|
134
|
+
|
|
135
|
+
def _secrets(self) -> list[_Secret]:
|
|
136
|
+
"""Deduplicated list of secrets referenced by the policy's replacements."""
|
|
137
|
+
return list(dict.fromkeys(r.secret for r in self._replacements if r.secret is not None))
|
|
138
|
+
|
|
139
|
+
def _to_proto(self) -> api_pb2.OutboundPolicy:
|
|
140
|
+
"""Convert to the wire format. Referenced secrets must be hydrated first."""
|
|
141
|
+
return api_pb2.OutboundPolicy(
|
|
142
|
+
header_replacements=[
|
|
143
|
+
api_pb2.OutboundPolicy.HeaderReplacement(
|
|
144
|
+
domain=replacement.domain,
|
|
145
|
+
secret_id=(replacement.secret.object_id if replacement.secret is not None else ""),
|
|
146
|
+
headers=replacement.headers,
|
|
147
|
+
)
|
|
148
|
+
for replacement in self._replacements
|
|
149
|
+
]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _validate_compatible_network_access(
|
|
154
|
+
outbound_policy: _OutboundPolicy | None,
|
|
155
|
+
block_network: bool,
|
|
156
|
+
outbound_domain_allowlist: Sequence[str] | None,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Reject combinations of network configuration and outbound policy that the server does not support."""
|
|
159
|
+
if outbound_policy is None:
|
|
160
|
+
return
|
|
161
|
+
if block_network:
|
|
162
|
+
raise InvalidError("`outbound_policy` cannot be used when `block_network` is enabled")
|
|
163
|
+
if outbound_domain_allowlist:
|
|
164
|
+
raise InvalidError("`outbound_policy` cannot be used with `outbound_domain_allowlist`")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
OutboundPolicy = synchronize_api(_OutboundPolicy)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import modal.secret
|
|
2
|
+
import modal_proto.api_pb2
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
def _header_value_has_invalid_chars(value: str) -> bool: ...
|
|
6
|
+
def _template_references_key(template: str) -> bool:
|
|
7
|
+
"""Whether a header value template references a `$KEY`."""
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
class _HeaderReplacement:
|
|
11
|
+
"""_HeaderReplacement(domain: 'str', headers: 'dict[str, str]', secret: '_Secret | None')"""
|
|
12
|
+
|
|
13
|
+
domain: str
|
|
14
|
+
headers: dict[str, str]
|
|
15
|
+
secret: typing.Optional[modal.secret._Secret]
|
|
16
|
+
|
|
17
|
+
def __init__(self, domain: str, headers: dict[str, str], secret: typing.Optional[modal.secret._Secret]) -> None:
|
|
18
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
"""Return repr(self)."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def __eq__(self, other):
|
|
26
|
+
"""Return self==value."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def __setattr__(self, name, value):
|
|
30
|
+
"""Implement setattr(self, name, value)."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def __delattr__(self, name):
|
|
34
|
+
"""Implement delattr(self, name)."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def __hash__(self):
|
|
38
|
+
"""Return hash(self)."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
class _OutboundPolicy:
|
|
42
|
+
"""Immutable configuration for replacing headers in outbound HTTPS requests
|
|
43
|
+
from a Sandbox.
|
|
44
|
+
|
|
45
|
+
Header values support templating with keys in a replacement's secret: a
|
|
46
|
+
`$`-prefixed key name in the secret is replaced with the secret value.
|
|
47
|
+
Literal `$` characters are written `$$`.
|
|
48
|
+
|
|
49
|
+
Secret values never enter the Sandbox: they are resolved and injected into
|
|
50
|
+
matching requests outside the container.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
```python
|
|
54
|
+
import modal
|
|
55
|
+
|
|
56
|
+
secret = modal.Secret.from_name("api-token")
|
|
57
|
+
|
|
58
|
+
outbound_policy = (
|
|
59
|
+
modal.OutboundPolicy()
|
|
60
|
+
# Inject a secret-backed Authorization header.
|
|
61
|
+
.with_header_replacement(
|
|
62
|
+
domain="example.com",
|
|
63
|
+
secret=secret,
|
|
64
|
+
headers={"Authorization": "Bearer $API_TOKEN"},
|
|
65
|
+
)
|
|
66
|
+
# Inject a static header into requests to another domain.
|
|
67
|
+
.with_header_replacement(
|
|
68
|
+
domain="modal.com",
|
|
69
|
+
headers={"X-Trace-Token": "trace_abcd"},
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
sb = modal.Sandbox.create(outbound_policy=outbound_policy)
|
|
74
|
+
```
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
_replacements: tuple[_HeaderReplacement, ...]
|
|
78
|
+
|
|
79
|
+
def __init__(self, _replacements: tuple[_HeaderReplacement, ...] = ()):
|
|
80
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def with_header_replacement(
|
|
84
|
+
self, *, domain: str, headers: typing.Mapping[str, str], secret: typing.Optional[modal.secret._Secret] = None
|
|
85
|
+
) -> _OutboundPolicy:
|
|
86
|
+
"""Return a new `OutboundPolicy` with an added header replacement.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
domain: Domain the replacements are scoped to. Supports `*.` wildcard
|
|
90
|
+
prefixes (matching the apex domain and subdomains) and a bare `"*"`.
|
|
91
|
+
headers: Header name -> header value. Values support `$KEY` templates
|
|
92
|
+
referencing keys in the replacement's `secret`.
|
|
93
|
+
secret: Named Secret (e.g. from `Secret.from_name`) whose keys may be
|
|
94
|
+
referenced in the header value templates. Static replacements pass no
|
|
95
|
+
secret.
|
|
96
|
+
"""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
def _validate(self) -> None:
|
|
100
|
+
"""Check all replacements, raising `InvalidError` on the first violation found."""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def _secrets(self) -> list[modal.secret._Secret]:
|
|
104
|
+
"""Deduplicated list of secrets referenced by the policy's replacements."""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def _to_proto(self) -> modal_proto.api_pb2.OutboundPolicy:
|
|
108
|
+
"""Convert to the wire format. Referenced secrets must be hydrated first."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
def _validate_compatible_network_access(
|
|
112
|
+
outbound_policy: typing.Optional[_OutboundPolicy],
|
|
113
|
+
block_network: bool,
|
|
114
|
+
outbound_domain_allowlist: typing.Optional[typing.Sequence[str]],
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Reject combinations of network configuration and outbound policy that the server does not support."""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
class OutboundPolicy:
|
|
120
|
+
"""Immutable configuration for replacing headers in outbound HTTPS requests
|
|
121
|
+
from a Sandbox.
|
|
122
|
+
|
|
123
|
+
Header values support templating with keys in a replacement's secret: a
|
|
124
|
+
`$`-prefixed key name in the secret is replaced with the secret value.
|
|
125
|
+
Literal `$` characters are written `$$`.
|
|
126
|
+
|
|
127
|
+
Secret values never enter the Sandbox: they are resolved and injected into
|
|
128
|
+
matching requests outside the container.
|
|
129
|
+
|
|
130
|
+
Examples:
|
|
131
|
+
```python
|
|
132
|
+
import modal
|
|
133
|
+
|
|
134
|
+
secret = modal.Secret.from_name("api-token")
|
|
135
|
+
|
|
136
|
+
outbound_policy = (
|
|
137
|
+
modal.OutboundPolicy()
|
|
138
|
+
# Inject a secret-backed Authorization header.
|
|
139
|
+
.with_header_replacement(
|
|
140
|
+
domain="example.com",
|
|
141
|
+
secret=secret,
|
|
142
|
+
headers={"Authorization": "Bearer $API_TOKEN"},
|
|
143
|
+
)
|
|
144
|
+
# Inject a static header into requests to another domain.
|
|
145
|
+
.with_header_replacement(
|
|
146
|
+
domain="modal.com",
|
|
147
|
+
headers={"X-Trace-Token": "trace_abcd"},
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
sb = modal.Sandbox.create(outbound_policy=outbound_policy)
|
|
152
|
+
```
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
_replacements: tuple[_HeaderReplacement, ...]
|
|
156
|
+
|
|
157
|
+
def __init__(self, _replacements: tuple[_HeaderReplacement, ...] = ()): ...
|
|
158
|
+
def with_header_replacement(
|
|
159
|
+
self, *, domain: str, headers: typing.Mapping[str, str], secret: typing.Optional[modal.secret.Secret] = None
|
|
160
|
+
) -> OutboundPolicy:
|
|
161
|
+
"""Return a new `OutboundPolicy` with an added header replacement.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
domain: Domain the replacements are scoped to. Supports `*.` wildcard
|
|
165
|
+
prefixes (matching the apex domain and subdomains) and a bare `"*"`.
|
|
166
|
+
headers: Header name -> header value. Values support `$KEY` templates
|
|
167
|
+
referencing keys in the replacement's `secret`.
|
|
168
|
+
secret: Named Secret (e.g. from `Secret.from_name`) whose keys may be
|
|
169
|
+
referenced in the header value templates. Static replacements pass no
|
|
170
|
+
secret.
|
|
171
|
+
"""
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
def _validate(self) -> None:
|
|
175
|
+
"""Check all replacements, raising `InvalidError` on the first violation found."""
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
def _secrets(self) -> list[modal.secret.Secret]:
|
|
179
|
+
"""Deduplicated list of secrets referenced by the policy's replacements."""
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
def _to_proto(self) -> modal_proto.api_pb2.OutboundPolicy:
|
|
183
|
+
"""Convert to the wire format. Referenced secrets must be hydrated first."""
|
|
184
|
+
...
|