modAL 1.5.3__tar.gz → 1.5.3.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.3 → modal-1.5.3.dev1}/PKG-INFO +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_clustered_functions.py +0 -11
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_clustered_functions.pyi +2 -6
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_container_entrypoint.py +4 -2
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_environments.py +81 -155
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_function_variants.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_functions.py +21 -90
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_grpc_client.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_object.py +4 -5
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_output/rich.py +3 -3
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_partial_function.py +3 -15
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_resources.py +4 -4
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/container_io_manager.py +30 -6
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/container_io_manager.pyi +16 -5
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/user_code_imports.py +1 -2
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_server.py +1 -21
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_traceback.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/async_utils.py +0 -5
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/blob_utils.py +0 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/bytes_io_segment_payload.py +1 -2
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/grpc_utils.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/pattern_utils.py +2 -2
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/task_command_router_client.py +3 -132
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/time_utils.py +14 -106
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_workspace.py +16 -78
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/app.py +3 -15
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/app.pyi +4 -8
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/billing.py +17 -121
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/cluster.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/config.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/dashboard.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/endpoint.py +3 -23
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/environment.py +45 -206
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/launch.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/network_file_system.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/profile.py +3 -3
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/shell.py +4 -23
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/client.py +8 -14
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/client.pyi +21 -15
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cloud_bucket_mount.py +5 -18
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cloud_bucket_mount.pyi +2 -2
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cls.py +2 -5
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cls.pyi +4 -4
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/config.py +1 -30
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/container_process.py +1 -13
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/container_process.pyi +0 -27
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/environments.py +0 -3
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/environments.pyi +66 -152
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/exception.py +1 -5
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/experimental/__init__.py +1 -11
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/file_pattern_matcher.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/functions.pyi +24 -85
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/mount.py +31 -89
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/mount.pyi +6 -18
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/partial_function.pyi +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/runner.py +9 -31
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/runner.pyi +1 -8
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/sandbox.py +61 -239
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/sandbox.pyi +46 -251
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/sandbox_fs.py +82 -33
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/sandbox_fs.pyi +6 -26
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/schedule.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/secret.py +2 -11
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/secret.pyi +0 -18
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/server.pyi +1 -24
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/serving.py +3 -3
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/snapshot.py +1 -17
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/snapshot.pyi +0 -22
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/types.py +1 -81
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/workspace.pyi +20 -95
- {modal-1.5.3 → modal-1.5.3.dev1}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal.egg-info/SOURCES.txt +0 -5
- modal-1.5.3.dev1/modal_docs/gen_reference_docs.py +361 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/mdmd/mdmd.py +46 -67
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/mdmd/types.py +0 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/api_grpc.py +0 -48
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/api_pb2.py +1135 -1276
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/api_pb2.pyi +51 -314
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/api_pb2_grpc.py +0 -99
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/api_pb2_grpc.pyi +0 -30
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/modal_api_grpc.py +0 -3
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/task_command_router_grpc.py +0 -16
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/task_command_router_pb2.py +88 -108
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/task_command_router_pb2.pyi +2 -47
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/task_command_router_pb2_grpc.py +0 -37
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/task_command_router_pb2_grpc.pyi +0 -20
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_version/__init__.py +1 -1
- {modal-1.5.3 → modal-1.5.3.dev1}/pyproject.toml +2 -49
- modal-1.5.3/modal/_logs_manager.py +0 -674
- modal-1.5.3/modal/_logs_manager.pyi +0 -1063
- modal-1.5.3/modal/_supports_logs.py +0 -32
- modal-1.5.3/modal/_utils/display_utils.py +0 -15
- modal-1.5.3/modal_docs/gen_reference_docs.py +0 -167
- modal-1.5.3/modal_docs/reference_intro.md +0 -115
- {modal-1.5.3 → modal-1.5.3.dev1}/LICENSE +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/README.md +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/__main__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_billing.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_image.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_ipython.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_load_context.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_location.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_logs.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_output/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_output/manager.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_output/pty.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_output/status.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_resolver.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_serialization.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_tunnel.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_tunnel.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_type_manager.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/logger.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_vendor/version.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/_watcher.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/billing.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/billing.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/README.md +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/builder/base-images.json +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/call_graph.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/_download.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/_help.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/_traceback.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/app.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/changelog.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/container.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/curl.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/dict.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/entry_point.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/image.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/import_refs.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/logo.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/queues.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/run.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/secret.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/selector.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/skills.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/token.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/utils.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/volume.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/cli/workspace.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/dict.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/dict.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/experimental/flash.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/experimental/ipython.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/file_io.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/file_io.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/functions.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/image.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/image.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/io_streams.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/io_streams.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/network_file_system.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/network_file_system.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/object.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/object.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/output.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/parallel_map.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/parallel_map.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/partial_function.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/proxy.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/proxy.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/py.typed +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/queue.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/queue.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/retries.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/running_app.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/scheduler_placement.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/server.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/serving.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/stream_type.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/token_flow.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/token_flow.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/volume.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/volume.pyi +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal/workspace.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/__init__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_proto/py.typed +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/modal_version/__main__.py +0 -0
- {modal-1.5.3 → modal-1.5.3.dev1}/setup.cfg +0 -0
|
@@ -15,7 +15,6 @@ class ClusterInfo:
|
|
|
15
15
|
cluster_id: str
|
|
16
16
|
container_ips: list[str]
|
|
17
17
|
container_ipv4_ips: list[str]
|
|
18
|
-
fabric_ids: list[str]
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
cluster_info: ClusterInfo | None = None
|
|
@@ -30,14 +29,6 @@ def get_cluster_info() -> ClusterInfo:
|
|
|
30
29
|
return cluster_info
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
def get_fabric_peers() -> list[int]:
|
|
34
|
-
info = get_cluster_info()
|
|
35
|
-
if info.rank >= len(info.fabric_ids) or not info.fabric_ids[info.rank]:
|
|
36
|
-
return [info.rank]
|
|
37
|
-
own_fabric_id = info.fabric_ids[info.rank]
|
|
38
|
-
return [rank for rank, fabric_id in enumerate(info.fabric_ids) if fabric_id == own_fabric_id]
|
|
39
|
-
|
|
40
|
-
|
|
41
32
|
async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
|
|
42
33
|
global cluster_info
|
|
43
34
|
|
|
@@ -79,7 +70,6 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
79
70
|
cluster_id=resp.cluster_id,
|
|
80
71
|
container_ips=resp.container_ips,
|
|
81
72
|
container_ipv4_ips=resp.container_ipv4_ips,
|
|
82
|
-
fabric_ids=resp.fabric_ids,
|
|
83
73
|
)
|
|
84
74
|
else:
|
|
85
75
|
cluster_info = ClusterInfo(
|
|
@@ -87,7 +77,6 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
87
77
|
cluster_id="", # No cluster ID for single-node # TODO(irfansharif): Is this right?
|
|
88
78
|
container_ips=[container_ip],
|
|
89
79
|
container_ipv4_ips=[], # No IPv4 IPs for single-node
|
|
90
|
-
fabric_ids=[],
|
|
91
80
|
)
|
|
92
81
|
|
|
93
82
|
|
|
@@ -3,17 +3,14 @@ import types
|
|
|
3
3
|
import typing_extensions
|
|
4
4
|
|
|
5
5
|
class ClusterInfo:
|
|
6
|
-
"""ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str]
|
|
6
|
+
"""ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str])"""
|
|
7
7
|
|
|
8
8
|
rank: int
|
|
9
9
|
cluster_id: str
|
|
10
10
|
container_ips: list[str]
|
|
11
11
|
container_ipv4_ips: list[str]
|
|
12
|
-
fabric_ids: list[str]
|
|
13
12
|
|
|
14
|
-
def __init__(
|
|
15
|
-
self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str]
|
|
16
|
-
) -> None:
|
|
13
|
+
def __init__(self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str]) -> None:
|
|
17
14
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
18
15
|
...
|
|
19
16
|
|
|
@@ -26,7 +23,6 @@ class ClusterInfo:
|
|
|
26
23
|
...
|
|
27
24
|
|
|
28
25
|
def get_cluster_info() -> ClusterInfo: ...
|
|
29
|
-
def get_fabric_peers() -> list[int]: ...
|
|
30
26
|
async def _initialize_clustered_function(client: modal.client._Client, task_id: str, world_size: int): ...
|
|
31
27
|
|
|
32
28
|
class __initialize_clustered_function_spec(typing_extensions.Protocol):
|
|
@@ -145,7 +145,7 @@ def call_function(
|
|
|
145
145
|
item_count += 1
|
|
146
146
|
|
|
147
147
|
await container_io_manager._send_outputs.aio(
|
|
148
|
-
io_context.output_items_generator_done(started_at, item_count)
|
|
148
|
+
started_at, io_context.output_items_generator_done(started_at, item_count)
|
|
149
149
|
)
|
|
150
150
|
else:
|
|
151
151
|
value = await io_context.call_function_async()
|
|
@@ -182,7 +182,9 @@ def call_function(
|
|
|
182
182
|
container_io_manager._queue_put(generator_queue, value)
|
|
183
183
|
item_count += 1
|
|
184
184
|
|
|
185
|
-
container_io_manager._send_outputs(
|
|
185
|
+
container_io_manager._send_outputs(
|
|
186
|
+
started_at, io_context.output_items_generator_done(started_at, item_count)
|
|
187
|
+
)
|
|
186
188
|
else:
|
|
187
189
|
values = io_context.call_function_sync()
|
|
188
190
|
container_io_manager.push_outputs(io_context, started_at, values)
|
|
@@ -16,13 +16,11 @@ from modal_proto import api_pb2
|
|
|
16
16
|
from ._load_context import LoadContext
|
|
17
17
|
from ._object import _Object
|
|
18
18
|
from ._resolver import Resolver
|
|
19
|
-
from ._utils.deprecation import deprecation_warning
|
|
20
19
|
from ._utils.name_utils import check_environment_name
|
|
21
|
-
from ._utils.time_utils import is_utc_month_aligned, parse_billing_cycle
|
|
22
20
|
from .client import _Client
|
|
23
21
|
from .config import config, logger
|
|
24
22
|
from .exception import InvalidError, WorkspaceManagementError
|
|
25
|
-
from .types import BillingReportItem
|
|
23
|
+
from .types import BillingReportItem
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
@dataclass(frozen=True)
|
|
@@ -105,38 +103,63 @@ class _EnvironmentManager:
|
|
|
105
103
|
await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
|
|
106
104
|
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
|
|
107
|
+
match role:
|
|
108
|
+
case "viewer":
|
|
109
|
+
return api_pb2.ENVIRONMENT_ROLE_VIEWER
|
|
110
|
+
case "contributor":
|
|
111
|
+
return api_pb2.ENVIRONMENT_ROLE_CONTRIBUTOR
|
|
112
|
+
case _:
|
|
113
|
+
raise InvalidError(f"Invalid Environment role: {role!r} (expected 'viewer' or 'contributor')")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _role_from_proto(proto_value: int) -> str:
|
|
117
|
+
match proto_value:
|
|
118
|
+
case api_pb2.ENVIRONMENT_ROLE_VIEWER:
|
|
119
|
+
return "viewer"
|
|
120
|
+
case api_pb2.ENVIRONMENT_ROLE_CONTRIBUTOR:
|
|
121
|
+
return "contributor"
|
|
122
|
+
case _:
|
|
123
|
+
raise ValueError(f"Unknown environment role: {proto_value}")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class _EnvironmentMembersManager:
|
|
127
|
+
"""mdmd:namespace
|
|
128
|
+
Namespace with methods for managing the membership of a restricted Environment.
|
|
129
|
+
|
|
130
|
+
See https://modal.com/docs/guide/rbac for more information on restricted Environments.
|
|
131
|
+
"""
|
|
110
132
|
|
|
111
133
|
def __init__(self, environment: "_Environment"):
|
|
112
134
|
"""mdmd:hidden"""
|
|
113
135
|
self._environment = environment
|
|
114
136
|
|
|
115
137
|
async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
|
|
116
|
-
"""
|
|
138
|
+
"""Return the members of a restricted Environment with their roles.
|
|
117
139
|
|
|
118
140
|
**Examples:**
|
|
119
141
|
|
|
120
142
|
```python notest
|
|
121
|
-
|
|
122
|
-
print(
|
|
143
|
+
members = modal.Environment.from_name("my-restricted-env").members.list()
|
|
144
|
+
print(members)
|
|
123
145
|
# {
|
|
124
|
-
# "users": {"alice": "contributor", "bob": "viewer"
|
|
125
|
-
# "service_users": {"alice-bot": "contributor"
|
|
146
|
+
# "users": {"alice": "contributor", "bob": "viewer"},
|
|
147
|
+
# "service_users": {"alice-bot": "contributor"},
|
|
126
148
|
# }
|
|
127
149
|
```
|
|
128
150
|
"""
|
|
129
151
|
await self._environment.hydrate()
|
|
130
|
-
req = api_pb2.
|
|
131
|
-
resp = await self._environment.client.stub.
|
|
152
|
+
req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
|
|
153
|
+
resp = await self._environment.client.stub.EnvironmentGetManaged(req)
|
|
132
154
|
|
|
133
155
|
users: dict[str, str] = {}
|
|
134
156
|
service_users: dict[str, str] = {}
|
|
135
157
|
for principal in resp.principal_roles:
|
|
158
|
+
role = _role_from_proto(principal.role)
|
|
136
159
|
if principal.user_id:
|
|
137
|
-
users[principal.user_name] =
|
|
160
|
+
users[principal.user_name] = role
|
|
138
161
|
elif principal.service_user_id:
|
|
139
|
-
service_users[principal.service_user_name] =
|
|
162
|
+
service_users[principal.service_user_name] = role
|
|
140
163
|
|
|
141
164
|
return {"users": users, "service_users": service_users}
|
|
142
165
|
|
|
@@ -146,17 +169,16 @@ class _EnvironmentRolesManager:
|
|
|
146
169
|
users: Mapping[str, str] | None = None,
|
|
147
170
|
service_users: Mapping[str, str] | None = None,
|
|
148
171
|
) -> None:
|
|
149
|
-
"""
|
|
172
|
+
"""Add or modify roles for members of a restricted Environment.
|
|
150
173
|
|
|
151
|
-
Each
|
|
152
|
-
|
|
153
|
-
role on restricted Environments.
|
|
174
|
+
Each user or service user will be added to the Environment if not currently a member;
|
|
175
|
+
if already a member, the user or service user's role will be updated.
|
|
154
176
|
|
|
155
177
|
**Examples:**
|
|
156
178
|
|
|
157
179
|
```python notest
|
|
158
180
|
env = modal.Environment.from_name("my-restricted-env")
|
|
159
|
-
env.
|
|
181
|
+
env.members.update(
|
|
160
182
|
users={"alice": "contributor", "bob": "viewer"},
|
|
161
183
|
service_users={"alice-bot": "contributor"},
|
|
162
184
|
)
|
|
@@ -166,13 +188,13 @@ class _EnvironmentRolesManager:
|
|
|
166
188
|
users = users or {}
|
|
167
189
|
service_users = service_users or {}
|
|
168
190
|
|
|
169
|
-
req = api_pb2.
|
|
170
|
-
resp = await self._environment.client.stub.
|
|
191
|
+
req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
|
|
192
|
+
resp = await self._environment.client.stub.EnvironmentGetManaged(req)
|
|
171
193
|
|
|
172
|
-
#
|
|
194
|
+
# Both current members and additional eligible workspace principals can be assigned a role
|
|
173
195
|
user_name_to_id: dict[str, str] = {}
|
|
174
196
|
service_user_name_to_id: dict[str, str] = {}
|
|
175
|
-
for principal in resp.principal_roles:
|
|
197
|
+
for principal in [*resp.principal_roles, *resp.additional_roles]:
|
|
176
198
|
if principal.user_id:
|
|
177
199
|
user_name_to_id[principal.user_name] = principal.user_id
|
|
178
200
|
elif principal.service_user_id:
|
|
@@ -185,7 +207,7 @@ class _EnvironmentRolesManager:
|
|
|
185
207
|
requests[f"User {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
|
|
186
208
|
environment_id=self._environment.object_id,
|
|
187
209
|
user_id=user_name_to_id[name],
|
|
188
|
-
|
|
210
|
+
role=_role_to_proto(role),
|
|
189
211
|
)
|
|
190
212
|
for name, role in service_users.items():
|
|
191
213
|
if name not in service_user_name_to_id:
|
|
@@ -193,70 +215,35 @@ class _EnvironmentRolesManager:
|
|
|
193
215
|
requests[f"Service user {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
|
|
194
216
|
environment_id=self._environment.object_id,
|
|
195
217
|
service_user_id=service_user_name_to_id[name],
|
|
196
|
-
|
|
218
|
+
role=_role_to_proto(role),
|
|
197
219
|
)
|
|
198
220
|
|
|
199
221
|
await self._dispatch_role_updates(requests)
|
|
200
222
|
|
|
201
|
-
async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
|
|
202
|
-
"""Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
|
|
203
|
-
results = await asyncio.gather(
|
|
204
|
-
*(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
|
|
205
|
-
return_exceptions=True,
|
|
206
|
-
)
|
|
207
|
-
errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
|
|
208
|
-
if errors:
|
|
209
|
-
n = len(errors)
|
|
210
|
-
header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment roles:"
|
|
211
|
-
details = "\n".join(f" - {label}: {e}" for label, e in errors)
|
|
212
|
-
raise WorkspaceManagementError(f"{header}\n{details}")
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
# Deprecated alias for `_EnvironmentRolesManager`; each method warns and delegates.
|
|
216
|
-
class _EnvironmentMembersManager:
|
|
217
|
-
"""mdmd:hidden"""
|
|
218
|
-
|
|
219
|
-
def __init__(self, environment: "_Environment"):
|
|
220
|
-
"""mdmd:hidden"""
|
|
221
|
-
self._environment = environment
|
|
222
|
-
|
|
223
|
-
async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
|
|
224
|
-
deprecation_warning(
|
|
225
|
-
(2026, 7, 23),
|
|
226
|
-
"`Environment.members.list()` is deprecated; use `Environment.roles.list()` instead.",
|
|
227
|
-
)
|
|
228
|
-
return await _EnvironmentRolesManager(self._environment).list()
|
|
229
|
-
|
|
230
|
-
async def update(
|
|
231
|
-
self,
|
|
232
|
-
*,
|
|
233
|
-
users: Mapping[str, str] | None = None,
|
|
234
|
-
service_users: Mapping[str, str] | None = None,
|
|
235
|
-
) -> None:
|
|
236
|
-
deprecation_warning(
|
|
237
|
-
(2026, 7, 23),
|
|
238
|
-
"`Environment.members.update()` is deprecated; use `Environment.roles.update()` instead.",
|
|
239
|
-
)
|
|
240
|
-
await _EnvironmentRolesManager(self._environment).update(users=users, service_users=service_users)
|
|
241
|
-
|
|
242
223
|
async def remove(
|
|
243
224
|
self,
|
|
244
225
|
*,
|
|
245
226
|
users: Iterable[str] | None = None,
|
|
246
227
|
service_users: Iterable[str] | None = None,
|
|
247
228
|
) -> None:
|
|
248
|
-
"""Remove
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
229
|
+
"""Remove members from a restricted Environment.
|
|
230
|
+
|
|
231
|
+
**Examples:**
|
|
232
|
+
|
|
233
|
+
```python notest
|
|
234
|
+
env = modal.Environment.from_name("my-restricted-env")
|
|
235
|
+
env.members.remove(
|
|
236
|
+
users=["alice"],
|
|
237
|
+
service_users=["alice-bot"],
|
|
253
238
|
)
|
|
239
|
+
```
|
|
240
|
+
"""
|
|
254
241
|
await self._environment.hydrate()
|
|
255
242
|
users = users or []
|
|
256
243
|
service_users = service_users or []
|
|
257
244
|
|
|
258
|
-
req = api_pb2.
|
|
259
|
-
resp = await self._environment.client.stub.
|
|
245
|
+
req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
|
|
246
|
+
resp = await self._environment.client.stub.EnvironmentGetManaged(req)
|
|
260
247
|
|
|
261
248
|
user_name_to_id: dict[str, str] = {}
|
|
262
249
|
service_user_name_to_id: dict[str, str] = {}
|
|
@@ -269,7 +256,7 @@ class _EnvironmentMembersManager:
|
|
|
269
256
|
requests: dict[str, api_pb2.EnvironmentRoleSetRequest] = {}
|
|
270
257
|
for name in users:
|
|
271
258
|
if name not in user_name_to_id:
|
|
272
|
-
raise InvalidError(f"User {name!r} not
|
|
259
|
+
raise InvalidError(f"User {name!r} is not a member of this Environment")
|
|
273
260
|
requests[f"User {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
|
|
274
261
|
environment_id=self._environment.object_id,
|
|
275
262
|
user_id=user_name_to_id[name],
|
|
@@ -277,14 +264,27 @@ class _EnvironmentMembersManager:
|
|
|
277
264
|
)
|
|
278
265
|
for name in service_users:
|
|
279
266
|
if name not in service_user_name_to_id:
|
|
280
|
-
raise InvalidError(f"Service user {name!r} not
|
|
267
|
+
raise InvalidError(f"Service user {name!r} is not a member of this Environment")
|
|
281
268
|
requests[f"Service user {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
|
|
282
269
|
environment_id=self._environment.object_id,
|
|
283
270
|
service_user_id=service_user_name_to_id[name],
|
|
284
271
|
role=api_pb2.ENVIRONMENT_ROLE_UNSPECIFIED,
|
|
285
272
|
)
|
|
286
273
|
|
|
287
|
-
await
|
|
274
|
+
await self._dispatch_role_updates(requests)
|
|
275
|
+
|
|
276
|
+
async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
|
|
277
|
+
"""Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
|
|
278
|
+
results = await asyncio.gather(
|
|
279
|
+
*(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
|
|
280
|
+
return_exceptions=True,
|
|
281
|
+
)
|
|
282
|
+
errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
|
|
283
|
+
if errors:
|
|
284
|
+
n = len(errors)
|
|
285
|
+
header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment members:"
|
|
286
|
+
details = "\n".join(f" - {label}: {e}" for label, e in errors)
|
|
287
|
+
raise WorkspaceManagementError(f"{header}\n{details}")
|
|
288
288
|
|
|
289
289
|
|
|
290
290
|
class _Environment(_Object, type_prefix="en"):
|
|
@@ -307,18 +307,8 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
307
307
|
def objects(cls) -> _EnvironmentManager:
|
|
308
308
|
return _EnvironmentManager()
|
|
309
309
|
|
|
310
|
-
@property
|
|
311
|
-
def roles(self) -> "_EnvironmentRolesManager":
|
|
312
|
-
"""Namespace with methods for managing the Environment Roles of users and service users.
|
|
313
|
-
|
|
314
|
-
See https://modal.com/docs/guide/rbac for more information on Environment Roles.
|
|
315
|
-
"""
|
|
316
|
-
return _EnvironmentRolesManager(self)
|
|
317
|
-
|
|
318
310
|
@property
|
|
319
311
|
def members(self) -> "_EnvironmentMembersManager":
|
|
320
|
-
"""mdmd:hidden"""
|
|
321
|
-
# Deprecated alias for `Environment.roles`.
|
|
322
312
|
return _EnvironmentMembersManager(self)
|
|
323
313
|
|
|
324
314
|
# TODO(michael) Keeping this private for now until we decide what else should be in it
|
|
@@ -403,12 +393,13 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
403
393
|
|
|
404
394
|
@property
|
|
405
395
|
def billing(self) -> "_EnvironmentBillingManager":
|
|
406
|
-
"""Namespace for Environment billing APIs."""
|
|
407
396
|
return _EnvironmentBillingManager(self)
|
|
408
397
|
|
|
409
398
|
|
|
410
399
|
class _EnvironmentBillingManager:
|
|
411
|
-
"""mdmd:namespace
|
|
400
|
+
"""mdmd:namespace
|
|
401
|
+
Namespace for Environment billing APIs
|
|
402
|
+
"""
|
|
412
403
|
|
|
413
404
|
def __init__(self, environment: _Environment):
|
|
414
405
|
"""mdmd:ignore"""
|
|
@@ -445,7 +436,7 @@ class _EnvironmentBillingManager:
|
|
|
445
436
|
- [`modal environment billing report`](https://modal.com/docs/cli/latest/environment#modal-environment-billing-report):
|
|
446
437
|
An environment report CLI that has convenience features around relative time range queries
|
|
447
438
|
and JSON/CSV output.
|
|
448
|
-
- [`Workspace.billing.report()`](https://modal.com/docs/sdk/py/latest/Workspace#billingreport):
|
|
439
|
+
- [`Workspace.billing.report()`](https://modal.com/docs/sdk/py/latest/modal.Workspace#billingreport):
|
|
449
440
|
An analogous report API for the entire Workspace.
|
|
450
441
|
|
|
451
442
|
"""
|
|
@@ -481,71 +472,6 @@ class _EnvironmentBillingManager:
|
|
|
481
472
|
async for pb_item in self._environment.client.stub.WorkspaceBillingReport.unary_stream(request)
|
|
482
473
|
]
|
|
483
474
|
|
|
484
|
-
async def summary(
|
|
485
|
-
self,
|
|
486
|
-
cycle: str | datetime | None = None, # Start of the summary, inclusive
|
|
487
|
-
) -> EnvironmentBillingSummary:
|
|
488
|
-
"""Return a summary of environment cost over a single billing cycle determined by `cycle`.
|
|
489
|
-
|
|
490
|
-
Unlike the analogous `Workspace.billing.summary()`, this API only emits metered cost
|
|
491
|
-
information. This is because billing adjustments due to credits, free storage, etc. are
|
|
492
|
-
applied at the Workspace level, and thus cannot be attributed to individual Environments.
|
|
493
|
-
|
|
494
|
-
Args:
|
|
495
|
-
cycle: Start of the summary, inclusive. Must be the first of a month, and must be in UTC
|
|
496
|
-
or timezone-naive (interpreted as UTC). If provided as a string, it must either be
|
|
497
|
-
formatted as an ISO 8601 month (YYYY-MM), or must be one of the convenience spellings
|
|
498
|
-
"this month" or "last month". If not provided, `cycle` defaults to the first of the
|
|
499
|
-
current month (in which case a summary is generated for the current billing cycle).
|
|
500
|
-
|
|
501
|
-
Returns:
|
|
502
|
-
A single `EnvironmentBillingSummary` dataclass containing the following fields:
|
|
503
|
-
- `metered_cost` representing cost before any adjustments, and
|
|
504
|
-
- `metered_cost_breakdown` containing a breakdown of that cost by the Modal resources
|
|
505
|
-
that generated it. The exact keys of this are subject to change as Modal's billing
|
|
506
|
-
model evolves.
|
|
507
|
-
|
|
508
|
-
All values are reported as `decimal.Decimal`s.
|
|
509
|
-
|
|
510
|
-
See also:
|
|
511
|
-
- [`modal environment billing summary`](https://modal.com/docs/cli/latest/billing#modal-environment-billing-summary):
|
|
512
|
-
An environment summary CLI that has convenience features around relative time range queries.
|
|
513
|
-
- [`Environment.billing.report()`](https://modal.com/docs/sdk/py/latest/Environment#billingreport):
|
|
514
|
-
An analogous report API that is scoped to a specific Environment.
|
|
515
|
-
"""
|
|
516
|
-
|
|
517
|
-
if cycle is None:
|
|
518
|
-
cycle = datetime.now(timezone.utc).replace(
|
|
519
|
-
day=1,
|
|
520
|
-
hour=0,
|
|
521
|
-
minute=0,
|
|
522
|
-
second=0,
|
|
523
|
-
microsecond=0,
|
|
524
|
-
)
|
|
525
|
-
elif isinstance(cycle, str):
|
|
526
|
-
cycle = parse_billing_cycle(cycle)
|
|
527
|
-
|
|
528
|
-
if cycle.tzinfo is None:
|
|
529
|
-
cycle = cycle.replace(tzinfo=timezone.utc)
|
|
530
|
-
elif cycle.tzinfo != timezone.utc:
|
|
531
|
-
raise InvalidError("Timezone-aware 'cycle' parameter must be in UTC.")
|
|
532
|
-
|
|
533
|
-
if not is_utc_month_aligned(cycle):
|
|
534
|
-
raise InvalidError("Provided 'cycle' parameter must be the first of a month.")
|
|
535
|
-
|
|
536
|
-
if cycle > datetime.now(timezone.utc):
|
|
537
|
-
raise InvalidError("Provided 'cycle' parameter cannot be in the future.")
|
|
538
|
-
|
|
539
|
-
if not self._environment.is_hydrated:
|
|
540
|
-
await self._environment.hydrate()
|
|
541
|
-
|
|
542
|
-
request = api_pb2.EnvironmentBillingSummaryRequest(environment_id=self._environment.object_id)
|
|
543
|
-
request.start_timestamp.FromDatetime(cycle)
|
|
544
|
-
|
|
545
|
-
return EnvironmentBillingSummary._from_proto(
|
|
546
|
-
await self._environment.client.stub.EnvironmentBillingSummary(request)
|
|
547
|
-
)
|
|
548
|
-
|
|
549
475
|
|
|
550
476
|
ENVIRONMENT_CACHE: dict[str, _Environment] = {}
|
|
551
477
|
|
|
@@ -621,4 +547,4 @@ def ensure_env(environment_name: str | None = None) -> str:
|
|
|
621
547
|
if environment_name is not None:
|
|
622
548
|
config.override_locally("environment", environment_name)
|
|
623
549
|
|
|
624
|
-
return config.get("environment")
|
|
550
|
+
return config.get("environment")
|
|
@@ -171,7 +171,7 @@ class _FunctionOptions:
|
|
|
171
171
|
replace_secret_ids=bool(self.secrets),
|
|
172
172
|
replace_volume_mounts=len(volume_mounts) > 0,
|
|
173
173
|
volume_mounts=volume_mounts,
|
|
174
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts)
|
|
174
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts),
|
|
175
175
|
replace_cloud_bucket_mounts=bool(self.cloud_bucket_mounts),
|
|
176
176
|
resources=self.resources,
|
|
177
177
|
retry_policy=self.retry_policy,
|