modAL 1.5.4__tar.gz → 1.5.4.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.4 → modal-1.5.4.dev1}/PKG-INFO +1 -1
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_clustered_functions.py +5 -8
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_functions.py +14 -71
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_image.py +11 -71
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_logs.py +7 -9
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_logs_manager.py +108 -340
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_logs_manager.pyi +173 -373
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_output/pty.py +9 -12
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_output/rich.py +2 -12
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/asgi.py +1 -1
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/container_io_manager.py +19 -107
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/container_io_manager.pyi +9 -21
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/execution_context.py +0 -6
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/execution_context.pyi +0 -1
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_server.py +4 -14
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_supports_logs.py +2 -20
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_tunnel.py +2 -2
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/async_utils.py +2 -19
- modal-1.5.4.dev1/modal/_utils/auth_token_manager.py +110 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/blob_utils.py +11 -17
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/function_utils.py +4 -32
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/grpc_utils.py +2 -4
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/task_command_router_client.py +13 -59
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/time_utils.py +1 -4
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_watcher.py +2 -4
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_workspace.py +1 -83
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/app.py +6 -37
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/app.pyi +6 -59
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/_download.py +6 -3
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/app.py +9 -16
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/billing.py +0 -30
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/container.py +99 -66
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/entry_point.py +0 -3
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/image.py +0 -37
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/run.py +8 -36
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/shell.py +7 -6
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/utils.py +6 -8
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/volume.py +17 -14
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/workspace.py +8 -34
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/client.py +0 -1
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/client.pyi +10 -4
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cls.py +1 -7
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cls.pyi +3 -16
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/config.py +0 -1
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/dict.py +15 -19
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/exception.py +0 -8
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/experimental/__init__.py +2 -3
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/experimental/flash.py +9 -15
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/experimental/flash.pyi +2 -6
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/functions.pyi +10 -41
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/image.pyi +0 -30
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/mount.py +2 -2
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/mount.pyi +2 -2
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/network_file_system.py +11 -15
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/parallel_map.py +12 -14
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/parallel_map.pyi +2 -3
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/queue.py +15 -19
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/runner.py +6 -13
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/runner.pyi +3 -9
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/sandbox.py +142 -267
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/sandbox.pyi +37 -174
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/server.pyi +6 -25
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/token_flow.py +5 -7
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/types.py +3 -49
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/volume.py +39 -61
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/volume.pyi +15 -21
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/workspace.pyi +0 -20
- {modal-1.5.4 → modal-1.5.4.dev1}/modal.egg-info/PKG-INFO +1 -1
- modal-1.5.4.dev1/modal_proto/__init__.py +4 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/api_grpc.py +0 -96
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/api_pb2.py +1282 -1438
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/api_pb2.pyi +26 -381
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/api_pb2_grpc.py +2 -201
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/api_pb2_grpc.pyi +6 -73
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/modal_api_grpc.py +0 -6
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/task_command_router_pb2.py +92 -92
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/task_command_router_pb2.pyi +2 -10
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_version/__init__.py +1 -1
- {modal-1.5.4 → modal-1.5.4.dev1}/pyproject.toml +35 -10
- modal-1.5.4/modal/_utils/auth_token_manager.py +0 -181
- modal-1.5.4/modal_proto/__init__.py +0 -11
- {modal-1.5.4 → modal-1.5.4.dev1}/LICENSE +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/README.md +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/__main__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_billing.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_environments.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_function_variants.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_grpc_client.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_ipython.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_load_context.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_location.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_object.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_output/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_output/manager.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_output/status.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_partial_function.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_resolver.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_resources.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_serialization.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_traceback.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_tunnel.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_type_manager.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/logger.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/_vendor/version.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/billing.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/billing.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/README.md +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/builder/base-images.json +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/call_graph.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/_help.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/_traceback.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/changelog.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/cluster.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/config.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/curl.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/dashboard.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/dict.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/endpoint.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/environment.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/import_refs.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/launch.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/logo.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/profile.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/queues.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/secret.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/selector.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/skills.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cli/token.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/container_process.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/container_process.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/dict.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/environments.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/environments.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/experimental/ipython.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/file_io.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/file_io.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/functions.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/image.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/io_streams.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/io_streams.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/network_file_system.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/object.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/object.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/output.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/partial_function.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/partial_function.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/proxy.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/proxy.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/py.typed +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/queue.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/retries.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/running_app.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/sandbox_fs.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/schedule.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/scheduler_placement.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/secret.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/secret.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/server.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/serving.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/serving.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/snapshot.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/snapshot.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/stream_type.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/token_flow.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal/workspace.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/py.typed +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/modal_version/__main__.py +0 -0
- {modal-1.5.4 → modal-1.5.4.dev1}/setup.cfg +0 -0
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import os
|
|
3
3
|
import socket
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import cast
|
|
6
5
|
|
|
7
6
|
from modal._utils.async_utils import synchronize_api
|
|
8
7
|
from modal.client import _Client
|
|
@@ -42,11 +41,9 @@ def get_fabric_peers() -> list[int]:
|
|
|
42
41
|
async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
|
|
43
42
|
global cluster_info
|
|
44
43
|
|
|
45
|
-
def get_i6pn()
|
|
44
|
+
def get_i6pn():
|
|
46
45
|
"""Returns the ipv6 address assigned to this container."""
|
|
47
|
-
|
|
48
|
-
# but getaddrinfo is typed for every address family, some of which lead with an int
|
|
49
|
-
return cast(str, socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0])
|
|
46
|
+
return socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0]
|
|
50
47
|
|
|
51
48
|
hostname = socket.gethostname()
|
|
52
49
|
container_ip = get_i6pn()
|
|
@@ -80,9 +77,9 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
80
77
|
cluster_info = ClusterInfo(
|
|
81
78
|
rank=resp.cluster_rank,
|
|
82
79
|
cluster_id=resp.cluster_id,
|
|
83
|
-
container_ips=
|
|
84
|
-
container_ipv4_ips=
|
|
85
|
-
fabric_ids=
|
|
80
|
+
container_ips=resp.container_ips,
|
|
81
|
+
container_ipv4_ips=resp.container_ipv4_ips,
|
|
82
|
+
fabric_ids=resp.fabric_ids,
|
|
86
83
|
)
|
|
87
84
|
else:
|
|
88
85
|
cluster_info = ClusterInfo(
|
|
@@ -45,7 +45,7 @@ from ._utils.async_utils import (
|
|
|
45
45
|
synchronizer,
|
|
46
46
|
warn_if_generator_is_not_consumed,
|
|
47
47
|
)
|
|
48
|
-
from ._utils.blob_utils import
|
|
48
|
+
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
49
49
|
from ._utils.function_utils import (
|
|
50
50
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
51
51
|
OUTPUTS_TIMEOUT,
|
|
@@ -56,9 +56,7 @@ from ._utils.function_utils import (
|
|
|
56
56
|
_stream_function_call_data,
|
|
57
57
|
get_function_type,
|
|
58
58
|
is_async,
|
|
59
|
-
normalize_fractional_target_concurrency,
|
|
60
59
|
parse_gpu_config,
|
|
61
|
-
validate_target_concurrency,
|
|
62
60
|
)
|
|
63
61
|
from ._utils.grpc_utils import Retry, RetryWarningMessage
|
|
64
62
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
|
|
@@ -95,7 +93,7 @@ from .proxy import _Proxy
|
|
|
95
93
|
from .retries import Retries, RetryManager
|
|
96
94
|
from .schedule import Schedule
|
|
97
95
|
from .secret import _Secret
|
|
98
|
-
from .types import
|
|
96
|
+
from .types import FunctionStats
|
|
99
97
|
from .volume import _Volume, _volume_to_mount_proto
|
|
100
98
|
|
|
101
99
|
if TYPE_CHECKING:
|
|
@@ -426,7 +424,6 @@ class _InputPlaneInvocation:
|
|
|
426
424
|
client: _Client,
|
|
427
425
|
input_plane_url: str,
|
|
428
426
|
input_plane_region: str,
|
|
429
|
-
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
|
|
430
427
|
) -> "_InputPlaneInvocation":
|
|
431
428
|
stub = await client.get_stub(input_plane_url)
|
|
432
429
|
|
|
@@ -438,7 +435,6 @@ class _InputPlaneInvocation:
|
|
|
438
435
|
kwargs,
|
|
439
436
|
control_plane_stub,
|
|
440
437
|
function=function,
|
|
441
|
-
function_call_invocation_type=function_call_invocation_type,
|
|
442
438
|
)
|
|
443
439
|
|
|
444
440
|
request = api_pb2.AttemptStartRequest(
|
|
@@ -640,12 +636,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
640
636
|
await self.hydrate()
|
|
641
637
|
if not self._app_id:
|
|
642
638
|
raise ExecutionError("app_id should have been set during function hydration")
|
|
643
|
-
return _LogQueryData(
|
|
644
|
-
self.client,
|
|
645
|
-
self._app_id,
|
|
646
|
-
LogsFilters(function_id=self.object_id),
|
|
647
|
-
source_object_id=self.object_id,
|
|
648
|
-
)
|
|
639
|
+
return _LogQueryData(self.client, self._app_id, LogsFilters(function_id=self.object_id))
|
|
649
640
|
|
|
650
641
|
@property
|
|
651
642
|
def logs(self) -> _FunctionLogsManager:
|
|
@@ -688,7 +679,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
688
679
|
scaleup_window: int | None = None,
|
|
689
680
|
scaledown_window: int | None = None,
|
|
690
681
|
max_concurrent_inputs: int | None = None,
|
|
691
|
-
target_concurrent_inputs:
|
|
682
|
+
target_concurrent_inputs: int | None = None,
|
|
692
683
|
batch_max_size: int | None = None,
|
|
693
684
|
batch_wait_ms: int | None = None,
|
|
694
685
|
cloud: str | None = None,
|
|
@@ -796,17 +787,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
796
787
|
scaledown_window=scaledown_window,
|
|
797
788
|
)
|
|
798
789
|
|
|
799
|
-
target_concurrent_inputs_int = 0
|
|
800
|
-
if target_concurrent_inputs is not None:
|
|
801
|
-
if is_server:
|
|
802
|
-
validate_target_concurrency(target_concurrent_inputs, "target_inputs", allow_fractional=True)
|
|
803
|
-
autoscaler_settings.target_concurrency_float = normalize_fractional_target_concurrency(
|
|
804
|
-
target_concurrent_inputs
|
|
805
|
-
)
|
|
806
|
-
else:
|
|
807
|
-
validate_target_concurrency(target_concurrent_inputs, "target_inputs", allow_fractional=False)
|
|
808
|
-
target_concurrent_inputs_int = int(target_concurrent_inputs)
|
|
809
|
-
|
|
810
790
|
# For clustered functions, container settings must be multiples of cluster_size
|
|
811
791
|
if cluster_size is not None and cluster_size > 1:
|
|
812
792
|
for field in ["min_containers", "max_containers", "buffer_containers"]:
|
|
@@ -1032,7 +1012,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1032
1012
|
app_name=app_name,
|
|
1033
1013
|
is_builder_function=is_builder_function,
|
|
1034
1014
|
max_concurrent_inputs=max_concurrent_inputs or 0,
|
|
1035
|
-
target_concurrent_inputs=
|
|
1015
|
+
target_concurrent_inputs=target_concurrent_inputs or 0,
|
|
1036
1016
|
batch_max_size=batch_max_size or 0,
|
|
1037
1017
|
batch_linger_ms=batch_wait_ms or 0,
|
|
1038
1018
|
worker_id=config.get("worker_id"),
|
|
@@ -1192,25 +1172,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1192
1172
|
return obj
|
|
1193
1173
|
|
|
1194
1174
|
async def _update_autoscaler(
|
|
1195
|
-
self,
|
|
1196
|
-
*,
|
|
1197
|
-
min_containers: int | None = None,
|
|
1198
|
-
max_containers: int | None = None,
|
|
1199
|
-
buffer_containers: int | None = None,
|
|
1200
|
-
scaledown_window: int | None = None,
|
|
1201
|
-
) -> FunctionAutoscalerSettings:
|
|
1202
|
-
settings = api_pb2.AutoscalerSettings(
|
|
1203
|
-
min_containers=min_containers,
|
|
1204
|
-
max_containers=max_containers,
|
|
1205
|
-
buffer_containers=buffer_containers,
|
|
1206
|
-
scaledown_window=scaledown_window,
|
|
1207
|
-
)
|
|
1208
|
-
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1209
|
-
response = await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1210
|
-
|
|
1211
|
-
return FunctionAutoscalerSettings._from_proto(response.current_settings)
|
|
1212
|
-
|
|
1213
|
-
async def _update_autoscaler_server(
|
|
1214
1175
|
self,
|
|
1215
1176
|
*,
|
|
1216
1177
|
min_containers: int | None = None,
|
|
@@ -1218,22 +1179,21 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1218
1179
|
buffer_containers: int | None = None,
|
|
1219
1180
|
scaleup_window: int | None = None,
|
|
1220
1181
|
scaledown_window: int | None = None,
|
|
1221
|
-
target_concurrency:
|
|
1222
|
-
) ->
|
|
1182
|
+
target_concurrency: int | None = None,
|
|
1183
|
+
) -> None:
|
|
1223
1184
|
settings = api_pb2.AutoscalerSettings(
|
|
1224
1185
|
min_containers=min_containers,
|
|
1225
1186
|
max_containers=max_containers,
|
|
1226
1187
|
buffer_containers=buffer_containers,
|
|
1227
1188
|
scaleup_window=scaleup_window,
|
|
1228
1189
|
scaledown_window=scaledown_window,
|
|
1190
|
+
target_concurrency=target_concurrency,
|
|
1229
1191
|
)
|
|
1230
|
-
if target_concurrency is not None:
|
|
1231
|
-
validate_target_concurrency(target_concurrency, "target_concurrency", allow_fractional=True)
|
|
1232
|
-
settings.target_concurrency_float = normalize_fractional_target_concurrency(target_concurrency)
|
|
1233
1192
|
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1234
|
-
|
|
1193
|
+
await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1235
1194
|
|
|
1236
|
-
return
|
|
1195
|
+
# One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
|
|
1196
|
+
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
1237
1197
|
|
|
1238
1198
|
@live_method
|
|
1239
1199
|
async def update_autoscaler(
|
|
@@ -1243,7 +1203,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1243
1203
|
max_containers: int | None = None,
|
|
1244
1204
|
buffer_containers: int | None = None,
|
|
1245
1205
|
scaledown_window: int | None = None,
|
|
1246
|
-
) ->
|
|
1206
|
+
) -> None:
|
|
1247
1207
|
"""Override the current autoscaler behavior for this Function.
|
|
1248
1208
|
|
|
1249
1209
|
Unspecified parameters will retain their current value, i.e. either the static value
|
|
@@ -1258,10 +1218,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1258
1218
|
buffer_containers: Extra containers to keep warm beyond current demand.
|
|
1259
1219
|
scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
|
|
1260
1220
|
|
|
1261
|
-
Returns:
|
|
1262
|
-
A `FunctionAutoscalerSettings` dataclass which contains the current autoscaler settings
|
|
1263
|
-
of this Function after the call.
|
|
1264
|
-
|
|
1265
1221
|
Examples:
|
|
1266
1222
|
```python notest
|
|
1267
1223
|
f = modal.Function.from_name("my-app", "function")
|
|
@@ -1281,7 +1237,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1281
1237
|
if self._is_method:
|
|
1282
1238
|
raise InvalidError("Cannot call .update_autoscaler() on a method. Call it on the class instance instead.")
|
|
1283
1239
|
|
|
1284
|
-
|
|
1240
|
+
await self._update_autoscaler(
|
|
1285
1241
|
min_containers=min_containers,
|
|
1286
1242
|
max_containers=max_containers,
|
|
1287
1243
|
buffer_containers=buffer_containers,
|
|
@@ -1471,11 +1427,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1471
1427
|
self._max_object_size_bytes = (
|
|
1472
1428
|
metadata.max_object_size_bytes if metadata.HasField("max_object_size_bytes") else MAX_OBJECT_SIZE_BYTES
|
|
1473
1429
|
)
|
|
1474
|
-
self._max_async_object_size_bytes = (
|
|
1475
|
-
metadata.max_async_object_size_bytes
|
|
1476
|
-
if metadata.HasField("max_async_object_size_bytes")
|
|
1477
|
-
else MAX_ASYNC_OBJECT_SIZE_BYTES
|
|
1478
|
-
)
|
|
1479
1430
|
self._experimental_flash_urls = metadata._experimental_flash_urls
|
|
1480
1431
|
self._app_id = metadata.app_id or None
|
|
1481
1432
|
|
|
@@ -1500,7 +1451,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1500
1451
|
input_plane_url=self._input_plane_url,
|
|
1501
1452
|
input_plane_region=self._input_plane_region,
|
|
1502
1453
|
max_object_size_bytes=self._max_object_size_bytes,
|
|
1503
|
-
max_async_object_size_bytes=self._max_async_object_size_bytes,
|
|
1504
1454
|
_experimental_flash_urls=self._experimental_flash_urls,
|
|
1505
1455
|
supported_input_formats=self._metadata.supported_input_formats if self._metadata else [],
|
|
1506
1456
|
supported_output_formats=self._metadata.supported_output_formats if self._metadata else [],
|
|
@@ -1764,7 +1714,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1764
1714
|
client=self.client,
|
|
1765
1715
|
input_plane_url=self._input_plane_url,
|
|
1766
1716
|
input_plane_region=self._input_plane_region,
|
|
1767
|
-
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
|
|
1768
1717
|
)
|
|
1769
1718
|
else:
|
|
1770
1719
|
invocation = await _Invocation.create(
|
|
@@ -1806,7 +1755,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1806
1755
|
client=self.client,
|
|
1807
1756
|
input_plane_url=self._input_plane_url,
|
|
1808
1757
|
input_plane_region=self._input_plane_region,
|
|
1809
|
-
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY,
|
|
1810
1758
|
)
|
|
1811
1759
|
else:
|
|
1812
1760
|
invocation = await _Invocation.create(
|
|
@@ -2082,12 +2030,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2082
2030
|
await self._hydrate_from_id_metadata()
|
|
2083
2031
|
if not self._app_id:
|
|
2084
2032
|
raise ExecutionError("app_id should have been set during function call hydration")
|
|
2085
|
-
return _LogQueryData(
|
|
2086
|
-
self.client,
|
|
2087
|
-
self._app_id,
|
|
2088
|
-
LogsFilters(function_call_id=self.object_id),
|
|
2089
|
-
source_object_id=self.object_id,
|
|
2090
|
-
)
|
|
2033
|
+
return _LogQueryData(self.client, self._app_id, LogsFilters(function_call_id=self.object_id))
|
|
2091
2034
|
|
|
2092
2035
|
@property
|
|
2093
2036
|
def logs(self) -> _FunctionCallLogsManager:
|
|
@@ -30,11 +30,9 @@ from modal_proto import api_pb2
|
|
|
30
30
|
|
|
31
31
|
from ._environments import _get_environment_cached
|
|
32
32
|
from ._load_context import LoadContext
|
|
33
|
-
from ._logs_manager import _ImageLogsManager
|
|
34
33
|
from ._object import _Object, live_method_gen
|
|
35
34
|
from ._resolver import Resolver
|
|
36
35
|
from ._serialization import get_preferred_payload_format, serialize
|
|
37
|
-
from ._supports_logs import _ImageLogQueryData
|
|
38
36
|
from ._utils.async_utils import TaskContext, deprecate_aio_usage, synchronizer
|
|
39
37
|
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
40
38
|
from ._utils.docker_utils import (
|
|
@@ -49,10 +47,10 @@ from .cloud_bucket_mount import _CloudBucketMount
|
|
|
49
47
|
from .config import config, logger, user_config_path
|
|
50
48
|
from .exception import (
|
|
51
49
|
ExecutionError,
|
|
52
|
-
ImageBuildError,
|
|
53
50
|
InternalError,
|
|
54
51
|
InvalidError,
|
|
55
52
|
NotFoundError,
|
|
53
|
+
RemoteError,
|
|
56
54
|
ServiceError,
|
|
57
55
|
VersionError,
|
|
58
56
|
)
|
|
@@ -479,12 +477,6 @@ def _requires_image_instance(method):
|
|
|
479
477
|
return wrapper
|
|
480
478
|
|
|
481
479
|
|
|
482
|
-
@dataclass(frozen=True)
|
|
483
|
-
class _FailedBuildAttempt:
|
|
484
|
-
image_id: str
|
|
485
|
-
client: _Client
|
|
486
|
-
|
|
487
|
-
|
|
488
480
|
class _Image(_Object, type_prefix="im"):
|
|
489
481
|
"""Base class for container images to run functions in.
|
|
490
482
|
|
|
@@ -501,9 +493,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
501
493
|
_added_python_source_set: frozenset[str] # used to warn about missing mounts during auto-mount deprecation
|
|
502
494
|
_metadata: api_pb2.ImageMetadata | None = None # set on hydration, private for now
|
|
503
495
|
_is_empty: bool
|
|
504
|
-
_build_steps: list[api_pb2.ImageBuildStep] | None = None
|
|
505
|
-
# A failed image ID is only queryable through the client that performed the build.
|
|
506
|
-
_failed_build_attempt: _FailedBuildAttempt | None = None
|
|
507
496
|
|
|
508
497
|
def _initialize_from_empty(self):
|
|
509
498
|
self.inside_exceptions = []
|
|
@@ -535,9 +524,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
535
524
|
if metadata:
|
|
536
525
|
assert isinstance(metadata, api_pb2.ImageMetadata)
|
|
537
526
|
self._metadata = metadata
|
|
538
|
-
# Successful hydration supersedes diagnostic state retained from a failed build.
|
|
539
|
-
self._failed_build_attempt = None
|
|
540
|
-
self._build_steps = None
|
|
541
527
|
|
|
542
528
|
def _add_mount_layer_or_copy(self, mount: _Mount, copy: bool = False):
|
|
543
529
|
if copy:
|
|
@@ -766,12 +752,12 @@ class _Image(_Object, type_prefix="im"):
|
|
|
766
752
|
|
|
767
753
|
if result.status == api_pb2.GenericResult.GENERIC_STATUS_FAILURE:
|
|
768
754
|
if result.exception:
|
|
769
|
-
raise
|
|
770
|
-
f"Image build for {image_id} failed with the exception:\n{result.exception}", image_id
|
|
771
|
-
)
|
|
755
|
+
raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
|
|
772
756
|
else:
|
|
773
|
-
msg = f"Image build for {image_id} failed
|
|
774
|
-
|
|
757
|
+
msg = f"Image build for {image_id} failed. See build logs for more details."
|
|
758
|
+
if not OutputManager.get().is_enabled:
|
|
759
|
+
msg += " (Hint: Use `modal.enable_output()` to see logs from the process building the Image.)"
|
|
760
|
+
raise RemoteError(msg)
|
|
775
761
|
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
|
|
776
762
|
msg = f"Image build for {image_id} terminated due to external shut-down. Please try again."
|
|
777
763
|
if result.exception:
|
|
@@ -779,16 +765,15 @@ class _Image(_Object, type_prefix="im"):
|
|
|
779
765
|
f"Image build for {image_id} terminated due to external shut-down with the exception:\n"
|
|
780
766
|
f"{result.exception}"
|
|
781
767
|
)
|
|
782
|
-
raise
|
|
768
|
+
raise RemoteError(msg)
|
|
783
769
|
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
|
|
784
|
-
raise
|
|
785
|
-
f"Image build for {image_id} timed out. Please try again with a larger `timeout` parameter."
|
|
786
|
-
image_id,
|
|
770
|
+
raise RemoteError(
|
|
771
|
+
f"Image build for {image_id} timed out. Please try again with a larger `timeout` parameter."
|
|
787
772
|
)
|
|
788
773
|
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
|
|
789
774
|
pass
|
|
790
775
|
else:
|
|
791
|
-
raise
|
|
776
|
+
raise RemoteError("Unknown status %s!" % result.status)
|
|
792
777
|
|
|
793
778
|
self._hydrate(image_id, load_context.client, metadata)
|
|
794
779
|
local_mounts: set[_Mount] = set()
|
|
@@ -1079,18 +1064,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1079
1064
|
|
|
1080
1065
|
resolver = Resolver()
|
|
1081
1066
|
async with TaskContext() as tc:
|
|
1082
|
-
self._build_steps = None
|
|
1083
|
-
self._failed_build_attempt = None
|
|
1084
1067
|
load_context = LoadContext(task_context=tc).merged_with(app._root_load_context)
|
|
1085
|
-
|
|
1086
|
-
await resolver.load(self, load_context)
|
|
1087
|
-
except ImageBuildError as e:
|
|
1088
|
-
self._failed_build_attempt = _FailedBuildAttempt(
|
|
1089
|
-
image_id=e.image_id,
|
|
1090
|
-
client=load_context.client,
|
|
1091
|
-
)
|
|
1092
|
-
|
|
1093
|
-
raise
|
|
1068
|
+
await resolver.load(self, load_context)
|
|
1094
1069
|
return self
|
|
1095
1070
|
|
|
1096
1071
|
@_requires_image_instance
|
|
@@ -3053,38 +3028,3 @@ class _Image(_Object, type_prefix="im"):
|
|
|
3053
3028
|
"Images cannot currently be hydrated on demand; you can build an Image by running an App that uses it."
|
|
3054
3029
|
)
|
|
3055
3030
|
return self
|
|
3056
|
-
|
|
3057
|
-
async def _resolve_image_id_client_for_logs(self) -> tuple[str, _Client]:
|
|
3058
|
-
if self._failed_build_attempt is not None:
|
|
3059
|
-
return self._failed_build_attempt.image_id, self._failed_build_attempt.client
|
|
3060
|
-
if self._object_id is None:
|
|
3061
|
-
raise InvalidError("Cannot fetch logs for an image that has not been built yet.")
|
|
3062
|
-
if self._is_hydrated:
|
|
3063
|
-
return self._object_id, self.client
|
|
3064
|
-
# Use self._load_context_overrides to handle case of explicitly passed
|
|
3065
|
-
# client in from_id
|
|
3066
|
-
client = (await self._load_context_overrides.apply_defaults()).client
|
|
3067
|
-
return self._object_id, client
|
|
3068
|
-
|
|
3069
|
-
async def _get_log_query_data(self) -> _ImageLogQueryData:
|
|
3070
|
-
image_id, client = await self._resolve_image_id_client_for_logs()
|
|
3071
|
-
|
|
3072
|
-
if self._build_steps is None:
|
|
3073
|
-
request = api_pb2.ImageBuildChainGetRequest(image_id=image_id)
|
|
3074
|
-
response = await client.stub.ImageBuildChainGet(request)
|
|
3075
|
-
self._build_steps = list(response.build_steps)
|
|
3076
|
-
return _ImageLogQueryData(client, self._build_steps or [], image_id)
|
|
3077
|
-
|
|
3078
|
-
@property
|
|
3079
|
-
def logs(self) -> _ImageLogsManager:
|
|
3080
|
-
"""Access logs for an `Image`.
|
|
3081
|
-
|
|
3082
|
-
Use [`fetch()`](#logsfetch)
|
|
3083
|
-
to read logs for individual build layers and [`tail()`](#logstail)
|
|
3084
|
-
to read the most recent logs.
|
|
3085
|
-
|
|
3086
|
-
See also:
|
|
3087
|
-
- [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
|
|
3088
|
-
CLI access to logs for an App.
|
|
3089
|
-
"""
|
|
3090
|
-
return _ImageLogsManager(self)
|
|
@@ -256,16 +256,13 @@ async def _refine_dense_ranges(
|
|
|
256
256
|
)
|
|
257
257
|
sub_resp = await client.stub.AppCountLogs(sub_req)
|
|
258
258
|
sub_ranges = _buckets_to_ranges(list(sub_resp.buckets), smaller_secs)
|
|
259
|
-
# Clamp the
|
|
260
|
-
#
|
|
261
|
-
# (e.g. parent (
|
|
262
|
-
#
|
|
263
|
-
# buckets produces (20,40)), causing overlaps with siblings.
|
|
259
|
+
# Clamp the last sub-range to the parent's end. When bucket_secs
|
|
260
|
+
# doesn't evenly divide the parent duration, the last bucket
|
|
261
|
+
# boundary overshoots (e.g. parent (0,30) with 20s buckets
|
|
262
|
+
# produces (20,40)), causing overlaps with sibling ranges.
|
|
264
263
|
if sub_ranges:
|
|
265
|
-
|
|
266
|
-
sub_ranges[
|
|
267
|
-
last_start, last_end, last_count = sub_ranges[-1]
|
|
268
|
-
sub_ranges[-1] = (last_start, min(last_end, end), last_count)
|
|
264
|
+
s, _, c = sub_ranges[-1]
|
|
265
|
+
sub_ranges[-1] = (s, min(s + smaller_secs, end), c)
|
|
269
266
|
return sub_ranges
|
|
270
267
|
|
|
271
268
|
refine_set = {i for i, _ in to_refine}
|
|
@@ -413,6 +410,7 @@ async def fetch_logs(
|
|
|
413
410
|
|
|
414
411
|
if until - since > _MAX_FETCH_RANGE:
|
|
415
412
|
raise LogsFetchError(f"Time range cannot exceed {_MAX_FETCH_RANGE.days} days.")
|
|
413
|
+
|
|
416
414
|
# Phase 1: Count logs per bucket
|
|
417
415
|
bucket_secs = _pick_bucket_secs(since, until)
|
|
418
416
|
count_req = api_pb2.AppCountLogsRequest(
|