modAL 1.3.6.dev8__tar.gz → 1.3.6.dev10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/PKG-INFO +1 -2
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/README.md +0 -1
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/async_utils.py +17 -2
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/blob_utils.py +2 -2
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/grpc_utils.py +15 -5
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/task_command_router_client.py +38 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/shell.py +57 -7
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/client.pyi +2 -2
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/functions.pyi +6 -6
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox.py +252 -12
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox.pyi +235 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/volume.py +3 -3
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/PKG-INFO +1 -2
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_version/__init__.py +1 -1
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/pyproject.toml +47 -1
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/LICENSE +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/__main__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_billing.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_clustered_functions.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_functions.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_grpc_client.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_ipython.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_load_context.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_location.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_logs.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_object.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/manager.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/pty.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/rich.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_output/status.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_partial_function.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_resolver.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_resources.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_serialization.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_server.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_traceback.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_tunnel.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_tunnel.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_type_manager.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/browser_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/logger.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_vendor/version.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/_watcher.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/app.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/app.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/billing.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/README.md +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/builder/base-images.json +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/call_graph.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/_download.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/_traceback.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/app.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/billing.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/changelog.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/cluster.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/config.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/container.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/dashboard.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/dict.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/entry_point.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/environment.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/import_refs.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/launch.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/profile.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/queues.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/run.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/secret.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/selector.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/token.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/utils.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cli/volume.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/client.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cls.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/cls.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/config.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/container_process.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/container_process.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/dict.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/dict.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/environments.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/environments.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/exception.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/flash.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/experimental/ipython.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/file_io.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/file_io.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/functions.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/gpu.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/image.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/image.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/io_streams.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/io_streams.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/mount.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/mount.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/network_file_system.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/network_file_system.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/object.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/object.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/output.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/parallel_map.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/parallel_map.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/partial_function.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/partial_function.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/proxy.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/proxy.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/py.typed +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/queue.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/queue.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/retries.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/runner.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/runner.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/running_app.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox_fs.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/sandbox_fs.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/schedule.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/scheduler_placement.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/secret.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/secret.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/server.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/server.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/serving.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/serving.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/snapshot.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/snapshot.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/stream_type.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/token_flow.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/token_flow.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal/volume.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/__init__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_grpc.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/py.typed +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/modal_version/__main__.py +0 -0
- {modal-1.3.6.dev8 → modal-1.3.6.dev10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.3.6.
|
|
3
|
+
Version: 1.3.6.dev10
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -36,7 +36,6 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
[](https://pypi.org/project/modal/)
|
|
38
38
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
39
|
-
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
40
39
|
[](https://modal.com/slack)
|
|
41
40
|
|
|
42
41
|
The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/modal/)
|
|
4
4
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
5
|
-
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
6
5
|
[](https://modal.com/slack)
|
|
7
6
|
|
|
8
7
|
The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
|
|
@@ -345,12 +345,24 @@ def synchronize_api(obj, target_module=None):
|
|
|
345
345
|
RETRY_N_ATTEMPTS_OVERRIDE: Optional[int] = None
|
|
346
346
|
|
|
347
347
|
|
|
348
|
-
def retry(
|
|
348
|
+
def retry(
|
|
349
|
+
direct_fn=None,
|
|
350
|
+
*,
|
|
351
|
+
n_attempts=3,
|
|
352
|
+
base_delay=0,
|
|
353
|
+
delay_factor=2,
|
|
354
|
+
attempt_timeout: Optional[float] = 90,
|
|
355
|
+
attempt_timeout_factor=1,
|
|
356
|
+
):
|
|
349
357
|
"""Decorator that calls an async function multiple times, with a given timeout.
|
|
350
358
|
|
|
351
359
|
If a `base_delay` is provided, the function is given an exponentially
|
|
352
360
|
increasing delay on each run, up until the maximum number of attempts.
|
|
353
361
|
|
|
362
|
+
If `attempt_timeout_factor` is set to a value > 1, the per-attempt timeout
|
|
363
|
+
increases exponentially (starting from `attempt_timeout`, multiplied by
|
|
364
|
+
`attempt_timeout_factor` after each failed attempt).
|
|
365
|
+
|
|
354
366
|
Usage:
|
|
355
367
|
|
|
356
368
|
```
|
|
@@ -375,10 +387,11 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
|
|
|
375
387
|
local_n_attempts = n_attempts
|
|
376
388
|
|
|
377
389
|
delay = base_delay
|
|
390
|
+
current_attempt_timeout = attempt_timeout
|
|
378
391
|
for i in range(local_n_attempts):
|
|
379
392
|
t0 = time.time()
|
|
380
393
|
try:
|
|
381
|
-
return await asyncio.wait_for(fn(*args, **kwargs), timeout=
|
|
394
|
+
return await asyncio.wait_for(fn(*args, **kwargs), timeout=current_attempt_timeout)
|
|
382
395
|
except asyncio.CancelledError:
|
|
383
396
|
logger.debug(f"Function {fn} was cancelled")
|
|
384
397
|
raise
|
|
@@ -392,6 +405,8 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
|
|
|
392
405
|
)
|
|
393
406
|
await asyncio.sleep(delay)
|
|
394
407
|
delay *= delay_factor
|
|
408
|
+
if current_attempt_timeout is not None:
|
|
409
|
+
current_attempt_timeout *= attempt_timeout_factor
|
|
395
410
|
|
|
396
411
|
return f_wrapped
|
|
397
412
|
|
|
@@ -58,7 +58,7 @@ MULTIPART_UPLOAD_THRESHOLD = 1024**3
|
|
|
58
58
|
BLOCK_SIZE: int = 8 * 1024 * 1024
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
@retry(n_attempts=3, base_delay=0.3,
|
|
61
|
+
@retry(n_attempts=3, base_delay=0.3, attempt_timeout=None)
|
|
62
62
|
async def _upload_to_s3_url(
|
|
63
63
|
upload_url,
|
|
64
64
|
payload: "BytesIOSegmentPayload",
|
|
@@ -310,7 +310,7 @@ async def blob_upload_file(
|
|
|
310
310
|
return blob_id
|
|
311
311
|
|
|
312
312
|
|
|
313
|
-
@retry(n_attempts=5, base_delay=0.1,
|
|
313
|
+
@retry(n_attempts=5, base_delay=0.1, attempt_timeout=None)
|
|
314
314
|
async def _download_from_url(download_url: str) -> bytes:
|
|
315
315
|
async with ClientSessionRegistry.get_session().get(download_url) as s3_resp:
|
|
316
316
|
# S3 signal to slow down request rate.
|
|
@@ -89,6 +89,10 @@ RETRYABLE_GRPC_STATUS_CODES = [
|
|
|
89
89
|
Status.UNKNOWN,
|
|
90
90
|
]
|
|
91
91
|
SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
|
|
92
|
+
# Initial per-attempt timeout for TCP/TLS handshake in connect_channel.
|
|
93
|
+
# Doubles each attempt (attempt_timeout_factor=2): 0.5 + 1 + 2 + 4 + 8 = 15.5s + ~1.5s inter-attempt delays.
|
|
94
|
+
CONNECT_TIMEOUT = 0.5
|
|
95
|
+
DEFAULT_MAX_RETRIES = 3
|
|
92
96
|
|
|
93
97
|
|
|
94
98
|
@dataclass
|
|
@@ -117,7 +121,7 @@ class ConnectionManager:
|
|
|
117
121
|
self._channels[server_url] = create_channel(server_url, self._metadata)
|
|
118
122
|
try:
|
|
119
123
|
await connect_channel(self._channels[server_url])
|
|
120
|
-
except OSError as exc:
|
|
124
|
+
except (OSError, asyncio.TimeoutError) as exc:
|
|
121
125
|
raise ConnectionError("Could not connect to the Modal server.") from exc
|
|
122
126
|
return self._channels[server_url]
|
|
123
127
|
|
|
@@ -221,7 +225,7 @@ def create_channel(
|
|
|
221
225
|
return channel
|
|
222
226
|
|
|
223
227
|
|
|
224
|
-
@retry(n_attempts=5, base_delay=0.1)
|
|
228
|
+
@retry(n_attempts=5, base_delay=0.1, attempt_timeout=CONNECT_TIMEOUT, attempt_timeout_factor=2)
|
|
225
229
|
async def connect_channel(channel: grpclib.client.Channel):
|
|
226
230
|
"""Connect to socket and raise exceptions when there is a connection issue."""
|
|
227
231
|
await channel.__connect__()
|
|
@@ -246,7 +250,7 @@ class Retry:
|
|
|
246
250
|
base_delay: float = 0.1
|
|
247
251
|
max_delay: float = 1
|
|
248
252
|
delay_factor: float = 2
|
|
249
|
-
max_retries: Optional[int] =
|
|
253
|
+
max_retries: Optional[int] = DEFAULT_MAX_RETRIES
|
|
250
254
|
additional_status_codes: list = field(default_factory=list)
|
|
251
255
|
attempt_timeout: Optional[float] = None # timeout for each attempt
|
|
252
256
|
total_timeout: Optional[float] = None # timeout for the entire function call
|
|
@@ -257,7 +261,7 @@ class Retry:
|
|
|
257
261
|
async def retry_transient_errors(
|
|
258
262
|
fn: "grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]",
|
|
259
263
|
req: RequestType,
|
|
260
|
-
max_retries: Optional[int] =
|
|
264
|
+
max_retries: Optional[int] = DEFAULT_MAX_RETRIES,
|
|
261
265
|
) -> ResponseType:
|
|
262
266
|
"""Minimum API version of _retry_transient_errors that works with grpclib.client.UnaryUnaryMethod.
|
|
263
267
|
|
|
@@ -373,7 +377,13 @@ async def _retry_transient_errors(
|
|
|
373
377
|
try:
|
|
374
378
|
with suppress_tb_frame():
|
|
375
379
|
return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
|
|
376
|
-
except (
|
|
380
|
+
except (
|
|
381
|
+
StreamTerminatedError,
|
|
382
|
+
GRPCError,
|
|
383
|
+
OSError,
|
|
384
|
+
asyncio.TimeoutError,
|
|
385
|
+
AttributeError,
|
|
386
|
+
) as exc:
|
|
377
387
|
# Note that we only catch AttributeError to handle a specific case that works around a bug
|
|
378
388
|
# in grpclib<=0.4.7. See above (search for `write_appdata`).
|
|
379
389
|
|
|
@@ -242,6 +242,44 @@ class TaskCommandRouterClient:
|
|
|
242
242
|
lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
|
|
243
243
|
)
|
|
244
244
|
|
|
245
|
+
async def container_create(self, request: sr_pb2.TaskContainerCreateRequest) -> sr_pb2.TaskContainerCreateResponse:
|
|
246
|
+
"""Create an additional container via task command router."""
|
|
247
|
+
with grpc_error_converter():
|
|
248
|
+
return await call_with_retries_on_transient_errors(
|
|
249
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerCreate, request)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
async def container_terminate(
|
|
253
|
+
self,
|
|
254
|
+
request: sr_pb2.TaskContainerTerminateRequest,
|
|
255
|
+
) -> sr_pb2.TaskContainerTerminateResponse:
|
|
256
|
+
"""Terminate an additional container via task command router."""
|
|
257
|
+
with grpc_error_converter():
|
|
258
|
+
return await call_with_retries_on_transient_errors(
|
|
259
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerTerminate, request)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
async def container_wait(self, request: sr_pb2.TaskContainerWaitRequest) -> sr_pb2.TaskContainerWaitResponse:
|
|
263
|
+
"""Wait for an additional container via task command router."""
|
|
264
|
+
with grpc_error_converter():
|
|
265
|
+
return await call_with_retries_on_transient_errors(
|
|
266
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerWait, request)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async def container_get(self, request: sr_pb2.TaskContainerGetRequest) -> sr_pb2.TaskContainerGetResponse:
|
|
270
|
+
"""Get the latest tracked container for a logical name via task command router."""
|
|
271
|
+
with grpc_error_converter():
|
|
272
|
+
return await call_with_retries_on_transient_errors(
|
|
273
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerGet, request)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
async def container_list(self, request: sr_pb2.TaskContainerListRequest) -> sr_pb2.TaskContainerListResponse:
|
|
277
|
+
"""List sandbox containers via task command router."""
|
|
278
|
+
with grpc_error_converter():
|
|
279
|
+
return await call_with_retries_on_transient_errors(
|
|
280
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerList, request)
|
|
281
|
+
)
|
|
282
|
+
|
|
245
283
|
async def exec_stdio_read(
|
|
246
284
|
self,
|
|
247
285
|
task_id: str,
|
|
@@ -18,6 +18,7 @@ from ..mount import _Mount
|
|
|
18
18
|
from ..runner import interactive_shell
|
|
19
19
|
from ..sandbox import Sandbox
|
|
20
20
|
from ..secret import Secret
|
|
21
|
+
from ..stream_type import StreamType
|
|
21
22
|
from ..volume import Volume
|
|
22
23
|
from .container import exec
|
|
23
24
|
from .import_refs import (
|
|
@@ -64,26 +65,75 @@ def _is_valid_modal_id(ref: str, prefix: str) -> bool:
|
|
|
64
65
|
return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
def _parse_sandbox_container_ref(ref: str) -> tuple[str, Optional[str]]:
|
|
69
|
+
"""Parse a sandbox:container reference into (sandbox_id, container_name).
|
|
70
|
+
|
|
71
|
+
Supports the pattern `sb-xyz:container_name` for accessing sandbox containers.
|
|
72
|
+
Returns (ref, None) if no container name is specified.
|
|
73
|
+
"""
|
|
74
|
+
if _is_valid_modal_id(ref, "sb-"):
|
|
75
|
+
return ref, None
|
|
76
|
+
|
|
77
|
+
if ":" in ref:
|
|
78
|
+
parts = ref.split(":", 1)
|
|
79
|
+
sandbox_id, container_name = parts[0], parts[1]
|
|
80
|
+
if _is_valid_modal_id(sandbox_id, "sb-") and container_name:
|
|
81
|
+
return sandbox_id, container_name
|
|
82
|
+
|
|
83
|
+
return ref, None
|
|
84
|
+
|
|
85
|
+
|
|
67
86
|
def _is_running_container_ref(ref: Optional[str]) -> bool:
|
|
68
87
|
if ref is None:
|
|
69
88
|
return False
|
|
70
|
-
|
|
89
|
+
sandbox_id, _ = _parse_sandbox_container_ref(ref)
|
|
90
|
+
return _is_valid_modal_id(sandbox_id, "sb-") or _is_valid_modal_id(ref, "ta-")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _start_shell_in_sandbox_container(sandbox_id: str, container_name: str, cmd: str, pty: bool) -> None:
|
|
94
|
+
"""Shell into a named container within a sandbox."""
|
|
95
|
+
try:
|
|
96
|
+
sandbox = Sandbox.from_id(sandbox_id)
|
|
97
|
+
except NotFoundError:
|
|
98
|
+
raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
sandbox_container = sandbox._experimental_containers.get(name=container_name)
|
|
104
|
+
if pty:
|
|
105
|
+
process = sandbox_container.exec(*shlex.split(cmd), pty=pty)
|
|
106
|
+
process.attach()
|
|
107
|
+
else:
|
|
108
|
+
process = sandbox_container.exec(
|
|
109
|
+
*shlex.split(cmd), pty=pty, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
|
|
110
|
+
)
|
|
111
|
+
process.wait()
|
|
112
|
+
except NotFoundError:
|
|
113
|
+
raise ClickException(f"Container '{container_name}' not found in Sandbox '{sandbox_id}'.")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
raise ClickException(f"Error exec-ing into container '{container_name}' in Sandbox '{sandbox_id}': {str(e)}")
|
|
71
116
|
|
|
72
117
|
|
|
73
118
|
def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
|
|
74
|
-
|
|
119
|
+
sandbox_id, container_name = _parse_sandbox_container_ref(ref)
|
|
120
|
+
|
|
121
|
+
if container_name is not None:
|
|
122
|
+
return _start_shell_in_sandbox_container(sandbox_id, container_name, cmd, pty)
|
|
123
|
+
|
|
124
|
+
if _is_valid_modal_id(sandbox_id, "sb-"):
|
|
75
125
|
try:
|
|
76
|
-
sandbox = Sandbox.from_id(
|
|
126
|
+
sandbox = Sandbox.from_id(sandbox_id)
|
|
77
127
|
ref = sandbox._get_task_id()
|
|
78
|
-
except NotFoundError
|
|
79
|
-
raise ClickException(f"Sandbox '{
|
|
128
|
+
except NotFoundError:
|
|
129
|
+
raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
|
|
80
130
|
except Exception as e:
|
|
81
|
-
raise ClickException(f"Error connecting to Sandbox '{
|
|
131
|
+
raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
|
|
82
132
|
|
|
83
133
|
assert _is_valid_modal_id(ref, "ta-")
|
|
84
134
|
try:
|
|
85
135
|
exec(container_id=ref, command=shlex.split(cmd), pty=pty)
|
|
86
|
-
except NotFoundError
|
|
136
|
+
except NotFoundError:
|
|
87
137
|
raise ClickException(f"Container '{ref}' not found (is it still running?)")
|
|
88
138
|
except Exception as e:
|
|
89
139
|
raise ClickException(f"Error connecting to container '{ref}': {str(e)}")
|
|
@@ -35,7 +35,7 @@ class _Client:
|
|
|
35
35
|
server_url: str,
|
|
36
36
|
client_type: int,
|
|
37
37
|
credentials: typing.Optional[tuple[str, str]],
|
|
38
|
-
version: str = "1.3.6.
|
|
38
|
+
version: str = "1.3.6.dev10",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -171,7 +171,7 @@ class Client:
|
|
|
171
171
|
server_url: str,
|
|
172
172
|
client_type: int,
|
|
173
173
|
credentials: typing.Optional[tuple[str, str]],
|
|
174
|
-
version: str = "1.3.6.
|
|
174
|
+
version: str = "1.3.6.dev10",
|
|
175
175
|
):
|
|
176
176
|
"""mdmd:hidden
|
|
177
177
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -408,7 +408,7 @@ class Function(
|
|
|
408
408
|
|
|
409
409
|
_call_generator: ___call_generator_spec
|
|
410
410
|
|
|
411
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
411
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
412
412
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
413
413
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
414
414
|
...
|
|
@@ -417,7 +417,7 @@ class Function(
|
|
|
417
417
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
418
418
|
...
|
|
419
419
|
|
|
420
|
-
remote: __remote_spec[modal._functions.
|
|
420
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
421
421
|
|
|
422
422
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
423
423
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -444,7 +444,7 @@ class Function(
|
|
|
444
444
|
"""
|
|
445
445
|
...
|
|
446
446
|
|
|
447
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
447
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
448
448
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
449
449
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
450
450
|
|
|
@@ -467,7 +467,7 @@ class Function(
|
|
|
467
467
|
"""
|
|
468
468
|
...
|
|
469
469
|
|
|
470
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
470
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
471
471
|
|
|
472
472
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
473
473
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -475,7 +475,7 @@ class Function(
|
|
|
475
475
|
|
|
476
476
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
477
477
|
|
|
478
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
478
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
479
479
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
480
480
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
481
481
|
|
|
@@ -496,7 +496,7 @@ class Function(
|
|
|
496
496
|
"""
|
|
497
497
|
...
|
|
498
498
|
|
|
499
|
-
spawn: __spawn_spec[modal._functions.
|
|
499
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
500
500
|
|
|
501
501
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
502
502
|
"""Return the inner Python object wrapped by this Modal Function."""
|