modal 1.5.6.dev11__tar.gz → 1.5.6.dev13__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.dev11 → modal-1.5.6.dev13}/PKG-INFO +1 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_functions.py +16 -5
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/status.py +5 -6
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/execution_context.py +1 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/auth_token_manager.py +28 -17
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/grpc_utils.py +20 -6
- modal-1.5.6.dev13/modal/_utils/idle_countdown.py +64 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/task_command_router_client.py +192 -82
- modal-1.5.6.dev13/modal/cli/_logs.py +131 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/app.py +16 -111
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/container.py +1 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/entry_point.py +2 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/function.py +148 -9
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/image.py +4 -2
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/import_refs.py +5 -2
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/profile.py +21 -6
- modal-1.5.6.dev13/modal/cli/server.py +136 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/token.py +9 -4
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/utils.py +13 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/client.py +0 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/client.pyi +2 -2
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/config.py +50 -6
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/exception.py +0 -4
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/functions.pyi +10 -8
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/io_streams.py +18 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/io_streams.pyi +18 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/runner.py +13 -3
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox.py +50 -152
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox.pyi +15 -188
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/token_flow.py +8 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/volume.py +138 -8
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/volume.pyi +28 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/SOURCES.txt +3 -2
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_reference_docs.py +0 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/reference_intro.md +0 -1
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_grpc.py +48 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2.py +1302 -1187
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2.pyi +277 -10
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2_grpc.py +99 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/api_pb2_grpc.pyi +30 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/modal_api_grpc.py +3 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_grpc.py +16 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2.py +45 -25
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2.pyi +29 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2_grpc.py +34 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/task_command_router_pb2_grpc.pyi +12 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_version/__init__.py +1 -1
- modal-1.5.6.dev11/modal/file_io.py +0 -564
- modal-1.5.6.dev11/modal/file_io.pyi +0 -447
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/LICENSE +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/README.md +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/__main__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_billing.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_clustered_functions.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_environments.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_function_variants.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_image.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_location.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_logs.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_object.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/manager.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/pty.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_output/rich.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_partial_function.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_resources.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_server.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_tunnel.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/_workspace.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/app.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/app.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/billing.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_stats.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/cluster.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/config.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/launch.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/volume.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cli/workspace.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cls.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/cls.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/container_process.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/dict.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/environments.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/flash.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/functions.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/image.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/image.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/mount.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/network_file_system.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/network_file_system.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/object.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/object.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/output.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/partial_function.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/partial_function.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/proxy.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/py.typed +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/queue.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/retries.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/running_app.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox_fs.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/schedule.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/secret.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/server.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/server.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/serving.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/types.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/workspace.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal/workspace.pyi +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/pyproject.toml +0 -0
- {modal-1.5.6.dev11 → modal-1.5.6.dev13}/setup.cfg +0 -0
|
@@ -16,6 +16,7 @@ from google.protobuf.message import Message
|
|
|
16
16
|
from grpclib import Status
|
|
17
17
|
from synchronicity.combined_types import MethodWithAio
|
|
18
18
|
|
|
19
|
+
from modal._utils.logger import logger
|
|
19
20
|
from modal.types import CloudBucketMountInfo, FunctionInfo, HttpInfo, VolumeMountInfo
|
|
20
21
|
from modal_proto import api_pb2
|
|
21
22
|
from modal_proto.modal_api_grpc import ModalClientModal
|
|
@@ -63,7 +64,7 @@ from ._utils.function_utils import (
|
|
|
63
64
|
parse_gpu_config,
|
|
64
65
|
validate_target_concurrency,
|
|
65
66
|
)
|
|
66
|
-
from ._utils.grpc_utils import Retry, RetryWarningMessage
|
|
67
|
+
from ._utils.grpc_utils import Retry, RetryWarningMessage, is_class_function_lookup_error
|
|
67
68
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
|
|
68
69
|
from .call_graph import InputInfo, _reconstruct_call_graph
|
|
69
70
|
from .client import _Client
|
|
@@ -1360,9 +1361,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1360
1361
|
env_context = (
|
|
1361
1362
|
f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
|
|
1362
1363
|
)
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1364
|
+
msg = f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
|
|
1365
|
+
if is_class_function_lookup_error(exc):
|
|
1366
|
+
msg = (
|
|
1367
|
+
f"Function '{name}' from the '{app_name}' app{env_context} is a modal.Cls. "
|
|
1368
|
+
f"Use `modal.Cls.from_name` instead."
|
|
1369
|
+
)
|
|
1370
|
+
raise NotFoundError(msg) from None
|
|
1366
1371
|
|
|
1367
1372
|
print_server_warnings(response.server_warnings)
|
|
1368
1373
|
|
|
@@ -2306,7 +2311,8 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2306
2311
|
"""Fetch information about the graph of Inputs this FunctionCall is part of.
|
|
2307
2312
|
|
|
2308
2313
|
Note: the call graph data is not populated in real-time, and its capture is best-effort.
|
|
2309
|
-
We do not recommend relying on this method
|
|
2314
|
+
Large call graphs may be truncated. We do not recommend relying on this method
|
|
2315
|
+
for critical use cases.
|
|
2310
2316
|
|
|
2311
2317
|
See the [`modal.types`](/docs/sdk/py/latest/types) reference for information
|
|
2312
2318
|
on the return values.
|
|
@@ -2317,6 +2323,11 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2317
2323
|
assert self._client and self._client.stub
|
|
2318
2324
|
request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
|
|
2319
2325
|
response = await self._client.stub.FunctionGetCallGraph(request)
|
|
2326
|
+
if response.truncated:
|
|
2327
|
+
logger.warning(
|
|
2328
|
+
f"Call graph for {self.object_id} was truncated; "
|
|
2329
|
+
f"returning the first {len(response.inputs)} inputs. The graph may be incomplete."
|
|
2330
|
+
)
|
|
2320
2331
|
return _reconstruct_call_graph(response)
|
|
2321
2332
|
|
|
2322
2333
|
@live_method
|
|
@@ -46,16 +46,17 @@ class FunctionCreationStatus:
|
|
|
46
46
|
|
|
47
47
|
if not self.response:
|
|
48
48
|
self.status_row.finish(f"Unknown error when creating function {self.tag}")
|
|
49
|
+
return
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
for warning in self.response.server_warnings:
|
|
52
|
+
self.status_row.warn(warning)
|
|
53
|
+
|
|
54
|
+
if web_url := self.response.handle_metadata.web_url:
|
|
51
55
|
url_info = self.response.function.web_url_info
|
|
52
56
|
requires_proxy_auth = self.response.function.webhook_config.requires_proxy_auth
|
|
53
57
|
proxy_auth_suffix = " 🔑" if requires_proxy_auth else ""
|
|
54
58
|
# Ensure terms used here match terms used in modal.com/docs/guide/webhook-urls doc.
|
|
55
59
|
suffix = _get_suffix_from_web_url_info(url_info)
|
|
56
|
-
# TODO: this is only printed when we're showing progress. Maybe move this somewhere else.
|
|
57
|
-
for warning in self.response.server_warnings:
|
|
58
|
-
self.status_row.warn(warning)
|
|
59
60
|
self.status_row.finish(
|
|
60
61
|
f"Created web function {self.tag} => [magenta underline]{web_url}[/magenta underline]"
|
|
61
62
|
f"{proxy_auth_suffix}{suffix}"
|
|
@@ -78,8 +79,6 @@ class FunctionCreationStatus:
|
|
|
78
79
|
)
|
|
79
80
|
|
|
80
81
|
else:
|
|
81
|
-
for warning in self.response.server_warnings:
|
|
82
|
-
self.status_row.warn(warning)
|
|
83
82
|
self.status_row.finish(f"Created function {self.tag}.")
|
|
84
83
|
if self.response.function.method_definitions_set:
|
|
85
84
|
for method_definition in self.response.function.method_definitions.values():
|
|
@@ -109,7 +109,7 @@ _current_input_id: ContextVar = ContextVar("_current_input_id")
|
|
|
109
109
|
_current_function_call_id: ContextVar = ContextVar("_current_function_call_id")
|
|
110
110
|
_current_attempt_token: ContextVar = ContextVar("_current_attempt_token")
|
|
111
111
|
|
|
112
|
-
_is_currently_importing = False
|
|
112
|
+
_is_currently_importing = False
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
@contextmanager
|
|
@@ -4,6 +4,7 @@ import base64
|
|
|
4
4
|
import json
|
|
5
5
|
import random
|
|
6
6
|
import time
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
from modal._utils.grpc_utils import DEFAULT_MAX_RETRIES, Retry
|
|
@@ -15,8 +16,15 @@ from .logger import logger
|
|
|
15
16
|
AUTH_DENIED_EXCEPTIONS = (AuthError, PermissionDeniedError)
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
TokenFetcher = Callable[[Retry], Awaitable[str]]
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
class _AuthTokenManager:
|
|
19
|
-
"""Handles fetching and refreshing of
|
|
23
|
+
"""Handles fetching and refreshing of a short-lived JWT.
|
|
24
|
+
|
|
25
|
+
Defaults to the input plane auth token (`AuthTokenGet`); pass `fetch` to manage a different token with the
|
|
26
|
+
same caching and refresh behavior.
|
|
27
|
+
"""
|
|
20
28
|
|
|
21
29
|
# Fraction of the token's lifetime to use before refreshing.
|
|
22
30
|
REFRESH_FRACTION = 0.5
|
|
@@ -26,7 +34,7 @@ class _AuthTokenManager:
|
|
|
26
34
|
# only ever delays a refresh, so the margin before expiry stays at no less than
|
|
27
35
|
# 1 - REFRESH_FRACTION - REFRESH_JITTER of the lifetime.
|
|
28
36
|
REFRESH_JITTER = 0.1
|
|
29
|
-
# Bound each
|
|
37
|
+
# Bound each fetch attempt so a refresh can't hang.
|
|
30
38
|
AUTH_TOKEN_TIMEOUT = 5.0
|
|
31
39
|
AUTH_TOKEN_RETRY_TOTAL_TIMEOUT = 3 * AUTH_TOKEN_TIMEOUT
|
|
32
40
|
# After a failed refresh, wait before hitting the server again, growing exponentially with
|
|
@@ -36,8 +44,9 @@ class _AuthTokenManager:
|
|
|
36
44
|
# If the token doesn't have an expiry field, default to current time plus this value (not expected).
|
|
37
45
|
DEFAULT_EXPIRY_OFFSET = 20 * 60
|
|
38
46
|
|
|
39
|
-
def __init__(self, stub: "modal_api_grpc.ModalClientModal"):
|
|
47
|
+
def __init__(self, stub: "modal_api_grpc.ModalClientModal", fetch: TokenFetcher | None = None):
|
|
40
48
|
self._stub = stub
|
|
49
|
+
self._fetch = fetch or self._fetch_auth_token
|
|
41
50
|
self._token = ""
|
|
42
51
|
self._expiry = 0.0
|
|
43
52
|
self._refresh_at = 0.0
|
|
@@ -105,31 +114,33 @@ class _AuthTokenManager:
|
|
|
105
114
|
# Retrieve the exception, if any, so a failure isn't logged as unhandled when no caller is waiting.
|
|
106
115
|
task.exception()
|
|
107
116
|
|
|
117
|
+
async def _fetch_auth_token(self, retry: Retry) -> str:
|
|
118
|
+
resp: api_pb2.AuthTokenGetResponse = await self._stub.AuthTokenGet(api_pb2.AuthTokenGetRequest(), retry=retry)
|
|
119
|
+
return resp.token
|
|
120
|
+
|
|
108
121
|
async def _fetch_token(self):
|
|
109
|
-
"""
|
|
122
|
+
"""Fetch a new token and cache it, or arm the failure backoff."""
|
|
110
123
|
try:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
total_timeout=self.AUTH_TOKEN_RETRY_TOTAL_TIMEOUT,
|
|
118
|
-
max_retries=0 if self._token else DEFAULT_MAX_RETRIES,
|
|
119
|
-
),
|
|
124
|
+
# No cached token to fall back on, so a failure is user-visible: retry transient errors.
|
|
125
|
+
# Otherwise one attempt, and _retry_after handles the cooldown.
|
|
126
|
+
retry = Retry(
|
|
127
|
+
attempt_timeout=self.AUTH_TOKEN_TIMEOUT,
|
|
128
|
+
total_timeout=self.AUTH_TOKEN_RETRY_TOTAL_TIMEOUT,
|
|
129
|
+
max_retries=0 if self._token else DEFAULT_MAX_RETRIES,
|
|
120
130
|
)
|
|
121
|
-
|
|
131
|
+
token = await self._fetch(retry)
|
|
132
|
+
if not token:
|
|
122
133
|
# Not expected
|
|
123
134
|
raise ExecutionError(
|
|
124
135
|
"Internal error: Did not receive auth token from server. Please contact Modal support."
|
|
125
136
|
)
|
|
126
|
-
if exp := self._decode_jwt(
|
|
137
|
+
if exp := self._decode_jwt(token).get("exp"):
|
|
127
138
|
expiry = float(exp)
|
|
128
139
|
else:
|
|
129
140
|
# This should never happen.
|
|
130
|
-
logger.warning("
|
|
141
|
+
logger.warning("Auth token does not contain exp field")
|
|
131
142
|
expiry = time.time() + self.DEFAULT_EXPIRY_OFFSET
|
|
132
|
-
self._token =
|
|
143
|
+
self._token = token
|
|
133
144
|
self._set_expiry(expiry)
|
|
134
145
|
self._retry_after = 0.0
|
|
135
146
|
self._backoff = self.FAILURE_BACKOFF_BASE
|
|
@@ -25,7 +25,7 @@ from grpclib.encoding.base import CodecBase, StatusDetailsCodecBase
|
|
|
25
25
|
from grpclib.exceptions import StreamTerminatedError
|
|
26
26
|
from grpclib.protocol import H2Protocol
|
|
27
27
|
|
|
28
|
-
from modal.exception import ClientClosed, ConnectionError
|
|
28
|
+
from modal.exception import ClientClosed, ConnectionError, NotFoundError
|
|
29
29
|
from modal_proto import api_pb2
|
|
30
30
|
from modal_version import __version__
|
|
31
31
|
|
|
@@ -121,12 +121,12 @@ class ModalChannel(grpclib.client.Channel):
|
|
|
121
121
|
self.__target_host = host
|
|
122
122
|
self.__target_port = port
|
|
123
123
|
self.__ssl_context = ssl_context
|
|
124
|
-
self.
|
|
125
|
-
self.
|
|
124
|
+
self._permanently_closed = False
|
|
125
|
+
self._closed_error_message = closed_error_message
|
|
126
126
|
|
|
127
127
|
async def __connect__(self):
|
|
128
|
-
if self.
|
|
129
|
-
raise ClientClosed(self.
|
|
128
|
+
if self._permanently_closed and self._closed_error_message is not None:
|
|
129
|
+
raise ClientClosed(self._closed_error_message)
|
|
130
130
|
return await super().__connect__()
|
|
131
131
|
|
|
132
132
|
async def _create_connection(self) -> H2Protocol:
|
|
@@ -161,8 +161,13 @@ class ModalChannel(grpclib.client.Channel):
|
|
|
161
161
|
and not self._protocol.connection.is_closing()
|
|
162
162
|
)
|
|
163
163
|
|
|
164
|
+
def release_connection(self):
|
|
165
|
+
"""Release the connection while leaving the channel reusable."""
|
|
166
|
+
return super().close()
|
|
167
|
+
|
|
164
168
|
def close(self):
|
|
165
|
-
|
|
169
|
+
"""Close the channel permanently."""
|
|
170
|
+
self._permanently_closed = True
|
|
166
171
|
return super().close()
|
|
167
172
|
|
|
168
173
|
|
|
@@ -450,6 +455,15 @@ async def retry_transient_errors(
|
|
|
450
455
|
return await _retry_transient_errors(fn, req, retry=Retry(max_retries=max_retries))
|
|
451
456
|
|
|
452
457
|
|
|
458
|
+
def is_class_function_lookup_error(exc: NotFoundError) -> bool:
|
|
459
|
+
"""Whether a function lookup failed because the requested name identifies a class."""
|
|
460
|
+
return any(
|
|
461
|
+
isinstance(detail, api_pb2.FunctionLookupError)
|
|
462
|
+
and detail.reason == api_pb2.FunctionLookupError.REASON_CLASS_NAME_USED
|
|
463
|
+
for detail in (getattr(exc, "_grpc_details", None) or [])
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
453
467
|
def get_server_retry_policy(exc: Exception) -> api_pb2.RPCRetryPolicy | None:
|
|
454
468
|
"""Get server retry policy."""
|
|
455
469
|
if not isinstance(exc, GRPCError) or not exc.details:
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
import asyncio
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IdleCountdown:
|
|
7
|
+
"""An extendable idle timeout tracker."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
10
|
+
self._loop = loop
|
|
11
|
+
self._handle: asyncio.TimerHandle | None = None
|
|
12
|
+
self._retired = False
|
|
13
|
+
self._expire_on_idle = False
|
|
14
|
+
self._release: Callable[[], None] | None = None
|
|
15
|
+
|
|
16
|
+
def _assert_owning_loop(self) -> None:
|
|
17
|
+
assert asyncio.get_running_loop() is self._loop, "Idle bookkeeping must run on its owning event loop"
|
|
18
|
+
|
|
19
|
+
def expire_when_idle(self) -> None:
|
|
20
|
+
"""Release at the current or next idle boundary, even if the timeout is disabled.
|
|
21
|
+
|
|
22
|
+
Must run on the owning event loop without yielding control.
|
|
23
|
+
"""
|
|
24
|
+
self._assert_owning_loop()
|
|
25
|
+
self._expire_on_idle = True
|
|
26
|
+
if self._release is not None and not self._retired:
|
|
27
|
+
release = self._release
|
|
28
|
+
self.stop()
|
|
29
|
+
release()
|
|
30
|
+
|
|
31
|
+
def stop(self) -> None:
|
|
32
|
+
"""Cancel the countdown on the owning event loop without yielding control."""
|
|
33
|
+
self._assert_owning_loop()
|
|
34
|
+
self._release = None
|
|
35
|
+
if self._handle is not None:
|
|
36
|
+
self._handle.cancel()
|
|
37
|
+
self._handle = None
|
|
38
|
+
|
|
39
|
+
def retire(self) -> None:
|
|
40
|
+
"""Stop the countdown and refuse every subsequent arm.
|
|
41
|
+
|
|
42
|
+
Must run on the owning event loop without yielding control.
|
|
43
|
+
"""
|
|
44
|
+
self._assert_owning_loop()
|
|
45
|
+
self._retired = True
|
|
46
|
+
self.stop()
|
|
47
|
+
|
|
48
|
+
def arm(self, timeout_secs: float, release: Callable[[], None]) -> None:
|
|
49
|
+
"""Start the countdown again, calling `release` when it elapses.
|
|
50
|
+
|
|
51
|
+
Must run on the owning event loop without yielding control.
|
|
52
|
+
A timeout of zero or less never fires unless `expire_when_idle` was
|
|
53
|
+
requested. `release` runs as a plain callback on the event loop, so it
|
|
54
|
+
must not block.
|
|
55
|
+
"""
|
|
56
|
+
self.stop()
|
|
57
|
+
if self._retired:
|
|
58
|
+
return
|
|
59
|
+
self._release = release
|
|
60
|
+
if self._expire_on_idle:
|
|
61
|
+
timeout_secs = 0
|
|
62
|
+
elif timeout_secs <= 0:
|
|
63
|
+
return
|
|
64
|
+
self._handle = self._loop.call_later(timeout_secs, release)
|