modal 1.5.2.dev13__tar.gz → 1.5.2.dev15__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.2.dev13 → modal-1.5.2.dev15}/PKG-INFO +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/__init__.py +2 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_billing.py +2 -53
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_environments.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_functions.py +1 -11
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_image.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/grpc_utils.py +49 -9
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/task_command_router_client.py +17 -5
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_workspace.py +73 -33
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/billing.py +2 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/billing.pyi +27 -28
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/call_graph.py +1 -30
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/_download.py +2 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/cluster.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/config.py +2 -2
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/container.py +18 -7
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/curl.py +6 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/dict.py +5 -5
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/environment.py +3 -3
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/image.py +7 -7
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/network_file_system.py +12 -6
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/profile.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/queues.py +5 -5
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/secret.py +2 -2
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/volume.py +17 -9
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/workspace.py +46 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/client.pyi +2 -2
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/config.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/dict.py +1 -13
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/dict.pyi +4 -24
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/environments.pyi +3 -3
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/file_io.py +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/file_io.pyi +9 -9
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/functions.pyi +11 -11
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/network_file_system.pyi +7 -7
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/queue.py +1 -13
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/queue.pyi +4 -24
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/sandbox.py +21 -33
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/sandbox.pyi +34 -42
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/sandbox_fs.py +1 -64
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/sandbox_fs.pyi +14 -117
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/secret.py +1 -13
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/secret.pyi +4 -24
- modal-1.5.2.dev15/modal/types.py +286 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/volume.py +1 -43
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/volume.pyi +10 -75
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/workspace.py +5 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/workspace.pyi +138 -10
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal.egg-info/SOURCES.txt +1 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/gen_cli_docs.py +22 -7
- modal-1.5.2.dev15/modal_docs/gen_reference_docs.py +361 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/mdmd/mdmd.py +91 -9
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/mdmd/signatures.py +28 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/api_pb2.py +857 -851
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/api_pb2.pyi +37 -9
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_version/__init__.py +1 -1
- modal-1.5.2.dev13/modal_docs/gen_reference_docs.py +0 -177
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/LICENSE +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/README.md +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/__main__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_clustered_functions.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_function_variants.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_grpc_client.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_ipython.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_load_context.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_location.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_logs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_object.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_output/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_output/manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_output/pty.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_output/rich.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_output/status.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_partial_function.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_resolver.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_resources.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_serialization.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_server.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_traceback.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_tunnel.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_tunnel.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_type_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/logger.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_vendor/version.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/_watcher.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/app.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/app.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/README.md +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/builder/base-images.json +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/_help.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/_traceback.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/app.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/billing.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/changelog.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/dashboard.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/endpoint.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/entry_point.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/import_refs.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/launch.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/logo.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/run.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/selector.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/shell.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/skills.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/token.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cli/utils.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/client.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cls.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/cls.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/container_process.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/container_process.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/environments.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/exception.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/experimental/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/experimental/flash.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/experimental/ipython.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/functions.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/image.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/image.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/io_streams.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/io_streams.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/mount.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/mount.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/network_file_system.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/object.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/object.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/output.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/parallel_map.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/parallel_map.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/partial_function.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/partial_function.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/proxy.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/proxy.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/py.typed +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/retries.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/runner.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/runner.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/running_app.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/schedule.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/scheduler_placement.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/server.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/server.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/serving.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/serving.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/snapshot.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/snapshot.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/stream_type.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/token_flow.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal/token_flow.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/__init__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/py.typed +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/modal_version/__main__.py +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/pyproject.toml +0 -0
- {modal-1.5.2.dev13 → modal-1.5.2.dev15}/setup.cfg +0 -0
|
@@ -9,7 +9,7 @@ if sys.version_info[:2] >= (3, 15):
|
|
|
9
9
|
from modal_version import __version__
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
|
-
from . import billing
|
|
12
|
+
from . import billing, types
|
|
13
13
|
from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
|
|
14
14
|
from ._tunnel import Tunnel, forward
|
|
15
15
|
from .app import App
|
|
@@ -100,6 +100,7 @@ __all__ = [
|
|
|
100
100
|
"interact",
|
|
101
101
|
"method",
|
|
102
102
|
"parameter",
|
|
103
|
+
"types",
|
|
103
104
|
"web_endpoint",
|
|
104
105
|
"web_server",
|
|
105
106
|
"wsgi_app",
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
|
-
from
|
|
3
|
-
from datetime import datetime, timezone
|
|
2
|
+
from datetime import datetime
|
|
4
3
|
from decimal import Decimal
|
|
5
|
-
from typing import
|
|
6
|
-
|
|
7
|
-
from modal_proto import api_pb2
|
|
4
|
+
from typing import TypedDict
|
|
8
5
|
|
|
9
6
|
from ._utils.deprecation import deprecation_warning
|
|
10
7
|
from .client import _Client
|
|
@@ -19,54 +16,6 @@ class WorkspaceBillingReportItem(TypedDict):
|
|
|
19
16
|
tags: dict[str, str]
|
|
20
17
|
|
|
21
18
|
|
|
22
|
-
@dataclass(slots=True, frozen=True)
|
|
23
|
-
class BillingReportItem:
|
|
24
|
-
object_id: str
|
|
25
|
-
description: str
|
|
26
|
-
environment_name: str
|
|
27
|
-
interval_start: datetime
|
|
28
|
-
cost: Decimal
|
|
29
|
-
cost_by_resource: dict[str, Decimal]
|
|
30
|
-
tags: dict[str, str]
|
|
31
|
-
|
|
32
|
-
def __getitem__(self, key: str) -> Any:
|
|
33
|
-
"""mdmd:ignore"""
|
|
34
|
-
if key not in self.__slots__:
|
|
35
|
-
raise KeyError(key)
|
|
36
|
-
|
|
37
|
-
return getattr(self, key)
|
|
38
|
-
|
|
39
|
-
def __setitem__(self, key: str, _: Any):
|
|
40
|
-
"""mdmd:ignore"""
|
|
41
|
-
raise FrozenInstanceError(f"cannot assign to field {key!r}")
|
|
42
|
-
|
|
43
|
-
def keys(self) -> Iterable[str]:
|
|
44
|
-
"""mdmd:ignore"""
|
|
45
|
-
yield from self.__slots__
|
|
46
|
-
|
|
47
|
-
def values(self) -> Iterable[Any]:
|
|
48
|
-
"""mdmd:ignore"""
|
|
49
|
-
for k in self.__slots__:
|
|
50
|
-
yield getattr(self, k)
|
|
51
|
-
|
|
52
|
-
def items(self) -> Iterable[tuple[str, Any]]:
|
|
53
|
-
"""mdmd:ignore"""
|
|
54
|
-
for k in self.__slots__:
|
|
55
|
-
yield k, getattr(self, k)
|
|
56
|
-
|
|
57
|
-
@classmethod
|
|
58
|
-
def _from_proto(cls, pb_item: api_pb2.WorkspaceBillingReportItem) -> "BillingReportItem":
|
|
59
|
-
return cls(
|
|
60
|
-
object_id=pb_item.object_id,
|
|
61
|
-
description=pb_item.description,
|
|
62
|
-
environment_name=pb_item.environment_name,
|
|
63
|
-
interval_start=pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
|
|
64
|
-
cost=Decimal(pb_item.cost),
|
|
65
|
-
cost_by_resource={k: Decimal(v) for k, v in pb_item.cost_by_resource.items()},
|
|
66
|
-
tags=dict(pb_item.tags),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
19
|
async def _workspace_billing_report(
|
|
71
20
|
*,
|
|
72
21
|
start: datetime, # Start of the report, inclusive
|
|
@@ -13,7 +13,6 @@ from synchronicity import classproperty
|
|
|
13
13
|
|
|
14
14
|
from modal_proto import api_pb2
|
|
15
15
|
|
|
16
|
-
from ._billing import BillingReportItem
|
|
17
16
|
from ._load_context import LoadContext
|
|
18
17
|
from ._object import _Object
|
|
19
18
|
from ._resolver import Resolver
|
|
@@ -21,6 +20,7 @@ from ._utils.name_utils import check_environment_name
|
|
|
21
20
|
from .client import _Client
|
|
22
21
|
from .config import config, logger
|
|
23
22
|
from .exception import InvalidError, WorkspaceManagementError
|
|
23
|
+
from .types import BillingReportItem
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@dataclass(frozen=True)
|
|
@@ -90,6 +90,7 @@ from .proxy import _Proxy
|
|
|
90
90
|
from .retries import Retries, RetryManager
|
|
91
91
|
from .schedule import Schedule
|
|
92
92
|
from .secret import _Secret
|
|
93
|
+
from .types import FunctionStats
|
|
93
94
|
from .volume import _Volume, _volume_to_mount_proto
|
|
94
95
|
|
|
95
96
|
if TYPE_CHECKING:
|
|
@@ -541,17 +542,6 @@ class _InputPlaneInvocation:
|
|
|
541
542
|
return [("x-modal-input-plane-region", input_plane_region), ("x-modal-auth-token", token)]
|
|
542
543
|
|
|
543
544
|
|
|
544
|
-
# Wrapper type for api_pb2.FunctionStats
|
|
545
|
-
@dataclass(frozen=True)
|
|
546
|
-
class FunctionStats:
|
|
547
|
-
"""Simple data structure storing stats for a running function."""
|
|
548
|
-
|
|
549
|
-
backlog: int
|
|
550
|
-
num_total_runners: int
|
|
551
|
-
num_running_inputs: int
|
|
552
|
-
input_headroom: int
|
|
553
|
-
|
|
554
|
-
|
|
555
545
|
@dataclass
|
|
556
546
|
class _FunctionSpec:
|
|
557
547
|
"""
|
|
@@ -31,7 +31,7 @@ from modal_version import __version__
|
|
|
31
31
|
|
|
32
32
|
from .._traceback import suppress_tb_frame
|
|
33
33
|
from ..config import config
|
|
34
|
-
from .async_utils import retry
|
|
34
|
+
from .async_utils import TaskContext, retry
|
|
35
35
|
from .logger import logger
|
|
36
36
|
|
|
37
37
|
RequestType = TypeVar("RequestType", bound=Message)
|
|
@@ -190,14 +190,7 @@ class ConnectionManager:
|
|
|
190
190
|
|
|
191
191
|
async def get_or_create_channel(self, server_url: str) -> grpclib.client.Channel:
|
|
192
192
|
if server_url not in self._channels:
|
|
193
|
-
self._channels[server_url] =
|
|
194
|
-
server_url,
|
|
195
|
-
self._metadata,
|
|
196
|
-
)
|
|
197
|
-
try:
|
|
198
|
-
await connect_channel(self._channels[server_url])
|
|
199
|
-
except (OSError, asyncio.TimeoutError) as exc:
|
|
200
|
-
raise ConnectionError("Could not connect to the Modal server.") from exc
|
|
193
|
+
self._channels[server_url] = await create_channel_with_fallbacks(server_url, self._metadata)
|
|
201
194
|
return self._channels[server_url]
|
|
202
195
|
|
|
203
196
|
def close(self):
|
|
@@ -296,6 +289,12 @@ def create_channel(
|
|
|
296
289
|
for k, v in metadata.items():
|
|
297
290
|
event.metadata[k] = v
|
|
298
291
|
|
|
292
|
+
if o.hostname is not None:
|
|
293
|
+
# GRPC actually injects a :authority pseudo-header containing the hostname.
|
|
294
|
+
# But this gets stripped out by the C/C++ extension server-side and is hard to fix.
|
|
295
|
+
# So let's just add a separate header we can parse easily.
|
|
296
|
+
event.metadata["x-modal-host"] = o.hostname
|
|
297
|
+
|
|
299
298
|
idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
|
|
300
299
|
if idempotency_key is None:
|
|
301
300
|
logger.debug(f"Sending request to {event.method_name}")
|
|
@@ -314,6 +313,47 @@ async def connect_channel(channel: grpclib.client.Channel):
|
|
|
314
313
|
await channel.__connect__()
|
|
315
314
|
|
|
316
315
|
|
|
316
|
+
async def create_channel_with_fallbacks(
|
|
317
|
+
server_url: str,
|
|
318
|
+
metadata: dict[str, str] = {},
|
|
319
|
+
stagger_delay: float = 1.0,
|
|
320
|
+
) -> grpclib.client.Channel:
|
|
321
|
+
"""Create a connected channel, supporting comma-separated fallback URLs.
|
|
322
|
+
|
|
323
|
+
`server_url` may be a single URL or a comma-separated list of URLs. The connection attempts
|
|
324
|
+
are raced against each other and the first one to connect wins, which lets the client fail
|
|
325
|
+
over to a secondary endpoint when the primary is unreachable (e.g. a DNS outage on the
|
|
326
|
+
primary's domain). Attempts are staggered: the candidate at index `i` waits `i * stagger_delay`
|
|
327
|
+
seconds before starting, so earlier URLs (e.g. the primary) get a head start and are preferred
|
|
328
|
+
when reachable. Losing attempts are cancelled and their channels closed.
|
|
329
|
+
"""
|
|
330
|
+
urls = [url.strip() for url in server_url.split(",")]
|
|
331
|
+
|
|
332
|
+
async def connect(index: int, url: str) -> grpclib.client.Channel:
|
|
333
|
+
# Stagger the start so earlier URLs are preferred; once started, all attempts race.
|
|
334
|
+
await asyncio.sleep(index * stagger_delay)
|
|
335
|
+
channel = create_channel(url, metadata)
|
|
336
|
+
try:
|
|
337
|
+
await connect_channel(channel)
|
|
338
|
+
except BaseException:
|
|
339
|
+
# Close the channel of a losing/cancelled attempt so its socket doesn't leak.
|
|
340
|
+
channel.close()
|
|
341
|
+
raise
|
|
342
|
+
return channel
|
|
343
|
+
|
|
344
|
+
first_exc: BaseException | None = None
|
|
345
|
+
async with TaskContext() as tc:
|
|
346
|
+
tasks = [tc.create_task(connect(i, url)) for i, url in enumerate(urls)]
|
|
347
|
+
for attempt in asyncio.as_completed(tasks):
|
|
348
|
+
try:
|
|
349
|
+
return await attempt
|
|
350
|
+
except Exception as exc:
|
|
351
|
+
if first_exc is None:
|
|
352
|
+
first_exc = exc
|
|
353
|
+
|
|
354
|
+
raise ConnectionError("Could not connect to the Modal server.") from first_exc
|
|
355
|
+
|
|
356
|
+
|
|
317
357
|
if typing.TYPE_CHECKING:
|
|
318
358
|
import modal.client
|
|
319
359
|
|
|
@@ -811,13 +811,25 @@ class TaskCommandRouterClient:
|
|
|
811
811
|
lambda: self._call_with_auth_retry(self._stub.TaskSetNetworkAccess, request)
|
|
812
812
|
)
|
|
813
813
|
|
|
814
|
-
async def reload_volumes(self, task_id: str) -> sr_pb2.TaskReloadVolumesResponse:
|
|
815
|
-
"""Reload all Volumes mounted in the task to reflect their latest committed state.
|
|
814
|
+
async def reload_volumes(self, task_id: str, timeout: float) -> sr_pb2.TaskReloadVolumesResponse:
|
|
815
|
+
"""Reload all Volumes mounted in the task to reflect their latest committed state.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
task_id: The task whose mounted Volumes should be reloaded.
|
|
819
|
+
timeout: Client-side deadline in seconds. If the reload does not complete within this
|
|
820
|
+
window, the call is cancelled and a `modal.exception.TimeoutError` is raised.
|
|
821
|
+
"""
|
|
816
822
|
request = sr_pb2.TaskReloadVolumesRequest(task_id=task_id)
|
|
817
823
|
with grpc_error_converter():
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
824
|
+
try:
|
|
825
|
+
return await asyncio.wait_for(
|
|
826
|
+
call_with_retries_on_transient_errors(
|
|
827
|
+
lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request, timeout=timeout),
|
|
828
|
+
),
|
|
829
|
+
timeout=timeout,
|
|
830
|
+
)
|
|
831
|
+
except asyncio.TimeoutError:
|
|
832
|
+
raise ModalTimeoutError("Timeout expired")
|
|
821
833
|
|
|
822
834
|
async def _snapshot_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
|
|
823
835
|
# helper method for snapshot_directory and snapshot_filesystem to handle grpc
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
import builtins
|
|
3
|
-
from dataclasses import dataclass
|
|
4
3
|
from datetime import datetime, timezone
|
|
5
|
-
from typing import
|
|
4
|
+
from typing import Optional
|
|
6
5
|
|
|
7
6
|
from google.protobuf.empty_pb2 import Empty
|
|
8
7
|
|
|
9
8
|
from modal.exception import InvalidError
|
|
10
9
|
from modal_proto import api_pb2
|
|
11
10
|
|
|
12
|
-
from ._billing import BillingReportItem
|
|
13
11
|
from ._load_context import LoadContext
|
|
14
12
|
from ._object import _Object
|
|
15
13
|
from ._resolver import Resolver
|
|
16
14
|
from ._utils.time_utils import timestamp_to_localized_dt
|
|
17
15
|
from .client import _Client
|
|
18
|
-
|
|
19
|
-
MemberRole = Literal["user", "manager", "owner"]
|
|
16
|
+
from .types import BillingReportItem, MemberRole, ProxyTokenInfo, TokenData, WorkspaceMemberInfo, WorkspaceSettings
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
def _member_role_from_proto(proto_value: int) -> MemberRole:
|
|
@@ -31,18 +28,6 @@ def _member_role_from_proto(proto_value: int) -> MemberRole:
|
|
|
31
28
|
raise ValueError(f"Unknown workspace member role: {proto_value}")
|
|
32
29
|
|
|
33
30
|
|
|
34
|
-
@dataclass(frozen=True)
|
|
35
|
-
class WorkspaceMemberInfo:
|
|
36
|
-
"""Metadata about a Workspace member."""
|
|
37
|
-
|
|
38
|
-
name: str
|
|
39
|
-
email: str
|
|
40
|
-
user_id: str
|
|
41
|
-
role: MemberRole
|
|
42
|
-
joined_at: datetime
|
|
43
|
-
last_active_at: Optional[datetime] # None if the member has never been active
|
|
44
|
-
|
|
45
|
-
|
|
46
31
|
class _WorkspaceMembersManager:
|
|
47
32
|
"""mdmd:namespace
|
|
48
33
|
Namespace with methods for managing the membership of a Workspace.
|
|
@@ -125,22 +110,9 @@ class _Workspace(_Object, type_prefix="ac"):
|
|
|
125
110
|
def proxy_tokens(self) -> "_WorkspaceProxyTokenManager":
|
|
126
111
|
return _WorkspaceProxyTokenManager(self)
|
|
127
112
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"""A token ID / secret pair."""
|
|
132
|
-
|
|
133
|
-
token_id: str
|
|
134
|
-
token_secret: str
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
@dataclass(frozen=True)
|
|
138
|
-
class ProxyTokenInfo:
|
|
139
|
-
"""Metadata about a proxy token, not including the token secret."""
|
|
140
|
-
|
|
141
|
-
token_id: str
|
|
142
|
-
created_at: datetime
|
|
143
|
-
scoped: bool
|
|
113
|
+
@property
|
|
114
|
+
def settings(self) -> "_WorkspaceSettingsManager":
|
|
115
|
+
return _WorkspaceSettingsManager(self)
|
|
144
116
|
|
|
145
117
|
|
|
146
118
|
class _WorkspaceProxyTokenManager:
|
|
@@ -340,3 +312,71 @@ class _WorkspaceBillingManager:
|
|
|
340
312
|
BillingReportItem._from_proto(pb_item)
|
|
341
313
|
async for pb_item in self._workspace.client.stub.WorkspaceBillingReport.unary_stream(request)
|
|
342
314
|
]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class _WorkspaceSettingsManager:
|
|
318
|
+
"""mdmd:namespace
|
|
319
|
+
Namespace for Workspace settings APIs.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def valid_settings(cls):
|
|
324
|
+
return ("default-environment", "image-builder-version")
|
|
325
|
+
|
|
326
|
+
def __init__(self, workspace: _Workspace):
|
|
327
|
+
"""mdmd:hidden"""
|
|
328
|
+
self._workspace = workspace
|
|
329
|
+
|
|
330
|
+
async def list(self):
|
|
331
|
+
"""Return a the current workspace settings.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
A `WorkspaceSettings` dataclass.
|
|
335
|
+
"""
|
|
336
|
+
if not self._workspace.is_hydrated:
|
|
337
|
+
await self._workspace.hydrate()
|
|
338
|
+
resp = await self._workspace.client.stub.WorkspaceSettings(Empty())
|
|
339
|
+
return WorkspaceSettings(
|
|
340
|
+
default_environment=resp.default_environment_name, image_builder_version=resp.image_builder_version
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
async def _set_image_builder_version(self, version: str) -> None:
|
|
344
|
+
"""mdmd:hidden
|
|
345
|
+
Set the image builder version for the Workspace.
|
|
346
|
+
"""
|
|
347
|
+
if not self._workspace.is_hydrated:
|
|
348
|
+
await self._workspace.hydrate()
|
|
349
|
+
req = api_pb2.WorkspaceSetImageBuilderVersionRequest(new_image_builder_version=version)
|
|
350
|
+
await self._workspace.client.stub.WorkspaceSetImageBuilderVersion(req)
|
|
351
|
+
|
|
352
|
+
async def _set_default_environment(self, name: str) -> None:
|
|
353
|
+
"""Set the default environment for the Workspace."""
|
|
354
|
+
if not self._workspace.is_hydrated:
|
|
355
|
+
await self._workspace.hydrate()
|
|
356
|
+
req = api_pb2.WorkspaceSetDefaultEnvironmentRequest(environment_name=name)
|
|
357
|
+
await self._workspace.client.stub.WorkspaceSetDefaultEnvironment(req)
|
|
358
|
+
|
|
359
|
+
async def set(self, name: str, value: str) -> None:
|
|
360
|
+
"""Set a workspace setting to a new value. Must be workspace manager or owner.
|
|
361
|
+
|
|
362
|
+
The following settings can be updated:
|
|
363
|
+
|
|
364
|
+
- image-builder-version: The image builder version determines the software included in our base images.
|
|
365
|
+
- default-environment: The default environment when the environment is omitted from SDK or CLI methods.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
name: The name of the setting.
|
|
369
|
+
value: The new value of the setting.
|
|
370
|
+
|
|
371
|
+
Examples:
|
|
372
|
+
```python notest
|
|
373
|
+
modal.Workspace.from_context().settings.set("default-environment", "dev")
|
|
374
|
+
```
|
|
375
|
+
"""
|
|
376
|
+
match name:
|
|
377
|
+
case "image-builder-version":
|
|
378
|
+
await self._set_image_builder_version(value)
|
|
379
|
+
case "default-environment":
|
|
380
|
+
await self._set_default_environment(value)
|
|
381
|
+
case _:
|
|
382
|
+
raise ValueError(f"Unknown setting {name!r}. Valid settings: {', '.join(self.valid_settings())}")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
|
-
from ._billing import
|
|
2
|
+
from ._billing import WorkspaceBillingReportItem, _workspace_billing_report
|
|
3
3
|
from ._utils.async_utils import synchronize_api
|
|
4
|
+
from .types import BillingReportItem
|
|
4
5
|
|
|
5
6
|
workspace_billing_report = synchronize_api(_workspace_billing_report, target_module=__name__)
|
|
6
7
|
|
|
@@ -2,12 +2,32 @@ import datetime
|
|
|
2
2
|
import decimal
|
|
3
3
|
import modal._billing
|
|
4
4
|
import modal.client
|
|
5
|
+
import modal.types
|
|
5
6
|
import modal_proto.api_pb2
|
|
6
7
|
import typing
|
|
7
8
|
import typing_extensions
|
|
8
9
|
|
|
10
|
+
class WorkspaceBillingReportItem(typing.TypedDict):
|
|
11
|
+
"""dict() -> new empty dictionary
|
|
12
|
+
dict(mapping) -> new dictionary initialized from a mapping object's
|
|
13
|
+
(key, value) pairs
|
|
14
|
+
dict(iterable) -> new dictionary initialized as if via:
|
|
15
|
+
d = {}
|
|
16
|
+
for k, v in iterable:
|
|
17
|
+
d[k] = v
|
|
18
|
+
dict(**kwargs) -> new dictionary initialized with the name=value pairs
|
|
19
|
+
in the keyword argument list. For example: dict(one=1, two=2)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
object_id: str
|
|
23
|
+
description: str
|
|
24
|
+
environment_name: str
|
|
25
|
+
interval_start: datetime.datetime
|
|
26
|
+
cost: decimal.Decimal
|
|
27
|
+
tags: dict[str, str]
|
|
28
|
+
|
|
9
29
|
class BillingReportItem:
|
|
10
|
-
"""
|
|
30
|
+
"""Costs generated by a specific object during a specific time interval."""
|
|
11
31
|
|
|
12
32
|
object_id: str
|
|
13
33
|
description: str
|
|
@@ -18,29 +38,27 @@ class BillingReportItem:
|
|
|
18
38
|
tags: dict[str, str]
|
|
19
39
|
|
|
20
40
|
def __getitem__(self, key: str) -> typing.Any:
|
|
21
|
-
"""mdmd:
|
|
41
|
+
"""mdmd:hidden"""
|
|
22
42
|
...
|
|
23
43
|
|
|
24
44
|
def __setitem__(self, key: str, _: typing.Any):
|
|
25
|
-
"""mdmd:
|
|
45
|
+
"""mdmd:hidden"""
|
|
26
46
|
...
|
|
27
47
|
|
|
28
48
|
def keys(self) -> typing.Iterable[str]:
|
|
29
|
-
"""mdmd:
|
|
49
|
+
"""mdmd:hidden"""
|
|
30
50
|
...
|
|
31
51
|
|
|
32
52
|
def values(self) -> typing.Iterable[typing.Any]:
|
|
33
|
-
"""mdmd:
|
|
53
|
+
"""mdmd:hidden"""
|
|
34
54
|
...
|
|
35
55
|
|
|
36
56
|
def items(self) -> typing.Iterable[tuple[str, typing.Any]]:
|
|
37
|
-
"""mdmd:
|
|
57
|
+
"""mdmd:hidden"""
|
|
38
58
|
...
|
|
39
59
|
|
|
40
60
|
@classmethod
|
|
41
|
-
def _from_proto(
|
|
42
|
-
cls, pb_item: modal_proto.api_pb2.WorkspaceBillingReportItem
|
|
43
|
-
) -> modal._billing.BillingReportItem: ...
|
|
61
|
+
def _from_proto(cls, pb_item: modal_proto.api_pb2.WorkspaceBillingReportItem) -> modal.types.BillingReportItem: ...
|
|
44
62
|
def __init__(
|
|
45
63
|
self,
|
|
46
64
|
object_id: str,
|
|
@@ -77,25 +95,6 @@ class BillingReportItem:
|
|
|
77
95
|
def __getstate__(self): ...
|
|
78
96
|
def __setstate__(self, state): ...
|
|
79
97
|
|
|
80
|
-
class WorkspaceBillingReportItem(typing.TypedDict):
|
|
81
|
-
"""dict() -> new empty dictionary
|
|
82
|
-
dict(mapping) -> new dictionary initialized from a mapping object's
|
|
83
|
-
(key, value) pairs
|
|
84
|
-
dict(iterable) -> new dictionary initialized as if via:
|
|
85
|
-
d = {}
|
|
86
|
-
for k, v in iterable:
|
|
87
|
-
d[k] = v
|
|
88
|
-
dict(**kwargs) -> new dictionary initialized with the name=value pairs
|
|
89
|
-
in the keyword argument list. For example: dict(one=1, two=2)
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
object_id: str
|
|
93
|
-
description: str
|
|
94
|
-
environment_name: str
|
|
95
|
-
interval_start: datetime.datetime
|
|
96
|
-
cost: decimal.Decimal
|
|
97
|
-
tags: dict[str, str]
|
|
98
|
-
|
|
99
98
|
class __workspace_billing_report_spec(typing_extensions.Protocol):
|
|
100
99
|
def __call__(
|
|
101
100
|
self,
|
|
@@ -1,36 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from enum import IntEnum
|
|
4
|
-
|
|
5
2
|
from modal_proto import api_pb2
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
class InputStatus(IntEnum):
|
|
9
|
-
"""Enum representing status of a function input."""
|
|
10
|
-
|
|
11
|
-
PENDING = 0
|
|
12
|
-
SUCCESS = api_pb2.GenericResult.GENERIC_STATUS_SUCCESS
|
|
13
|
-
FAILURE = api_pb2.GenericResult.GENERIC_STATUS_FAILURE
|
|
14
|
-
INIT_FAILURE = api_pb2.GenericResult.GENERIC_STATUS_INIT_FAILURE
|
|
15
|
-
TERMINATED = api_pb2.GenericResult.GENERIC_STATUS_TERMINATED
|
|
16
|
-
TIMEOUT = api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT
|
|
17
|
-
|
|
18
|
-
@classmethod
|
|
19
|
-
def _missing_(cls, value):
|
|
20
|
-
return cls.PENDING
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@dataclass
|
|
24
|
-
class InputInfo:
|
|
25
|
-
"""Simple data structure storing information about a function input."""
|
|
26
|
-
|
|
27
|
-
input_id: str
|
|
28
|
-
function_call_id: str
|
|
29
|
-
task_id: str
|
|
30
|
-
status: InputStatus
|
|
31
|
-
function_name: str
|
|
32
|
-
module_name: str
|
|
33
|
-
children: list["InputInfo"]
|
|
4
|
+
from .types import InputInfo, InputStatus
|
|
34
5
|
|
|
35
6
|
|
|
36
7
|
def _reconstruct_call_graph(ser_graph: api_pb2.FunctionGetCallGraphResponse) -> list[InputInfo]:
|
|
@@ -13,7 +13,8 @@ from click import UsageError
|
|
|
13
13
|
from modal._utils.async_utils import TaskContext
|
|
14
14
|
from modal.config import logger
|
|
15
15
|
from modal.network_file_system import _NetworkFileSystem
|
|
16
|
-
from modal.
|
|
16
|
+
from modal.types import FileEntry, FileEntryType
|
|
17
|
+
from modal.volume import _Volume
|
|
17
18
|
|
|
18
19
|
PIPE_PATH = Path("-")
|
|
19
20
|
|
|
@@ -59,7 +59,7 @@ async def list_(env: str | None = None, json: bool = False):
|
|
|
59
59
|
display_table(column_names, rows, json=json, title=f"Active Multi-node Clusters in environment: {environment_name}")
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
@cluster_cli.command("shell")
|
|
62
|
+
@cluster_cli.command("shell", no_args_is_help=True)
|
|
63
63
|
@click.argument("cluster_id")
|
|
64
64
|
@click.option("--rank", default=0, help="Rank of the node to shell into")
|
|
65
65
|
@synchronizer.create_blocking
|
|
@@ -40,7 +40,7 @@ when running a command that requires an environment.
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
@config_cli.command("set-environment", help=SET_DEFAULT_ENV_HELP)
|
|
43
|
+
@config_cli.command("set-environment", help=SET_DEFAULT_ENV_HELP, no_args_is_help=True)
|
|
44
44
|
@click.argument("environment_name")
|
|
45
45
|
def set_environment(environment_name: str):
|
|
46
46
|
# Confirm that the environment exists by looking it up
|
|
@@ -49,7 +49,7 @@ def set_environment(environment_name: str):
|
|
|
49
49
|
click.echo(f"New default environment for profile {_profile}: {environment_name}")
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
@config_cli.command("set", hidden=True)
|
|
52
|
+
@config_cli.command("set", hidden=True, no_args_is_help=True)
|
|
53
53
|
@click.argument("key")
|
|
54
54
|
@click.argument("value")
|
|
55
55
|
def set(key: str, value: str):
|
|
@@ -317,7 +317,7 @@ async def _exec_impl(
|
|
|
317
317
|
).wait()
|
|
318
318
|
|
|
319
319
|
|
|
320
|
-
@container_cli.command("exec")
|
|
320
|
+
@container_cli.command("exec", no_args_is_help=True)
|
|
321
321
|
@click.option("--pty/--no-pty", default=None, help="Run the command using a PTY.")
|
|
322
322
|
@click.argument("container_id")
|
|
323
323
|
@click.argument("command", nargs=-1, required=True)
|
|
@@ -330,16 +330,27 @@ def exec(
|
|
|
330
330
|
_exec_impl(pty=pty, container_id=container_id, command=command)
|
|
331
331
|
|
|
332
332
|
|
|
333
|
-
@container_cli.command("stop")
|
|
333
|
+
@container_cli.command("stop", no_args_is_help=True)
|
|
334
334
|
@click.argument("container_id")
|
|
335
|
+
@click.option(
|
|
336
|
+
"--graceful",
|
|
337
|
+
is_flag=True,
|
|
338
|
+
default=False,
|
|
339
|
+
help="Let the container finish its current inputs before exiting, instead of cancelling them.",
|
|
340
|
+
)
|
|
335
341
|
@yes_option
|
|
336
342
|
@synchronizer.create_blocking
|
|
337
|
-
async def stop(container_id: str = "", *, yes: bool = False):
|
|
343
|
+
async def stop(container_id: str = "", *, graceful: bool = False, yes: bool = False):
|
|
338
344
|
"""Terminate a running container.
|
|
339
345
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
on other containers.
|
|
346
|
+
By default, this will send the container a SIGINT signal that Modal will handle.
|
|
347
|
+
For Functions, any inputs that are currently running on the container will be cancelled
|
|
348
|
+
and rescheduled on other containers.
|
|
349
|
+
|
|
350
|
+
With `--graceful`, the container will be allowed to finish the inputs it is currently
|
|
351
|
+
running, exiting once they complete. Graceful stops are only supported for containers
|
|
352
|
+
running a Modal Function.
|
|
353
|
+
|
|
343
354
|
"""
|
|
344
355
|
client = await _Client.from_env()
|
|
345
356
|
resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=container_id))
|
|
@@ -347,5 +358,5 @@ async def stop(container_id: str = "", *, yes: bool = False):
|
|
|
347
358
|
raise SystemExit(f"Container '{container_id}' is already stopped.")
|
|
348
359
|
if not yes:
|
|
349
360
|
confirm_or_suggest_yes(f"Are you sure you want to stop container '{container_id}'?")
|
|
350
|
-
request = api_pb2.ContainerStopRequest(task_id=container_id)
|
|
361
|
+
request = api_pb2.ContainerStopRequest(task_id=container_id, graceful=graceful)
|
|
351
362
|
await client.stub.ContainerStop(request)
|
|
@@ -96,7 +96,12 @@ def _run_curl(curl_args: tuple[str, ...], token: str | None = None) -> None:
|
|
|
96
96
|
sys.exit(subprocess.call(cmd))
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
@click.command(
|
|
99
|
+
@click.command(
|
|
100
|
+
"curl",
|
|
101
|
+
cls=ModalCommand,
|
|
102
|
+
no_args_is_help=True,
|
|
103
|
+
context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
|
|
104
|
+
)
|
|
100
105
|
@click.argument("curl_args", nargs=-1, required=True, type=click.UNPROCESSED)
|
|
101
106
|
@synchronizer.create_blocking
|
|
102
107
|
async def curl(curl_args: tuple[str, ...]):
|