modAL 1.5.1__tar.gz → 1.5.1.dev1__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.1 → modal-1.5.1.dev1}/PKG-INFO +3 -6
- {modal-1.5.1 → modal-1.5.1.dev1}/README.md +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/__init__.py +0 -2
- modal-1.5.1.dev1/modal/_billing.py +94 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_environments.py +1 -84
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_functions.py +9 -13
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_grpc_client.py +3 -3
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_image.py +4 -39
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_partial_function.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -5
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/task_lifecycle_manager.py +4 -9
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_server.py +10 -45
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/blob_utils.py +1 -3
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/grpc_utils.py +8 -85
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/http_utils.py +1 -20
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/task_command_router_client.py +2 -29
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/time_utils.py +0 -23
- modal-1.5.1.dev1/modal/_workspace.py +116 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/app.py +23 -37
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/app.pyi +42 -56
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/billing.py +2 -3
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/_help.py +3 -3
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/app.py +3 -24
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/billing.py +77 -108
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/changelog.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/config.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/entry_point.py +2 -8
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/environment.py +1 -140
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/shell.py +2 -2
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/utils.py +3 -8
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/client.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/client.pyi +10 -2
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cls.py +2 -2
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cls.pyi +4 -4
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/config.py +0 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/environments.py +0 -2
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/environments.pyi +1 -91
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/exception.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/functions.pyi +24 -27
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/partial_function.pyi +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/runner.py +12 -16
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/sandbox.py +46 -104
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/sandbox.pyi +24 -90
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/sandbox_fs.py +2 -39
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/schedule.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/secret.py +9 -39
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/secret.pyi +0 -19
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/server.pyi +14 -75
- modal-1.5.1.dev1/modal/workspace.py +12 -0
- modal-1.5.1.dev1/modal/workspace.pyi +105 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal.egg-info/PKG-INFO +3 -6
- {modal-1.5.1 → modal-1.5.1.dev1}/modal.egg-info/SOURCES.txt +0 -6
- {modal-1.5.1 → modal-1.5.1.dev1}/modal.egg-info/requires.txt +1 -5
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/gen_cli_docs.py +2 -48
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/mdmd/mdmd.py +1 -10
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/mdmd/types.py +0 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/api_grpc.py +3 -195
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/api_pb2.py +1095 -1340
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/api_pb2.pyi +13 -494
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/api_pb2_grpc.py +3 -401
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/api_pb2_grpc.pyi +2 -126
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/modal_api_grpc.py +0 -12
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/task_command_router_grpc.py +5 -21
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/task_command_router_pb2.py +93 -113
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/task_command_router_pb2.pyi +5 -41
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/task_command_router_pb2_grpc.py +8 -44
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/task_command_router_pb2_grpc.pyi +8 -24
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_version/__init__.py +1 -1
- {modal-1.5.1 → modal-1.5.1.dev1}/pyproject.toml +11 -29
- modal-1.5.1/modal/_billing.py +0 -134
- modal-1.5.1/modal/_utils/curl_utils.py +0 -115
- modal-1.5.1/modal/_utils/proxy_support.py +0 -102
- modal-1.5.1/modal/_workspace.py +0 -342
- modal-1.5.1/modal/billing.pyi +0 -176
- modal-1.5.1/modal/cli/curl.py +0 -137
- modal-1.5.1/modal/cli/endpoint.py +0 -352
- modal-1.5.1/modal/cli/workspace.py +0 -129
- modal-1.5.1/modal/workspace.py +0 -25
- modal-1.5.1/modal/workspace.pyi +0 -450
- {modal-1.5.1 → modal-1.5.1.dev1}/LICENSE +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/__main__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_clustered_functions.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_function_variants.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_ipython.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_load_context.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_location.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_logs.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_object.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_output/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_output/manager.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_output/pty.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_output/rich.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_output/status.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_resolver.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_resources.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_serialization.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_traceback.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_tunnel.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_tunnel.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_type_manager.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/logger.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_vendor/version.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/_watcher.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/README.md +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/builder/base-images.json +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/call_graph.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/_download.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/_traceback.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/cluster.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/container.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/dashboard.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/dict.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/image.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/import_refs.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/launch.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/logo.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/profile.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/queues.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/run.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/secret.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/selector.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/skills.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/token.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cli/volume.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/container_process.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/container_process.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/dict.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/dict.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/experimental/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/experimental/flash.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/experimental/ipython.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/file_io.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/file_io.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/functions.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/image.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/image.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/io_streams.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/io_streams.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/mount.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/mount.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/network_file_system.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/network_file_system.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/object.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/object.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/output.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/parallel_map.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/parallel_map.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/partial_function.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/proxy.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/proxy.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/py.typed +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/queue.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/queue.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/retries.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/runner.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/running_app.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/scheduler_placement.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/server.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/serving.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/serving.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/snapshot.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/snapshot.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/stream_type.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/token_flow.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/token_flow.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/volume.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal/volume.pyi +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/__init__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_proto/py.typed +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/modal_version/__main__.py +0 -0
- {modal-1.5.1 → modal-1.5.1.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.5.1
|
|
3
|
+
Version: 1.5.1.dev1
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -23,15 +23,12 @@ Requires-Dist: grpclib<0.4.10,>=0.4.7; python_version < "3.14"
|
|
|
23
23
|
Requires-Dist: grpclib<0.4.10,>=0.4.9; python_version >= "3.14"
|
|
24
24
|
Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
|
|
25
25
|
Requires-Dist: rich>=12.0.0
|
|
26
|
-
Requires-Dist: synchronicity~=0.12.
|
|
26
|
+
Requires-Dist: synchronicity~=0.12.3
|
|
27
27
|
Requires-Dist: toml
|
|
28
28
|
Requires-Dist: types-certifi
|
|
29
29
|
Requires-Dist: types-toml
|
|
30
30
|
Requires-Dist: watchfiles
|
|
31
31
|
Requires-Dist: typing_extensions~=4.6
|
|
32
|
-
Provides-Extra: api-proxy-support
|
|
33
|
-
Requires-Dist: python-socks[asyncio]>=2.5; extra == "api-proxy-support"
|
|
34
|
-
Requires-Dist: aiohttp-socks>=0.8; extra == "api-proxy-support"
|
|
35
32
|
Dynamic: license-file
|
|
36
33
|
|
|
37
34
|
# Modal Python SDK
|
|
@@ -48,7 +45,7 @@ access to serverless cloud compute from Python scripts on your local computer.
|
|
|
48
45
|
See the [online documentation](https://modal.com/docs/guide) for many
|
|
49
46
|
[example applications](https://modal.com/docs/examples),
|
|
50
47
|
a [user guide](https://modal.com/docs/guide), and the detailed
|
|
51
|
-
[API reference](https://modal.com/docs/
|
|
48
|
+
[API reference](https://modal.com/docs/reference).
|
|
52
49
|
|
|
53
50
|
## Installation
|
|
54
51
|
|
|
@@ -12,7 +12,7 @@ access to serverless cloud compute from Python scripts on your local computer.
|
|
|
12
12
|
See the [online documentation](https://modal.com/docs/guide) for many
|
|
13
13
|
[example applications](https://modal.com/docs/examples),
|
|
14
14
|
a [user guide](https://modal.com/docs/guide), and the detailed
|
|
15
|
-
[API reference](https://modal.com/docs/
|
|
15
|
+
[API reference](https://modal.com/docs/reference).
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -43,7 +43,6 @@ try:
|
|
|
43
43
|
from .schedule import Cron, Period
|
|
44
44
|
from .scheduler_placement import SchedulerPlacement
|
|
45
45
|
from .secret import Secret
|
|
46
|
-
from .server import Server
|
|
47
46
|
from .snapshot import SandboxSnapshot
|
|
48
47
|
from .volume import Volume
|
|
49
48
|
from .workspace import Workspace
|
|
@@ -81,7 +80,6 @@ __all__ = [
|
|
|
81
80
|
"SandboxSnapshot",
|
|
82
81
|
"SchedulerPlacement",
|
|
83
82
|
"Secret",
|
|
84
|
-
"Server",
|
|
85
83
|
"Tunnel",
|
|
86
84
|
"Volume",
|
|
87
85
|
"Workspace",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
from modal_proto import api_pb2
|
|
7
|
+
|
|
8
|
+
from .client import _Client
|
|
9
|
+
from .exception import InvalidError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WorkspaceBillingReportItem(TypedDict):
|
|
13
|
+
object_id: str
|
|
14
|
+
description: str
|
|
15
|
+
environment_name: str
|
|
16
|
+
interval_start: datetime
|
|
17
|
+
cost: Decimal
|
|
18
|
+
tags: dict[str, str]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def _workspace_billing_report(
|
|
22
|
+
*,
|
|
23
|
+
start: datetime, # Start of the report, inclusive
|
|
24
|
+
end: datetime | None = None, # End of the report, exclusive
|
|
25
|
+
resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
|
|
26
|
+
tag_names: list[str] | None = None, # Optional additional metadata to include
|
|
27
|
+
client: _Client | None = None,
|
|
28
|
+
) -> list[WorkspaceBillingReportItem]:
|
|
29
|
+
"""Generate a tabular report of workspace usage by object and time.
|
|
30
|
+
|
|
31
|
+
The result will be a list of dictionaries for each interval (determined by `resolution`)
|
|
32
|
+
between the `start` and `end` limits. The dictionary represents a single Modal object
|
|
33
|
+
that billing can be attributed to (e.g., an App) along with metadata (including user-defined
|
|
34
|
+
tags) for identifying that object.
|
|
35
|
+
|
|
36
|
+
The `start` and `end` parameters are required to either have a UTC timezone or to be
|
|
37
|
+
timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
|
|
38
|
+
be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
|
|
39
|
+
parameters are partial: `start` will be rounded to the beginning of its interval, while
|
|
40
|
+
partial `end` intervals will be excluded.
|
|
41
|
+
|
|
42
|
+
Additional user-provided metadata can be included in the report if the objects have tags
|
|
43
|
+
and `tag_names` (i.e., keys) are specified in the request. Alternatively, pass `tag_names=["*"]`
|
|
44
|
+
to include all tags in the report. Note that tags will be attributed to the entire interval even
|
|
45
|
+
if they were added or removed at some point within it. If the tag name was not in use during an
|
|
46
|
+
interval, it will be absent from the tags dictionary in that output row.
|
|
47
|
+
|
|
48
|
+
In most cases, billing data will be available in the database that this API queries within
|
|
49
|
+
minutes, although there may be collection delays. If completeness is important for your use
|
|
50
|
+
case, we recommend leaving a buffer after the end of the query interval.
|
|
51
|
+
|
|
52
|
+
It's also possible to generate reports using the
|
|
53
|
+
[`modal billing report`](https://modal.com/docs/reference/cli/billing) CLI command. The CLI
|
|
54
|
+
has a few convenience features for generating reports across relative time ranges.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
if client is None:
|
|
58
|
+
client = await _Client.from_env()
|
|
59
|
+
|
|
60
|
+
tag_names = tag_names or []
|
|
61
|
+
|
|
62
|
+
if end is None:
|
|
63
|
+
end = datetime.now(timezone.utc)
|
|
64
|
+
|
|
65
|
+
if start.tzinfo is None:
|
|
66
|
+
start = start.replace(tzinfo=timezone.utc)
|
|
67
|
+
elif start.tzinfo != timezone.utc:
|
|
68
|
+
raise InvalidError("Timezone-aware 'start' parameter must be in UTC.")
|
|
69
|
+
|
|
70
|
+
if end.tzinfo is None:
|
|
71
|
+
end = end.replace(tzinfo=timezone.utc)
|
|
72
|
+
elif end.tzinfo != timezone.utc:
|
|
73
|
+
raise InvalidError("Timezone-aware 'end' parameter must be in UTC.")
|
|
74
|
+
|
|
75
|
+
request = api_pb2.WorkspaceBillingReportRequest(
|
|
76
|
+
resolution=resolution,
|
|
77
|
+
tag_names=tag_names,
|
|
78
|
+
)
|
|
79
|
+
request.start_timestamp.FromDatetime(start)
|
|
80
|
+
request.end_timestamp.FromDatetime(end)
|
|
81
|
+
|
|
82
|
+
rows = []
|
|
83
|
+
async for pb_item in client.stub.WorkspaceBillingReport.unary_stream(request):
|
|
84
|
+
item: WorkspaceBillingReportItem = {
|
|
85
|
+
"object_id": pb_item.object_id,
|
|
86
|
+
"description": pb_item.description,
|
|
87
|
+
"environment_name": pb_item.environment_name,
|
|
88
|
+
"interval_start": pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
|
|
89
|
+
"cost": Decimal(pb_item.cost),
|
|
90
|
+
"tags": dict(pb_item.tags),
|
|
91
|
+
}
|
|
92
|
+
rows.append(item)
|
|
93
|
+
|
|
94
|
+
return rows
|
|
@@ -3,7 +3,6 @@ import asyncio
|
|
|
3
3
|
import builtins
|
|
4
4
|
from collections.abc import Iterable, Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from datetime import datetime, timezone
|
|
7
6
|
from typing import Literal
|
|
8
7
|
|
|
9
8
|
from google.protobuf.empty_pb2 import Empty
|
|
@@ -13,7 +12,6 @@ from synchronicity import classproperty
|
|
|
13
12
|
|
|
14
13
|
from modal_proto import api_pb2
|
|
15
14
|
|
|
16
|
-
from ._billing import BillingReportItem
|
|
17
15
|
from ._load_context import LoadContext
|
|
18
16
|
from ._object import _Object
|
|
19
17
|
from ._resolver import Resolver
|
|
@@ -320,7 +318,7 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
320
318
|
# def settings(self) -> EnvironmentSettings:
|
|
321
319
|
# return self._settings
|
|
322
320
|
|
|
323
|
-
def _hydrate_metadata(self, metadata: Message
|
|
321
|
+
def _hydrate_metadata(self, metadata: Message):
|
|
324
322
|
# Overridden concrete implementation of base class method
|
|
325
323
|
assert metadata and isinstance(metadata, api_pb2.EnvironmentMetadata)
|
|
326
324
|
self._name = metadata.name or None
|
|
@@ -394,87 +392,6 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
394
392
|
client=client,
|
|
395
393
|
)
|
|
396
394
|
|
|
397
|
-
@property
|
|
398
|
-
def billing(self) -> "_EnvironmentBillingManager":
|
|
399
|
-
return _EnvironmentBillingManager(self)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
class _EnvironmentBillingManager:
|
|
403
|
-
"""mdmd:namespace
|
|
404
|
-
Namespace for Environment billing APIs
|
|
405
|
-
"""
|
|
406
|
-
|
|
407
|
-
def __init__(self, environment: _Environment):
|
|
408
|
-
"""mdmd:ignore"""
|
|
409
|
-
self._environment = environment
|
|
410
|
-
|
|
411
|
-
async def report(
|
|
412
|
-
self,
|
|
413
|
-
*,
|
|
414
|
-
start: datetime, # Start of the report, inclusive
|
|
415
|
-
end: datetime | None = None, # End of the report, exclusive
|
|
416
|
-
resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
|
|
417
|
-
tag_names: list[str] | None = None, # Optional additional metadata to include
|
|
418
|
-
) -> list[BillingReportItem]:
|
|
419
|
-
"""Return a cost report for Environment usage, broken down by object and time.
|
|
420
|
-
|
|
421
|
-
Args:
|
|
422
|
-
start: Start of the report, inclusive and rounded to the beginning of the interval.
|
|
423
|
-
Must be in UTC or timezone-naive (interpreted as UTC).
|
|
424
|
-
end: End of the report, exclusive. Must be in UTC or timezone-naive. Partial final
|
|
425
|
-
intervals will be excluded from the report.
|
|
426
|
-
resolution: Resolution, e.g. "d" for daily or "h" for hourly.
|
|
427
|
-
tag_names: List of tag names; each row will include the tag name and value in use
|
|
428
|
-
for that object during the relevant time interval. Pass `["*"]` to include all
|
|
429
|
-
tags in the report.
|
|
430
|
-
|
|
431
|
-
Returns:
|
|
432
|
-
A list of `BillingReportItem` dataclasses. Each item reports the cost attributed to
|
|
433
|
-
a specific Modal object during a given time interval. Cost is further broken down by
|
|
434
|
-
the resource type that generated it (e.g. CPU, Memory, specific GPU usage).
|
|
435
|
-
Note that the specific resource types included in the breakdown are subject to change
|
|
436
|
-
as Modal's billing model evolves.
|
|
437
|
-
|
|
438
|
-
See also:
|
|
439
|
-
- [`modal environment billing report`](https://modal.com/docs/cli/latest/environment#modal-environment-billing-report):
|
|
440
|
-
An environment report CLI that has convenience features around relative time range queries
|
|
441
|
-
and JSON/CSV output.
|
|
442
|
-
- [`Workspace.billing.report()`](https://modal.com/docs/sdk/py/latest/modal.Workspace#billingreport):
|
|
443
|
-
An analogous report API for the entire Workspace.
|
|
444
|
-
|
|
445
|
-
"""
|
|
446
|
-
if tag_names is None:
|
|
447
|
-
tag_names = []
|
|
448
|
-
|
|
449
|
-
if end is None:
|
|
450
|
-
end = datetime.now(timezone.utc)
|
|
451
|
-
|
|
452
|
-
if start.tzinfo is None:
|
|
453
|
-
start = start.replace(tzinfo=timezone.utc)
|
|
454
|
-
elif start.tzinfo != timezone.utc:
|
|
455
|
-
raise InvalidError("Timezone-aware 'start' parameter must be in UTC.")
|
|
456
|
-
|
|
457
|
-
if end.tzinfo is None:
|
|
458
|
-
end = end.replace(tzinfo=timezone.utc)
|
|
459
|
-
elif end.tzinfo != timezone.utc:
|
|
460
|
-
raise InvalidError("Timezone-aware 'end' parameter must be in UTC.")
|
|
461
|
-
|
|
462
|
-
if not self._environment.is_hydrated:
|
|
463
|
-
await self._environment.hydrate()
|
|
464
|
-
|
|
465
|
-
request = api_pb2.WorkspaceBillingReportRequest(
|
|
466
|
-
resolution=resolution,
|
|
467
|
-
tag_names=tag_names,
|
|
468
|
-
environment_ids=[self._environment.object_id],
|
|
469
|
-
)
|
|
470
|
-
request.start_timestamp.FromDatetime(start)
|
|
471
|
-
request.end_timestamp.FromDatetime(end)
|
|
472
|
-
|
|
473
|
-
return [
|
|
474
|
-
BillingReportItem._from_proto(pb_item)
|
|
475
|
-
async for pb_item in self._environment.client.stub.WorkspaceBillingReport.unary_stream(request)
|
|
476
|
-
]
|
|
477
|
-
|
|
478
395
|
|
|
479
396
|
ENVIRONMENT_CACHE: dict[str, _Environment] = {}
|
|
480
397
|
|
|
@@ -661,7 +661,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
661
661
|
min_containers: int | None = None,
|
|
662
662
|
max_containers: int | None = None,
|
|
663
663
|
buffer_containers: int | None = None,
|
|
664
|
-
scaleup_window: int | None = None,
|
|
665
664
|
scaledown_window: int | None = None,
|
|
666
665
|
max_concurrent_inputs: int | None = None,
|
|
667
666
|
target_concurrent_inputs: int | None = None,
|
|
@@ -767,7 +766,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
767
766
|
min_containers=min_containers,
|
|
768
767
|
max_containers=max_containers,
|
|
769
768
|
buffer_containers=buffer_containers,
|
|
770
|
-
scaleup_window=scaleup_window,
|
|
771
769
|
scaledown_window=scaledown_window,
|
|
772
770
|
)
|
|
773
771
|
|
|
@@ -1159,7 +1157,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1159
1157
|
min_containers: int | None = None,
|
|
1160
1158
|
max_containers: int | None = None,
|
|
1161
1159
|
buffer_containers: int | None = None,
|
|
1162
|
-
scaleup_window: int | None = None,
|
|
1163
1160
|
scaledown_window: int | None = None,
|
|
1164
1161
|
target_concurrency: int | None = None,
|
|
1165
1162
|
) -> None:
|
|
@@ -1167,7 +1164,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1167
1164
|
min_containers=min_containers,
|
|
1168
1165
|
max_containers=max_containers,
|
|
1169
1166
|
buffer_containers=buffer_containers,
|
|
1170
|
-
scaleup_window=scaleup_window,
|
|
1171
1167
|
scaledown_window=scaledown_window,
|
|
1172
1168
|
target_concurrency=target_concurrency,
|
|
1173
1169
|
)
|
|
@@ -1195,10 +1191,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1195
1191
|
its static configuration.
|
|
1196
1192
|
|
|
1197
1193
|
Args:
|
|
1198
|
-
min_containers: Minimum number of containers to keep running.
|
|
1199
|
-
max_containers: Maximum concurrent containers.
|
|
1200
|
-
buffer_containers: Extra containers to keep warm beyond
|
|
1201
|
-
scaledown_window:
|
|
1194
|
+
min_containers: Minimum number of containers to keep running, or `None` to leave unchanged.
|
|
1195
|
+
max_containers: Maximum concurrent containers, or `None` to leave unchanged.
|
|
1196
|
+
buffer_containers: Extra containers to keep warm beyond demand, or `None` to leave unchanged.
|
|
1197
|
+
scaledown_window: Seconds idle containers wait before scaling down, or `None` to leave unchanged.
|
|
1202
1198
|
|
|
1203
1199
|
Examples:
|
|
1204
1200
|
```python notest
|
|
@@ -1212,7 +1208,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1212
1208
|
|
|
1213
1209
|
# Extend the scaledown window to increase the amount of time that idle containers stay alive
|
|
1214
1210
|
f.update_autoscaler(scaledown_window=300)
|
|
1215
|
-
|
|
1211
|
+
```
|
|
1216
1212
|
|
|
1217
1213
|
"""
|
|
1218
1214
|
# Assert .update_autoscaler() is not called on a method as opposed to the Object. Applicable for Cls only.
|
|
@@ -1912,9 +1908,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1912
1908
|
**kwargs: Keyword arguments forwarded to the remote function.
|
|
1913
1909
|
|
|
1914
1910
|
Returns:
|
|
1915
|
-
A [`modal.FunctionCall`](https://modal.com/docs/
|
|
1911
|
+
A [`modal.FunctionCall`](https://modal.com/docs/reference/modal.FunctionCall) object
|
|
1916
1912
|
that can later be polled or waited for using
|
|
1917
|
-
[`.get(timeout=...)`](https://modal.com/docs/
|
|
1913
|
+
[`.get(timeout=...)`](https://modal.com/docs/reference/modal.FunctionCall#get).
|
|
1918
1914
|
"""
|
|
1919
1915
|
self._check_no_web_url("spawn")
|
|
1920
1916
|
if self._is_generator:
|
|
@@ -2065,7 +2061,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2065
2061
|
"""Returns a structure representing the call graph from a given root
|
|
2066
2062
|
call ID, along with the status of execution for each node.
|
|
2067
2063
|
|
|
2068
|
-
See [`modal.call_graph`](https://modal.com/docs/
|
|
2064
|
+
See [`modal.call_graph`](https://modal.com/docs/reference/modal.call_graph) reference page
|
|
2069
2065
|
for documentation on the structure of the returned `InputInfo` items.
|
|
2070
2066
|
|
|
2071
2067
|
Returns:
|
|
@@ -2083,7 +2079,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2083
2079
|
terminate_containers: bool = False,
|
|
2084
2080
|
):
|
|
2085
2081
|
"""Cancels the function call, which will stop its execution and mark its inputs as
|
|
2086
|
-
[`TERMINATED`](https://modal.com/docs/
|
|
2082
|
+
[`TERMINATED`](https://modal.com/docs/reference/modal.call_graph#modalcall_graphinputstatus).
|
|
2087
2083
|
|
|
2088
2084
|
If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
|
|
2089
2085
|
causing any non-cancelled inputs on those containers to be rescheduled in new containers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
from collections.abc import Collection, Mapping
|
|
3
|
-
from typing import TYPE_CHECKING, Any,
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, overload
|
|
4
4
|
|
|
5
5
|
import grpclib.client
|
|
6
6
|
from google.protobuf.message import Message
|
|
@@ -178,9 +178,9 @@ class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
|
|
|
178
178
|
|
|
179
179
|
async def unary_stream(
|
|
180
180
|
self,
|
|
181
|
-
request
|
|
181
|
+
request,
|
|
182
182
|
metadata: Any | None = None,
|
|
183
|
-
)
|
|
183
|
+
):
|
|
184
184
|
from .client import _Client
|
|
185
185
|
|
|
186
186
|
if self.client._snapshotted:
|
|
@@ -9,7 +9,6 @@ import typing
|
|
|
9
9
|
import warnings
|
|
10
10
|
from collections.abc import Callable, Collection, Sequence
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
|
-
from functools import wraps
|
|
13
12
|
from inspect import isfunction
|
|
14
13
|
from pathlib import Path, PurePosixPath
|
|
15
14
|
from typing import (
|
|
@@ -425,20 +424,6 @@ async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.I
|
|
|
425
424
|
return result_response
|
|
426
425
|
|
|
427
426
|
|
|
428
|
-
def _requires_image_instance(method):
|
|
429
|
-
@wraps(method)
|
|
430
|
-
def wrapper(self, *args, **kwargs):
|
|
431
|
-
if not isinstance(self, _Image):
|
|
432
|
-
raise InvalidError(
|
|
433
|
-
"Image has not been constructed yet. "
|
|
434
|
-
f"Use one of the static factory methods prior to calling {method.__name__} "
|
|
435
|
-
"like `modal.Image.debian_slim`, `modal.Image.from_registry`, or `modal.Image.micromamba`"
|
|
436
|
-
)
|
|
437
|
-
return method(self, *args, **kwargs)
|
|
438
|
-
|
|
439
|
-
return wrapper
|
|
440
|
-
|
|
441
|
-
|
|
442
427
|
class _Image(_Object, type_prefix="im"):
|
|
443
428
|
"""Base class for container images to run functions in.
|
|
444
429
|
|
|
@@ -770,7 +755,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
770
755
|
context_mount_function=lambda: mount,
|
|
771
756
|
)
|
|
772
757
|
|
|
773
|
-
@_requires_image_instance
|
|
774
758
|
def add_local_file(self, local_path: str | Path, remote_path: str, *, copy: bool = False) -> "_Image":
|
|
775
759
|
"""Adds a local file to the image at `remote_path` within the container.
|
|
776
760
|
|
|
@@ -807,7 +791,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
807
791
|
mount = _Mount._from_local_file(local_path, remote_path)
|
|
808
792
|
return self._add_mount_layer_or_copy(mount, copy=copy)
|
|
809
793
|
|
|
810
|
-
@_requires_image_instance
|
|
811
794
|
def add_local_dir(
|
|
812
795
|
self,
|
|
813
796
|
local_path: str | Path,
|
|
@@ -886,7 +869,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
886
869
|
mount = _Mount._add_local_dir(Path(local_path), PurePosixPath(remote_path), ignore=_ignore_fn(ignore))
|
|
887
870
|
return self._add_mount_layer_or_copy(mount, copy=copy)
|
|
888
871
|
|
|
889
|
-
@_requires_image_instance
|
|
890
872
|
def add_local_python_source(
|
|
891
873
|
self, *modules: str, copy: bool = False, ignore: Sequence[str] | Callable[[Path], bool] = NON_PYTHON_FILES
|
|
892
874
|
) -> "_Image":
|
|
@@ -1030,7 +1012,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1030
1012
|
await resolver.load(self, load_context)
|
|
1031
1013
|
return self
|
|
1032
1014
|
|
|
1033
|
-
@_requires_image_instance
|
|
1034
1015
|
def pip_install(
|
|
1035
1016
|
self,
|
|
1036
1017
|
*packages: str | list[str],
|
|
@@ -1117,7 +1098,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1117
1098
|
secrets=secrets,
|
|
1118
1099
|
)
|
|
1119
1100
|
|
|
1120
|
-
@_requires_image_instance
|
|
1121
1101
|
def pip_install_private_repos(
|
|
1122
1102
|
self,
|
|
1123
1103
|
*repositories: str,
|
|
@@ -1238,7 +1218,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1238
1218
|
force_build=self.force_build or force_build,
|
|
1239
1219
|
)
|
|
1240
1220
|
|
|
1241
|
-
@_requires_image_instance
|
|
1242
1221
|
def pip_install_from_requirements(
|
|
1243
1222
|
self,
|
|
1244
1223
|
requirements_txt: str,
|
|
@@ -1300,7 +1279,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1300
1279
|
secrets=secrets,
|
|
1301
1280
|
)
|
|
1302
1281
|
|
|
1303
|
-
@_requires_image_instance
|
|
1304
1282
|
def pip_install_from_pyproject(
|
|
1305
1283
|
self,
|
|
1306
1284
|
pyproject_toml: str,
|
|
@@ -1384,7 +1362,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1384
1362
|
gpu_config=parse_gpu_config(gpu),
|
|
1385
1363
|
)
|
|
1386
1364
|
|
|
1387
|
-
@_requires_image_instance
|
|
1388
1365
|
def uv_pip_install(
|
|
1389
1366
|
self,
|
|
1390
1367
|
*packages: str | list[str],
|
|
@@ -1516,7 +1493,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1516
1493
|
secrets=secrets,
|
|
1517
1494
|
)
|
|
1518
1495
|
|
|
1519
|
-
@_requires_image_instance
|
|
1520
1496
|
def poetry_install_from_file(
|
|
1521
1497
|
self,
|
|
1522
1498
|
poetry_pyproject_toml: str,
|
|
@@ -1626,7 +1602,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1626
1602
|
gpu_config=parse_gpu_config(gpu),
|
|
1627
1603
|
)
|
|
1628
1604
|
|
|
1629
|
-
@_requires_image_instance
|
|
1630
1605
|
def uv_sync(
|
|
1631
1606
|
self,
|
|
1632
1607
|
uv_project_dir: str = "./",
|
|
@@ -1810,7 +1785,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1810
1785
|
gpu_config=parse_gpu_config(gpu),
|
|
1811
1786
|
)
|
|
1812
1787
|
|
|
1813
|
-
@_requires_image_instance
|
|
1814
1788
|
def dockerfile_commands(
|
|
1815
1789
|
self,
|
|
1816
1790
|
*dockerfile_commands: str | list[str],
|
|
@@ -1899,7 +1873,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1899
1873
|
build_args=build_args,
|
|
1900
1874
|
)
|
|
1901
1875
|
|
|
1902
|
-
@_requires_image_instance
|
|
1903
1876
|
def entrypoint(
|
|
1904
1877
|
self,
|
|
1905
1878
|
entrypoint_commands: list[str],
|
|
@@ -1920,7 +1893,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1920
1893
|
|
|
1921
1894
|
return self.dockerfile_commands(dockerfile_cmd)
|
|
1922
1895
|
|
|
1923
|
-
@_requires_image_instance
|
|
1924
1896
|
def shell(
|
|
1925
1897
|
self,
|
|
1926
1898
|
shell_commands: list[str],
|
|
@@ -1941,7 +1913,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1941
1913
|
|
|
1942
1914
|
return self.dockerfile_commands(dockerfile_cmd)
|
|
1943
1915
|
|
|
1944
|
-
@_requires_image_instance
|
|
1945
1916
|
def run_commands(
|
|
1946
1917
|
self,
|
|
1947
1918
|
*commands: str | list[str],
|
|
@@ -2026,7 +1997,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2026
1997
|
_namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
|
2027
1998
|
)
|
|
2028
1999
|
|
|
2029
|
-
@_requires_image_instance
|
|
2030
2000
|
def micromamba_install(
|
|
2031
2001
|
self,
|
|
2032
2002
|
*packages: str | list[str],
|
|
@@ -2558,7 +2528,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2558
2528
|
_namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
|
2559
2529
|
)
|
|
2560
2530
|
|
|
2561
|
-
@_requires_image_instance
|
|
2562
2531
|
def apt_install(
|
|
2563
2532
|
self,
|
|
2564
2533
|
*packages: str | list[str],
|
|
@@ -2610,7 +2579,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2610
2579
|
secrets=secrets,
|
|
2611
2580
|
)
|
|
2612
2581
|
|
|
2613
|
-
@_requires_image_instance
|
|
2614
2582
|
def run_function(
|
|
2615
2583
|
self,
|
|
2616
2584
|
raw_f: Callable[..., Any],
|
|
@@ -2729,7 +2697,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2729
2697
|
force_build=self.force_build or force_build,
|
|
2730
2698
|
)
|
|
2731
2699
|
|
|
2732
|
-
@_requires_image_instance
|
|
2733
2700
|
def env(self, vars: dict[str, str]) -> "_Image":
|
|
2734
2701
|
"""Sets the environment variables in an Image.
|
|
2735
2702
|
|
|
@@ -2760,7 +2727,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2760
2727
|
dockerfile_function=build_dockerfile,
|
|
2761
2728
|
)
|
|
2762
2729
|
|
|
2763
|
-
@_requires_image_instance
|
|
2764
2730
|
def workdir(self, path: str | PurePosixPath) -> "_Image":
|
|
2765
2731
|
"""Set the working directory for subsequent image build steps and function execution.
|
|
2766
2732
|
|
|
@@ -2790,7 +2756,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2790
2756
|
dockerfile_function=build_dockerfile,
|
|
2791
2757
|
)
|
|
2792
2758
|
|
|
2793
|
-
@_requires_image_instance
|
|
2794
2759
|
def cmd(self, cmd: list[str]) -> "_Image":
|
|
2795
2760
|
"""Set the default command (`CMD`) to run when a container is started.
|
|
2796
2761
|
|
|
@@ -2819,7 +2784,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2819
2784
|
|
|
2820
2785
|
return self.dockerfile_commands(dockerfile_cmd)
|
|
2821
2786
|
|
|
2822
|
-
@_requires_image_instance
|
|
2823
2787
|
def pipe(
|
|
2824
2788
|
self,
|
|
2825
2789
|
func: Callable[Concatenate["modal.Image", P], "modal.Image"],
|
|
@@ -2965,16 +2929,17 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2965
2929
|
"""
|
|
2966
2930
|
tag = _parse_named_image_ref(name)
|
|
2967
2931
|
|
|
2968
|
-
if self.
|
|
2932
|
+
if not self.is_hydrated:
|
|
2969
2933
|
raise InvalidError("Cannot publish an image that has not been created yet. Call `.build()` first.")
|
|
2970
2934
|
|
|
2971
|
-
_client = client or
|
|
2935
|
+
_client = client or self._client
|
|
2936
|
+
assert _client is not None
|
|
2972
2937
|
|
|
2973
2938
|
resolved_env = environment_name or config.get("environment") or ""
|
|
2974
2939
|
|
|
2975
2940
|
await _client.stub.ImagePublish(
|
|
2976
2941
|
api_pb2.ImagePublishRequest(
|
|
2977
|
-
image_id=self.
|
|
2942
|
+
image_id=self.object_id,
|
|
2978
2943
|
environment_name=resolved_env,
|
|
2979
2944
|
is_public=False,
|
|
2980
2945
|
tag=tag,
|
|
@@ -477,7 +477,7 @@ def _wsgi_app(
|
|
|
477
477
|
Web Server Gateway Interface (WSGI) is a standard for synchronous Python web apps.
|
|
478
478
|
It has been [succeeded by the ASGI interface](https://asgi.readthedocs.io/en/latest/introduction.html#wsgi-compatibility)
|
|
479
479
|
which is compatible with ASGI and supports additional functionality such as web sockets.
|
|
480
|
-
Modal supports ASGI via [`asgi_app`](https://modal.com/docs/
|
|
480
|
+
Modal supports ASGI via [`asgi_app`](https://modal.com/docs/reference/modal.asgi_app).
|
|
481
481
|
|
|
482
482
|
Examples:
|
|
483
483
|
```python
|
|
@@ -17,11 +17,6 @@ from modal.config import config, logger
|
|
|
17
17
|
|
|
18
18
|
CUDA_CHECKPOINT_PATH: str = config.get("cuda_checkpoint_path")
|
|
19
19
|
|
|
20
|
-
# Exit code used to signal to modal-runtime that a GPU memory snapshot restore failed
|
|
21
|
-
# and the task should be retried without a snapshot. This is a sentinel value that must
|
|
22
|
-
# stay in sync with `GPU_SNAPSHOT_RESTORE_FAILED_EXIT_CODE` in runner.rs.
|
|
23
|
-
SNAPSHOT_RESTORE_FAILED_EXIT_CODE: int = 222
|
|
24
|
-
|
|
25
20
|
# Maximum total duration for each individual `cuda-checkpoint` invocation.
|
|
26
21
|
CUDA_CHECKPOINT_TIMEOUT: float = 3 * 60.0
|
|
27
22
|
|
|
@@ -180,15 +180,10 @@ class _TaskLifecycleManager:
|
|
|
180
180
|
if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
|
|
181
181
|
logger.debug("GPU memory snapshot enabled. Attempting to restore GPU memory.")
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
except gpu_memory_snapshot.CudaCheckpointException as exc:
|
|
188
|
-
logger.warning(f"GPU memory snapshot restore failed; retrying task without snapshot. Error: {exc}")
|
|
189
|
-
sys.stderr.flush()
|
|
190
|
-
# Exit with a sentinel code that the runtime will use to retry the task without a snapshot.
|
|
191
|
-
os._exit(gpu_memory_snapshot.SNAPSHOT_RESTORE_FAILED_EXIT_CODE)
|
|
183
|
+
assert self._cuda_checkpoint_session, (
|
|
184
|
+
"CudaCheckpointSession not found when attempting to restore GPU memory"
|
|
185
|
+
)
|
|
186
|
+
self._cuda_checkpoint_session.restore()
|
|
192
187
|
|
|
193
188
|
self._client = await _Client.from_env()
|
|
194
189
|
|