modal 1.5.4.dev2__tar.gz → 1.5.4.dev4__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.4.dev2 → modal-1.5.4.dev4}/PKG-INFO +1 -1
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_tunnel.py +2 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_watcher.py +4 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/app.py +2 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/app.pyi +4 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/_download.py +3 -6
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/app.py +16 -9
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/container.py +16 -80
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/run.py +14 -4
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/shell.py +3 -4
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/utils.py +8 -6
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/volume.py +7 -10
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/client.pyi +2 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/functions.pyi +6 -6
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/mount.py +2 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/mount.pyi +2 -2
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox.py +58 -1
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox.pyi +33 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_version/__init__.py +1 -1
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/pyproject.toml +1 -8
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/LICENSE +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/README.md +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/__main__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_billing.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_clustered_functions.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_environments.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_function_variants.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_functions.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_grpc_client.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_image.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_ipython.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_load_context.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_location.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_logs.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_logs_manager.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_object.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/manager.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/pty.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/rich.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_output/status.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_partial_function.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_resolver.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_resources.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_serialization.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_server.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_supports_logs.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_traceback.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_tunnel.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_type_manager.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/logger.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_vendor/version.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/_workspace.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/billing.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/billing.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/README.md +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/builder/base-images.json +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/call_graph.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/_help.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/_traceback.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/billing.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/changelog.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/cluster.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/config.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/curl.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/dashboard.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/dict.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/endpoint.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/entry_point.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/environment.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/image.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/import_refs.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/launch.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/logo.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/profile.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/queues.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/secret.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/selector.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/skills.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/token.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cli/workspace.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/client.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cls.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/cls.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/config.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/container_process.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/container_process.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/dict.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/dict.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/environments.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/environments.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/exception.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/flash.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/experimental/ipython.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/file_io.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/file_io.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/functions.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/image.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/image.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/io_streams.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/io_streams.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/network_file_system.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/network_file_system.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/object.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/object.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/output.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/parallel_map.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/parallel_map.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/partial_function.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/partial_function.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/proxy.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/proxy.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/py.typed +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/queue.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/queue.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/retries.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/runner.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/runner.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/running_app.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox_fs.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/schedule.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/scheduler_placement.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/secret.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/secret.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/server.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/server.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/serving.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/serving.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/snapshot.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/snapshot.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/stream_type.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/token_flow.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/token_flow.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/types.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/volume.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/volume.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/workspace.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal/workspace.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/__init__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/py.typed +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/modal_version/__main__.py +0 -0
- {modal-1.5.4.dev2 → modal-1.5.4.dev4}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2023
|
|
2
2
|
"""Client for Modal relay servers, allowing users to expose TLS."""
|
|
3
3
|
|
|
4
|
-
from collections.abc import
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
7
|
from synchronicity.async_wrap import asynccontextmanager
|
|
@@ -60,7 +60,7 @@ class Tunnel:
|
|
|
60
60
|
@asynccontextmanager
|
|
61
61
|
async def _forward(
|
|
62
62
|
port: int, *, unencrypted: bool = False, h2_enabled: bool = False, client: _Client | None = None
|
|
63
|
-
) ->
|
|
63
|
+
) -> AsyncGenerator[Tunnel, None]:
|
|
64
64
|
"""Expose a port publicly from inside a running Modal container, with TLS.
|
|
65
65
|
|
|
66
66
|
If `unencrypted` is set, this also exposes the TCP socket without encryption on a random port
|
|
@@ -79,6 +79,8 @@ def _watch_args_from_mounts(mounts: list[_Mount]) -> tuple[set[Path], AppFilesFi
|
|
|
79
79
|
paths = set()
|
|
80
80
|
dir_filters: dict[Path, set[Path] | None] = defaultdict(set)
|
|
81
81
|
for mount in mounts:
|
|
82
|
+
if mount._entries is None:
|
|
83
|
+
continue
|
|
82
84
|
# TODO(elias): Make this part of the mount class instead, since it uses so much internals
|
|
83
85
|
for entry in mount._entries:
|
|
84
86
|
path, filter_file = entry.watch_entry()
|
|
@@ -86,8 +88,8 @@ def _watch_args_from_mounts(mounts: list[_Mount]) -> tuple[set[Path], AppFilesFi
|
|
|
86
88
|
paths.add(path)
|
|
87
89
|
if filter_file is None:
|
|
88
90
|
dir_filters[path] = None
|
|
89
|
-
elif dir_filters[path] is not None:
|
|
90
|
-
|
|
91
|
+
elif (filters := dir_filters[path]) is not None:
|
|
92
|
+
filters.add(filter_file.absolute().resolve())
|
|
91
93
|
|
|
92
94
|
watch_filter = AppFilesFilter(dir_filters=dict(dir_filters))
|
|
93
95
|
return paths, watch_filter
|
|
@@ -1310,7 +1310,7 @@ class _App:
|
|
|
1310
1310
|
include_source: bool | None = None, # Whether to add source to container
|
|
1311
1311
|
# Experimental options
|
|
1312
1312
|
experimental_options: dict[str, Any] | None = None,
|
|
1313
|
-
) -> Callable[[
|
|
1313
|
+
) -> Callable[[type[Any] | _PartialFunction], _Server]:
|
|
1314
1314
|
"""
|
|
1315
1315
|
Decorator to register a new Modal Server with this App.
|
|
1316
1316
|
|
|
@@ -1403,7 +1403,7 @@ class _App:
|
|
|
1403
1403
|
if env:
|
|
1404
1404
|
secrets_list.append(_Secret.from_dict(env))
|
|
1405
1405
|
|
|
1406
|
-
def wrapper(wrapped_user_cls:
|
|
1406
|
+
def wrapper(wrapped_user_cls: type[Any] | _PartialFunction | Callable) -> _Server:
|
|
1407
1407
|
_Server._validate_wrapped_user_cls_decorators(wrapped_user_cls, enable_memory_snapshot)
|
|
1408
1408
|
|
|
1409
1409
|
# Validate the server class
|
|
@@ -766,7 +766,7 @@ class _App:
|
|
|
766
766
|
include_source: typing.Optional[bool] = None,
|
|
767
767
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
768
768
|
) -> collections.abc.Callable[
|
|
769
|
-
[typing.Union[
|
|
769
|
+
[typing.Union[type[typing.Any], modal._partial_function._PartialFunction]], modal._server._Server
|
|
770
770
|
]:
|
|
771
771
|
"""Decorator to register a new Modal Server with this App.
|
|
772
772
|
|
|
@@ -1771,7 +1771,9 @@ class App:
|
|
|
1771
1771
|
enable_memory_snapshot: bool = False,
|
|
1772
1772
|
include_source: typing.Optional[bool] = None,
|
|
1773
1773
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1774
|
-
) -> collections.abc.Callable[
|
|
1774
|
+
) -> collections.abc.Callable[
|
|
1775
|
+
[typing.Union[type[typing.Any], modal.partial_function.PartialFunction]], modal.server.Server
|
|
1776
|
+
]:
|
|
1775
1777
|
"""Decorator to register a new Modal Server with this App.
|
|
1776
1778
|
|
|
1777
1779
|
Servers run HTTP servers that are started in a `@modal.enter()` method.
|
|
@@ -24,14 +24,9 @@ async def _volume_download(
|
|
|
24
24
|
remote_path: str,
|
|
25
25
|
local_destination: Path,
|
|
26
26
|
overwrite: bool,
|
|
27
|
+
progress_cb: Callable,
|
|
27
28
|
concurrency: int | None = None,
|
|
28
|
-
progress_cb: Callable | None = None,
|
|
29
29
|
):
|
|
30
|
-
if progress_cb is None:
|
|
31
|
-
|
|
32
|
-
def progress_cb(*_, **__):
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
30
|
if concurrency is None:
|
|
36
31
|
concurrency = max(128, 2 * multiprocessing.cpu_count())
|
|
37
32
|
|
|
@@ -91,6 +86,8 @@ async def _volume_download(
|
|
|
91
86
|
|
|
92
87
|
file_progress_cb(complete=True)
|
|
93
88
|
else:
|
|
89
|
+
# When not streaming to a pipe, the producer always enqueues a real output path.
|
|
90
|
+
assert output_path is not None
|
|
94
91
|
if entry.type == FileEntryType.FILE:
|
|
95
92
|
progress_task_id = progress_cb(name=entry.path, size=entry.size)
|
|
96
93
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -27,12 +27,12 @@ from .._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
|
|
|
27
27
|
from .._utils.time_utils import locale_tz, timestamp_to_localized_str
|
|
28
28
|
from ._help import ModalGroup
|
|
29
29
|
from .utils import (
|
|
30
|
+
_fetch_app_logs,
|
|
31
|
+
_stream_app_logs,
|
|
32
|
+
_tail_app_logs,
|
|
30
33
|
confirm_or_suggest_yes,
|
|
31
34
|
display_table,
|
|
32
35
|
env_option,
|
|
33
|
-
fetch_app_logs,
|
|
34
|
-
stream_app_logs,
|
|
35
|
-
tail_app_logs,
|
|
36
36
|
yes_option,
|
|
37
37
|
)
|
|
38
38
|
|
|
@@ -117,7 +117,7 @@ async def list_(env: str | None = None, json: bool = False):
|
|
|
117
117
|
"Created at",
|
|
118
118
|
"Stopped at",
|
|
119
119
|
]
|
|
120
|
-
rows: list[list[Text | str]] = []
|
|
120
|
+
rows: list[list[Text | str | None]] = []
|
|
121
121
|
for app_stats in resp.apps:
|
|
122
122
|
state = APP_STATE_TO_MESSAGE.get(app_stats.state, Text("unknown", style="gray"))
|
|
123
123
|
rows.append(
|
|
@@ -305,7 +305,7 @@ async def logs(
|
|
|
305
305
|
)
|
|
306
306
|
|
|
307
307
|
if follow:
|
|
308
|
-
await
|
|
308
|
+
await _stream_app_logs(
|
|
309
309
|
app_id,
|
|
310
310
|
task_id=container_id or "",
|
|
311
311
|
show_timestamps=timestamps,
|
|
@@ -326,9 +326,9 @@ async def logs(
|
|
|
326
326
|
if effective_until - since_dt > _MAX_FETCH_RANGE:
|
|
327
327
|
raise UsageError(f"Log fetch time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
|
|
328
328
|
|
|
329
|
-
if
|
|
329
|
+
if since_dt and tail is None:
|
|
330
330
|
# Range mode: --since without --tail fetches everything in the range.
|
|
331
|
-
await
|
|
331
|
+
await _fetch_app_logs(
|
|
332
332
|
app_id,
|
|
333
333
|
since_dt,
|
|
334
334
|
until_dt or now,
|
|
@@ -340,7 +340,7 @@ async def logs(
|
|
|
340
340
|
# Tail mode: single fetch with limit.
|
|
341
341
|
# --since is a hard floor, --until shifts the anchor.
|
|
342
342
|
effective_tail = tail if tail is not None else _DEFAULT_LOGS_TAIL
|
|
343
|
-
await
|
|
343
|
+
await _tail_app_logs(
|
|
344
344
|
app_id,
|
|
345
345
|
effective_tail,
|
|
346
346
|
show_timestamps=timestamps,
|
|
@@ -586,9 +586,16 @@ async def history(
|
|
|
586
586
|
for idx, app_stats in enumerate(resp.app_deployment_histories):
|
|
587
587
|
style = "bold green" if idx == 0 else ""
|
|
588
588
|
|
|
589
|
+
if app_stats.deployed_at:
|
|
590
|
+
ts = timestamp_to_localized_str(app_stats.deployed_at, json)
|
|
591
|
+
assert ts is not None
|
|
592
|
+
deployed_at = Text(ts, style=style)
|
|
593
|
+
else:
|
|
594
|
+
deployed_at = None
|
|
595
|
+
|
|
589
596
|
row = [
|
|
590
597
|
Text(f"v{app_stats.version}", style=style),
|
|
591
|
-
|
|
598
|
+
deployed_at,
|
|
592
599
|
Text(app_stats.client_version, style=style),
|
|
593
600
|
Text(app_stats.deployed_by, style=style),
|
|
594
601
|
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
-
import uuid
|
|
3
2
|
import warnings
|
|
4
3
|
from datetime import datetime, timezone
|
|
5
4
|
|
|
@@ -11,27 +10,23 @@ from rich.text import Text
|
|
|
11
10
|
from modal._environments import ensure_env
|
|
12
11
|
from modal._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
|
|
13
12
|
from modal._object import _get_environment_name
|
|
14
|
-
from modal._output.pty import get_pty_info
|
|
15
13
|
from modal._utils.async_utils import synchronizer
|
|
16
|
-
from modal._utils.task_command_router_client import TaskCommandRouterClient
|
|
17
14
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
18
15
|
from modal.cli.app import _DEFAULT_LOGS_TAIL, _SOURCE_OPTIONS, _parse_time_arg
|
|
19
16
|
from modal.cli.utils import (
|
|
17
|
+
_fetch_app_logs,
|
|
18
|
+
_stream_app_logs,
|
|
19
|
+
_tail_app_logs,
|
|
20
20
|
confirm_or_suggest_yes,
|
|
21
21
|
display_table,
|
|
22
22
|
env_option,
|
|
23
|
-
fetch_app_logs,
|
|
24
23
|
is_tty,
|
|
25
|
-
stream_app_logs,
|
|
26
|
-
tail_app_logs,
|
|
27
24
|
yes_option,
|
|
28
25
|
)
|
|
29
26
|
from modal.client import _Client
|
|
30
|
-
from modal.config import config
|
|
31
|
-
from modal.container_process import _ContainerProcess
|
|
32
27
|
from modal.exception import InvalidError
|
|
33
|
-
from modal.
|
|
34
|
-
from modal_proto import api_pb2
|
|
28
|
+
from modal.sandbox import _container_exec
|
|
29
|
+
from modal_proto import api_pb2
|
|
35
30
|
|
|
36
31
|
from ._help import ModalGroup
|
|
37
32
|
|
|
@@ -70,7 +65,7 @@ async def list_(
|
|
|
70
65
|
task_stats.task_id,
|
|
71
66
|
task_stats.app_id,
|
|
72
67
|
task_stats.app_description,
|
|
73
|
-
timestamp_to_localized_str(task_stats.started_at, json)
|
|
68
|
+
timestamp_to_localized_str(task_stats.started_at, json) or "Pending",
|
|
74
69
|
]
|
|
75
70
|
)
|
|
76
71
|
|
|
@@ -184,7 +179,7 @@ async def logs(
|
|
|
184
179
|
)
|
|
185
180
|
|
|
186
181
|
if follow:
|
|
187
|
-
await
|
|
182
|
+
await _stream_app_logs(
|
|
188
183
|
task_id=task_id,
|
|
189
184
|
sandbox_id=sandbox_id,
|
|
190
185
|
show_timestamps=timestamps,
|
|
@@ -199,6 +194,7 @@ async def logs(
|
|
|
199
194
|
sb_resp = await client.stub.SandboxGetTaskId(api_pb2.SandboxGetTaskIdRequest(sandbox_id=sandbox_id))
|
|
200
195
|
task_id = sb_resp.task_id
|
|
201
196
|
|
|
197
|
+
assert task_id
|
|
202
198
|
task_info_resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=task_id))
|
|
203
199
|
app_id = task_info_resp.app_id
|
|
204
200
|
|
|
@@ -236,14 +232,13 @@ async def logs(
|
|
|
236
232
|
warnings.warn("--until time is before the Container started, no logs to fetch.", UserWarning)
|
|
237
233
|
return
|
|
238
234
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
raise UsageError(f"Log fetch time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
|
|
235
|
+
effective_until = until_dt or now
|
|
236
|
+
if effective_until - since_dt > _MAX_FETCH_RANGE:
|
|
237
|
+
raise UsageError(f"Log fetch time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
|
|
243
238
|
|
|
244
239
|
if all_logs or (since and tail is None):
|
|
245
240
|
# Range mode: --since without --tail fetches everything in the range.
|
|
246
|
-
await
|
|
241
|
+
await _fetch_app_logs(
|
|
247
242
|
app_id,
|
|
248
243
|
since_dt,
|
|
249
244
|
until_dt or now,
|
|
@@ -254,7 +249,7 @@ async def logs(
|
|
|
254
249
|
# Tail mode: single fetch with limit.
|
|
255
250
|
# --since is a hard floor, --until shifts the anchor.
|
|
256
251
|
effective_tail = tail if tail is not None else _DEFAULT_LOGS_TAIL
|
|
257
|
-
await
|
|
252
|
+
await _tail_app_logs(
|
|
258
253
|
app_id,
|
|
259
254
|
effective_tail,
|
|
260
255
|
show_timestamps=timestamps,
|
|
@@ -264,67 +259,6 @@ async def logs(
|
|
|
264
259
|
)
|
|
265
260
|
|
|
266
261
|
|
|
267
|
-
@synchronizer.create_blocking
|
|
268
|
-
async def _exec_impl(
|
|
269
|
-
pty: bool | None = None,
|
|
270
|
-
container_id: str = "",
|
|
271
|
-
command: tuple[str, ...] = (),
|
|
272
|
-
object_id_for_v2: str | None = None,
|
|
273
|
-
):
|
|
274
|
-
"""Execute a command in a container (implementation).
|
|
275
|
-
|
|
276
|
-
For tasks belonging to V2 sandboxes, `object_id_for_v2` must be set to the
|
|
277
|
-
sandbox ID.
|
|
278
|
-
"""
|
|
279
|
-
|
|
280
|
-
if pty is None:
|
|
281
|
-
pty = is_tty()
|
|
282
|
-
|
|
283
|
-
client = await _Client.from_env()
|
|
284
|
-
|
|
285
|
-
if object_id_for_v2 is not None:
|
|
286
|
-
command_router_client = await TaskCommandRouterClient.init_v2(client, object_id_for_v2, container_id)
|
|
287
|
-
else:
|
|
288
|
-
command_router_client = await TaskCommandRouterClient.init(client, container_id)
|
|
289
|
-
|
|
290
|
-
process_id = str(uuid.uuid4())
|
|
291
|
-
|
|
292
|
-
start_req = sr_pb2.TaskExecStartRequest(
|
|
293
|
-
task_id=container_id,
|
|
294
|
-
exec_id=process_id,
|
|
295
|
-
command_args=command,
|
|
296
|
-
stdout_config=sr_pb2.TaskExecStdoutConfig.TASK_EXEC_STDOUT_CONFIG_PIPE,
|
|
297
|
-
stderr_config=sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_PIPE,
|
|
298
|
-
pty_info=get_pty_info(shell=True) if pty else None,
|
|
299
|
-
runtime_debug=config.get("function_runtime_debug"),
|
|
300
|
-
)
|
|
301
|
-
await command_router_client.exec_start(start_req)
|
|
302
|
-
|
|
303
|
-
if pty:
|
|
304
|
-
# PTY output is raw terminal bytes (control sequences, mouse events,
|
|
305
|
-
# glyphs in non-UTF-8 locales, etc.) — not text. Strict UTF-8 decode on
|
|
306
|
-
# this stream crashes the shell as soon as anything emits a byte that
|
|
307
|
-
# isn't valid UTF-8, e.g. vim drawing a Latin-1 file under LC_CTYPE=C.
|
|
308
|
-
# Pass bytes through unmodified; `attach()` writes them straight to the
|
|
309
|
-
# local fd.
|
|
310
|
-
await _ContainerProcess(
|
|
311
|
-
process_id,
|
|
312
|
-
container_id,
|
|
313
|
-
client,
|
|
314
|
-
command_router_client=command_router_client,
|
|
315
|
-
text=False,
|
|
316
|
-
).attach()
|
|
317
|
-
else:
|
|
318
|
-
await _ContainerProcess(
|
|
319
|
-
process_id,
|
|
320
|
-
container_id,
|
|
321
|
-
client,
|
|
322
|
-
command_router_client=command_router_client,
|
|
323
|
-
stdout=StreamType.STDOUT,
|
|
324
|
-
stderr=StreamType.STDOUT,
|
|
325
|
-
).wait()
|
|
326
|
-
|
|
327
|
-
|
|
328
262
|
@container_cli.command("exec", no_args_is_help=True)
|
|
329
263
|
@click.option("--pty/--no-pty", default=None, help="Run the command using a PTY.")
|
|
330
264
|
@click.argument("container_id")
|
|
@@ -335,7 +269,9 @@ def exec(
|
|
|
335
269
|
command: tuple[str, ...] = (),
|
|
336
270
|
):
|
|
337
271
|
"""Execute a command in a container."""
|
|
338
|
-
|
|
272
|
+
if pty is None:
|
|
273
|
+
pty = is_tty()
|
|
274
|
+
_container_exec(pty=pty, container_id=container_id, command=command)
|
|
339
275
|
|
|
340
276
|
|
|
341
277
|
@container_cli.command("stop", no_args_is_help=True)
|
|
@@ -252,7 +252,7 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
|
|
|
252
252
|
def _get_signature(func: typing.Any) -> inspect.Signature:
|
|
253
253
|
"""Returns signature with the original source annotations."""
|
|
254
254
|
kwargs: dict[str, typing.Any] = {}
|
|
255
|
-
if sys.version_info
|
|
255
|
+
if sys.version_info >= (3, 14):
|
|
256
256
|
import annotationlib
|
|
257
257
|
|
|
258
258
|
kwargs["annotation_format"] = annotationlib.Format.STRING
|
|
@@ -263,6 +263,7 @@ def _get_click_command_for_function(app: App, function: Function, ctx: click.Con
|
|
|
263
263
|
if function.is_generator:
|
|
264
264
|
raise InvalidError("`modal run` is not supported for generator functions")
|
|
265
265
|
|
|
266
|
+
assert function.info.raw_f
|
|
266
267
|
sig: inspect.Signature = _get_signature(function.info.raw_f)
|
|
267
268
|
type_hints = safe_get_type_hints(function.info.raw_f)
|
|
268
269
|
signature: CliRunnableSignature = _get_cli_runnable_signature(sig, type_hints)
|
|
@@ -344,6 +345,8 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference, ctx: click
|
|
|
344
345
|
|
|
345
346
|
def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoint):
|
|
346
347
|
func = entrypoint.info.raw_f
|
|
348
|
+
assert func
|
|
349
|
+
|
|
347
350
|
isasync = inspect.iscoroutinefunction(func)
|
|
348
351
|
|
|
349
352
|
signature = _get_cli_runnable_signature(_get_signature(func), safe_get_type_hints(func))
|
|
@@ -378,7 +381,10 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
|
378
381
|
else:
|
|
379
382
|
res = func(*args, **kwargs)
|
|
380
383
|
except Exception as exc:
|
|
381
|
-
|
|
384
|
+
user_source = inspect.getsourcefile(func)
|
|
385
|
+
if user_source is None:
|
|
386
|
+
raise exc
|
|
387
|
+
raise _CliUserExecutionError(user_source) from exc
|
|
382
388
|
|
|
383
389
|
if result_path := ctx.obj["result_path"]:
|
|
384
390
|
_write_local_result(result_path, res)
|
|
@@ -405,13 +411,15 @@ def _get_runnable_list(all_usable_commands: list[CLICommand]) -> str:
|
|
|
405
411
|
class RunGroup(ModalGroup):
|
|
406
412
|
"""Click group that resolves subcommands dynamically from a file/module ref."""
|
|
407
413
|
|
|
408
|
-
def get_command(self, ctx,
|
|
414
|
+
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
|
|
409
415
|
# note: get_command here is run before the "group logic" in the `run` logic below
|
|
410
416
|
# so to ensure that `env` has been globally populated before user code is loaded, it
|
|
411
417
|
# needs to be handled here, and not in the `run` logic below
|
|
412
418
|
ctx.ensure_object(dict)
|
|
413
419
|
ctx.obj["env"] = ensure_env(ctx.params["env"])
|
|
414
420
|
|
|
421
|
+
func_ref = cmd_name
|
|
422
|
+
|
|
415
423
|
import_ref = parse_import_ref(func_ref, use_module_mode=ctx.params["m"], command="modal run")
|
|
416
424
|
runnable, all_usable_commands = import_and_filter(
|
|
417
425
|
import_ref, base_cmd="modal run", accept_local_entrypoint=True, accept_webhook=False
|
|
@@ -553,6 +561,7 @@ def deploy(
|
|
|
553
561
|
import_ref = parse_import_ref(app_ref, use_module_mode=use_module_mode, command="modal deploy")
|
|
554
562
|
|
|
555
563
|
OutputManager.get().set_timestamps(timestamps)
|
|
564
|
+
# Keep the app import on the main thread; user code must not run on the synchronizer thread.
|
|
556
565
|
app = import_app_from_ref(import_ref, base_cmd="modal deploy")
|
|
557
566
|
|
|
558
567
|
name = name or app.name or ""
|
|
@@ -569,7 +578,8 @@ def deploy(
|
|
|
569
578
|
res = deploy_app(app, name=name, environment_name=env or "", tag=tag, deployment_strategy=strategy)
|
|
570
579
|
|
|
571
580
|
if stream_logs:
|
|
572
|
-
stream_app_logs
|
|
581
|
+
# stream_app_logs is defined in cli/utils.py, which does not get type stubs
|
|
582
|
+
stream_app_logs( # pyright: ignore[reportCallIssue]
|
|
573
583
|
app_id=res.app_id,
|
|
574
584
|
app_logs_url=res.app_logs_url,
|
|
575
585
|
show_timestamps=timestamps,
|
|
@@ -17,12 +17,11 @@ from ..functions import Function
|
|
|
17
17
|
from ..image import Image
|
|
18
18
|
from ..mount import _Mount
|
|
19
19
|
from ..runner import interactive_shell
|
|
20
|
-
from ..sandbox import _MAIN_CONTAINER_NAME, Sandbox, _is_v2_sandbox_id
|
|
20
|
+
from ..sandbox import _MAIN_CONTAINER_NAME, Sandbox, _container_exec, _is_v2_sandbox_id
|
|
21
21
|
from ..secret import Secret
|
|
22
22
|
from ..stream_type import StreamType
|
|
23
23
|
from ..volume import Volume
|
|
24
24
|
from ._help import ModalCommand
|
|
25
|
-
from .container import _exec_impl
|
|
26
25
|
from .import_refs import (
|
|
27
26
|
MethodReference,
|
|
28
27
|
import_and_filter,
|
|
@@ -115,7 +114,7 @@ def _start_shell_in_sidecar_container(sandbox_id: str, container_name: str, cmd:
|
|
|
115
114
|
if pty:
|
|
116
115
|
# PTY output is raw terminal bytes, not text; strict UTF-8 decode
|
|
117
116
|
# crashes on the first non-UTF-8 byte (e.g. vim drawing a Latin-1
|
|
118
|
-
# file under LC_CTYPE=C). See the matching call in `
|
|
117
|
+
# file under LC_CTYPE=C). See the matching call in `_container_exec`.
|
|
119
118
|
process = sandbox_container.exec(*shlex.split(cmd), pty=True, text=False)
|
|
120
119
|
process.attach()
|
|
121
120
|
else:
|
|
@@ -150,7 +149,7 @@ def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
|
|
|
150
149
|
|
|
151
150
|
assert _is_valid_modal_id(ref, "ta-")
|
|
152
151
|
try:
|
|
153
|
-
|
|
152
|
+
_container_exec(pty, container_id=ref, command=tuple(shlex.split(cmd)), object_id_for_v2=object_id_for_v2)
|
|
154
153
|
except NotFoundError:
|
|
155
154
|
raise ClickException(f"Container '{ref}' not found (is it still running?)")
|
|
156
155
|
except Exception as e:
|
|
@@ -21,8 +21,7 @@ from ..exception import InvalidError
|
|
|
21
21
|
from ..output import OutputManager
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
async def stream_app_logs(
|
|
24
|
+
async def _stream_app_logs(
|
|
26
25
|
app_id: str | None = None,
|
|
27
26
|
task_id: str | None = None,
|
|
28
27
|
sandbox_id: str | None = None,
|
|
@@ -66,6 +65,11 @@ async def stream_app_logs(
|
|
|
66
65
|
pass
|
|
67
66
|
|
|
68
67
|
|
|
68
|
+
# Blocking variant just used by the `modal deploy` handler,
|
|
69
|
+
# which must run on synchronizer thread.
|
|
70
|
+
stream_app_logs = synchronizer.create_blocking(_stream_app_logs)
|
|
71
|
+
|
|
72
|
+
|
|
69
73
|
async def _drain_batches(output_mgr, batches, prefixes, search_text=""):
|
|
70
74
|
"""Iterate over log batches and display them via the output manager."""
|
|
71
75
|
last_data = ""
|
|
@@ -84,8 +88,7 @@ async def _drain_batches(output_mgr, batches, prefixes, search_text=""):
|
|
|
84
88
|
output_mgr.print("")
|
|
85
89
|
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
async def tail_app_logs(
|
|
91
|
+
async def _tail_app_logs(
|
|
89
92
|
app_id: str,
|
|
90
93
|
n: int,
|
|
91
94
|
show_timestamps: bool = False,
|
|
@@ -104,8 +107,7 @@ async def tail_app_logs(
|
|
|
104
107
|
await _drain_batches(output_mgr, batches, prefix_fields or [], filters.search_text)
|
|
105
108
|
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
async def fetch_app_logs(
|
|
110
|
+
async def _fetch_app_logs(
|
|
109
111
|
app_id: str,
|
|
110
112
|
since: datetime,
|
|
111
113
|
until: datetime,
|
|
@@ -33,17 +33,13 @@ volume_cli = ModalGroup(
|
|
|
33
33
|
def humanize_filesize(value: int) -> str:
|
|
34
34
|
if value < 0:
|
|
35
35
|
raise ValueError("value should be >= 0")
|
|
36
|
-
suffix = (" KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB")
|
|
37
|
-
format = "%.1f"
|
|
38
36
|
base = 1024
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
break
|
|
46
|
-
return format % (base * bytes_ / unit) + s
|
|
37
|
+
size = float(value)
|
|
38
|
+
for unit in ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"):
|
|
39
|
+
if size < base:
|
|
40
|
+
return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
|
|
41
|
+
size /= base
|
|
42
|
+
return f"{size:.1f} ZiB"
|
|
47
43
|
|
|
48
44
|
|
|
49
45
|
@volume_cli.command("create", help="Create a named, persistent modal.Volume.", panel="Management", no_args_is_help=True)
|
|
@@ -198,6 +194,7 @@ async def put(
|
|
|
198
194
|
"""
|
|
199
195
|
ensure_env(env)
|
|
200
196
|
vol = await _Volume.from_name(volume_name, environment_name=env).hydrate()
|
|
197
|
+
assert vol._metadata
|
|
201
198
|
|
|
202
199
|
if remote_path.endswith("/"):
|
|
203
200
|
remote_path = remote_path + os.path.basename(local_path)
|
|
@@ -37,7 +37,7 @@ class _Client:
|
|
|
37
37
|
server_url: str,
|
|
38
38
|
client_type: int,
|
|
39
39
|
credentials: typing.Optional[tuple[str, str]],
|
|
40
|
-
version: str = "1.5.4.
|
|
40
|
+
version: str = "1.5.4.dev4",
|
|
41
41
|
):
|
|
42
42
|
"""mdmd:hidden
|
|
43
43
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -207,7 +207,7 @@ class Client:
|
|
|
207
207
|
server_url: str,
|
|
208
208
|
client_type: int,
|
|
209
209
|
credentials: typing.Optional[tuple[str, str]],
|
|
210
|
-
version: str = "1.5.4.
|
|
210
|
+
version: str = "1.5.4.dev4",
|
|
211
211
|
):
|
|
212
212
|
"""mdmd:hidden
|
|
213
213
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -546,7 +546,7 @@ class Function(
|
|
|
546
546
|
|
|
547
547
|
_call_generator: ___call_generator_spec
|
|
548
548
|
|
|
549
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
549
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
550
550
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
551
551
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
552
552
|
|
|
@@ -571,7 +571,7 @@ class Function(
|
|
|
571
571
|
"""
|
|
572
572
|
...
|
|
573
573
|
|
|
574
|
-
remote: __remote_spec[modal._functions.
|
|
574
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
575
575
|
|
|
576
576
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
577
577
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -621,7 +621,7 @@ class Function(
|
|
|
621
621
|
"""
|
|
622
622
|
...
|
|
623
623
|
|
|
624
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
624
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
625
625
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
626
626
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
627
627
|
|
|
@@ -654,7 +654,7 @@ class Function(
|
|
|
654
654
|
"""
|
|
655
655
|
...
|
|
656
656
|
|
|
657
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
657
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
658
658
|
|
|
659
659
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
660
660
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -662,7 +662,7 @@ class Function(
|
|
|
662
662
|
|
|
663
663
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
664
664
|
|
|
665
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
665
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
666
666
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
667
667
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
668
668
|
|
|
@@ -695,7 +695,7 @@ class Function(
|
|
|
695
695
|
"""
|
|
696
696
|
...
|
|
697
697
|
|
|
698
|
-
spawn: __spawn_spec[modal._functions.
|
|
698
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
699
699
|
|
|
700
700
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
701
701
|
"""Return the inner Python object wrapped by this Modal Function.
|
|
@@ -94,7 +94,7 @@ class _MountEntry(metaclass=abc.ABCMeta):
|
|
|
94
94
|
def get_files_to_upload(self) -> typing.Iterator[tuple[Path, str]]: ...
|
|
95
95
|
|
|
96
96
|
@abc.abstractmethod
|
|
97
|
-
def watch_entry(self) -> tuple[Path, Path]: ...
|
|
97
|
+
def watch_entry(self) -> tuple[Path, Path | None]: ...
|
|
98
98
|
|
|
99
99
|
@abc.abstractmethod
|
|
100
100
|
def top_level_paths(self) -> list[tuple[Path, PurePosixPath]]: ...
|
|
@@ -270,7 +270,7 @@ class _MountedPythonModule(_MountEntry):
|
|
|
270
270
|
for entry in self._proxy_entries():
|
|
271
271
|
yield from entry.get_files_to_upload()
|
|
272
272
|
|
|
273
|
-
def watch_entry(self) -> tuple[Path, Path]:
|
|
273
|
+
def watch_entry(self) -> tuple[Path, Path | None]:
|
|
274
274
|
for entry in self._proxy_entries():
|
|
275
275
|
# TODO: fix watch for mounts of multi-path packages
|
|
276
276
|
return entry.watch_entry()
|
|
@@ -23,7 +23,7 @@ def python_standalone_mount_name(version: str) -> str:
|
|
|
23
23
|
class _MountEntry:
|
|
24
24
|
def description(self) -> str: ...
|
|
25
25
|
def get_files_to_upload(self) -> typing.Iterator[tuple[pathlib.Path, str]]: ...
|
|
26
|
-
def watch_entry(self) -> tuple[pathlib.Path, pathlib.Path]: ...
|
|
26
|
+
def watch_entry(self) -> tuple[pathlib.Path, typing.Optional[pathlib.Path]]: ...
|
|
27
27
|
def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
|
|
28
28
|
|
|
29
29
|
def _select_files(entries: list[_MountEntry]) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
|
|
@@ -105,7 +105,7 @@ class _MountedPythonModule(_MountEntry):
|
|
|
105
105
|
def description(self) -> str: ...
|
|
106
106
|
def _proxy_entries(self) -> list[_MountEntry]: ...
|
|
107
107
|
def get_files_to_upload(self) -> typing.Iterator[tuple[pathlib.Path, str]]: ...
|
|
108
|
-
def watch_entry(self) -> tuple[pathlib.Path, pathlib.Path]: ...
|
|
108
|
+
def watch_entry(self) -> tuple[pathlib.Path, typing.Optional[pathlib.Path]]: ...
|
|
109
109
|
def top_level_paths(self) -> list[tuple[pathlib.Path, pathlib.PurePosixPath]]: ...
|
|
110
110
|
def __init__(
|
|
111
111
|
self,
|