modal 1.4.3.dev10__tar.gz → 1.4.3.dev12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/PKG-INFO +1 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/execution_context.py +6 -3
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/execution_context.pyi +5 -3
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/sandbox_fs_utils.py +30 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/app.py +2 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/app.pyi +3 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/_help.py +79 -48
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/run.py +8 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/utils.py +4 -5
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/client.pyi +2 -2
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/functions.pyi +6 -6
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/runner.py +4 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/runner.pyi +3 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/sandbox_fs.py +53 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/sandbox_fs.pyi +72 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/serving.py +4 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/serving.pyi +3 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/volume.py +32 -4
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/volume.pyi +9 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_version/__init__.py +1 -1
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/LICENSE +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/README.md +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/__main__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_billing.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_clustered_functions.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_functions.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_grpc_client.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_ipython.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_load_context.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_location.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_logs.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_object.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_output/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_output/manager.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_output/pty.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_output/rich.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_output/status.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_partial_function.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_resolver.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_resources.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_serialization.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_server.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_traceback.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_tunnel.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_tunnel.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_type_manager.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/logger.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_vendor/version.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/_watcher.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/billing.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/README.md +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/builder/base-images.json +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/call_graph.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/_download.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/_traceback.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/app.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/billing.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/bootstrap.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/changelog.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/cluster.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/config.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/container.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/dashboard.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/dict.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/entry_point.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/environment.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/import_refs.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/launch.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/logo.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/profile.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/queues.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/secret.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/selector.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/shell.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/token.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cli/volume.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/client.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cls.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/cls.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/config.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/container_process.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/container_process.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/dict.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/dict.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/environments.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/environments.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/exception.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/experimental/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/experimental/flash.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/experimental/ipython.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/file_io.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/file_io.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/functions.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/image.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/image.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/io_streams.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/io_streams.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/mount.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/mount.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/network_file_system.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/network_file_system.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/object.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/object.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/output.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/parallel_map.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/parallel_map.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/partial_function.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/partial_function.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/proxy.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/proxy.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/py.typed +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/queue.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/queue.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/retries.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/running_app.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/sandbox.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/sandbox.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/schedule.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/scheduler_placement.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/secret.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/secret.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/server.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/server.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/snapshot.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/snapshot.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/stream_type.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/token_flow.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal/token_flow.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/__init__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/api_grpc.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/api_pb2.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/py.typed +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/modal_version/__main__.py +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/pyproject.toml +0 -0
- {modal-1.4.3.dev10 → modal-1.4.3.dev12}/setup.cfg +0 -0
|
@@ -10,10 +10,13 @@ from .container_io_manager import _ContainerIOManager
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def is_local() -> bool:
|
|
13
|
-
"""
|
|
13
|
+
"""Indicate the execution context of the current process.
|
|
14
|
+
|
|
15
|
+
Note: this function specifically returns False when the current process is
|
|
16
|
+
running a Modal Function and True in all other cases. It will return True
|
|
17
|
+
when called from a child process of a Function or inside a Modal Sandbox,
|
|
18
|
+
even though those processes are running on Modal hardware.
|
|
14
19
|
|
|
15
|
-
Returns `True` when executed locally on the user's machine.
|
|
16
|
-
Returns `False` when executed from a Modal container in the cloud.
|
|
17
20
|
"""
|
|
18
21
|
return not _ContainerIOManager._singleton
|
|
19
22
|
|
|
@@ -4,10 +4,12 @@ import typing
|
|
|
4
4
|
import typing_extensions
|
|
5
5
|
|
|
6
6
|
def is_local() -> bool:
|
|
7
|
-
"""
|
|
7
|
+
"""Indicate the execution context of the current process.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Note: this function specifically returns False when the current process is
|
|
10
|
+
running a Modal Function and True in all other cases. It will return True
|
|
11
|
+
when called from a child process of a Function or inside a Modal Sandbox,
|
|
12
|
+
even though those processes are running on Modal hardware.
|
|
11
13
|
"""
|
|
12
14
|
...
|
|
13
15
|
|
|
@@ -255,6 +255,36 @@ def make_make_directory_command(remote_path: str, create_parents: bool) -> str:
|
|
|
255
255
|
return json.dumps({"MakeDirectory": {"path": remote_path, "parents": create_parents}})
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
def raise_stat_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
|
|
259
|
+
if payload := try_parse_error_payload(stderr):
|
|
260
|
+
logger.debug(
|
|
261
|
+
f"sandbox-fs-tools stat error: path={remote_path}, "
|
|
262
|
+
f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
|
|
263
|
+
)
|
|
264
|
+
if payload.error_kind == "NotFound":
|
|
265
|
+
raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
|
|
266
|
+
if payload.error_kind == "NotDirectory":
|
|
267
|
+
raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
|
|
268
|
+
if payload.error_kind == "PermissionDenied":
|
|
269
|
+
raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
|
|
270
|
+
raise SandboxFilesystemError(payload.message)
|
|
271
|
+
|
|
272
|
+
if stderr_text := _stderr_to_text(stderr):
|
|
273
|
+
logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
|
|
274
|
+
raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def make_stat_command(remote_path: str) -> str:
|
|
278
|
+
"""Build the JSON command string for a Stat operation.
|
|
279
|
+
|
|
280
|
+
The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
|
|
281
|
+
Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
|
|
282
|
+
this schema like protobuf changes: fields must not be removed or renamed,
|
|
283
|
+
only added with backwards-compatible defaults.
|
|
284
|
+
"""
|
|
285
|
+
return json.dumps({"Stat": {"path": remote_path}})
|
|
286
|
+
|
|
287
|
+
|
|
258
288
|
def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
|
|
259
289
|
if not PurePosixPath(remote_path).is_absolute():
|
|
260
290
|
raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
|
|
@@ -379,6 +379,7 @@ class _App:
|
|
|
379
379
|
async def run(
|
|
380
380
|
self,
|
|
381
381
|
*,
|
|
382
|
+
name: Optional[str] = None,
|
|
382
383
|
client: Optional[_Client] = None,
|
|
383
384
|
detach: bool = False,
|
|
384
385
|
interactive: bool = False,
|
|
@@ -426,7 +427,7 @@ class _App:
|
|
|
426
427
|
from .runner import _run_app # Defer import of runner.py, which imports a lot from Rich
|
|
427
428
|
|
|
428
429
|
async with _run_app(
|
|
429
|
-
self, client=client, detach=detach, interactive=interactive, environment_name=environment_name
|
|
430
|
+
self, name=name, client=client, detach=detach, interactive=interactive, environment_name=environment_name
|
|
430
431
|
):
|
|
431
432
|
yield self
|
|
432
433
|
|
|
@@ -256,6 +256,7 @@ class _App:
|
|
|
256
256
|
def run(
|
|
257
257
|
self,
|
|
258
258
|
*,
|
|
259
|
+
name: typing.Optional[str] = None,
|
|
259
260
|
client: typing.Optional[modal.client._Client] = None,
|
|
260
261
|
detach: bool = False,
|
|
261
262
|
interactive: bool = False,
|
|
@@ -864,6 +865,7 @@ class App:
|
|
|
864
865
|
self,
|
|
865
866
|
/,
|
|
866
867
|
*,
|
|
868
|
+
name: typing.Optional[str] = None,
|
|
867
869
|
client: typing.Optional[modal.client.Client] = None,
|
|
868
870
|
detach: bool = False,
|
|
869
871
|
interactive: bool = False,
|
|
@@ -913,6 +915,7 @@ class App:
|
|
|
913
915
|
self,
|
|
914
916
|
/,
|
|
915
917
|
*,
|
|
918
|
+
name: typing.Optional[str] = None,
|
|
916
919
|
client: typing.Optional[modal.client.Client] = None,
|
|
917
920
|
detach: bool = False,
|
|
918
921
|
interactive: bool = False,
|
|
@@ -5,11 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import inspect
|
|
7
7
|
import os
|
|
8
|
+
import shutil
|
|
8
9
|
import sys
|
|
9
10
|
from typing import Any, Optional
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
|
-
from rich.console import Console
|
|
13
|
+
from rich.console import Console, Group, RenderableType
|
|
13
14
|
from rich.markdown import Markdown
|
|
14
15
|
from rich.padding import Padding
|
|
15
16
|
from rich.panel import Panel
|
|
@@ -24,6 +25,7 @@ _OPTION_METAVAR_STYLE = "dim"
|
|
|
24
25
|
_ERROR_STYLE = "red"
|
|
25
26
|
|
|
26
27
|
_MAX_HELP_WIDTH = 80
|
|
28
|
+
_HELP_PADDING = 1
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def use_rich_style() -> bool:
|
|
@@ -34,25 +36,24 @@ def use_rich_style() -> bool:
|
|
|
34
36
|
return sys.stdout.isatty()
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
+
def _make_help_console() -> Console:
|
|
40
|
+
columns, _ = shutil.get_terminal_size()
|
|
41
|
+
return Console(highlight=False, width=min(_MAX_HELP_WIDTH, columns))
|
|
39
42
|
|
|
40
43
|
|
|
41
|
-
def
|
|
44
|
+
def _build_usage(cmd: click.Command, ctx: click.Context) -> RenderableType:
|
|
42
45
|
pieces = " ".join(cmd.collect_usage_pieces(ctx))
|
|
43
46
|
usage = Text()
|
|
44
47
|
usage.append("Usage: ", style=_HEADING_STYLE)
|
|
45
48
|
usage.append(f"{ctx.command_path} {pieces}".rstrip())
|
|
46
|
-
|
|
47
|
-
console.print()
|
|
49
|
+
return usage
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
def
|
|
52
|
+
def _build_help_text(cmd: click.Command) -> Optional[RenderableType]:
|
|
51
53
|
text = cmd.help or cmd.short_help or ""
|
|
52
54
|
if not text:
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
console.print()
|
|
55
|
+
return None
|
|
56
|
+
return Markdown(inspect.cleandoc(text))
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def _option_label(param: click.Parameter, ctx: click.Context) -> Text:
|
|
@@ -73,7 +74,7 @@ def _option_label(param: click.Parameter, ctx: click.Context) -> Text:
|
|
|
73
74
|
return text
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
def
|
|
77
|
+
def _build_options(cmd: click.Command, ctx: click.Context) -> Optional[RenderableType]:
|
|
77
78
|
rows: list[tuple[Text, str]] = []
|
|
78
79
|
for param in cmd.get_params(ctx):
|
|
79
80
|
rec = param.get_help_record(ctx)
|
|
@@ -81,22 +82,20 @@ def _render_options(cmd: click.Command, ctx: click.Context, console: Console) ->
|
|
|
81
82
|
continue
|
|
82
83
|
rows.append((_option_label(param, ctx), rec[1] or ""))
|
|
83
84
|
if not rows:
|
|
84
|
-
return
|
|
85
|
+
return None
|
|
85
86
|
|
|
86
|
-
console.print(Text("Options", style=_HEADING_STYLE))
|
|
87
87
|
table = Table(box=None, show_header=False, pad_edge=False, padding=(0, 2))
|
|
88
88
|
table.add_column(no_wrap=True) # styling lives in the Text cells
|
|
89
89
|
table.add_column(overflow="fold")
|
|
90
90
|
for label, help_str in rows:
|
|
91
91
|
table.add_row(label, help_str)
|
|
92
|
-
|
|
93
|
-
console.print()
|
|
92
|
+
return Group(Text("Options", style=_HEADING_STYLE), table)
|
|
94
93
|
|
|
95
94
|
|
|
96
|
-
def
|
|
97
|
-
if cmd.epilog:
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
def _build_epilog(cmd: click.Command) -> Optional[RenderableType]:
|
|
96
|
+
if not cmd.epilog:
|
|
97
|
+
return None
|
|
98
|
+
return Text(cmd.epilog)
|
|
100
99
|
|
|
101
100
|
|
|
102
101
|
def _group_commands_by_panel(group: click.Group) -> dict[str, list[tuple[str, click.Command]]]:
|
|
@@ -109,23 +108,25 @@ def _group_commands_by_panel(group: click.Group) -> dict[str, list[tuple[str, cl
|
|
|
109
108
|
return panels
|
|
110
109
|
|
|
111
110
|
|
|
112
|
-
def
|
|
111
|
+
def _build_commands(group: click.Group, available_width: int) -> Optional[RenderableType]:
|
|
113
112
|
panels = _group_commands_by_panel(group)
|
|
114
113
|
if not panels:
|
|
115
|
-
return
|
|
114
|
+
return None
|
|
116
115
|
|
|
117
116
|
# We want the name / description columns to be the same widths across groups
|
|
118
117
|
name_width = max(len(name) for items in panels.values() for name, _ in items)
|
|
119
|
-
table_width = min(_MAX_HELP_WIDTH, console.width)
|
|
120
118
|
|
|
119
|
+
parts: list[RenderableType] = []
|
|
121
120
|
for panel_name, items in panels.items():
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
if parts:
|
|
122
|
+
parts.append(Text(""))
|
|
123
|
+
parts.append(Text(panel_name.ljust(available_width), style=f"{_HEADING_STYLE} underline"))
|
|
124
|
+
parts.append(_build_command_table(items, name_width, available_width))
|
|
125
|
+
return Group(*parts)
|
|
125
126
|
|
|
126
127
|
|
|
127
128
|
def build_command_table(name_width: int, table_width: Optional[int] = None) -> Table:
|
|
128
|
-
kwargs: dict[str, Any] = {"box": None, "show_header": False, "pad_edge":
|
|
129
|
+
kwargs: dict[str, Any] = {"box": None, "show_header": False, "pad_edge": True, "padding": (0, 1)}
|
|
129
130
|
if table_width is not None:
|
|
130
131
|
kwargs["width"] = table_width
|
|
131
132
|
table = Table(**kwargs)
|
|
@@ -137,16 +138,39 @@ def build_command_table(name_width: int, table_width: Optional[int] = None) -> T
|
|
|
137
138
|
return table
|
|
138
139
|
|
|
139
140
|
|
|
140
|
-
def
|
|
141
|
-
console: Console,
|
|
141
|
+
def _build_command_table(
|
|
142
142
|
items: list[tuple[str, click.Command]],
|
|
143
143
|
name_width: int,
|
|
144
144
|
table_width: int,
|
|
145
|
-
) ->
|
|
145
|
+
) -> Table:
|
|
146
146
|
table = build_command_table(name_width, table_width)
|
|
147
147
|
for name, sub in items:
|
|
148
148
|
table.add_row(name, sub.get_short_help_str(limit=80))
|
|
149
|
-
|
|
149
|
+
return table
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _available_width(console: Console) -> int:
|
|
153
|
+
return min(_MAX_HELP_WIDTH, console.width) - _HELP_PADDING * 2
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _emit(
|
|
157
|
+
console: Console,
|
|
158
|
+
sections: list[Optional[RenderableType]],
|
|
159
|
+
formatter: click.HelpFormatter,
|
|
160
|
+
) -> None:
|
|
161
|
+
parts: list[RenderableType] = []
|
|
162
|
+
for section in sections:
|
|
163
|
+
if section is None:
|
|
164
|
+
continue
|
|
165
|
+
if parts:
|
|
166
|
+
parts.append(Text(""))
|
|
167
|
+
parts.append(section)
|
|
168
|
+
if parts:
|
|
169
|
+
# Always pad with a blank line before the next prompt
|
|
170
|
+
parts.append(Text(""))
|
|
171
|
+
with console.capture() as capture:
|
|
172
|
+
console.print(Padding(Group(*parts), (0, _HELP_PADDING)))
|
|
173
|
+
formatter.write(capture.get())
|
|
150
174
|
|
|
151
175
|
|
|
152
176
|
# -- Public Command / Group subclasses ---------------------------------------
|
|
@@ -162,13 +186,17 @@ class ModalCommand(click.Command):
|
|
|
162
186
|
def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
163
187
|
if not use_rich_style():
|
|
164
188
|
return super().format_help(ctx, formatter)
|
|
165
|
-
console =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
189
|
+
console = _make_help_console()
|
|
190
|
+
_emit(
|
|
191
|
+
console,
|
|
192
|
+
[
|
|
193
|
+
_build_usage(self, ctx),
|
|
194
|
+
_build_help_text(self),
|
|
195
|
+
_build_options(self, ctx),
|
|
196
|
+
_build_epilog(self),
|
|
197
|
+
],
|
|
198
|
+
formatter,
|
|
199
|
+
)
|
|
172
200
|
|
|
173
201
|
|
|
174
202
|
class ModalGroup(click.Group):
|
|
@@ -209,22 +237,25 @@ class ModalGroup(click.Group):
|
|
|
209
237
|
def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
210
238
|
if not use_rich_style():
|
|
211
239
|
return super().format_help(ctx, formatter)
|
|
212
|
-
console =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
240
|
+
console = _make_help_console()
|
|
241
|
+
_emit(
|
|
242
|
+
console,
|
|
243
|
+
[
|
|
244
|
+
_build_usage(self, ctx),
|
|
245
|
+
_build_help_text(self),
|
|
246
|
+
_build_options(self, ctx),
|
|
247
|
+
_build_commands(self, _available_width(console)),
|
|
248
|
+
_build_epilog(self),
|
|
249
|
+
],
|
|
250
|
+
formatter,
|
|
251
|
+
)
|
|
220
252
|
|
|
221
253
|
|
|
222
254
|
# -- Error rendering ---------------------------------------------------------
|
|
223
255
|
|
|
224
256
|
|
|
225
257
|
def _render_click_exception(exc: click.ClickException, file: Any) -> None:
|
|
226
|
-
console =
|
|
227
|
-
title = "Error"
|
|
258
|
+
console = Console(file=file if file is not None else sys.stderr, highlight=False)
|
|
228
259
|
|
|
229
260
|
if isinstance(exc, click.UsageError) and exc.ctx is not None:
|
|
230
261
|
ctx = exc.ctx
|
|
@@ -236,7 +267,7 @@ def _render_click_exception(exc: click.ClickException, file: Any) -> None:
|
|
|
236
267
|
console.print(
|
|
237
268
|
Panel(
|
|
238
269
|
Text(exc.format_message()),
|
|
239
|
-
title=
|
|
270
|
+
title="Error",
|
|
240
271
|
title_align="left",
|
|
241
272
|
border_style=_ERROR_STYLE,
|
|
242
273
|
expand=True,
|
|
@@ -235,6 +235,7 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
|
|
|
235
235
|
output_mgr.set_timestamps(ctx.obj["show_timestamps"])
|
|
236
236
|
with run_app(
|
|
237
237
|
app,
|
|
238
|
+
name=ctx.obj["name"],
|
|
238
239
|
detach=ctx.obj["detach"],
|
|
239
240
|
environment_name=ctx.obj["env"],
|
|
240
241
|
interactive=ctx.obj["interactive"],
|
|
@@ -365,6 +366,7 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
|
365
366
|
output_mgr.set_timestamps(ctx.obj["show_timestamps"])
|
|
366
367
|
with run_app(
|
|
367
368
|
app,
|
|
369
|
+
name=ctx.obj["name"],
|
|
368
370
|
detach=ctx.obj["detach"],
|
|
369
371
|
environment_name=ctx.obj["env"],
|
|
370
372
|
interactive=ctx.obj["interactive"],
|
|
@@ -448,6 +450,7 @@ class RunGroup(ModalGroup):
|
|
|
448
450
|
cls=RunGroup,
|
|
449
451
|
subcommand_metavar="FUNC_REF",
|
|
450
452
|
)
|
|
453
|
+
@click.option("-n", "--name", help="Name for this run of the App.")
|
|
451
454
|
@click.option("-w", "--write-result", help="Write return value (which must be str or bytes) to this local path.")
|
|
452
455
|
@click.option("-q", "--quiet", is_flag=True, help="Don't show Modal progress indicators.")
|
|
453
456
|
@click.option("-d", "--detach", is_flag=True, help="Don't stop the app if the local process dies or disconnects.")
|
|
@@ -458,6 +461,7 @@ class RunGroup(ModalGroup):
|
|
|
458
461
|
@click.pass_context
|
|
459
462
|
def run(
|
|
460
463
|
ctx: click.Context,
|
|
464
|
+
name: Optional[str],
|
|
461
465
|
write_result: Optional[str],
|
|
462
466
|
detach: bool,
|
|
463
467
|
quiet: bool,
|
|
@@ -498,6 +502,7 @@ def run(
|
|
|
498
502
|
```
|
|
499
503
|
"""
|
|
500
504
|
ctx.ensure_object(dict)
|
|
505
|
+
ctx.obj["name"] = name
|
|
501
506
|
ctx.obj["result_path"] = write_result
|
|
502
507
|
ctx.obj["detach"] = detach # if subcommand would be a click command...
|
|
503
508
|
ctx.obj["show_progress"] = False if quiet else True
|
|
@@ -573,6 +578,7 @@ def deploy(
|
|
|
573
578
|
|
|
574
579
|
@click.command("serve", cls=ModalCommand, no_args_is_help=True)
|
|
575
580
|
@click.argument("app_ref")
|
|
581
|
+
@click.option("-n", "--name", help="Name for this run of the App.")
|
|
576
582
|
@click.option("--timeout", default=None, type=float)
|
|
577
583
|
@click.option("-e", "--env", default=None, help=ENV_OPTION_HELP)
|
|
578
584
|
@click.option(
|
|
@@ -585,6 +591,7 @@ def deploy(
|
|
|
585
591
|
@click.option("--timestamps", is_flag=True, default=False, help="Show timestamps for each log line.")
|
|
586
592
|
def serve(
|
|
587
593
|
app_ref: str,
|
|
594
|
+
name: Optional[str] = None,
|
|
588
595
|
timeout: Optional[float] = None,
|
|
589
596
|
env: Optional[str] = None,
|
|
590
597
|
use_module_mode: bool = False,
|
|
@@ -611,7 +618,7 @@ def serve(
|
|
|
611
618
|
app = import_app_from_ref(import_ref, base_cmd="modal serve")
|
|
612
619
|
if app.description is None:
|
|
613
620
|
app.set_description(_get_clean_app_description(app_ref))
|
|
614
|
-
with serve_app(app, import_ref, environment_name=env):
|
|
621
|
+
with serve_app(app, import_ref, name=name, environment_name=env):
|
|
615
622
|
if timeout is None:
|
|
616
623
|
timeout = config["serve_timeout"]
|
|
617
624
|
if timeout is None:
|
|
@@ -162,11 +162,10 @@ def display_table(
|
|
|
162
162
|
output.print(table)
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
ENV_OPTION_HELP =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"""
|
|
165
|
+
ENV_OPTION_HELP = (
|
|
166
|
+
"Environment to interact with. If unspecified, defers to `MODAL_ENVIRONMENT`, "
|
|
167
|
+
"your active local profile, or your workspace default, in that order."
|
|
168
|
+
)
|
|
170
169
|
|
|
171
170
|
|
|
172
171
|
def env_option(func):
|
|
@@ -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.4.3.
|
|
38
|
+
version: str = "1.4.3.dev12",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -175,7 +175,7 @@ class Client:
|
|
|
175
175
|
server_url: str,
|
|
176
176
|
client_type: int,
|
|
177
177
|
credentials: typing.Optional[tuple[str, str]],
|
|
178
|
-
version: str = "1.4.3.
|
|
178
|
+
version: str = "1.4.3.dev12",
|
|
179
179
|
):
|
|
180
180
|
"""mdmd:hidden
|
|
181
181
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -347,7 +347,7 @@ class Function(
|
|
|
347
347
|
|
|
348
348
|
_call_generator: ___call_generator_spec
|
|
349
349
|
|
|
350
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
350
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
351
351
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
352
352
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
353
353
|
...
|
|
@@ -356,7 +356,7 @@ class Function(
|
|
|
356
356
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
357
357
|
...
|
|
358
358
|
|
|
359
|
-
remote: __remote_spec[modal._functions.
|
|
359
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
360
360
|
|
|
361
361
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
362
362
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -383,7 +383,7 @@ class Function(
|
|
|
383
383
|
"""
|
|
384
384
|
...
|
|
385
385
|
|
|
386
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
386
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
387
387
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
388
388
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
389
389
|
|
|
@@ -406,7 +406,7 @@ class Function(
|
|
|
406
406
|
"""
|
|
407
407
|
...
|
|
408
408
|
|
|
409
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
409
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
410
410
|
|
|
411
411
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
412
412
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -414,7 +414,7 @@ class Function(
|
|
|
414
414
|
|
|
415
415
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
416
416
|
|
|
417
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
417
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
418
418
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
419
419
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
420
420
|
|
|
@@ -435,7 +435,7 @@ class Function(
|
|
|
435
435
|
"""
|
|
436
436
|
...
|
|
437
437
|
|
|
438
|
-
spawn: __spawn_spec[modal._functions.
|
|
438
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
439
439
|
|
|
440
440
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
441
441
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -360,6 +360,7 @@ async def _status_based_disconnect(client: _Client, app_id: str, exc_info: Optio
|
|
|
360
360
|
async def _run_app(
|
|
361
361
|
app: "modal.app._App",
|
|
362
362
|
*,
|
|
363
|
+
name: Optional[str] = None,
|
|
363
364
|
client: Optional[_Client] = None,
|
|
364
365
|
detach: bool = False,
|
|
365
366
|
environment_name: Optional[str] = None,
|
|
@@ -380,7 +381,9 @@ async def _run_app(
|
|
|
380
381
|
"You should not use `app.run` or `run_app` within a Modal `local_entrypoint`"
|
|
381
382
|
)
|
|
382
383
|
|
|
383
|
-
if
|
|
384
|
+
if name:
|
|
385
|
+
app.set_description(name)
|
|
386
|
+
elif app.description is None:
|
|
384
387
|
import __main__
|
|
385
388
|
|
|
386
389
|
if "__file__" in dir(__main__):
|
|
@@ -75,6 +75,7 @@ async def _status_based_disconnect(
|
|
|
75
75
|
def _run_app(
|
|
76
76
|
app: modal.app._App,
|
|
77
77
|
*,
|
|
78
|
+
name: typing.Optional[str] = None,
|
|
78
79
|
client: typing.Optional[modal.client._Client] = None,
|
|
79
80
|
detach: bool = False,
|
|
80
81
|
environment_name: typing.Optional[str] = None,
|
|
@@ -170,6 +171,7 @@ class __run_app_spec(typing_extensions.Protocol):
|
|
|
170
171
|
/,
|
|
171
172
|
app: modal.app.App,
|
|
172
173
|
*,
|
|
174
|
+
name: typing.Optional[str] = None,
|
|
173
175
|
client: typing.Optional[modal.client.Client] = None,
|
|
174
176
|
detach: bool = False,
|
|
175
177
|
environment_name: typing.Optional[str] = None,
|
|
@@ -184,6 +186,7 @@ class __run_app_spec(typing_extensions.Protocol):
|
|
|
184
186
|
/,
|
|
185
187
|
app: modal.app.App,
|
|
186
188
|
*,
|
|
189
|
+
name: typing.Optional[str] = None,
|
|
187
190
|
client: typing.Optional[modal.client.Client] = None,
|
|
188
191
|
detach: bool = False,
|
|
189
192
|
environment_name: typing.Optional[str] = None,
|
|
@@ -18,11 +18,13 @@ from ._utils.sandbox_fs_utils import (
|
|
|
18
18
|
make_make_directory_command,
|
|
19
19
|
make_read_file_command,
|
|
20
20
|
make_remove_command,
|
|
21
|
+
make_stat_command,
|
|
21
22
|
make_write_file_command,
|
|
22
23
|
raise_list_files_error,
|
|
23
24
|
raise_make_directory_error,
|
|
24
25
|
raise_read_file_error,
|
|
25
26
|
raise_remove_error,
|
|
27
|
+
raise_stat_error,
|
|
26
28
|
raise_write_file_error,
|
|
27
29
|
translate_exec_errors,
|
|
28
30
|
validate_absolute_remote_path,
|
|
@@ -419,6 +421,57 @@ class _SandboxFilesystem:
|
|
|
419
421
|
if returncode != 0:
|
|
420
422
|
raise_remove_error(returncode, stderr, remote_path)
|
|
421
423
|
|
|
424
|
+
async def stat(self, remote_path: str) -> FileInfo:
|
|
425
|
+
"""Return metadata for a single file, directory, or symlink in the Sandbox.
|
|
426
|
+
|
|
427
|
+
`remote_path` must be an absolute path in the Sandbox. If `remote_path` is a symlink, the returned
|
|
428
|
+
`FileInfo` object describes the symlink, not the target it points to.
|
|
429
|
+
|
|
430
|
+
**Raises**
|
|
431
|
+
|
|
432
|
+
- `SandboxFilesystemNotFoundError`: the path does not exist.
|
|
433
|
+
- `SandboxFilesystemNotADirectoryError`: a non-leaf component of the path is not a directory.
|
|
434
|
+
- `SandboxFilesystemPermissionError`: a component of the path is not searchable.
|
|
435
|
+
- `SandboxFilesystemError`: the command fails for any other reason.
|
|
436
|
+
|
|
437
|
+
**Usage**
|
|
438
|
+
|
|
439
|
+
```python fixture:sandbox
|
|
440
|
+
sandbox.filesystem.write_text("Hello, world!\\n", "/tmp/hello.txt")
|
|
441
|
+
info = sandbox.filesystem.stat("/tmp/hello.txt")
|
|
442
|
+
print(info.size, info.permissions, info.modified_time)
|
|
443
|
+
```
|
|
444
|
+
"""
|
|
445
|
+
validate_absolute_remote_path(remote_path, "stat")
|
|
446
|
+
|
|
447
|
+
t0 = time.monotonic()
|
|
448
|
+
with translate_exec_errors("stat", remote_path):
|
|
449
|
+
process = await self._sandbox.exec(_SANDBOX_FS_TOOLS_PATH, make_stat_command(remote_path))
|
|
450
|
+
stdout, stderr, returncode = await asyncio.gather(
|
|
451
|
+
process.stdout.read(), process.stderr.read(), process.wait()
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if returncode != 0:
|
|
455
|
+
raise_stat_error(returncode, stderr, remote_path)
|
|
456
|
+
|
|
457
|
+
entry = json.loads(stdout)
|
|
458
|
+
result = FileInfo(
|
|
459
|
+
name=entry["name"],
|
|
460
|
+
path=entry["path"],
|
|
461
|
+
type=FileType(entry["type"]),
|
|
462
|
+
size=entry["size"],
|
|
463
|
+
mode=entry["mode"],
|
|
464
|
+
permissions=entry["permissions"],
|
|
465
|
+
owner=entry["owner"],
|
|
466
|
+
group=entry["group"],
|
|
467
|
+
modified_time=entry["modified_time"],
|
|
468
|
+
symlink_target=entry.get("symlink_target"),
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
dur_s = max(time.monotonic() - t0, 0.001)
|
|
472
|
+
logger.debug(f"sandbox stat {remote_path}: ({dur_s:.2f}s)")
|
|
473
|
+
return result
|
|
474
|
+
|
|
422
475
|
async def write_bytes(self, data: Union[bytes, bytearray, memoryview], remote_path: str) -> None:
|
|
423
476
|
"""Write binary content to a file in the Sandbox.
|
|
424
477
|
|