modal 1.5.4.dev22__tar.gz → 1.5.5.dev0__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.dev22 → modal-1.5.5.dev0}/PKG-INFO +1 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_functions.py +12 -2
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_image.py +4 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_logs_manager.py +124 -9
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_logs_manager.pyi +267 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/asgi.py +1 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/container_io_manager.py +9 -6
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/container_io_manager.pyi +6 -5
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_supports_logs.py +1 -3
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/app.py +29 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/app.pyi +47 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/volume.py +7 -7
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/client.pyi +2 -2
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/__init__.py +3 -2
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/functions.pyi +6 -6
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox.py +3 -4
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox.pyi +9 -12
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/types.py +4 -3
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2.py +931 -924
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2.pyi +22 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_version/__init__.py +1 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/pyproject.toml +1 -1
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/LICENSE +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/README.md +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/__main__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_billing.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_environments.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_function_variants.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_ipython.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_load_context.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_location.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_logs.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_object.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/manager.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/pty.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/rich.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_output/status.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_partial_function.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_resolver.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_resources.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_serialization.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_server.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_traceback.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_tunnel.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_type_manager.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_vendor/version.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_watcher.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/_workspace.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/billing.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/billing.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/README.md +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/call_graph.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/_download.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/_help.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/app.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/billing.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/changelog.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/config.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/container.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/curl.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/dict.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/endpoint.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/environment.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/image.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/launch.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/logo.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/profile.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/queues.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/run.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/secret.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/selector.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/shell.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/skills.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/token.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/utils.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cli/workspace.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/client.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cls.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/cls.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/config.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/container_process.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/container_process.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/dict.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/dict.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/environments.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/environments.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/exception.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/file_io.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/file_io.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/functions.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/image.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/image.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/io_streams.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/mount.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/mount.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/network_file_system.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/object.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/object.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/output.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/parallel_map.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/partial_function.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/proxy.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/proxy.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/py.typed +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/queue.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/queue.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/retries.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/runner.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/runner.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/running_app.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox_fs.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/schedule.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/secret.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/secret.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/server.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/server.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/serving.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/serving.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/snapshot.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/stream_type.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/token_flow.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/volume.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/volume.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/workspace.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal/workspace.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/py.typed +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/modal_version/__main__.py +0 -0
- {modal-1.5.4.dev22 → modal-1.5.5.dev0}/setup.cfg +0 -0
|
@@ -640,7 +640,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
640
640
|
await self.hydrate()
|
|
641
641
|
if not self._app_id:
|
|
642
642
|
raise ExecutionError("app_id should have been set during function hydration")
|
|
643
|
-
return _LogQueryData(
|
|
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
|
+
)
|
|
644
649
|
|
|
645
650
|
@property
|
|
646
651
|
def logs(self) -> _FunctionLogsManager:
|
|
@@ -2077,7 +2082,12 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2077
2082
|
await self._hydrate_from_id_metadata()
|
|
2078
2083
|
if not self._app_id:
|
|
2079
2084
|
raise ExecutionError("app_id should have been set during function call hydration")
|
|
2080
|
-
return _LogQueryData(
|
|
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
|
+
)
|
|
2081
2091
|
|
|
2082
2092
|
@property
|
|
2083
2093
|
def logs(self) -> _FunctionCallLogsManager:
|
|
@@ -764,7 +764,10 @@ class _Image(_Object, type_prefix="im"):
|
|
|
764
764
|
if build_resp.HasField("metadata"):
|
|
765
765
|
metadata = build_resp.metadata
|
|
766
766
|
|
|
767
|
-
if result.status
|
|
767
|
+
if result.status in (
|
|
768
|
+
api_pb2.GenericResult.GENERIC_STATUS_FAILURE,
|
|
769
|
+
api_pb2.GenericResult.GENERIC_STATUS_MEMORY_MANAGER_EVICTION,
|
|
770
|
+
):
|
|
768
771
|
if result.exception:
|
|
769
772
|
raise ImageBuildError(
|
|
770
773
|
f"Image build for {image_id} failed with the exception:\n{result.exception}", image_id
|
|
@@ -78,6 +78,13 @@ def _resolve_source(source: LogSource | None) -> api_pb2.FileDescriptor.ValueTyp
|
|
|
78
78
|
|
|
79
79
|
def _entry_context_ids(object_id: str, item: api_pb2.TaskLogs, batch: api_pb2.TaskLogsBatch) -> list[str]:
|
|
80
80
|
match object_id[:2]:
|
|
81
|
+
case "ap":
|
|
82
|
+
context_ids = [
|
|
83
|
+
batch.function_id,
|
|
84
|
+
item.function_call_id,
|
|
85
|
+
item.input_id or batch.input_id,
|
|
86
|
+
item.container_id or batch.task_id,
|
|
87
|
+
]
|
|
81
88
|
case "fu":
|
|
82
89
|
context_ids = [
|
|
83
90
|
item.function_call_id,
|
|
@@ -140,10 +147,14 @@ class _LogsManager:
|
|
|
140
147
|
since = _normalize_utc_datetime(since, "since")
|
|
141
148
|
until = _normalize_utc_datetime(until or datetime.now(timezone.utc), "until")
|
|
142
149
|
target = await self._params()
|
|
143
|
-
filters = dataclasses.replace(
|
|
150
|
+
filters = dataclasses.replace(
|
|
151
|
+
target.filters,
|
|
152
|
+
source=_resolve_source(source),
|
|
153
|
+
search_text=search_text,
|
|
154
|
+
)
|
|
144
155
|
async for batch in fetch_logs(target.client, target.app_id, since=since, until=until, filters=filters):
|
|
145
156
|
for item in batch.items:
|
|
146
|
-
yield _entry_from_item(
|
|
157
|
+
yield _entry_from_item(target.source_object_id, item, batch)
|
|
147
158
|
|
|
148
159
|
async def tail(
|
|
149
160
|
self,
|
|
@@ -161,7 +172,7 @@ class _LogsManager:
|
|
|
161
172
|
filters=filters,
|
|
162
173
|
):
|
|
163
174
|
for item in batch.items:
|
|
164
|
-
yield _entry_from_item(
|
|
175
|
+
yield _entry_from_item(params.source_object_id, item, batch)
|
|
165
176
|
|
|
166
177
|
def _create_log_stream(
|
|
167
178
|
self, params: _LogQueryData, last_entry_id: str, timeout: float
|
|
@@ -179,10 +190,10 @@ class _LogsManager:
|
|
|
179
190
|
)
|
|
180
191
|
return params.client.stub.AppGetLogs.unary_stream(request)
|
|
181
192
|
|
|
182
|
-
def _stream_entries(self, batch: api_pb2.TaskLogsBatch):
|
|
193
|
+
def _stream_entries(self, batch: api_pb2.TaskLogsBatch, source_object_id: str):
|
|
183
194
|
for item in batch.items:
|
|
184
195
|
if item.data:
|
|
185
|
-
yield _entry_from_item(
|
|
196
|
+
yield _entry_from_item(source_object_id, item, batch)
|
|
186
197
|
|
|
187
198
|
@staticmethod
|
|
188
199
|
def _advance_batch(batch: api_pb2.TaskLogsBatch, last_entry_id: str) -> tuple[str, bool]:
|
|
@@ -205,7 +216,7 @@ class _LogsManager:
|
|
|
205
216
|
log_stream = self._create_log_stream(params, last_entry_id, timeout)
|
|
206
217
|
try:
|
|
207
218
|
async for batch in log_stream:
|
|
208
|
-
for entry in self._stream_entries(batch):
|
|
219
|
+
for entry in self._stream_entries(batch, params.source_object_id):
|
|
209
220
|
yield entry
|
|
210
221
|
if batch.app_done:
|
|
211
222
|
return
|
|
@@ -243,7 +254,7 @@ class _LogsManager:
|
|
|
243
254
|
except StopAsyncIteration:
|
|
244
255
|
break
|
|
245
256
|
last_entry_id, app_done = self._advance_batch(batch, last_entry_id)
|
|
246
|
-
for log_entry in self._stream_entries(batch):
|
|
257
|
+
for log_entry in self._stream_entries(batch, params.source_object_id):
|
|
247
258
|
deadline.reset(timeout)
|
|
248
259
|
yield log_entry
|
|
249
260
|
if app_done:
|
|
@@ -262,7 +273,7 @@ class _LogsManager:
|
|
|
262
273
|
except StopAsyncIteration:
|
|
263
274
|
break
|
|
264
275
|
last_entry_id, app_done = self._advance_batch(batch, last_entry_id)
|
|
265
|
-
for log_entry in self._stream_entries(batch):
|
|
276
|
+
for log_entry in self._stream_entries(batch, params.source_object_id):
|
|
266
277
|
deadline.reset(timeout)
|
|
267
278
|
yield log_entry
|
|
268
279
|
if app_done:
|
|
@@ -538,7 +549,7 @@ class _FunctionCallLogsManager:
|
|
|
538
549
|
await asyncio.sleep(1)
|
|
539
550
|
continue
|
|
540
551
|
raise
|
|
541
|
-
raise
|
|
552
|
+
raise AssertionError("unreachable")
|
|
542
553
|
|
|
543
554
|
async def _determine_function_call_stop(self) -> bool:
|
|
544
555
|
try:
|
|
@@ -789,3 +800,107 @@ class _ImageLogsManager:
|
|
|
789
800
|
|
|
790
801
|
|
|
791
802
|
ImageLogsManager = synchronize_api(_ImageLogsManager, target_module=__name__)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
class _AppLogsManager:
|
|
806
|
+
"""mdmd:namespace"""
|
|
807
|
+
|
|
808
|
+
def __init__(self, source: _SupportsLogs):
|
|
809
|
+
"""mdmd:hidden"""
|
|
810
|
+
self._manager = _LogsManager(source)
|
|
811
|
+
|
|
812
|
+
async def fetch(
|
|
813
|
+
self,
|
|
814
|
+
*,
|
|
815
|
+
since: datetime,
|
|
816
|
+
until: datetime | None = None,
|
|
817
|
+
source: LogSource | None = None,
|
|
818
|
+
search_text: str = "",
|
|
819
|
+
) -> AsyncGenerator[LogEntry, None]:
|
|
820
|
+
"""Fetch App logs corresponding to the date range and filters.
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
since: Start date to fetch logs from. Must be in UTC or timezone-naive,
|
|
824
|
+
which is interpreted as local time.
|
|
825
|
+
until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
|
|
826
|
+
as local time.
|
|
827
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
828
|
+
search_text: Filter by search text.
|
|
829
|
+
|
|
830
|
+
Yields:
|
|
831
|
+
`LogEntry` objects in chronological order.
|
|
832
|
+
|
|
833
|
+
Examples:
|
|
834
|
+
|
|
835
|
+
```python notest
|
|
836
|
+
app = modal.App.lookup("my-app")
|
|
837
|
+
|
|
838
|
+
for entry in app.logs.fetch(
|
|
839
|
+
since=datetime.now() - timedelta(hours=4),
|
|
840
|
+
source="stdout",
|
|
841
|
+
):
|
|
842
|
+
print(entry.message, end="")
|
|
843
|
+
```
|
|
844
|
+
"""
|
|
845
|
+
async for log_entry in self._manager.fetch(
|
|
846
|
+
since=since,
|
|
847
|
+
until=until,
|
|
848
|
+
source=source,
|
|
849
|
+
search_text=search_text,
|
|
850
|
+
):
|
|
851
|
+
yield log_entry
|
|
852
|
+
|
|
853
|
+
async def tail(
|
|
854
|
+
self,
|
|
855
|
+
entries: int = 100,
|
|
856
|
+
*,
|
|
857
|
+
source: LogSource | None = None,
|
|
858
|
+
) -> AsyncGenerator[LogEntry, None]:
|
|
859
|
+
"""Fetch the most recent App logs.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
entries: The number of log entries to return.
|
|
863
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
864
|
+
|
|
865
|
+
Yields:
|
|
866
|
+
`LogEntry` objects in chronological order.
|
|
867
|
+
|
|
868
|
+
Examples:
|
|
869
|
+
|
|
870
|
+
```python notest
|
|
871
|
+
app = modal.App.lookup("my-app")
|
|
872
|
+
|
|
873
|
+
for entry in app.logs.tail(20):
|
|
874
|
+
print(entry.message, end="")
|
|
875
|
+
```
|
|
876
|
+
"""
|
|
877
|
+
async for log_entry in self._manager.tail(
|
|
878
|
+
entries,
|
|
879
|
+
source=source,
|
|
880
|
+
):
|
|
881
|
+
yield log_entry
|
|
882
|
+
|
|
883
|
+
async def stream(self, timeout: float | None = None) -> AsyncGenerator[LogEntry, None]:
|
|
884
|
+
"""Stream new App logs until the timeout is reached.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
timeout: Number of seconds to wait between log entries before terminating the stream.
|
|
888
|
+
By default, this will block until it is interrupted.
|
|
889
|
+
|
|
890
|
+
Yields:
|
|
891
|
+
`LogEntry` objects as they arrive.
|
|
892
|
+
|
|
893
|
+
Examples:
|
|
894
|
+
|
|
895
|
+
```python notest
|
|
896
|
+
app = modal.App.lookup("my-app")
|
|
897
|
+
|
|
898
|
+
for entry in app.logs.stream(timeout=60):
|
|
899
|
+
print(entry.message, end="")
|
|
900
|
+
```
|
|
901
|
+
"""
|
|
902
|
+
async for log_entry in self._manager.stream(timeout=timeout):
|
|
903
|
+
yield log_entry
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
AppLogsManager = synchronize_api(_AppLogsManager, target_module=__name__)
|
|
@@ -78,7 +78,7 @@ class _LogsManager:
|
|
|
78
78
|
"""Helper for creating the log stream RPC."""
|
|
79
79
|
...
|
|
80
80
|
|
|
81
|
-
def _stream_entries(self, batch: modal_proto.api_pb2.TaskLogsBatch): ...
|
|
81
|
+
def _stream_entries(self, batch: modal_proto.api_pb2.TaskLogsBatch, source_object_id: str): ...
|
|
82
82
|
@staticmethod
|
|
83
83
|
def _advance_batch(batch: modal_proto.api_pb2.TaskLogsBatch, last_entry_id: str) -> tuple[str, bool]: ...
|
|
84
84
|
@staticmethod
|
|
@@ -995,3 +995,269 @@ class ImageLogsManager:
|
|
|
995
995
|
...
|
|
996
996
|
|
|
997
997
|
tail: __tail_spec
|
|
998
|
+
|
|
999
|
+
class _AppLogsManager:
|
|
1000
|
+
"""mdmd:namespace"""
|
|
1001
|
+
def __init__(self, source: modal._supports_logs._SupportsLogs):
|
|
1002
|
+
"""mdmd:hidden"""
|
|
1003
|
+
...
|
|
1004
|
+
|
|
1005
|
+
def fetch(
|
|
1006
|
+
self,
|
|
1007
|
+
*,
|
|
1008
|
+
since: datetime.datetime,
|
|
1009
|
+
until: typing.Optional[datetime.datetime] = None,
|
|
1010
|
+
source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
|
|
1011
|
+
search_text: str = "",
|
|
1012
|
+
) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
|
|
1013
|
+
"""Fetch App logs corresponding to the date range and filters.
|
|
1014
|
+
|
|
1015
|
+
Args:
|
|
1016
|
+
since: Start date to fetch logs from. Must be in UTC or timezone-naive,
|
|
1017
|
+
which is interpreted as local time.
|
|
1018
|
+
until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
|
|
1019
|
+
as local time.
|
|
1020
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
1021
|
+
search_text: Filter by search text.
|
|
1022
|
+
|
|
1023
|
+
Yields:
|
|
1024
|
+
`LogEntry` objects in chronological order.
|
|
1025
|
+
|
|
1026
|
+
Examples:
|
|
1027
|
+
|
|
1028
|
+
```python notest
|
|
1029
|
+
app = modal.App.lookup("my-app")
|
|
1030
|
+
|
|
1031
|
+
for entry in app.logs.fetch(
|
|
1032
|
+
since=datetime.now() - timedelta(hours=4),
|
|
1033
|
+
source="stdout",
|
|
1034
|
+
):
|
|
1035
|
+
print(entry.message, end="")
|
|
1036
|
+
```
|
|
1037
|
+
"""
|
|
1038
|
+
...
|
|
1039
|
+
|
|
1040
|
+
def tail(
|
|
1041
|
+
self, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
|
|
1042
|
+
) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
|
|
1043
|
+
"""Fetch the most recent App logs.
|
|
1044
|
+
|
|
1045
|
+
Args:
|
|
1046
|
+
entries: The number of log entries to return.
|
|
1047
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
1048
|
+
|
|
1049
|
+
Yields:
|
|
1050
|
+
`LogEntry` objects in chronological order.
|
|
1051
|
+
|
|
1052
|
+
Examples:
|
|
1053
|
+
|
|
1054
|
+
```python notest
|
|
1055
|
+
app = modal.App.lookup("my-app")
|
|
1056
|
+
|
|
1057
|
+
for entry in app.logs.tail(20):
|
|
1058
|
+
print(entry.message, end="")
|
|
1059
|
+
```
|
|
1060
|
+
"""
|
|
1061
|
+
...
|
|
1062
|
+
|
|
1063
|
+
def stream(
|
|
1064
|
+
self, timeout: typing.Optional[float] = None
|
|
1065
|
+
) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
|
|
1066
|
+
"""Stream new App logs until the timeout is reached.
|
|
1067
|
+
|
|
1068
|
+
Args:
|
|
1069
|
+
timeout: Number of seconds to wait between log entries before terminating the stream.
|
|
1070
|
+
By default, this will block until it is interrupted.
|
|
1071
|
+
|
|
1072
|
+
Yields:
|
|
1073
|
+
`LogEntry` objects as they arrive.
|
|
1074
|
+
|
|
1075
|
+
Examples:
|
|
1076
|
+
|
|
1077
|
+
```python notest
|
|
1078
|
+
app = modal.App.lookup("my-app")
|
|
1079
|
+
|
|
1080
|
+
for entry in app.logs.stream(timeout=60):
|
|
1081
|
+
print(entry.message, end="")
|
|
1082
|
+
```
|
|
1083
|
+
"""
|
|
1084
|
+
...
|
|
1085
|
+
|
|
1086
|
+
class AppLogsManager:
|
|
1087
|
+
"""mdmd:namespace"""
|
|
1088
|
+
def __init__(self, source: modal._supports_logs._SupportsLogs):
|
|
1089
|
+
"""mdmd:hidden"""
|
|
1090
|
+
...
|
|
1091
|
+
|
|
1092
|
+
class __fetch_spec(typing_extensions.Protocol):
|
|
1093
|
+
def __call__(
|
|
1094
|
+
self,
|
|
1095
|
+
/,
|
|
1096
|
+
*,
|
|
1097
|
+
since: datetime.datetime,
|
|
1098
|
+
until: typing.Optional[datetime.datetime] = None,
|
|
1099
|
+
source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
|
|
1100
|
+
search_text: str = "",
|
|
1101
|
+
) -> typing.Generator[modal.types.LogEntry, None, None]:
|
|
1102
|
+
"""Fetch App logs corresponding to the date range and filters.
|
|
1103
|
+
|
|
1104
|
+
Args:
|
|
1105
|
+
since: Start date to fetch logs from. Must be in UTC or timezone-naive,
|
|
1106
|
+
which is interpreted as local time.
|
|
1107
|
+
until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
|
|
1108
|
+
as local time.
|
|
1109
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
1110
|
+
search_text: Filter by search text.
|
|
1111
|
+
|
|
1112
|
+
Yields:
|
|
1113
|
+
`LogEntry` objects in chronological order.
|
|
1114
|
+
|
|
1115
|
+
Examples:
|
|
1116
|
+
|
|
1117
|
+
```python notest
|
|
1118
|
+
app = modal.App.lookup("my-app")
|
|
1119
|
+
|
|
1120
|
+
for entry in app.logs.fetch(
|
|
1121
|
+
since=datetime.now() - timedelta(hours=4),
|
|
1122
|
+
source="stdout",
|
|
1123
|
+
):
|
|
1124
|
+
print(entry.message, end="")
|
|
1125
|
+
```
|
|
1126
|
+
"""
|
|
1127
|
+
...
|
|
1128
|
+
|
|
1129
|
+
def aio(
|
|
1130
|
+
self,
|
|
1131
|
+
/,
|
|
1132
|
+
*,
|
|
1133
|
+
since: datetime.datetime,
|
|
1134
|
+
until: typing.Optional[datetime.datetime] = None,
|
|
1135
|
+
source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None,
|
|
1136
|
+
search_text: str = "",
|
|
1137
|
+
) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
|
|
1138
|
+
"""Fetch App logs corresponding to the date range and filters.
|
|
1139
|
+
|
|
1140
|
+
Args:
|
|
1141
|
+
since: Start date to fetch logs from. Must be in UTC or timezone-naive,
|
|
1142
|
+
which is interpreted as local time.
|
|
1143
|
+
until: Defaults to current date if None. Must be in UTC or timezone-naive, which is interpreted
|
|
1144
|
+
as local time.
|
|
1145
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
1146
|
+
search_text: Filter by search text.
|
|
1147
|
+
|
|
1148
|
+
Yields:
|
|
1149
|
+
`LogEntry` objects in chronological order.
|
|
1150
|
+
|
|
1151
|
+
Examples:
|
|
1152
|
+
|
|
1153
|
+
```python notest
|
|
1154
|
+
app = modal.App.lookup("my-app")
|
|
1155
|
+
|
|
1156
|
+
for entry in app.logs.fetch(
|
|
1157
|
+
since=datetime.now() - timedelta(hours=4),
|
|
1158
|
+
source="stdout",
|
|
1159
|
+
):
|
|
1160
|
+
print(entry.message, end="")
|
|
1161
|
+
```
|
|
1162
|
+
"""
|
|
1163
|
+
...
|
|
1164
|
+
|
|
1165
|
+
fetch: __fetch_spec
|
|
1166
|
+
|
|
1167
|
+
class __tail_spec(typing_extensions.Protocol):
|
|
1168
|
+
def __call__(
|
|
1169
|
+
self, /, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
|
|
1170
|
+
) -> typing.Generator[modal.types.LogEntry, None, None]:
|
|
1171
|
+
"""Fetch the most recent App logs.
|
|
1172
|
+
|
|
1173
|
+
Args:
|
|
1174
|
+
entries: The number of log entries to return.
|
|
1175
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
1176
|
+
|
|
1177
|
+
Yields:
|
|
1178
|
+
`LogEntry` objects in chronological order.
|
|
1179
|
+
|
|
1180
|
+
Examples:
|
|
1181
|
+
|
|
1182
|
+
```python notest
|
|
1183
|
+
app = modal.App.lookup("my-app")
|
|
1184
|
+
|
|
1185
|
+
for entry in app.logs.tail(20):
|
|
1186
|
+
print(entry.message, end="")
|
|
1187
|
+
```
|
|
1188
|
+
"""
|
|
1189
|
+
...
|
|
1190
|
+
|
|
1191
|
+
def aio(
|
|
1192
|
+
self, /, entries: int = 100, *, source: typing.Optional[typing.Literal["stdout", "stderr", "system"]] = None
|
|
1193
|
+
) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
|
|
1194
|
+
"""Fetch the most recent App logs.
|
|
1195
|
+
|
|
1196
|
+
Args:
|
|
1197
|
+
entries: The number of log entries to return.
|
|
1198
|
+
source: Filter by source: 'stdout', 'stderr', or 'system'.
|
|
1199
|
+
|
|
1200
|
+
Yields:
|
|
1201
|
+
`LogEntry` objects in chronological order.
|
|
1202
|
+
|
|
1203
|
+
Examples:
|
|
1204
|
+
|
|
1205
|
+
```python notest
|
|
1206
|
+
app = modal.App.lookup("my-app")
|
|
1207
|
+
|
|
1208
|
+
for entry in app.logs.tail(20):
|
|
1209
|
+
print(entry.message, end="")
|
|
1210
|
+
```
|
|
1211
|
+
"""
|
|
1212
|
+
...
|
|
1213
|
+
|
|
1214
|
+
tail: __tail_spec
|
|
1215
|
+
|
|
1216
|
+
class __stream_spec(typing_extensions.Protocol):
|
|
1217
|
+
def __call__(
|
|
1218
|
+
self, /, timeout: typing.Optional[float] = None
|
|
1219
|
+
) -> typing.Generator[modal.types.LogEntry, None, None]:
|
|
1220
|
+
"""Stream new App logs until the timeout is reached.
|
|
1221
|
+
|
|
1222
|
+
Args:
|
|
1223
|
+
timeout: Number of seconds to wait between log entries before terminating the stream.
|
|
1224
|
+
By default, this will block until it is interrupted.
|
|
1225
|
+
|
|
1226
|
+
Yields:
|
|
1227
|
+
`LogEntry` objects as they arrive.
|
|
1228
|
+
|
|
1229
|
+
Examples:
|
|
1230
|
+
|
|
1231
|
+
```python notest
|
|
1232
|
+
app = modal.App.lookup("my-app")
|
|
1233
|
+
|
|
1234
|
+
for entry in app.logs.stream(timeout=60):
|
|
1235
|
+
print(entry.message, end="")
|
|
1236
|
+
```
|
|
1237
|
+
"""
|
|
1238
|
+
...
|
|
1239
|
+
|
|
1240
|
+
def aio(
|
|
1241
|
+
self, /, timeout: typing.Optional[float] = None
|
|
1242
|
+
) -> collections.abc.AsyncGenerator[modal.types.LogEntry, None]:
|
|
1243
|
+
"""Stream new App logs until the timeout is reached.
|
|
1244
|
+
|
|
1245
|
+
Args:
|
|
1246
|
+
timeout: Number of seconds to wait between log entries before terminating the stream.
|
|
1247
|
+
By default, this will block until it is interrupted.
|
|
1248
|
+
|
|
1249
|
+
Yields:
|
|
1250
|
+
`LogEntry` objects as they arrive.
|
|
1251
|
+
|
|
1252
|
+
Examples:
|
|
1253
|
+
|
|
1254
|
+
```python notest
|
|
1255
|
+
app = modal.App.lookup("my-app")
|
|
1256
|
+
|
|
1257
|
+
for entry in app.logs.stream(timeout=60):
|
|
1258
|
+
print(entry.message, end="")
|
|
1259
|
+
```
|
|
1260
|
+
"""
|
|
1261
|
+
...
|
|
1262
|
+
|
|
1263
|
+
stream: __stream_spec
|
|
@@ -523,6 +523,6 @@ def web_server_proxy(host: str, port: int):
|
|
|
523
523
|
except aiohttp.ClientConnectorError as exc:
|
|
524
524
|
# If the server is not running or not reachable, we should stop fetching new inputs.
|
|
525
525
|
logger.warning(f"Terminating runner due to @web_server connection issue: {exc}")
|
|
526
|
-
stop_fetching_inputs()
|
|
526
|
+
await stop_fetching_inputs.aio() # type: ignore
|
|
527
527
|
|
|
528
528
|
return web_server_proxy_app
|
|
@@ -554,7 +554,6 @@ class _ContainerIOManager:
|
|
|
554
554
|
_waiting_for_memory_snapshot: bool
|
|
555
555
|
|
|
556
556
|
_is_interactivity_enabled: bool
|
|
557
|
-
_fetching_inputs: bool
|
|
558
557
|
|
|
559
558
|
_singleton: ClassVar["_ContainerIOManager | None"] = None
|
|
560
559
|
|
|
@@ -595,7 +594,6 @@ class _ContainerIOManager:
|
|
|
595
594
|
self._waiting_for_memory_snapshot = False
|
|
596
595
|
|
|
597
596
|
self._is_interactivity_enabled = False
|
|
598
|
-
self._fetching_inputs = True
|
|
599
597
|
|
|
600
598
|
self._max_object_size_bytes, self._max_async_object_size_bytes = _resolve_output_size_limits(container_args)
|
|
601
599
|
|
|
@@ -863,7 +861,7 @@ class _ContainerIOManager:
|
|
|
863
861
|
]:
|
|
864
862
|
request = api_pb2.FunctionGetInputsRequest(function_id=self.function_id)
|
|
865
863
|
iteration = 0
|
|
866
|
-
while
|
|
864
|
+
while True:
|
|
867
865
|
await self._input_slots.acquire()
|
|
868
866
|
|
|
869
867
|
request.input_concurrency = self.get_input_concurrency()
|
|
@@ -1112,10 +1110,15 @@ class _ContainerIOManager:
|
|
|
1112
1110
|
io_manager._input_slots.set_value(concurrency)
|
|
1113
1111
|
|
|
1114
1112
|
@classmethod
|
|
1115
|
-
def stop_fetching_inputs(cls):
|
|
1116
|
-
|
|
1113
|
+
async def stop_fetching_inputs(cls):
|
|
1114
|
+
io_manager = cls._singleton
|
|
1115
|
+
if not io_manager:
|
|
1117
1116
|
raise RuntimeError("Must be called from within a Modal container.")
|
|
1118
|
-
|
|
1117
|
+
request = api_pb2.ContainerStopRequest(task_id=io_manager.task_id, graceful=True)
|
|
1118
|
+
await io_manager._client.stub.ContainerStop(
|
|
1119
|
+
request,
|
|
1120
|
+
retry=Retry(total_timeout=5.0),
|
|
1121
|
+
)
|
|
1119
1122
|
|
|
1120
1123
|
|
|
1121
1124
|
ContainerIOManager = synchronize_api(_ContainerIOManager)
|
|
@@ -124,7 +124,6 @@ class _ContainerIOManager:
|
|
|
124
124
|
_heartbeat_condition: typing.Optional[asyncio.locks.Condition]
|
|
125
125
|
_waiting_for_memory_snapshot: bool
|
|
126
126
|
_is_interactivity_enabled: bool
|
|
127
|
-
_fetching_inputs: bool
|
|
128
127
|
_singleton: typing.ClassVar[typing.Optional[_ContainerIOManager]]
|
|
129
128
|
|
|
130
129
|
def _init(self, container_args: modal_proto.api_pb2.ContainerArguments, client: modal.client._Client): ...
|
|
@@ -241,7 +240,7 @@ class _ContainerIOManager:
|
|
|
241
240
|
...
|
|
242
241
|
|
|
243
242
|
@classmethod
|
|
244
|
-
def stop_fetching_inputs(cls): ...
|
|
243
|
+
async def stop_fetching_inputs(cls): ...
|
|
245
244
|
|
|
246
245
|
class ContainerIOManager:
|
|
247
246
|
"""Synchronizes all RPC calls and network operations for a running container.
|
|
@@ -266,7 +265,6 @@ class ContainerIOManager:
|
|
|
266
265
|
_heartbeat_condition: typing.Optional[asyncio.locks.Condition]
|
|
267
266
|
_waiting_for_memory_snapshot: bool
|
|
268
267
|
_is_interactivity_enabled: bool
|
|
269
|
-
_fetching_inputs: bool
|
|
270
268
|
_singleton: typing.ClassVar[typing.Optional[ContainerIOManager]]
|
|
271
269
|
|
|
272
270
|
def __init__(self, /, *args, **kwargs):
|
|
@@ -515,7 +513,10 @@ class ContainerIOManager:
|
|
|
515
513
|
"""
|
|
516
514
|
...
|
|
517
515
|
|
|
518
|
-
|
|
519
|
-
|
|
516
|
+
class __stop_fetching_inputs_spec(typing_extensions.Protocol):
|
|
517
|
+
def __call__(self, /): ...
|
|
518
|
+
async def aio(self, /): ...
|
|
519
|
+
|
|
520
|
+
stop_fetching_inputs: typing.ClassVar[__stop_fetching_inputs_spec]
|
|
520
521
|
|
|
521
522
|
MAX_OUTPUT_BATCH_SIZE: int
|
|
@@ -19,6 +19,7 @@ class _LogQueryData:
|
|
|
19
19
|
client: _Client
|
|
20
20
|
app_id: str
|
|
21
21
|
filters: LogsFilters
|
|
22
|
+
source_object_id: str
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class _SupportsLogs(Protocol):
|
|
@@ -29,9 +30,6 @@ class _SupportsLogs(Protocol):
|
|
|
29
30
|
|
|
30
31
|
async def _get_log_query_data(self) -> _LogQueryData: ...
|
|
31
32
|
|
|
32
|
-
@property
|
|
33
|
-
def object_id(self) -> str: ...
|
|
34
|
-
|
|
35
33
|
|
|
36
34
|
@dataclasses.dataclass(frozen=True)
|
|
37
35
|
class _ImageLogQueryData:
|
|
@@ -15,12 +15,14 @@ import typing_extensions
|
|
|
15
15
|
from google.protobuf.message import Message
|
|
16
16
|
from synchronicity.async_wrap import asynccontextmanager
|
|
17
17
|
|
|
18
|
+
from modal._logs import LogsFilters
|
|
18
19
|
from modal_proto import api_pb2
|
|
19
20
|
|
|
20
21
|
from ._functions import _Function
|
|
21
22
|
from ._image import _Image
|
|
22
23
|
from ._ipython import is_interactive_ipython
|
|
23
24
|
from ._load_context import LoadContext
|
|
25
|
+
from ._logs_manager import _AppLogsManager
|
|
24
26
|
from ._object import _get_environment_name, _Object
|
|
25
27
|
from ._partial_function import (
|
|
26
28
|
_find_partial_methods_for_user_cls,
|
|
@@ -29,6 +31,7 @@ from ._partial_function import (
|
|
|
29
31
|
verify_concurrent_params,
|
|
30
32
|
)
|
|
31
33
|
from ._server import _Server, validate_http_server_config
|
|
34
|
+
from ._supports_logs import _LogQueryData
|
|
32
35
|
from ._utils.async_utils import synchronize_api
|
|
33
36
|
from ._utils.deprecation import (
|
|
34
37
|
deprecation_warning,
|
|
@@ -1615,5 +1618,31 @@ class _App:
|
|
|
1615
1618
|
"""Only used for tests."""
|
|
1616
1619
|
cls._container_app = None
|
|
1617
1620
|
|
|
1621
|
+
async def _get_log_query_data(self) -> _LogQueryData:
|
|
1622
|
+
"""Get the data needed to query logs for this app."""
|
|
1623
|
+
if not self._app_id:
|
|
1624
|
+
raise InvalidError("`app._logs` requires a running/stopped app.")
|
|
1625
|
+
client = self._client or await _Client.from_env()
|
|
1626
|
+
filters = LogsFilters()
|
|
1627
|
+
return _LogQueryData(client=client, app_id=self._app_id, filters=filters, source_object_id=self._app_id)
|
|
1628
|
+
|
|
1629
|
+
@property
|
|
1630
|
+
def logs(self) -> _AppLogsManager:
|
|
1631
|
+
"""Access logs for an `App`.
|
|
1632
|
+
|
|
1633
|
+
Use [`fetch()`](#logsfetch)
|
|
1634
|
+
to read logs from a UTC time range, [`tail()`](#logstail)
|
|
1635
|
+
to read the most recent logs, and [`stream()`](#logsstream)
|
|
1636
|
+
to follow new logs as they arrive.
|
|
1637
|
+
|
|
1638
|
+
See also:
|
|
1639
|
+
- [`modal app logs`](https://modal.com/docs/cli/latest/app#modal-app-logs):
|
|
1640
|
+
CLI access to logs for an App.
|
|
1641
|
+
"""
|
|
1642
|
+
if not self._app_id:
|
|
1643
|
+
raise InvalidError("`app.logs` requires a running/stopped app.")
|
|
1644
|
+
|
|
1645
|
+
return _AppLogsManager(self)
|
|
1646
|
+
|
|
1618
1647
|
|
|
1619
1648
|
App = synchronize_api(_App)
|